Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

In this tutorial, you'll learn how to create a simple CRM app that fetches data from WebAPI with the help of AppFramework and creating shared domain models between the server and the client. You will also be able to add, delete or edit customers with Floating Action Buttons as well swipe gestures. At the end of this tutorial, you should have the following result:

Panel

Samples:

SimpleCRM.zip

Follow these steps:

Table of Contents
maxLevel3
stylecircle

Anchor
preqreuisite
preqreuisite

Prerequisite

Before starting the walkthrough, it is recommended that you have followed these walkthroughs in order:

It is also recommended that you have read through these conceptual topics in order to get a better understanding:

To use this walkthrough, you will need to use at least Crosslight version 5.0.5000.538 in order to achieve the desired result. You'll also need to have installed Visual Studio 2012 or higher with Mobile Studio properly installed.

Let's get started. 

Preparing the Project

First, create a Blank Crosslight app using Crosslight Project Wizard. Set the project name as SimpleCRM. 

Creating WebApi and DomainModels Project

Next, we're going to create the shared domain models that can be used for both the client and the server. In this tutorial we will use MDF as well as the LDF files from SimpleCRM sample that you can download from this link: http://git.intersoftsolutions.com/projects/CS/repos/app-simplecrm/browse/SimpleCRM.WebApi/App_Data. Download all 4 files. We're going to create the models from these MDF files with the help of WebAPI EDM Extensions which is pre-installed to your Visual Studio once you've successfully installed Mobile Studio. Follow these steps in order. 

Adding New WebAPI Project

To add a new WebAPI project, right-click on your solution and select Add, New Project.

In the next dialog that appears, choose ASP.NET Web Application.

Add a new WebApi project to your solution and name it SimpleCRM.WebApi. In the next dialog, choose Web API.

Copy over CrosslightDb.mdf as well as CrosslightDb_log.ldf from the files you've downloaded earlier into the SimpleCRM.WebApi/App_Data folder.

Adding DomainModels Project

Next, we're going to add the DomainModels project that will hold the shared models between the server and the client. Right-click on your solution, choose Add, New Project. From the dialog that appears, choose Intersoft Crosslight Domain Models project under Intersoft Solutions, Mobile. Give the project the name SimpleCRM.DomainModels. Click OK.

In the next dialog choose, ADO.NET Entity Data Model and give it a name of SimpleCRMModel.

In the next dialog, configure the connection to your imported CrosslightDb.mdf file.

In the next screen, you'll be prompted to choose the EntityFramework version you would like to use. Choose Entity Framework 5.0.

Select all tables when prompted to choose your database objects.

Hit Finish.

Your EDMX is now ready. Now we're going to take advantage of the Intersoft WebAPI EDM Extension to generate the shared models. With the EDMX file still in focus, press F4 on your keyboard. When the Properties tab is opened, set the Intersoft WebAPI Enabled to True and Code Generation Version to V4.

Save the EDMX file. You may be prompted several times for the code generator template to start generating the domain models. Hit OK on all dialogs that appear. Your entity data model, model, repository and controller will be generated automatically. If you not familiar with the concept of domain models and repository, check out this article: Design Pattern Overview.

Preparing NuGet Packages

Before we can start working with the project, we'll need to fix the Crosslight references first. Follow these steps to properly configure your project with Crosslight NuGet packages. On your solution, perform right-click, then choose Manage Package for Solution.

In the next dialog that appears, search for Intersoft.Crosslight and install/update them to all of your projects.

Next, install/update Intersoft.Crosslight.Android.v7 NuGet package and install it onto the SimpleCRM.Android project.

Next, install Intersoft.AppFramework to all of your projects.

Next, install Intersoft.Data.WebApi.v4 to SimpleCRM.WebApi project.

Next, install Intersoft.Crosslight.Service.ImageLoader to SimpleCRM.Core project.

Lastly, install Microsoft.BCL.Async to SimpleCRM.Core project.

Testing WebApi Controller

Before we proceed, it is recommended to test our WebApi controller to ensure everything works correctly. But first, you need to configure the following files in the WebApi project as follows.

Modify WebApiConfig.cs in App_Start folder to looks like the following. This will configure the URL routing for our WebAPI server to use the pattern: data/{controller}/{action}.

Code Block
languagec#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using Microsoft.Owin.Security.OAuth;
using Newtonsoft.Json.Serialization;
namespace SimpleCRM.WebApi
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            // Configure Web API to use only bearer token authentication.
            //config.SuppressDefaultHostAuthentication();
            //config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
            // Web API routes
            config.MapHttpAttributeRoutes();
            config.Routes.MapHttpRoute(name: "DataApi", routeTemplate: "data/{controller}/{action}");
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

Next, open Global.asax.cs and use the following code. This will register the required initializers, routings and services for your WebAPI application.

Code Block
languagec#
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using Intersoft.Crosslight.Containers;
using Intersoft.Crosslight.Data;
using Intersoft.Data.WebApi;
namespace SimpleCRM.WebApi
{
    public class WebApiApplication : System.Web.HttpApplication
    {
        #region Nested type: AppDomainInfo
        public class AppDomainInfo : IAppDomainInfo
        {
            #region Properties
            public ApplicationType ApplicationType
            {
                get { return ApplicationType.Server; }
            }
            public ProviderType ProviderType
            {
                get { return ProviderType.IntersoftWebApi; }
            }
            #endregion
        }
        #endregion
        protected void Application_Start()
        {
            IocContainer.Current.Register<IAppDomainInfo, AppDomainInfo>();
            EntityServerConfig.Initialize();
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }
}

Then, open applicationhost.config inside .vs/config (this is a hidden folder, you might need to show all files from Windows Explorer). Add the following bindings to applicationhost.config in the your website node.

Code Block
languagec#
<binding protocol="http" bindingInformation="*:19119:localhost" />
<binding protocol="http" bindingInformation="*:19119:*" />

The binding above is required so that your WebApi is accessible from your local network. You might also need to disable Windows Firewall to ensure that the WebApi controller is accessible from your local network. To test WebApi controller, right click at controllers folders, and select View in Browser. Then change localhost to your ip address. To access controllers, use this format:

{IPAddress}:port/data/{ControllerClassName}/{ControllerMethodsName}. For example : http://192.168.10.30:19119/data/CrosslightDb/Customers.

To find your IP address and the port of your WebApi server, simply run the WebApi server on your local machine in a separate VS instance. 

Congratulations, you've successfully created the WebApi server for your application.

Preparing Images in WebApi

In this sample, we will use some images as customer photos to be displayed in a list. Follow these steps to prepare the images in the WebApi project. Add the code inside CrosslightDbController class in SimpleCRM.WebApi/Controllers folder:

Code Block
languagec#
#region Fields

private readonly EntityContextProvider<CrosslightDbEntities> db = new EntityContextProvider<CrosslightDbEntities>();

#endregion

Then create a new class, called CRMController.Partial.cs inside the SimpleCRM.WebApi/Controllers folder: This will allow for images to be saved later during add/edit operation.

Code Block
titleCRMController.Partial.cs
languagec#
using System.IO;
using System.Linq;
using System.Web;
namespace SimpleCRM.DomainModels.Controllers
{
    partial class CrosslightDbController
    {
        #region Constructors
        public CrosslightDbController()
        {
            db.AfterSaveChangesDelegate = (context, affectedRows) =>
            {
                if (affectedRows > 0)
                {
                    string imagePath = HttpContext.Current.Server.MapPath("~/images/crm");
                    if (db.Files != null && db.Files.Count() > 0)
                    {
                        foreach (var file in db.Files)
                        {
                            file.Save(Path.Combine(imagePath, file.Name == "Thumbnail" ? "thumbs" : ""));
                        }
                    }
                }
            };
        }
        #endregion
    }
}

Create a new folder called images folder inside the SimpleCRM.WebApi project. Download and extract this file inside the newly created folder. Your final project structure should look like this:

