Wednesday, March 26, 2014

Combo breaker: Cordova + JQM + Windows8

This blog post is about issues you probably come across while you're creating a Windows 8.1(not phone) hybrid app with Cordova. First of all let me set some context variables. I use Cordova 3.4, jQuery Mobile 1.4.0, jQuery 2.0.2 and i'm creating a Windows 8.1 app for a desktop or tablet environment. I've already created the app for Android 2.3 or higher, and iOS 6 or higher.

I'll try to explain the whole process step by step. If you have any questions feel free to reply.

1. Update Cordova and its plugins
If you want to support Windows 8 you have to update to the newest version of Cordova. You can simply update Cordova using the following commands:
> npm update -g cordova
Or if you want a specific version:
> npm update -g cordova@3.4.0
Update the plugins after you've updated Cordova. Just re-add the plugins and they will be updated to the newest version. For more information please visit the Cordova manual.

2. Add Windows 8 platform
Add the Windows 8 platform to your Cordova project. Use the following command:
> cordova add platform windows8
3. Try building your project
Use the Visual Studio Command prompt(VS2012 x64 Cross Tools Command Prompt) to build a Windows 8 platform. This is needed because Cordova uses Visual Studio's build tools to create your Windows 8 app.
> cordova build windows8
4. Try running your App
Open your solution in Visual Studio after you've build with Cordova. Try to run your App on your Local Machine. You might get some JavaScript errors but that depends on whether or not you've added a platform to an existing project. In my case, I've already created an App with thousands of lines of JavaScript. That JavaScript needs to be compatible with the new Internet Explorer and WinJS environment. You probaly run into some issues, this is what I ran into:

Usage of unsafe HTML elements and attributes.
HTML thats get injected into your page needs to be filtered by the toStaticHTML method. This method sanitizes your HTML and strips all unsafe elements and attributes. The list of unsafe elements and attributes can be found here. Does this mean that you can't inject an image element with a source attribute? No it doesn't, you can add it but you can't inject strings with unsanitized HTML. You should add HTML elements with unsafe elements in the following way:

/* Old way */
h1Element.prepend("<img src='someUrl.png' />");
/* New way */
var img = document.createElement("img");
img.className = "icon";
img.src = "someUrl.png"
h1Element.prepend(img);

Invalid use of dynamic content using jQuery Mobile.
One of the most difficult problems is the dynamic content issue that is caused by jQuery Mobile. jQuery Mobile uses AJAX navigation to switch between pages, this means that the content of the new page is injected in the old page. Therefore you don't have to add all JavaScript references to all pages. This works great, however it causes the following issue in Windows 8:

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.

The exception is thrown on the appendChild of jQuery. appendChild adds the HTML from the new page to the old page. However, the new page contains, in my case, some unsafe HTML. I use for attributes on label elements. This causes the error because the for attribute is considered unsafe. I haven't found a solid fix yet and therefore I present my workaround.

You can add unsafe HTML in WinJS but you have to wrap the call in a MSApp.execUnsafeLocalFunction. This will prevent WinJS from throwing an error when unsafe HTML gets added to the DOM. Unfortunately appendChild isn't the only place that throws errors when jQuery Mobile switches between pages that contain unsafe HTML. You should wrap the domManip function in a execUnsafeLocalFunction. Look at the following code:

// Check if the userAgent is Microsoft Internet Explorer
// This doesn't work in Internet Explorer but only in Windows 8 (Metro) apps.
if (/MSIE/.test(navigator.userAgent)) {
    // Cache the old domManip function.
    jQuery.fn.oldDomManIp = jQuery.fn.domManip;
    // Override the domManip function with a call to the cached domManip function wrapped in a MSapp.execUnsafeLocalFunction call.
    jQuery.fn.domManip = function (args, callback, allowIntersection) {
        var that = this;
        return MSApp.execUnsafeLocalFunction(function () {
            return that.oldDomManIp(args, callback, allowIntersection);
        });
    };
}

Invalid objects XML Document.
My App which handles large amounts of business data uses SOAP to communicate with the back-end. The XML response from the back-end is without any conversion used a data source in the App. The XML gets stored in a so called reader object which can extract the rows and columns from the XML, this worked fine until Windows 8 came around the corner.

The XML documents become invalid after a certain amount of time. You can't do anything about it, even caching the requests or documents them self don't solve the problem, it only delays the problem. All calls to a XML document that has become invalid throw an "Invalid calling object" error.

One solution to this problem is converting your XML document to JavaScript Objects. This may hurt your performance but the lifetime of JavaScript objects is in your hand and not in Microsoft's.

5. Test App
You have to test your App if you want to upload it to the Windows Store. This test can be done after you've created the app package. A package can be created by: right-clicking on your project > Store > Create App Packages. After the package is created Windows will prompt you with  a dialog to start the Windows App Certification Kit. Run all tests and see if the test fails. Don't interact with your system while the kit is running its tests. It may influence the test results, yes I tried it :) And grab a cup of coffee because it takes several minutes...

My App failed because the encoding of the JavaScript and CSS files were incorrect. They need to be UTF-8 encoded. I searched the web for a simple PowerShell script to encode the files correctly, and modified the script to be recursive and look for CSS and JavaScript files.

Make sure that you run the PowerShell script in the platform\windows8 folder. It will screw up your project if you run it in the project folder. Here is the script:

Get-ChildItem .\* -include *.js,*.css -Recurse | ForEach-Object {
    $content = $_ | Get-Content
    Set-Content -PassThru $_.Fullname $content -Encoding UTF8 -Force
}

6. Deploy the App
With the build comes a PowerShell script to deploy your App locally. Note that this script installs the certificate that is selected in the appxmanifest. This can be a security risk, since you probably don't store your key in a (Software) vault. The PowerShell script can be found in the AppPackages folder in the Visual Studio's project folder.

I haven't deployed my App to the Windows Store yet. I'll add a description to this blogpost when I have.

References: