Thursday, April 26, 2012

PhoneGap: Building native mobile apps with HTML5

PhoneGap is an HTML5 app platform which allows you to develop an application in HTML5 and run it as an native app on your mobile device. PhoneGap does this by running the application in a web browser control in the background. So when the user starts navigating trough your application, PhoneGap will intercept this and navigate to the files which are stored locally on the phone. This way there is no need to have an connection to the internet to run the application. But PhoneGap offers more then only building an HTML5 app. Because most of the HTML5 specifications aren’t implemented in mobile browsers yet, PhoneGap provides an framework, which allows you to access features of the mobile device. For example the camera, file system, contacts, …

Because all these features are OS depended (even device depended sometimes, certainly for Android), there is for every OS an different implementation of the PhoneGap framework. This is because the JS framework which is provided will make calls to a dll (in case of Windows Phone), .jar (in case of Android), …to access the specific features of the mobile device. An overview of all the features can be found here. It gives you also an overview on which platforms the features are supported. The documentation of the API can be found here.

Getting started

Because I am an .NET developer, my starting point will be developing an PhoneGap application for Windows Phone. Further on, I will show how you can reuse your code to target other platforms. The first things you need so we can get started is:

  • The Windows Phone SDK (This will install everything you need to develop a Windows Phone Application. Incl. Visual Studio if it isn’t installed on your computer.)
    http://www.microsoft.com/download/en/details.aspx?id=27570
  • The PhoneGap SDK (The 1.6.0 is already released, but I had some issues with it so I recommend to use the 1.5.0 version for now)
    https://github.com/phonegap/phonegap/zipball/1.5.0
    • The above link is an zip file. When downloaded, extract this file
    • In the extracted folder, navigate to phonegap-phonegap-<xxx>/lib/Windows and copy the following files:

Cordova-1.4.1-Custom.zip
Cordova-1.4.1-Starter.zip
Cordova-1.5.0-Custom.zip
Cordova-1.5.0-Starter.zip

    • Navigate to C:\Users\<UserName>\Documents\Visual Studio 2010\Templates\ProjectTemplates\Silverlight for Windows Phone and past the files of the previous step in here. (If the folder Silverlight for Windows Phone doesn’t exist, Create it)

Now, when we start up Visual Studio, we will see the PhoneGap templates appear under the Silverlight for Windows Phone folder.

image

Developing A phoneGap Application

Now everything is installed, we can start to develop our application. I’ll choose for the Cordova-1.5.0-Starter, this way I’m using the almost latest version of PhoneGap and the framework is already referenced. This will result in the follow structure.

image

In the GapLib folder we will find a dll. This dll is used to provide access to the native features of the phone. This way we can call for example the camera trough the PhoneGap JS framework. This framework is present in the www folder. This is the API we will use in our application.

All files we want to use in out PhoneGap application need to be added to the www folder. When the PhoneGap application starts up, the first thing it will look for is the index.html file. So make sure it’s always present in the www folder. One of the first lines in the index.html file is the following:

   1: <meta name="viewport" 
   2:       content="width=device-width
   3:              , height=device-height
   4:              , initial-scale=1.0
   5:              , maximum-scale=1.0
   6:              , user-scalable=no;" />

his makes sure the application is always shown maximized and uses the whole view port.


If you want to make use of the PhoneGap functionalities, you can do this once the device ready has fired. This event gets triggered once all PhoneGap functionalities are loaded.



   1: document.addEventListener("deviceready",onDeviceReady,false);
   2:  
   3: // once the device ready event fires, you can safely do your thing!
   4: function onDeviceReady()
   5: {
   6:     console.log("Device ready, you can now access the PhoneGap library.");
   7: }




Deploying to other platforms


You have two ways to deploy your HTML5 application to other platforms. One is to install the environments of the other platforms and including your www folder in the PhoneGap template of the other platform or use the PhoneGap build service. In both cases you need to make sure the Cordova.js file of the specific platform is referenced. This means if you include the www folder of your windows phone application into the template of an android application, you need to use the Cordova.js file which is provided by the template and not the one you used for Windows Phone)development. If you do so, you will notice the device ready event will not fire and the PhoneGap functionality won’t work. If you are working with jQuery mobile, you can get page navigation error when navigating with jQuery mobile.


