Wednesday, February 18, 2015

Async Cordova Windows plugin

Recently I had to write a Cordova plugin for Windows Phone 8.1 and the Windows RT and desktop environment. I went to the Cordova plugin documentation and was disappointed by the lack of information. Hopefully this blog post will help you out.

So I assume that you know a little bit about a Cordova plugin structure. Please take a detailed look at existing plugins when you don't. One important part of a plugin are its platforms. Note that Windows and WP8 are two different platforms. Windows will apply on Windows Phone 8.1 and Windows RT/Metro while the WP8 platform will only apply on Windows Phone 8. This is because the Windows platform generates an Universal App which can be applied on almost every Microsoft Windows runtime.

Every plugin has a plugin.xml which contains information about the files of the plugin. Note that the example below is a stripped version of a plugin.xml file. Every plugin.xml should contain 1 or more platform elements with the supported platforms. Make sure that you create a separate xml element for every platform (ios, android, wp8, windows, etc).

<plugin id="com.foo.bar"
        version="1.0.0"
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns="http://apache.org/cordova/ns/plugins/1.0">
    <name>FooBar</name>
    <platform name="windows">  
        <framework custom="true" src="src/windows/FooBar.dll"></framework>

        <js-module name="WebSqlProxy" src="src/windows/FooBarProxy.js">
            <runs />
        </js-module>
    </platform>
</plugin>

You can specify additional files inside the platform element. These files will be loaded into your application or executed in the Cordova.js context. The framework elements are files that will be added to your application and usually contain native libraries, for Windows usually DLL or WINMD files. WINMD files are commonly used for Windows Phone projects.

Next to the native library files there are JavaScript files inside the js-module elements. There are two kind of JavaScript files that you want to add, namely the proxy JavaScript files and functional JavaScript files. The proxy JavaScript files are used to build a bridge between your native library calls and the JavaScript calls. The functional JavaScript files on the other hand are commonly used to initialize the JavaScript or for example extend the window object with a certain namespace.

Inside element inside the js-module element states the mode of the execution of the JavaScript file. There are several options available but the two major ones are <runs \>, which just executes the JavaScript file and <merges target="x"> which gives you the power to merge a JavaScript object with a certain JavaScript object, such as window.

So there's the plugin.xml. So lets get back to those native libaries. I told you that the Windows platform generates a Universal App. The native library of your plugin should also be a library for Universal Apps. You can create one using Visual Studio > New Project > Templates > Store Apps > Universal Apps -> Windows Runtime Component project. Please choose your name carefully because your namspace is very important. I will use Nimrod.FooBar as namespace in my example.

using System;

namespace Nimrod
{
    // This class must be sealed because Cordova only recognizes sealed classes.
    public sealed class FooBar
    {
        public static string Echo(string value)
        {
            return value;
        }
    }
}

Compile the project and look for the WINMD file. This file should be added to the src\windows plugin directory. Also add it to the plugin.xml as a framework file. After that you can write a JavaScript proxy. It will look like this:

module.exports = {
    echo: function(success, fail, args) {
        var value = args.shift();
        var res = Nimrod.FooBar.echo(value);
        if (res != undefined) {
            success(res);
        } else {
            fail();
        }
    };
};
require("cordova/exec/proxy").add("Nimrod", module.exports);

The module.exports object is extended with an echo function. This function has a call to the native library; Nimrod.FooBar.echo(value). Please note that this syntax is confirm the Namespace.Class.Method syntax used in the native library. The Cordova proxy will map this JavaScript call to the native library.

So how do you use the plugin in your code? Well note that the module.exports is added with Nimrod as key. This will result in a Nimrod object on the window object. So use can use the plugin like this:

// This is a Cordova event that states that all plugins are loaded.
document.addEventListener("deviceready", function () {
    // window.Nimrod doesn't exist in iOS or Android.
    if(window.Nimrod) {
        window.Nimrod.echo(
            function(res) {
                console.log(res);
            }, function () {
                console.log("Something went wrong");
            }, 
            "Hello world"
        );
    }
});

So this is great isn't it? Well not exactly. What if my native library does a time consuming action, such as a database call or a mathematical calculation? My App will be blocked for the entire call. This is because the plugin isn't setup asynchronously. So lets fix that.

The .NET framework has excellent support for asynchronous programming. Especially .NET 4.5 or higher. Please take a look at this example:

using System;
using System.Threading.Tasks;
using Windows.Foundation;

namespace Nimrod
{
    // This class must be sealed because Cordova only recognizes sealed classes.
    public sealed class FooBar
    {
        public static IAsyncOperation Echo(string value)
        {
            return FooBar.doSomeTimeConsumingThings(value).AsAsyncOperation();
        }

        private static async Task doSomeTimeConsumingThings(string value)
        {
            string result = null;
            
            await Task.Run(() =>
            {
                Task.Delay(1000);

                result = value;
            });

            return result;
        }
    }
}

The echo method now returns an IAsyncOperation instead of the string. This AsyncOperation has as effect on the return value in the JavaScript proxy. Instead of being the string value it now will be an JavaScript promise. This will not be blocking the JavaScript. Take a look at the updated proxy code:

module.exports = {
    echo: function(success, fail, args) {
        var value = args.shift();
        var promise = Nimrod.FooBar.echo(value);
        promise.done(function (res) {
            success(res);
        });
        promise.fail(function () {
            fail();
        });
    };
};
require("cordova/exec/proxy").add("Nimrod", module.exports);

I hope this helped, if you have any questions feel free to ask. I might be able to help you out any further.

Thursday, February 5, 2015

Combo breaker reloaded

In March 2014 I wrote a blog post about some issues I ran into while developing an HTML5 App using Cordova and JQuery Mobile for Windows 8. Well it has been 10 months and I would like to share some additional solutions to those problems. Especially the "dynamic content" and "usage of unsafe HTML" issues.

As you probably know, jQuery Mobile uses AJAX navigation to switch between pages. This creates a richer user experience but also causes some issues on the Windows platform(both metro and phone). Your page might contain some unsafe HTML which will cause the following error:

Unhandled exception at line 5472, column 5 in ms-appx://<app identifier>/www/js/jquery/jquery-2.0.2.js

0x800c001c - JavaScript runtime error: Unable to add dynamic content. A script attempted to inject dynamic content, or elements previously modified dynamically, that might be unsafe. For example, using the innerHTML property to add script or malformed HTML will generate this exception. Use the toStaticHTML method to filter dynamic content, or explicitly create elements and attributes with a method such as createElement.  For more information, see http://go.microsoft.com/fwlink/?LinkID=247104.

If there is a handler for this exception, the program may be safely continued.

In the previous blog post I created a hack in jQuery with a MSApp.execUnsafeLocalfunction wrapper which allows adding dynamic content. Well this hack was highly volatile and probably incomplete. Luckily for you and me we have some smart guys at MSOpenTech which solved this problem for us. They created a "JavaScript Dynamic Content shim for Windows Store apps".

The shim basically wraps all unsafe properties and functions in a MSApp.execUnsafeLocalfunction call. This will prevent WinJS from throwing exceptions when you dynamically add unsafe HTML. Please take a look at their Github page for more information.

Hope this helps!