Getting jQuery Mobile and knockout.js to play together
My current project is an HTML5 application to assemble text snippets for use in email or SMS messages – TextByNumbers. I have to say that the experience has been pretty good. The two main frameworks I used were jQuery Mobile and knockout.js. I said at the time I would do a separate post about getting them to play nicely together, and here it is.
First of all I started to use the jQuery Mobile ListView for everything because it looked so cool and was very easy to get going. However I soon found that I was trying to twist the ListView beyond what it was intended to do. For instance one of the pages has a list where each item is a label, an edit field and a button. I did try to do this using a list view but it became very difficult to attach bindings to elements that were being dynamically generated by jQuery Mobile.. Instead I fell back to using what I would usually use to do this – a knockout.js foreach
<div id='tokens'> <!-- ko foreach: userTokenStorageContainer.persistentArray --> <div class="ui-bar ui-bar-a" data-type="horizontal"> <div data-mini="false" data-bind="text: name"></div> <input type="text" data-bind="value: value"/> <div data-role="controlgroup" data-type="horizontal" data-mini="true"> <a href="#" data-bind="click: $root.removeToken" data-theme="b" data-role="button" data-icon="delete" data-iconpos="notext">Delete</a> </div> </div> <!-- /ko --> </div>
All was good the generated page looked like this
However if I dynamically populated the bound object userTokenStorageContainer.persistentArray then the page would not refresh properly
This was a recurring problem as jQuery Mobile would render correctly when bound to knockout.js objects but if those object were then updated the display would not refresh. Luckily there was comparatively few operations that would update the data so I could manually ask jQuery Mobile to refresh the display like this
self.addFromMessage = function () { var numberOfTokens = self.tokenProcessor.tokens().length; for (var tokenIndex = 0; tokenIndex < numberOfTokens; tokenIndex++) { self.userTokenStorageContainer.addItem( new UserToken( self.tokenProcessor.tokens()[tokenIndex].name(), self.tokenProcessor.tokens()[tokenIndex].value() ) ) }; self.refreshTokens(); }; self.refreshTokens = function () { // force JQM to update UI $('#tokens').trigger('create'); };
The main issue is which jQuery Mobile method / event to use to get the page to refresh correctly. For ListViews we need to call refresh like this
$(listview).listview('refresh');
And other times it is necessary to trigger the page create like this
if ($.mobile.activePage) { $.mobile.activePage.trigger('pagecreate'); }
Another problem was page navigation. On the same page I needed the Message button to go to another page but also persist the bound data objects. At first I bound the button to this javascript
self.gotoMessage = function () { self.saveAll(); $.mobile.changePage("message.html"); };
However jQuery Mobile was being too clever in the way it did the page navigation meaning that the knockout bindings were not being run.
Going old school in the javascript
self.gotoMessage = function () { self.saveAll(); location.href = "message.html"; };
Gave the correct page
So jQuery Mobile and knowckout.js can play well together but some care is needed.