  • SimpleCRM.WebApi
    • images

Don't forget to all files's BuildAction to EmbeddedResource in the Properties pane, also ensure that you can have the write permission enabled to the folder, or else, the images cannot be saved.

Configuring App Service

Next, we'll want to modify the AppService.cs file to include various settings needed for this application. Open up AppService.cs file inside SimpleCRM.Core/Infrastructure folder and use the following code.

Code Block
AppSettings appSettings = new AppSettings();
appSettings.WebServerUrl = "http://192.168.10.30:19119/";
appSettings.BaseAppUrl = appSettings.WebServerUrl;
appSettings.BaseImageUrl = appSettings.BaseAppUrl + "/images/";
appSettings.RestServiceUrl = appSettings.BaseAppUrl + "/data/CrosslightDb";
appSettings.RequiresInternetConnection = true;

// shared services registration
this.GetService<ITypeResolverService>().Register(typeof(CrosslightAppAppService).GetTypeInfo().Assembly);

// components specific registration
this.GetService<IActivatorService>().Register<IRestClient>(c =>
{
	RestClient restClient = new RestClient(appSettings.RestServiceUrl);
	restClient.TypeResolver = new EntityTypeResolver();
	return restClient;
});

// application-specific containers registration
// such as data repositories and account services
Container.Current.RegisterInstance(appSettings);
Container.Current.Register<IEntityContainer>("Default", c => new EntityContainer()).WithLifetimeManager(new ContainerLifetime());

// for best practices, data repositories shouldn't use life-time container
Container.Current.Register<ICustomerRepository>((c) => new CustomerRepository(c.Resolve<IEntityContainer>("Default")));
Container.Current.Register<ICustomerTypeRepository>((c) => new CustomerTypeRepository(c.Resolve<IEntityContainer>("Default")));
Container.Current.Register<ICityRepository>((c) => new CityRepository(c.Resolve<IEntityContainer>("Default")));
Container.Current.Register<ICountryRepository>((c) => new CountryRepository(c.Resolve<IEntityContainer>("Default")));

// add new services (extensions)
ServiceProvider.AddService<IResourceLoaderService, ResourceLoaderService>();
ServiceProvider.AddService<IResourceCacheService, ResourceCacheService>();
ServiceProvider.AddService<IImageLoaderService, ImageLoaderService>();

The above code contains additional services that are needed for this sample to work properly, such as the RestClient, repositories and the additional services.

Creating List Page

Let's begin creating the customer list page. Follow these steps. Create a new empty class called CustomerQueryDefinition.cs inside SimpleCRM.Core/Models folder. This QueryDefinition will define how the list will be sorted when it first loads. As seen in the code below, when the list loads, it will be sorted based on the customer's FirstName. The Includes allows for additional nested properties to be accessed when obtaining the CustomerAddress. The AddFilter method adds the filters whenever search is executed.

Code Block
titleCustomerQueryDefinition.cs
languagec#
using Intersoft.AppFramework;
using Intersoft.Crosslight.Data.ComponentModel;

namespace SimpleCRM.Models
{
    public class CustomerQueryDefinition : QueryDefinitionBase
    {
        #region Constructors

        public CustomerQueryDefinition()
        {
            this.SortExpression = "FirstName";
        }

        #endregion

        #region Methods

        protected override QueryDescriptor GetBaseQueryDescriptor()
        {
            QueryDescriptor queryDescriptor = base.GetBaseQueryDescriptor();
            queryDescriptor.Includes.Add("CustomerAddress");
            queryDescriptor.Includes.Add("CustomerAddress.Country");
            queryDescriptor.Includes.Add("CustomerAddress.City");
            return queryDescriptor;
        }
        public override QueryDescriptor GetQueryDescriptor()
        {
            QueryDescriptor queryDescriptor = this.GetBaseQueryDescriptor();
            if (this.FilterScope == "FirstName")
                this.AddFilter("FirstName", FilterOperator.StartsWith, this.FilterQuery);
            else if (this.FilterScope == "CompanyName")
                this.AddFilter("CompanyName", FilterOperator.StartsWith, this.FilterQuery);
            return queryDescriptor;
        }

        #endregion
    }
}

Next, create a new empty class called Customer.Partial.cs inside SimpleCRM.Core/DomainModels folder and use the following code. This is just a simple partial class for the Customer class. The IgnoreDataMemberAttribute tells the JSON serializer to skip the marked properties when it is sent over the wire.

Code Block
titleCustomer.Partial.cs
languagec#
using System.Runtime.Serialization;
using Intersoft.AppFramework;
using Intersoft.Crosslight;

namespace SimpleCRM.DomainModels
{
    [Serializable]
    public partial class Customer
    {
        #region Fields

        private byte[] _largeImage;
        private AppSettings _settings;
        private byte[] _thumbnailImage;

        #endregion

        #region Properties

        [IgnoreDataMember]
        public string FullName
        {
            get { return string.Format("{0} {1}", this.FirstName, this.LastName); }
        }

        [IgnoreDataMember]
        public byte[] LargeImage
        {
            get { return _largeImage; }
            set
            {
                if (_largeImage != value)
                {
                    _largeImage = value;
                    OnPropertyChanged("LargeImage");
                }
            }
        }

        [IgnoreDataMember]
        public string ResolvedLargeImage
        {
            get
            {
                if (!string.IsNullOrEmpty(this.Photo))
                    return this.Settings.BaseImageUrl + "CRM/" + this.Photo + ".jpg";
                return string.Empty;
            }
        }

        [IgnoreDataMember]
        public string ResolvedThumbnailImage
        {
            get
            {
                if (!string.IsNullOrEmpty(this.Photo))
                    return this.Settings.BaseImageUrl + "CRM/thumbs/" + this.Photo + ".jpg";
                return string.Empty;
            }
        }

        private AppSettings Settings
        {
            get
            {
                if (_settings == null)
                    _settings = Container.Current.Resolve<AppSettings>();
                return _settings;
            }
        }

