I’ve spent the last month coding a mobile application for a client using a very fun set of tools:
- knockout.js – An awesome MVVM data-binding framework built in Javascript
- jQuery Mobile – A must have Javascript framework for UI theming
- Apache Cordova Framework (AKA PhoneGap) – A multi-platform HTML5-based mobile app framework
For my UI-rendering implementation, I chose to use the really awesome template: data-binding functionality within knockout.js to inject div “views” into a single HTML page as the user navigates around. These views are bound to knockout ViewModels and leverage the standard knockout data-bind paradigm for data rendering.
The Base Example
My knockout view models were as follows:
ApplicationViewModel – The housing VM for the entire application. It controlled the view navigation. In this example, it’s stripped down to just loading the logonViewModel for simplicity.
LoginViewModel – The VM controlling the user login/out state. In this example, it’s stripped down to a fake login call.
Page Initialization – The code to initialize the page to the correct ViewModel bindings in Knockout
The HTML Markup – The page body that is bound to my ViewModels
The UI – Functional but completely un-styled
Now What?
After getting the business logic completely implemented, it was time to go back and style the app to look really killer. To do this, I chose the simplicity of jQuery Mobile’s theme-roller and it’s data attribute capabilities for theme application. This makes the maintenance of the application CSS really simple and the HTML attribute mark-up clean and concise.
This is where I ran into my first “gotcha”…
After applying my themes, I was seeing that all non-templated content was themed appropriately, but any injected div “views” weren’t getting themes applied!
Here’s the modified version of the HTML Markup from above
The resulting UI – still not styled!
So, what’s the problem here?
I and my colleagues were nearly positive that the issue lay in the timing of the dynamic content injection as it related to the theme application. After doing some research on how to re-apply jQuery Mobile themes as dynamic content is injected into a page, it turns out the solution has a few parts.
Part 1
The first part to this problem is ensuring that the template view has themes applied after view-injection has completed. This is done by adding some jQuery code to the “afterViewRendered” event handler of the knockout.js template binding.
Here’s the added code as it relates to the example from above:
The resulting UI – It’s fixed!
Alas, this doesn’t work for ALL knockout-bound dynamic content.
Part 2
The second part of the solution is to tell jQuery theme objects to refresh themselves whenever dynamic content from knockout.js has changed. For example, a jQuery listview (<ul data-role=”listview” />) bound to an observableArray form knockout.js will need to have it’s list view theming refreshed whenever the list changes.
The code to update a listview:
Thoughts
The toolset of knockout.js / jQuery Mobile / PhoneGap is a very compelling solution for rapidly developing multi-platform mobile applications. Having said that, we all need to realize there are always going to be nuances that need to be ferreted out when integrating between frameworks. This example above just happens to be one of them. If you haven’t tried using this toolset yet, I highly recommend taking the time to give it a shot.
Nick Nieslanik
VP of Engineering
Have you deployed your app to your phone yet and tested it out? I just got a JQM+Phonegap app rejected because JQM is slowwwww inside of native apps.
Sorry for the late response on this. We have loaded the app onto a variety of iOS devices as well as a few android devices. The performance seemed fine. We just submitted to the app store and are currently in the review queue. I’ll keep you posted on the final status.
-Nick
Hello Nick,
Interesting post!
I am also using the same combination of tools(PhoneGap + jQuery Mobile + Knockout) for a mobile app.
However I am using separate HTML for each view.
There are few design decisions which are not quite clear to me which would be better to implement.
For example, how are view-models created and bound to the views? I don’t want to have VMs created from the start, I want them created only when view is actually rendered.
I was able to implement this but I can’t say it’s clean nor it’s the best implementation.
I would love to hear from your experience.
Thanks,
Andrew
Andrew, Your want to create viewmodels as views are rendered is a good one! I did in fact implement a solution along those lines, but simplified the housing “ApplicationViewModel” down for the blog post. Our real implementation used a knockout observable array to host a stack of viewmodels to manage navigation. With each navigation action taken by the user, a viewmodel is created & pushed onto the stack. Then we used the ‘template’ binding in knockout to bind the current top-of-stack viewmodel to a ‘div’. The trick here was to allow the viewmodels to define their associated view by name, so that the act of pushing the viewmodel onto the stack triggers the navigation. An example:
function ApplicationViewModel() {
/// The view model that manages the view model back-stack
//the default view for the application, shown when a user is not logged in.
this.template = "welcomeView";
// The observable array managing the UI navigation back stack for the application.
this.viewModelBackStack = ko.observableArray();
// The computed current view model to display in the UI.
this.currentViewModel = ko.computed(function () {
var stacklength = this.viewModelBackStack().length;
if (stacklength > 0) {
return this.viewModelBackStack()[stacklength - 1];
} else {
return this;
}
}, this);
// The computed indicator denoting if a back button should be shown.
this.backButtonRequired = ko.dependentObservable(function () {
return (this.viewModelBackStack().length > 0);
}, this);
//functions
// Navigates to the viewModel indicated, optionally clearing back stack history.
this.navigateTo = function (viewModel, clear) {
if (clear != undefined && clear == true) {
this.viewModelBackStack([]);
}
this.viewModelBackStack.push(viewModel);
};
// Navigates back to the previous view if possible.
this.back = function () {
this.viewModelBackStack.pop();
};
// Fetches the view to display for the current viewModel.
this.templateSelector = function (viewModel) {
return viewModel.template;
};
this.afterViewRender = function (elements) {
};
};
And the associated template binding in HTML
<div style="vertical-align: center;"
data-bind="template: {name: templateSelector, data: currentViewModel, afterRender: afterViewRender}">
</div>
hi ,
can you please let me know if there are multiple templates how to call
$(#id).trigger(“create”)
on them when they are rendered
thanks in advance
Very cool application, it looks as though you enjoyed coding it. Thanks for the share.
I am also a fan of using PhoneGap, jQuery Mobile and KnockoutJS especially for enterprise apps. I am also using jQuery Templates, but found a way to externalize them as their own file and wrote a “view loader” (see https://github.com/techarch/jquery-viewloader). By using a very modular approach and MVC-style patterns it is then possible to create a complex yet maintainable app. Check-out my tutorial on “creating mobile apps with KnockoutJS, PhoneGap, and jQuery Mobile” at: http://blog.monnet-usa.com/?p=420