When using the PhoneGap build service, the first thing you need to do is creating an account. Once you have this, you can create an new application. Here you need to provide the following:



  • The name of the application

  • Choose upload an archive or index.html file

  • The file you need to provide is a zip of your www folder where your application is present. Note: make sure the Cordova.js, Cordova-1.5.0.js or phonegap.js file aren’t present in the zip. Otherwise the correct version of these files for the specified platform, aren’t included. In that case, your application might not work on the other platforms.

  • When the file is uploaded and you click on create, you can let the build service do his magic. After some minutes, you will have the possibility of downloading the packages for the other platforms.

image


The build service supports the following platforms:



  • iOs

  • Android

  • Windows Phone (very recently)

  • Blackberry

  • webOS

  • Symbian

 


Conclusion


The PhoneGap API makes it easier to build your native mobile application once (in HTML) and deploy it to multiple platforms. This is a cheap way if you want to provide native apps in several platforms whiteout building it from scratch in his native language. Of course, you can’t expect that the PhoneGap has the same performance as your real native app would have. Also not all functionality is fully supported on every platform, so make sure you check this before implementing it in your application.


The last thing I want to point out is the experience you get of this kind of application. Because this targets multiple platforms, it can’t provide the platform specific experience like transitions when navigating, the styling, … This is one of the compromises you need to make if you want to reuse your code.

Sunday, April 8, 2012

CRUD operations with WCF Ria Services, Upshot and Knockout

In my previous post, I showed you how to retrieve data with upshot using WCF Ria Services. Today I’m extending this example with simple CRUD operations, including validation. If you want to see this sample in action, I have added it to codeplex. You can find it here.

Server side

We will start at the server side, because this needs the least adjustments. Just like the Silverlight client, you can use the CUD operations by the naming convention (“InsertClassname”, “UpdateClassename” or “DeleteClassname”). Or by using the “insert”, “update” or “delete” attribute on the corresponding attributes. In my example I have done both. Once all this is done, the server side is configured.

[Insert]
public void InsertTodoItem(TodoItem todoItem)
{
    // Insert code
}
 
[Update]
public void UpdateTodoItem(TodoItem todoItem)
{
    // Update code
}
 
[Delete]
public void DeleteTodoItem(TodoItem todoItem)
{
    // Delete code
}

Client side


All changes made on the client side are by default immediately persisted to the server, however the upshot framework provides a way to buffer the changes. These changes will only be persisted to the server when the commitChanges method is called. If you want to undo the changes you made, you can call the revertChanges method. This way the situation will be the same as the last commit of the data. If you want to use the buffer functionality, you can do this by putting the bufferChanges property on true in the data source configuration object.



upshot.dataSources.GetTodoItems = upshot.RemoteDataSource({
    providerParameters: { 
        url: "/Ria4HTML-Services-TodoItemDomainService.svc",
        operationName: "GetTodoItems" 
    },
    provider: upshot.riaDataProvider,
    bufferChanges: true,
    entityType: "TodoItem:#Ria4html.Models",
    mapping: TodoItem
});