        [IgnoreDataMember]
        public byte[] ThumbnailImage
        {
            get { return _thumbnailImage; }
            set
            {
                if (_thumbnailImage != value)
                {
                    _thumbnailImage = value;
                    OnPropertyChanged("ThumbnailImage");
                }
            }
        }

        #endregion

        #region Methods

        public override void BeginEdit()
        {
            base.BeginEdit();
            if (this.CustomerAddress != null)
                this.CustomerAddress.BeginEdit();
        }

        public override void CancelEdit()
        {
            base.CancelEdit();
            if (this.CustomerAddress != null)
                this.CustomerAddress.CancelEdit();
        }

        public override void EndEdit()
        {
            base.EndEdit();
            if (this.CustomerAddress != null)
                this.CustomerAddress.EndEdit();
        }

        protected override void OnPropertyChanged(string propertyName)
        {
            base.OnPropertyChanged(propertyName);
            if (propertyName == "FirstName" || propertyName == "LastName")
                this.OnPropertyChanged("FullName");
        }

        #endregion
    }
}

Then, create a new file called CustomerAddress.Partial.cs inside SimpleCRM.Core/DomainModels folder and use the following code. The following class simply provides a better experience when editing the customer. When the CountryId is changed, the City property is automatically nulled since it won't be valid anymore whenever the Country is changed.

Code Block
titleCustomerAddress.Partial.cs
languagec#
using Intersoft.Crosslight;

namespace SimpleCRM.DomainModels
{
    [Serializable]
    public partial class CustomerAddress
    {
        #region Methods

        protected override void OnPropertyChanged(string propertyName)
        {
            base.OnPropertyChanged(propertyName);
            if (this.IsEditing)
            {
                if (propertyName == "CountryId")
                    this.City = null;
            }
        }
 
        #endregion
    }
}

After we've finished creating the models, let's proceed by creating the ViewModel inside the Core project. Create a new Crosslight Data List View Model class and give it a name of CustomerListViewModel.cs and put it inside SimpleCRM.Core/ViewModels folder. This class contains nothing except for the Title which will be displayed when the screen loads, as well as providing the previously created FilterQuery and ViewQuery for this screen.

Code Block
titleCustomerListViewModel.cs
languagec#
using Intersoft.AppFramework;
using Intersoft.AppFramework.ViewModels;
using SimpleCRM.DomainModels;
using SimpleCRM.Models;
namespace SimpleCRM.ViewModels
{
    public class CustomerListViewModel : DataListViewModelBase<Customer, ICustomerRepository>
    {
        #region Constructors

        public CustomerListViewModel()
        {
            this.Title = "Accounts";
        }

        #endregion

        #region Query Definition

        protected IQueryDefinition _filterDefinition;
        protected IQueryDefinition _queryDefinition;

        protected override IQueryDefinition FilterQuery
        {
            get
            {
                if (_filterDefinition == null)
                    _filterDefinition = new CustomerQueryDefinition();
                return _filterDefinition;
            }
        }

        protected override IQueryDefinition ViewQuery
        {
            get
            {
                if (_queryDefinition == null)
                    _queryDefinition = new CustomerQueryDefinition();
                return _queryDefinition;
            }
        }

        #endregion
    }
}

Now that you have the ViewModel ready, let's proceed by creating a new BindingProvider called CustomerListBindingProvider.cs inside the SimpleCRM.Core/BindingProviders folder. Here, we bind the list with the ID TableView to the corresponding properties in the ViewModel. The built-in list views in Crosslight are automatically given a name of TableView

Code Block
titleCustomerListBindingProviders.cs
languagec#
using Intersoft.Crosslight;

namespace SimpleCRM
{
    public class CustomerListBindingProvider : BindingProvider
    {
        #region Constructors

        public CustomerListBindingProvider()
        {
            // item binding
            ItemBindingDescription itemBinding = new ItemBindingDescription()
            {
                DisplayMemberPath = "FullName",
                DetailMemberPath = "CompanyName",
                ImageMemberPath = "ResolvedThumbnailImage",
                ImagePlaceholder = "item_placeholder.png"
            };
            // list binding
            this.AddBinding("TableView", BindableProperties.ItemsSourceProperty, "Items");
            this.AddBinding("TableView", BindableProperties.ItemTemplateBindingProperty, itemBinding, true);
            this.AddBinding("TableView", BindableProperties.SelectedItemProperty, "SelectedItem", BindingMode.TwoWay);
            this.AddBinding("TableView", BindableProperties.SelectedItemsProperty, "SelectedItems", BindingMode.TwoWay);
        }

        #endregion
    }
}

Open up AppService.cs inside SimpleCRM.Core/Infrastructure folder and change the RootViewModel to CustomerListViewModel.

Code Block
languagec#
protected override void OnStart(StartParameter parameter)
{
    base.OnStart(parameter);
    //Specify the first ViewModel to use when launching the application.
    this.SetRootViewModel<CustomerListViewModel>();
}

Now that we've finished preparing the Core section, let's move on by creating the Android view counterpart to display these list of customers.

Preparing the Android Project

To begin with, create a new AppActivity.cs inside the SimpleCRM.Android project and use the following code. This will be needed as the main container for the first view.

Code Block
titleAppActivity.cs
languagec#
using Android.App;
using SimpleCRM.ViewModels;
using Intersoft.Crosslight.Android.v7;
namespace SimpleCRM.Android.Activities
{
    [Activity]
    public class AppActivity : AppCompatActivity<CustomerListViewModel>
    {
    }
}

Next, create a new Crosslight Material RecyclerView Fragment inside SimpleCRM.Android/Fragments folder. Use the following code. Here, we simply provided the RecyclerViewFragment to display the list of customers. 

Code Block
titleCustomerListFragment.cs
languagec#
using System;
using Android.Runtime;
using Intersoft.Crosslight;
using Intersoft.Crosslight.Android.v7;
using SimpleCRM.ViewModels;

namespace SimpleCRM.Android.Activities
{
    [ImportBinding(typeof(CustomerListBindingProvider))]
    public class CustomerListFragment : RecyclerViewFragment<CustomerListViewModel>
    {
        #region Constructors

        public CustomerListFragment()
        {
        }
        public CustomerListFragment(IntPtr javaReference, JniHandleOwnership transfer)
            : base(javaReference, transfer)
        {
        }

        #endregion

        #region Methods

        protected override void Initialize()
        {
            base.Initialize();
            this.CellStyle = CellStyle.SubTitle;
            this.ImageLoaderSettings.AnimateOnLoad = true;
            this.ImageLoaderSettings.CacheExpirationPolicy = CacheExpirationPolicy.AutoDetect;
            this.ImageSettings.CornerRadius = 40;
        }

        #endregion
    }
}

Next, copy the following item_placeholder.png file inside the SimpleCRM.Android/Resources/Drawable folder.

Run the project, and you should get the following result.

 

Enabling Pull to Refresh

To enable pull to refresh, simply provide this code to CustomerListViewModel constructor:

Code Block
languagec#
this.EnableRefresh = true;

After that you can refresh the list by performing a pull gesture to the list view.

Enabling Incremental Loading

To enable incremental loading, simply add set the EnableIncrementalLoading property and provide the IncrementalLoadingSize to CustomerListViewModel's constructor:

Code Block
titleCustomerListViewModel.cs
languagec#
#region Constructors

public CustomerListViewModel()
{
	this.Title = "Accounts";
	// enable pull to refresh
	this.EnableRefresh = true;
	// enable incremental loading
	this.EnableIncrementalLoading = true;
	this.IncrementalLoadingSize = 20;
	this.LoadIncrementalCommand = new DelegateCommand(ExecuteLoadIncrementalCommand);
}

#endregion

#region Incremental Loading

public DelegateCommand LoadIncrementalCommand { get; set; }

private void ExecuteLoadIncrementalCommand(object parameter)
{
	this.LoadDataIncremental();
}

#endregion

This will limit the file size query to 20 items per page, which will considerably reduce the loading time and data payload if the number of items is large.

Enabling Search

Follow these steps to enable search. Change the parent class for CustomerListFragment from RecyclerViewFragment to SearchableRecyclerViewFragment. Then, add this code to Initialize methods of CustomerListFragment.

Code Block
languagec#
this.FilterScope = "Name";
this.AddBarItem(new BarItem("SearchButton", CommandItemType.Search));

Then, add the following methods to CustomerListViewModel.

Code Block
languagec#
public override void Filter(string query, string scope)
{
	base.Filter(query, scope);
	if(scope=="Name")
		this.FilterItems = this.Items.Where(o => o.FullName.ToLowerInvariant().StartsWith(query.ToLowerInvariant())).ToList();
}

Run the application and now you can search the customers based on their names.

Adding and Updating Data

Now that you've successfully enabled search on Recycler View Fragment, follow these steps to add and update data. Create a new empty class named CityQueryDefinition.cs in SimpleCRM.Core/Models folder and use the following code.

Code Block
languagec#
using Intersoft.AppFramework;
using Intersoft.Crosslight.Data.ComponentModel;
namespace SimpleCRM.Models
{
    public class CityQueryDefinition : QueryDefinitionBase
    {
        #region Constructors

        public CityQueryDefinition()
        {
            this.SortExpression = "Name";
        }

        #endregion

        #region Methods

        public override QueryDescriptor GetQueryDescriptor()
        {
            var queryDescriptor = this.GetBaseQueryDescriptor();
            this.AddFilter("CountryId", FilterOperator.IsEqualTo, this.FilterQuery);
            return queryDescriptor;
        }

        #endregion
    }
}

Next, add CustomerFormMetadata.cs in Simple.CRM/Models folder and use the following code. This will be used as the form metadata (Crosslight Form Builder) for the Add/Edit screen.

Code Block
languagec#
using Intersoft.Crosslight;
using Intersoft.Crosslight.Forms;
using SimpleCRM.DomainModels;
using SimpleCRM.ViewModels;
using System;

namespace SimpleCRM.Models
{
    [Form(Title = "{FormState} Customer")]
    public class CustomerFormMetadata
    {
        [Section(Style = SectionLayoutStyle.ImageWithFields)]
        public static GeneralSection General;
        [Section(Header = "Info")]
        public static InfoSection Company;
        [Section(Header = "Contacts")]
        public static ContactSection Contact;
        [Section(Header = "Address")]
        public static AddressSection Address;

        public class GeneralSection
        {
            [Editor(EditorType.Image)]
            [Image(UseCircleMask = true, Height = 83, Width = 80, Placeholder = "item_placeholder.png", Frame = "frame.png", FramePadding = 6, FrameShadowHeight = 3)]
            [ImagePicker(ImageResultMode = ImageResultMode.Both, ActivateCommand = "ActivateImagePickerCommand", PickerResultCommand = "FinishImagePickerCommand")]
            public static string ResolvedThumbnailImage;
 
            [StringInput(Placeholder = "First Name")]
            [Layout(Style = LayoutStyle.DetailOnly)]
            public static string FirstName;
 
            [StringInput(Placeholder = "Last Name")]
            [Layout(Style = LayoutStyle.DetailOnly)]
            public static string LastName;
        }

        public class InfoSection
        {
            [StringInput(Placeholder = "Company Name")]
            [Layout(Style = LayoutStyle.DetailOnly)]
            public static string CompanyName;
 
            [StringInput(Placeholder = "Job Title")]
            [Layout(Style = LayoutStyle.DetailOnly)]
            public static string JobTitle;
 
            [Editor(EditorType.Date)]
            [StringInput(Placeholder = "Date of Birth")]
            [Layout(Style = LayoutStyle.DetailOnly)]
            [Binding(StringFormat = "{0:yyyy/MMM/dd}")]
            public static DateTime DateOfBirth;
        }

        public class ContactSection
        {
            [StringInput(Placeholder = "Email")]
            [Layout(Style = LayoutStyle.DetailOnly)]
            public static string Email;
 
            [StringInput(Placeholder = "Business Phone")]
            [Layout(Style = LayoutStyle.DetailOnly)]
            public static string BusinessPhone;
 
            [StringInput(Placeholder = "Mobile Phone")]
            [Layout(Style = LayoutStyle.DetailOnly)]
            public static string MobilePhone;
 
            [StringInput(Placeholder = "Fax")]
            [Layout(Style = LayoutStyle.DetailOnly)]
            public static string Fax;
        }

        public class AddressSection
        {
            [StringInput(Placeholder = "Address")]
            [Layout(Style = LayoutStyle.DetailOnly)]
            [Binding(Path = "CustomerAddress.Address")]
            public static string Address;
 
            [Editor(EditorType.Selection)]
            [SelectedItemBinding(Path = "CustomerAddress.Country")]
            [Binding(Path = "CustomerAddress.Country.Name")]
            [SelectionInput(SelectionMode.Single, DisplayMemberPath = "Name", ListSourceType = typeof(CountryListViewModel))]
            public static Country Country;

            [Editor(EditorType.Selection)]
            [SelectedItemBinding(Path = "CustomerAddress.City")]
            [Binding(Path = "CustomerAddress.City.Name")]
            [SelectionInput(SelectionMode.Single, DisplayMemberPath = "Name", ListSourceType = typeof(CityListViewModel))]
            public static City City;
        }
    }
}

Next, add a new Crosslight Data List View Model named CityListViewModel.cs inside SimpleCRM.Core/ViewModels folder.

Code Block
languagec#
using Intersoft.AppFramework;
using Intersoft.AppFramework.ViewModels;
using SimpleCRM.DomainModels;
using SimpleCRM.Models;
namespace SimpleCRM.ViewModels
{
    public class CityListViewModel : DataListViewModelBase<City, ICityRepository>
    {
        #region Constructors

        public CityListViewModel()
        {
        }

        #endregion

        #region Methods