In the data source configuration object, we also need to provide an entityType and a mapping if we want create new entities on the client side. The mapping contains a JavaScript object that is represents the viewmodel for creating a new entity of the provided entityType. The entityType contains the classname + namespace of the objects on the server side. (Naming convention: <Classname>:#<namespace>)



function TodoItem(properties) {
    var self = this;
    properties = properties || {};
    self.TodoItemId = ko.observable(properties.TodoItemId || 0);
    self.Title = ko.observable(properties.Title);
    self.IsDone = ko.observable(properties.IsDone);
    upshot.addEntityProperties(self); // add properties managed by upshot
};



Todoitem is an example of a mapping object. The only thing we need to do to allow entity tracking, is adding the entity to upshot. This is done by calling the addEntityProperties method with the viewmodel as parameter. When this is done, the entity will be known in upshot and will be persisted to the server when saved.


Now we can start building up our viewmodel



function TodoItemViewModel(dataSource) {
    var self = this;
 
    self.dataSource = upshot.dataSources.GetTodoItems.refresh();
    self.localDataSource = upshot.LocalDataSource({ 
            source: self.dataSource
          , autoRefresh: true 
    });
    // Public data properties
    self.todoItems = self.dataSource.getEntities();
}

This was the situation we currently have when we just want to retrieve data. So lets start implementing our CUD operations.


Inserting Data


The first thing we will do if we want to start inserting data is adding a new instance of the mapping viewmodel to the TodoItemView model.



// Creates a new TodoItem and adds it to the viewmodel
self.newTodoItem = ko.observable(new TodoItem());
// Not possible on the localDataSource only on the remote
self.validationConfig = $.extend(
    {}
  , self.dataSource.getEntityValidationRules()
  , { submitHandler: handleSubmit}
);
 
var handleSubmit = function () {
    // Add new TodoItem to data source
    self.todoItems.unshift(self.newTodoItem()); 
    // Revert form to blank state
    self.newTodoItem(new TodoItem());     
}

Next, we add a validationConfig to the viewModel, this way we can enable client validation. The extend method of jQuery is used to merge multiple objects into the object provided in the first parameter. In this case we are merging the validation rules and an success handler. When using upshot, the rules of validation are added in the metadata and will be retrieve by calling the getEntityValidationRules method on the data source with the data.


If all validations are satisfied the submit handler will get called. In here we will add the new created object (values are filled up due the 2way binding of knockout). to the TodoItem collection of the viewmodel. Next we need to instantiate the newTodoItem property again with a new instance of TodoItem.


Now all the logic is implemented for inserting data, the only thing left to do is adding a view for inserting the data. Note that the autovalidate attribute has to be set on true for every field you want to validate. If you don’t enable this, you will have to call the validate method on each field manually.



<form data-bind="validate: validationConfig, with: newTodoItem">
    <fieldset>
        <table>
            <caption>TodoItem Information</caption>
            <tr>
                <th>Title</th>
                <td><input data-bind="value: Title, autovalidate: true"
                           name="Title" /></td>
            </tr>
            <tr>
                <th>Is done</th>
                <td><input data-bind="checked: IsDone, autovalidate: true"
                           name="IsDone" type="checkbox" /></td>
            </tr>
            <tr>
                <th>Action</th>
                <td><button class="addButton" type="submit">add</button></td>
            </tr>
        </table>
    </fieldset>
</form>

Important: If you want to use validation in combination with RIA services without manually providing the metadata, then you can only apply the binding when the data from the server is retrieved. If you don’t do this, no validation rules will get found when the validationConfiguration object is created. This way the validation rules will always be satisfied (since there are none) and the new entity will be added. So the solution is to provide a success callback on the refresh of the data source.



// necessary for validation
upshot.dataSources.GetTodoItems.refresh({}, function () {
    ko.applyBindings(new TodoItemViewModel());
});

UPDATING DATA


Since we use knockout.js for databinding all changes will be detected automatically and upshot will make sure the entitystate is adjusted as we start changing the entities. 


Removing data


Removing data is done by calling the deleteEntity method on the data source. For this we need to pass the entity we want to delete as parameter. On our view model we can provide a method for handling the delete of an entity



// Public operations
self.removeTodoItem = function (todoItem) {
    self.dataSource.deleteEntity(todoItem);
};

In our view we can then add a button that will handle the deletion of the item. By binding the removeTodoItem function on the parent, the current todoItem will be passed as parameter and will get deleted.



<ol data-bind="foreach: todoItems">
    <li>
        <strong data-bind="text: Title"></strong>
        <button class="removeButton" 
                data-bind="click: $parent.removeTodoItem">remove</button>
    </li>
</ol>



If you are working in buffermode, the entity will still appear in the list. This is because the entity is only deleted on the data source, and will only disappear when you save the changes. If you want to have it disappear immediately, you will have to provide a filter.


BufferMode


The last thing I want to add is how to manually commit or revert changes when the bufferMode is on. This is done by calling the commitChanges or revertChanges method on the data source. In the viewmodel we can provide to methods who enable this functionality



// Operations
self.saveAll = function () { self.dataSource.commitChanges() }
self.revertAll = function () { self.dataSource.revertChanges() }


In the view we can then add 2 buttons to save/revert our changes.



<button data-bind="click: saveAll">Save all</button>
<button data-bind="click: revertAll">Revert all</button>

Sunday, April 1, 2012

Upshot.js & knockout.js: The HTML5 client for WCF RIA services

As Microsoft is leaving the Silverlight track and more and more focusing on HTML5 for cross browser support, solutions are coming for several existing frameworks. One of the things I’ve been looking at is the possibility to reuse your existing RIA services ,who you currently use for your Silverlight application, and port them to HTML5 enabled website.

The HTML client for ria services started as an jQuery plugin called Ria.js. Currently this project is hosted into the ASP.NET Single Page Application. This is a new project template for building single page applications. The advantage of these kind of application is that they run completely in the browser, this way it’s pretty easy to enable your application to be used offline.

The JS client framework used for communicating with the server is Upshot.js. This framework provides a rich context for the object used on the client side, this includes change tracking, paging, sorting, … More information of this subject can be found on the Denver Developers blog. If you prefer watching a live demo, Steve Sanderson gave one at TechDays Netherlands.

While searching on the net for some more information, I noticed that the most examples focus on the use of Web API for communication with the server. Next to the standard Web API provider for upshot, there is also support for RIA and OData. Since my post is handling RIA services, I‘ll be handling the riaDataProvider.

Building a live demo

Perquisite:

So lets get started by creating a new MVC4 project:

Create SPAWithRiaServices project

If we press the OK button, we can choose which MVC4 project template we want to use. In our case we choose for the Single Page Application.

Create SPAWithRiaServices project step 2

This template includes already some models, controls and views, but when we open up the scripts folder, we will see the script references for upshot.js, knockout.js, … Everything we need for building an HTML5 application with a rich context. But these frameworks evolve fast and thank god we have a thing called Nuget so we can easily update to the latest version. So this will be the next step in my tutorial, updating to the latest version of the single page application. Search for SinglePageAppliction in your Nuget packet management explorer or type the following in your package manager explorer:

Install-Package SinglePageApplication.CSharp



Once this is done, we need to add the reference to the ServiceModel so we can use the domainService class of the RIA services. The following references need to be added (note that these references come from the latest version of the WCF Ria Services SDK V1,  C:\Program Files (x86)\Microsoft SDKs\RIA Services\v1.0\Libraries\Server):





System.ServiceModel.DomainServices.Hosting
System.ServiceModel.DomainServices.Server



And one reference from the SP2 of the WCF Ria Services SDK V1, C:\Program Files (x86)\Microsoft SDKs\RIA Services\v1.0\Toolkit\Libraries\Server\SP2\





Microsoft.ServiceModel.DomainServices.Hosting



After adding the references, we need to configure our DomainServices. First thing we need to do is adding a new configuration section for the DomainService






<configSections>
  <sectionGroup name="system.serviceModel">
    <section name="domainServices" 
type="System.ServiceModel.DomainServices.Hosting.DomainServicesSection, 
      System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, 
      Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
      allowDefinition="MachineToApplication" requirePermission="false" />
  </sectionGroup>
</configSections>



Next we add a new httpModule for the DomainService to the system.web section






<system.web>
  <httpModules>
    <add name="DomainServiceModule" 
type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, 
      System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, 
      Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  </httpModules>
</system.web>



In the system.webServer section, we add a new module for the DomainService






<system.webServer>
  <validation validateIntegratedModeConfiguration="false" />
  <modules runAllManagedModulesForAllRequests="true">
    <add name="DomainServiceModule" preCondition="managedHandler"
 type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, 
       System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, 
       Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  </modules>
</system.webServer> 



As last we add the following system.serviceModel config section. Most important thing here is that we add a JSON endpoint, and on this endpoint we add the attribute transmitMetadata=”true” so the metadata is send to our HTML client.






<system.serviceModel>
  <domainServices>
    <endpoints>
      <add name="JSON" 
 type="Microsoft.ServiceModel.DomainServices.Hosting.JsonEndpointFactory, 
       Microsoft.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, 
       Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
       transmitMetadata="true" />
    </endpoints>
  </domainServices>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
        multipleSiteBindingsEnabled="true" />
</system.serviceModel>





Once all the references and configuration sections are added, we can start to code. The first thing we do is adding a new class and let it derive from DomainService.






using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
using SPAWithRiaServices.Models;
 
namespace SPAWithRiaServices.Services
{
    /// <summary>
    /// Domain Service responsible for todo items.
    /// </summary>
    [EnableClientAccess]
    public class TodoItemDomainService : DomainService
    {
        [Query(IsDefault = true)]
        public IQueryable<TodoItem> GetTodoItems()
        {
            IList<TodoItem> todoItems = new List<TodoItem>();
 
            todoItems.Add(new TodoItem() 
                { TodoItemId = 1, Title = "Todo item 1", IsDone = false });
            todoItems.Add(new TodoItem() 
                { TodoItemId = 2, Title = "Todo item 2", IsDone = false });
            todoItems.Add(new TodoItem() 
                { TodoItemId = 3, Title = "Todo item 3", IsDone = false });
            todoItems.Add(new TodoItem() 
                { TodoItemId = 4, Title = "Todo item 4", IsDone = false });
 
            return todoItems.AsQueryable<TodoItem>();
        }
    }
}




The TodoItem Class looks as follows:






using System.ComponentModel.DataAnnotations;
 
namespace SPAWithRiaServices.Models
{
    public class TodoItem
    {
        [Key]
        public int TodoItemId { get; set; }
        [Required]
        public string Title { get; set; }
        public bool IsDone { get; set; }
    }
}



Now we can go to edit the views. The first view we need to change is the _Layout.cshtml in the shared folder. In here we delete the following line:






<script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Scripts/js")"></script>



And add the following references instead:






<script src="~/Scripts/jquery-1.6.2.js" 
        type="text/javascript"></script>
<script src="~/Scripts/modernizr-2.0.6-development-only.js" 
        type="text/javascript"></script>
<script src="~/Scripts/knockout-2.0.0.debug.js" 
        type="text/javascript"></script>
<script src="~/Scripts/upshot.js" 
        type="text/javascript"></script>
<script src="~/Scripts/upshot.compat.knockout.js" 
        type="text/javascript"></script>
<script src="~/Scripts/upshot.knockout.extensions.js" 
        type="text/javascript"></script>
<script src="~/Scripts/native.history.js" 
        type="text/javascript"></script>
<script src="~/Scripts/nav.js" 
        type="text/javascript"></script>


In the index.cshtml in the home folder, we add the following:



The configuration for using upshot






<script type='text/javascript'>
    upshot.dataSources = upshot.dataSources || {};
    
    upshot.dataSources.GetTodoItems = upshot.RemoteDataSource({
        providerParameters: { 
            url: "/SPAWithRiaServices-Services-TodoItemDomainService.svc", 
            operationName: "GetTodoItems" 
        },
        provider: upshot.riaDataProvider,
        bufferChanges: true,
        dataContext: undefined,
        mapping: {}
    });
</script>



The knockout view






<ol data-bind="foreach: todoItems">
    <li><strong data-bind="text: Title"></strong>
        <label>
            <input data-bind="checked: IsDone" type="checkbox" />
            Done
        </label>
    </li>
</ol>



And as last the ViewModel definition






<script type="text/javascript">
    function TodoItemViewModel() {
        // Data
        var self = this;
        self.dataSource = upshot.dataSources.GetTodoItems.refresh();
        self.localDataSource = upshot.LocalDataSource({ 
                                    source: self.dataSource
                                  , autoRefresh: true });
        self.todoItems = self.localDataSource.getEntities();
    }
 
    $(function () {
        ko.applyBindings(new TodoItemViewModel());
    });
</script>



When you run the application now, you should see the todo items appear on your screen with a little delay.



Conclusion



In your HTML applications you can take advantage of your existing RIA services with a minimum of effort. For now the possibilities are limited, but I think in the future we will be able to use all features as they are present in SL. By adding the transmitMetadata attribute to our JSON endpoint, it isn’t necessary to define the model again on the client side.