        public override void Navigated(Intersoft.Crosslight.NavigatedParameter parameter)
        {
            if (parameter != null)
            {
                CustomerEditorViewModel editorViewModel = parameter.Sender as CustomerEditorViewModel;
                if (editorViewModel != null)
                {
                    Customer customer = editorViewModel.Item as Customer;
                    if (customer != null && customer.CustomerAddress != null && customer.CustomerAddress.Country != null)
                        this.ViewQuery.FilterQuery = customer.CustomerAddress.Country.CountryId.ToString();
                }
            }
            base.Navigated(parameter);
        }

        #endregion

        #region Query Definition

        private IQueryDefinition _queryDefinition;
        protected override IQueryDefinition ViewQuery
        {
            get
            {
                if (_queryDefinition == null)
                    _queryDefinition = new CityQueryDefinition();
                return _queryDefinition;
            }
        }

        #endregion
    }
}

Next, add another Crosslight Data List View Model named CountryListViewModel inside SimpleCRM.Core/ViewModels folder.

Code Block
languagec#
using Intersoft.AppFramework.ViewModels;
using SimpleCRM.DomainModels;
namespace SimpleCRM.ViewModels
{
    public class CountryListViewModel : DataListViewModelBase<Country, ICountryRepository>
    {
    }
}

Next, add a new Crosslight Data Detail View Model named CustomerDetailViewModel inside SimpleCRM.Core/ViewModels folder.

Code Block
languagec#
using System.Linq;
using Intersoft.AppFramework.ViewModels;
using Intersoft.Crosslight;
using SimpleCRM.DomainModels;
namespace SimpleCRM.ViewModels
{
    public class CustomerDetailViewModel : DataDetailViewModelBase<Customer, ICustomerRepository>
    {
        #region Constructors

        public CustomerDetailViewModel()
        {
            this.Title = "Customer Detail";
        }

        #endregion

        #region Properties

        /// Represents a property member that participates in the state saving during application suspension.
        /// Use the [StateAware] attribute to define members that will be saved and restored during application life cycle changes.
        /// </summary>
        [StateAware]
        public string ItemKey
        {
            get { return this.Item.CustomerId; }
            set
            {
                if (string.IsNullOrEmpty(value))
                    this.Item = this.Repository.GetAll().First();
                else
                    this.Item = this.Repository.GetSingle(this.Item.CustomerId);
            }
        }

        #endregion

        #region Methods

        protected override void ExecuteAdd(object parameter)
        {
            this.NavigationService.Navigate<CustomerEditorViewModel>(new NavigationParameter()
            {
                NavigationMode = NavigationMode.Modal
            });
        }

        protected override void ExecuteEdit(object parameter)
        {
            this.NavigationService.Navigate<CustomerEditorViewModel>(new NavigationParameter(this.Item)
            {
                NavigationMode = NavigationMode.Modal
            });
        }

        #endregion
    }
}

Let's provide the editor view model for editing the customer. Create a new Crosslight Data Editor View Model inside SimpleCRM.Core/ViewModels folder called CustomerEditorViewModel.

Code Block
languagec#
using System;
using System.Collections.Generic;
using System.Windows.Input;
using Intersoft.AppFramework.Identity;
using Intersoft.AppFramework.ViewModels;
using Intersoft.Crosslight;
using Intersoft.Crosslight.Forms;
using Intersoft.Crosslight.Input;
using SimpleCRM.DomainModels;
using SimpleCRM.Models;

namespace SimpleCRM.ViewModels
{
    public class CustomerEditorViewModel : DataEditorViewModelBase<Customer, ICustomerRepository>
    {
        #region Constructors

        public CustomerEditorViewModel()
        {
            this.Title = "Edit Item";
            this.TrackRelatedEntityChanges = true;
            this.ActivateImagePickerCommand = new DelegateCommand(ExecuteActivateImagePicker);
            this.FinishImagePickerCommand = new DelegateCommand(ExecuteFinishImagePickerCommand);
            this.ViewLargeImageCommand = new DelegateCommand(ExecuteViewLargeImage, CanExecuteViewLargeImage);
        }

        #endregion

        #region Properties

        public override Type FormMetadataType
        {
            get { return typeof(CustomerFormMetadata); }
        }
        public CustomerListViewModel Owner { get; set; }

        #endregion

        #region Methods

        protected override void ExecuteSave(object parameter)
        {
            if (this.IsDirty)
            {
                IUserService userService = this.GetService<IUserService>();
                DateTime now = DateTime.Now;
                if (this.IsNewItem)
                {
                    this.Item.CreatedDate = now;
                    this.Item.CustomerAddress.CreatedDate = now;
                }
                this.Item.ModifiedDate = now;
                this.Item.CustomerAddress.ModifiedDate = now;
            }
            base.ExecuteSave(parameter);
        }
        protected override void InitializeNewItem(Customer item)
        {
            base.InitializeNewItem(item);
            item.CustomerId = Guid.NewGuid().ToString("N");
            item.CustomerTypeId = 1;
            item.CustomerAddress = new CustomerAddress();
            item.CustomerAddress.CustomerId = item.CustomerId;
        }
        public override void Navigated(NavigatedParameter parameter)
        {
            this.Owner = parameter.Sender as CustomerListViewModel;
            base.Navigated(parameter);
            string formState = this.IsNewItem ? "New " : "Edit ";
			formState+="Customer";

            this.Title = formState;
        }
        protected override void OnItemCancelEdit(Customer item)
        {
            base.OnItemCancelEdit(item);
            // if users have select a photo, clear it when changes are cancelled.
            item.ThumbnailImage = null;
            item.LargeImage = null;
        }

        #endregion

        #region Commands

        public DelegateCommand ActivateImagePickerCommand { get; set; }
        public DelegateCommand FinishImagePickerCommand { get; set; }
        public DelegateCommand ViewLargeImageCommand { get; set; }
        private bool CanExecuteViewLargeImage(object parameter)
        {
            if (this.IsNewItem || string.IsNullOrEmpty(this.Item.ResolvedThumbnailImage))
                return false;
            return true;
        }

        private void ExecuteActivateImagePicker(object parameter)
        {
            ImagePickerActivateParameter activateParameter = parameter as ImagePickerActivateParameter;
            if (activateParameter != null)
            {
                activateParameter.CustomCommands = new Dictionary<string, ICommand>();
                activateParameter.CustomCommands.Add("View Larger", this.ViewLargeImageCommand);
            }
        }

        private void ExecuteFinishImagePickerCommand(object parameter)
        {
            var resultParameter = parameter as ImagePickerResultParameter;
            var cacheSession = this.GetService<IImageLoaderService>().GetImageLoaderCacheSession();
            var cacheService = this.GetService<IResourceCacheService>();
            bool raisePropertyChanged = false;
            if (resultParameter.Result != null)
            {
                this.Item.Photo = this.Item.CustomerId;
                this.Item.ThumbnailImage = resultParameter.Result.ThumbnailImageData;
                this.Item.LargeImage = resultParameter.Result.ImageData;
                // add to cache service to avoid losing the image changes in case the upload/save failed (due to connection etc)
                if (cacheService != null && cacheSession != null)
                {
                    cacheService.AddToCache(cacheSession, this.Item.ResolvedThumbnailImage, this.Item.ThumbnailImage, ResourceCacheMode.DiskAndMemory);
                    cacheService.AddToCache(cacheSession, this.Item.ResolvedLargeImage, this.Item.LargeImage, ResourceCacheMode.DiskAndMemory);
                }
                raisePropertyChanged = true;
            }
            else
            {
                if (resultParameter.SelectedCommandId == "Delete Photo")
                {
                    // user tapped the Delete Photo option
                    this.Item.Photo = null;
                    if (cacheService != null && cacheSession != null)
                    {
                        cacheService.RemoveFromCache(cacheSession, this.Item.ResolvedThumbnailImage);
                        cacheService.RemoveFromCache(cacheSession, this.Item.ResolvedLargeImage);
                    }
                    raisePropertyChanged = true;
                }
            }
            if (raisePropertyChanged)
            {
                // notify other views that consume these properties, so the image changes are reflected automatically
                this.Item.RaisePropertyChanged("ResolvedThumbnailImage");
                this.Item.RaisePropertyChanged("ResolvedLargeImage");
            }
        }

        private void ExecuteViewLargeImage(object parameter)
        {
            this.NavigationService.Navigate(new NavigationTarget(typeof(CustomerDetailViewModel), "PhotoDetail", new NavigationParameter(this.Item)));
        }

        #endregion
    }
}

To provide the add functionality in the list, add this method in CustomerListViewModel.

 

Code Block
languagec#
protected override void ExecuteAdd(object parameter)
{
	this.NavigationService.Navigate<CustomerEditorViewModel>(new NavigationParameter()
	{
		NavigationMode = NavigationMode.Modal,
		EnsureNavigationContext = true,
		ModalPresentationStyle = ModalPresentationStyle.FormSheet,
		CommandId = "Add"
	});
}

Next, add a new empty class called CustomerRepostiory.partial.cs inside SimpleCRM.Core/Models folder.

Code Block
languagec#
using System.Collections.Generic;
using System.Linq;
using Intersoft.Crosslight.Data;
using Intersoft.Crosslight.Data.EntityModel;
using Intersoft.Crosslight.RestClient;
namespace SimpleCRM.DomainModels
{
    public partial class CustomerRepository
    {
        #region Methods

        public override void Delete(Customer entity)
        {
            base.Delete(entity);
            if (entity.CustomerAddress != null)
                entity.CustomerAddress.EntityAspect.Delete();
        }

        protected override void InitializeSaveRequest(RestRequest request, IEnumerable<IEntity> entities)
        {
            var items = entities.OfType<Customer>();
            // send the new images (both thumbnail and large) along with the save request
            foreach (var item in items)
            {
                var originalItem = this.GetSingle(item.CustomerId);
                if (originalItem.ThumbnailImage != null)
                    request.AddFile("Thumbnail", originalItem.ThumbnailImage, item.Photo, "image/jpg");
                if (originalItem.LargeImage != null)
                    request.AddFile("Large", originalItem.LargeImage, item.Photo, "image/jpg");
            }
        }

        public override void Insert(Customer entity)
        {
            base.Insert(entity);
            if (entity.CustomerAddress != null)
                entity.CustomerAddress.EntityAspect.EntityState = EntityState.Added;
        }

        #endregion
    }
}

Add the following code to CustomerListBindingProvider inside SimpleCRM.Core/BindingProviders folder.

Code Block
languagec#
// navigation
this.AddBinding("TableView", BindableProperties.DetailNavigationTargetProperty, new NavigationTarget(typeof(CustomerEditorViewModel)), true);

// editing
this.AddBinding("TableView", BindableProperties.IsBatchUpdatingProperty, "IsBatchUpdating");
this.AddBinding("TableView", BindableProperties.IsEditingProperty, "IsEditing", BindingMode.TwoWay);
this.AddBinding("TableView", BindableProperties.AddItemCommandProperty, "AddCommand");
this.AddBinding("TableView", BindableProperties.DeleteItemCommandProperty, "DeleteCommand");
this.AddBinding("TableView", BindableProperties.RefreshCommandProperty, "RefreshCommand");
this.AddBinding("AddButton", BindableProperties.CommandProperty, "AddCommand");

Add a new Crosslight Binding Provider file inside SimpleCRM.Core/BindingProviders and name it CustomerDetailBindingProvider.cs.

Code Block
languagec#
using Intersoft.Crosslight;
namespace SimpleCRM
{
    public class CustomerDetailBindingProvider : BindingProvider
    {
        #region Constructors
        public CustomerDetailBindingProvider()
        {
            this.AddBinding("NameLabel", BindableProperties.TextProperty, "Item.FullName");
            this.AddBinding("ImageView", BindableProperties.ImageSourceProperty, "Item.ResolvedLargeImage");
            this.AddBinding("CloseButton", BindableProperties.CommandProperty, "CloseCommand");
        }
        #endregion
    }
}

Next, add a new layout inside SimpleCRM.Android/Resources/layout folder called view_image_layout.axml. The layout simply contains a TextView to show the customer's name as well as an ImageView to show the customer's photo.

Code Block
languagec#
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/NameLabel"
        android:gravity="center_vertical|center_horizontal"
        android:textAppearance="@style/TextAppearance.AppCompat.Headline"
        android:layout_width="match_parent"
        android:layout_height="48dp" />
    <ImageView
        android:id="@+id/ImageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

Then add a new Crosslight Material Fragment called ViewImageFragment. This is just a Fragment that will be used to display the customer's photo when we tap on View Larger Image button in the FormFragment.

Code Block
languagec#
using System;
using Android.Runtime;
using Android.Transitions;
using Intersoft.Crosslight;
using Intersoft.Crosslight.Android.v7;
using SimpleCRM.ViewModels;
namespace SimpleCRM.Android.Activities
{
    [ImportBinding(typeof(CustomerDetailBindingProvider))]
    [RegisterNavigation("PhotoDetail")]
    public class ViewImageFragment : Fragment<CustomerDetailViewModel>
    {
        #region Constructors

        public ViewImageFragment()
        {
        }
        public ViewImageFragment(IntPtr javaReference, JniHandleOwnership transfer)
            : base(javaReference, transfer)
        {
        }

        #endregion

        #region Properties

        protected override int ContentLayoutId
        {
            get { return Resource.Layout.view_image_layout; }
        }
        protected override bool ShowActionBarUpButton
        {
            get { return true; }
        }

        #endregion

        #region Methods

        protected override void Initialize()
        {
            base.Initialize();
            if (this.Activity.IsApiLevel21())
            {
                this.EnterTransition = new Slide();
                this.ExitTransition = new Fade();
                this.ReturnTransition = new Fade();
                this.ReenterTransition = new Fade();
            }
        }

        #endregion
    }
}

Add a new Crosslight Material Form Fragment called CustomerEditFragment inside SimpleCRM.Android/Fragments folder. The FormFragment contains nothing except for the Save button which we've placed as the bar items.

Code Block
languagec#
using System;
using Android.Runtime;
using Intersoft.Crosslight;
using Intersoft.Crosslight.Android.v7;
using SimpleCRM.ViewModels;
namespace SimpleCRM.Android.Activities
{
    public class CustomerEditFragment : FormFragment<CustomerEditorViewModel>
    {
        #region Constructors

        public CustomerEditFragment()
        {
        }
        public CustomerEditFragment(IntPtr javaReference, JniHandleOwnership transfer)
            : base(javaReference, transfer)
        {
        }

        #endregion

        #region Methods

        protected override void Initialize()
        {
            base.Initialize();
            this.BarItems.Add(new BarItem("SaveButton", CommandItemType.Done));
        }

        #endregion
    }
}

Add the following code to the Initialize method in CustomerListFragment inside SimpleCRM.Android/Fragments folder. This will add the Floating Action Button to your fragment which will allow you to add a new customer.

Code Block
languagec#
// Defines floating action button.
this.FloatingActionButtons.Add(new FloatingActionButton("AddButton")
{
	Position = FloatingActionButtonPosition.BottomRight,
	CommandItemType = CommandItemType.Add,
	Direction = FloatingActionButtonDirection.Up,
	HideOnScrollUp = true
});
this.InteractionMode = ListViewInteraction.Navigation;

Now you should be able to add and edit the list of customers.

Deleting Data

Now you've successfully enabled add and edit data to your customer list. Let's proceed by adding the delete functionality. Add this code to CustomerListBindingProvider inside SimpleCRM.Core/BindingProviders folder.

Code Block
languagec#
this.AddBinding("DeleteButton", BindableProperties.TextProperty, "DeleteText");
this.AddBinding("DeleteButton", BindableProperties.CommandProperty, "DeleteCommand");
this.AddBinding("DeleteButton", BindableProperties.CommandParameterProperty, "SelectedItems");

Then, in the Initialize method in CustomerListFragment, add the following code.

Code Block
languagec#
this.EditingOptions = EditingOptions.AllowEditing | EditingOptions.AllowMultipleSelection;
ListContextualToolbarSettings settings = this.ContextualToolbarSettings as ListContextualToolbarSettings;
if (settings != null)
{
	settings.Mode = ContextualMode.Default;
	settings.CheckAllEnabled = true;
	settings.CheckAllMargin = 4;
	settings.BarItems.Add(new BarItem("DeleteButton", CommandItemType.Trash));
}

Now you can delete any customers by performing long-click on a list.

Using Swipe Gesture for Additional Editing Actions

In this section, we're going to learn how to add swipe gestures to enable additional editing actions. Follow these steps to enable swipe gestures. Open up CustomerListFragment.cs inside SimpleCRM.Android/Fragments folder and add the following code to the Initialize method.

Code Block
languagec#
this.EditActions.Add(new Intersoft.Crosslight.Android.v7.ComponentModels.EditAction("Action 1", new Color(255, 136, 31)));
this.EditActions.Add(new Intersoft.Crosslight.Android.v7.ComponentModels.EditAction("Action 2", new Color(66, 217, 0)));

Then, open up CustomerListBindingProvider.cs inside SimpleCRM.Android/Fragments folder and add the following code.

Code Block
languagec#
this.AddBinding("TableView", BindableProperties.EditActionCommandProperty, "EditActionCommand", BindingMode.TwoWay);

Lastly, add this method inside CustomerListViewModel.cs inside SimpleCRM.Core/ViewModels folder.

Code Block
languagec#
protected override void ExecuteEditAction(object parameter)
{
	EditingParameter editingParameter = parameter as EditingParameter;
	if (editingParameter != null)
	{
		string editAction = editingParameter.CustomAction.ToLowerInvariant();
		if (editAction == "action 1")
		{
			this.ToastPresenter.Show("Action 1 Selected");
		}
		else if (editAction == "action 2")
		{
			this.ToastPresenter.Show("Action 2 Selected");
		}
		editingParameter.ShouldEndEditing = true;
	}
}

The following image shows the result after you've run the project.

 

Preparing the iOS Project

Now that you've finished the Android project, let's move on to the iOS project. Basically, all you have to do is just prepare the views for the iOS project, since you've prepared the Core project with all the ViewModels and BindingProviders. Begin by creating a new Crosslight iOS Table View Controller for iPhone, give it a name of CustomerListViewController.cs. Use the following code.

Code Block
languagec#
using CoreGraphics;
using Intersoft.Crosslight;
using Intersoft.Crosslight.iOS;
using SimpleCRM.ViewModels;
using UIKit;
namespace SimpleCRM.iOS
{
    [ImportBinding(typeof(CustomerListBindingProvider))]
    [RegisterNavigation(DeviceKind.Phone)]
	public class CustomerListViewController : UITableViewController<CustomerListViewModel>
    {
        private UIBarButtonItem AddButton { get; set; }
        public override bool AllowSearching
        {
            get { return true; }
        }
        public override ImageSettings CellImageSettings
        {
            get
            {
                return new ImageSettings()
                {
                    ImageSize = new CGSize(36, 36)
                };
            }
        }
        public override TableViewCellStyle CellStyle
        {
            get { return TableViewCellStyle.Subtitle; }
        }
        public override EditingOptions EditingOptions
        {
            get { return EditingOptions.AllowEditing | EditingOptions.AllowMultipleSelection; }
        }
        private UIBarButtonItem[] EditToolBarItems { get; set; }
        public override bool HideSearchBarInitially
        {
            get { return true; }
        }
        public override BasicImageLoaderSettings ImageLoaderSettings
        {
            get
            {
                return new BasicImageLoaderSettings()
                {
                    AnimateOnLoad = true,
                    CacheExpirationPolicy = CacheExpirationPolicy.AutoDetect
                };
            }
        }
        public override TableViewInteraction InteractionMode
        {
            get { return TableViewInteraction.Navigation; }
        }
        private UIBarButtonItem[] NormalToolbarItems { get; set; }
        public override string[] SearchScopes
        {
            get { return new string[] {"FirstName", "CompanyName"}; }
        }

        private UIView CreateEditStatusView()
        {
            UIView statusView = new UIView();
            UILabel selectionLabel = new UILabel();
            selectionLabel.Frame = new CGRect(0, 0, 200, 44);
            selectionLabel.Font = selectionLabel.Font.WithSize(13f);
            selectionLabel.TextAlignment = UITextAlignment.Center;
            statusView.Frame = new CGRect(0, 0, 200, 44);
            statusView.AddSubview(selectionLabel);
            this.RegisterViewIdentifier("SelectionLabel", selectionLabel);
            return statusView;
        }
        private UIView CreateStatusView()
        {
            UIView statusView = new UIView();
            UILabel updatedLabel = new UILabel();
            UILabel countLabel = new UILabel();
            updatedLabel.Frame = new CGRect(0, 4, 200, 18);
            updatedLabel.Font = updatedLabel.Font.WithSize(12f);
            updatedLabel.TextAlignment = UITextAlignment.Center;
            countLabel.Frame = new CGRect(0, 20, 200, 18);
            countLabel.Font = updatedLabel.Font.WithSize(12f);
            countLabel.TextColor = UIColor.Gray;
            countLabel.TextAlignment = UITextAlignment.Center;
            statusView.Frame = new CGRect(0, 0, 200, 44);
            statusView.AddSubview(updatedLabel);
            statusView.AddSubview(countLabel);
            this.RegisterViewIdentifier("UpdatedLabel", updatedLabel);
            this.RegisterViewIdentifier("CountLabel", countLabel);
            return statusView;
        }
        protected override void InitializeView()
        {
            base.InitializeView();
            UIBarButtonItem addButton = new UIBarButtonItem(UIBarButtonSystemItem.Add);
            //this.NavigationItem.Title = "Customers";
            this.NavigationItem.SetRightBarButtonItem(this.EditButtonItem, false);
            // Configure ToolBar
            UIBarButtonItem deleteButton = new UIBarButtonItem(UIBarButtonSystemItem.Trash);
            UIBarButtonItem flexibleWidth = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace);
            UIBarButtonItem flexibleWidth2 = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace);
            UIBarButtonItem normalStatusView = new UIBarButtonItem(this.CreateStatusView());
            UIBarButtonItem editStatusView = new UIBarButtonItem(this.CreateEditStatusView());
            this.EditToolBarItems = new UIBarButtonItem[] {flexibleWidth, editStatusView, deleteButton};
            this.NormalToolbarItems = new UIBarButtonItem[] {flexibleWidth, normalStatusView, flexibleWidth2, addButton};
            this.SetToolbarHidden(false, false);
            this.SetToolbarItems(this.NormalToolbarItems, false);
            // Configure Footer
            UILabel totalCountLabel = new UILabel(new CGRect(0, 0, this.TableView.Bounds.Width, 44));
            totalCountLabel.TextAlignment = UITextAlignment.Center;
            totalCountLabel.TextColor = UIColor.Gray;
            this.TableView.TableFooterView = totalCountLabel;
            // Register Views
            this.RegisterViewIdentifier("AddButton", addButton);
            this.RegisterViewIdentifier("EditButton", this.EditButtonItem);
            this.RegisterViewIdentifier("DeleteButton", deleteButton);
			this.AddButton = addButton;
			this.EditActions.Add(new UIEditAction("Action 1", UIColor.FromRGB(255,136,31)));
			this.EditActions.Add(new UIEditAction("Action 2", UIColor.FromRGB(66, 217, 0)));
        }
        protected override void OnViewModelPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnViewModelPropertyChanged(e);
            if (e.PropertyName == "IsEditing")
            {
                if (this.ViewModel.IsEditing)
                    this.SetToolbarItems(this.EditToolBarItems, true);
                else
                    this.SetToolbarItems(this.NormalToolbarItems, true);
            }
        }
    }
}

From the code above, we've configured how our table view should look like and we've also configured the Toolbar, Footer as well as registering the necessary views. If you run the project now you should get the following result.

Swipe gestures should work as well.

Next, create a new Crosslight Form View Controller, give it a name of CustomerEditViewController. This ViewController will act as the add/edit view when adding/editing a customer. Use the following code.

Code Block
languagec#
using CoreGraphics;
using Intersoft.Crosslight;
using Intersoft.Crosslight.iOS;
using SimpleCRM.ViewModels;
namespace SimpleCRM.iOS
{
    public partial class CustomerEditViewController : UIFormViewController<CustomerEditorViewModel>
    {
        public CustomerEditViewController()
        {
            // Uncomment for iOS6
            // this.ContentSizeForViewInPopover = new SizeF(350f, 550f);
            this.PreferredContentSize = new CGSize(350f, 550f);
        }

        public override bool HideKeyboardOnScroll
        {
            get { return true; }
        }

        public override void DetermineNavigationMode(NavigationParameter parameter)
        {
            if (this.GetService<IApplicationService>().GetContext().Device.Kind == DeviceKind.Tablet)
            {
                // Only customize the navigation mode for editing (default navigation command)
                if (parameter.CommandId == null)
                {
                    parameter.PreferPopover = true;
                    parameter.EnsureNavigationContext = true;
                }
            }
        }

        protected override void InitializeView()
        {
            base.InitializeView();
            // dynamically set the form title based on the actual account type
            this.Form.Title = this.ViewModel.Title;
        }
    }
}

Now you should be able to add/edit a customer, shown in the following shot.

Lastly, we're going to create a simple Crosslight View Controller for iPhone. Give it a name of PhotoDetailViewController. Use the following code.

Code Block
languagec#
using CoreGraphics;
using Intersoft.Crosslight;
using Intersoft.Crosslight.iOS;
using SimpleCRM.ViewModels;
using UIKit;
namespace SimpleCRM.iOS
{
    [ImportBinding(typeof(CustomerDetailBindingProvider))]
    [RegisterNavigation("PhotoDetail")]
    public partial class PhotoDetailViewController : UIViewController<CustomerDetailViewModel>
    {
        public PhotoDetailViewController()
            : base("PhotoDetailViewController", null)
        {
            // Uncomment for iOS6
            // this.ContentSizeForViewInPopover = new SizeF(350f, 550f);
            this.PreferredContentSize = new CGSize(400f, 500f);
        }

        private UIBarStyle _originalBarStyle;

        public override void ViewWillAppear(bool animated)
        {
            base.ViewWillAppear(animated);
            _originalBarStyle = this.NavigationController.NavigationBar.BarStyle;
            this.NavigationController.NavigationBar.BarStyle = UIBarStyle.Black;
        }
        public override void ViewWillDisappear(bool animated)
        {
            base.ViewWillDisappear(animated);
            this.NavigationController.NavigationBar.BarStyle = _originalBarStyle;
        }
    }
}

Then, move the PhotoDetailViewController.xib to the Views folder. Open the file in Xcode Interface Builder. Provide the following layout.

Give the Label an outlet called NameLabel and the ImageView an outlet called ImageView. When run, you should get the following result.

Conclusion

Congratulations! You already learn how to create a simple, yet full-fledged CRM application that fetches data from WebApi controller complete with CRUD functionalities and swipe gestures.

Sample

You can also find the resulting sample here: SimpleCRM.zip . Simply open this sample with Xamarin Studio or Visual Studio and run the project.

 

Related Topics

Content by Label
spacescrosslight
reversetrue
showLabelsfalse
max5
sortmodified
labelsenterprisegetting-datastarted-accesswith-walkthroughs crosslight -walkthrough-create-crm-app-with-crosslight-appframework-and-webapi
showSpacefalse
typepage