Colin Eberhardt's Technology Adventures

Integrating Knockout and jQueryMobile

October 23rd, 2012

This blog post looks at the issues regarding integration of KnockoutJS and jQueryMobile, and provides a simple worked example – a Twitter Search application – where the two technologies play nicely together!

The code for this blog post can be found on github. You can also try it out on your phone or browser.

Integration Woes!

Knockout is a great JavaScript framework for managing the various UI layers of your application, and I use it wherever possible. Recently, while I was working on an HTML5-based cross platform project Knockout was the obvious choice and for the Windows Phone version it worked very well.

For the iOS version I wanted to mimic the platform look and feel, which requires quite a lot of CSS, so rather than try to write this all myself I opted for jQueryMobile (jQM) which is a well established framework for transforming HTML into iOS styled pages.

However, integrating Knockout and jQM poses something of a challenge. One of the biggest issues is that jQM is more than just a set of stylesheets; In order to support the iOS styling it processes the DOM adding supporting structure. For example, a simple button is transformed from this:

<button type="submit" data-bind="enable: isSearchEnabled, click: executeSearch">Go</button>

To this:

<div class="ui-btn ui-btn-inline ui-btn-corner-all ui-shadow ui-btn-up-c">
  <span class="ui-btn-inner ui-btn-corner-all">
    <span class="ui-btn-text">Go</span>
  </span>
  <input class="ui-btn-hidden" type="button" value="Go"
                  data-bind="enable: isSearchEnabled, 
                                    click: executeSearch"/>
</div>

This means that any changes to the DOM that occur as a result of changes to the underlying data (via the Knockout bindings), must then be further processed by jQM to add the structures that support the CSS.

My previous HTML5-based JavaScript applications have included a view model, the ApplicationViewModel, which exposes the top most view-model, which is rendered via a suitable template. This makes sense in that it reflects the way that native applications are written and navigated, making handling the Windows Phone back stack an easy task. Although this approach results in a lot of disruption to the DOM as pages are added / removes. Using this approach I was unable to make jQM and Knockout play-nice and opted for simply copying the jQM stylesheets, which is far from ideal (you lose animations and end up with verbose HTML templates).

I decided it was time to re-visit this issue and see if I could come up with a better solution.

Integration -Take Two

The application I am going to present in this blog post is a simple Twitter Search application which I originally wrote for an MSDN Magazine article. It has three view models, each of which has a template which is rendered into the DOM when the user navigates from one page to the next.

I found anecdotal evidence on the web that it was not advisable to re-bind parts of the DOM using ko.applyBindings, this coupled with the fact that it is best to provide jQM with the entire application markup and allow it to do its ‘thing’ all in one go, lead me to try a different approach …

I removed all of the templates and placed them directly within the HTML document, using the standard jQM approach of adding an id for each page:

<!DOCTYPE html>
<html>
<head>
  ...
</head>
<body>
 
  <!-- ### A page that renders the TwitterSearchViewModel -->
  <div data-role="page" data-theme="b"
        id="twitterSearchView">
    <div data-role="header" data-theme="b">
      <h1>Twitter Search</h1>
    </div>
    <div data-role="content" data-theme="c">
      <div>
        <!-- search form -->
        ...
      </div>
    </div>
  </div>
 
  <!-- ### A page that renders the SearchResultsViewModel -->
  <div data-role="page" data-theme="b" data-add-back-btn="true"
       id="searchResultsView">
    <div data-role="header" data-theme="b">
      <h1>Results</h1>
    </div>
    <div data-role="content" data-theme="c">
      ...
    </div>
  </div>
 
  <!-- ### A page that renders the TweetViewModel -->
  <div data-role="page" data-theme="b" data-add-back-btn="true"
       id="tweetView">
    <div data-role="header" data-theme="b">
      <h1 data-bind="text: author">
    </div>
    <div data-role="content" data-theme="c" class="tweet">
      <!-- a simple tweet view -->
      ...
    </div>
  </div>
 
</body>
</html>

Binding to this structure is simply a matter of creating view models that ‘back’ each jQM page and binding them via ko.applyBindings:

// create the various view models
var twitterSearchViewModel = new TwitterSearchViewModel(),
searchResultsViewModel = new SearchResultsViewModel(),
tweetViewModel = new TweetViewModel();
 
$(document).ready(function () {
  // bind each view model to a jQueryMobile page
  ko.applyBindings(twitterSearchViewModel, document.getElementById("twitterSearchView"));
  ko.applyBindings(searchResultsViewModel, document.getElementById("searchResultsView"));
  ko.applyBindings(tweetViewModel, document.getElementById("tweetView"));
});

Note, I could have created a single view model that exposes each of the three view models above and bound this at the body element level. But this would add very little value, it would not make communication between vie models any easier.

Page Navigation

The next problem to tackle is page navigation. With the Twitter Search application, the HTML for the search form is as follows:

<!-- search form -->
<form>
  <input type="search" name="search"
          data-bind="value: searchTerm, valueUpdate: 'afterkeydown'" />
  <input type="button" value="Go"
          data-bind="click: search" />
</form>

The TwitterSearchViewModel which this part of the page is bound to exposes the search function which queries twitter and navigates to the results page:

this.search = function () {
  /// <summary>
  /// Searches twitter for the current search term.
  /// </summary>
 
  if ($.trim(this.searchTerm()) === "") {
    return;
  }
 
  this.userMessage("");
  this.isSearching(true);
 
  twitterSearchService.searchForKeyword(that.searchTerm(), 1, function (tweets) {
    if (tweets.length > 0) {
      // store this search
      addSearchTermToRecentSearches();
 
      // navigate to the results view model
      searchResultsViewModel.init(that.searchTerm(), tweets);
      $.mobile.changePage("#" + searchResultsViewModel.template);
    } else {
      that.userMessage("There were no matches for the given search term");
    }
 
    that.isSearching(false);
  });
};

The interesting code in the above snippet is this part:

// navigate to the results view model
searchResultsViewModel.init(that.searchTerm(), tweets);
$.mobile.changePage("#" + searchResultsViewModel.template);

Here we initialise the searchResultsViewModel which is global variable, providing it with the search result. The call to $.mobile.changePage causes jQM to ‘navigate’ to the next page via a fancy slide transition.

The SearchResultsViewModel takes the valus supplied by the init method and updates its bound observables:

function SearchResultsViewModel() {
  /// <summary>
  /// A view model that renders the results of a twitter search.
  /// </summary>
 
  var that = this,
      twitterSearchService = new TwitterSearchService();
 
  // --- properties
  this.template = "searchResultsView";
  this.tweets = ko.observableArray();
  this.pageNumber = ko.observable(1);
  this.searchString = "";
 
  // --- public functions
  this.init = function (searchText, tweetViewModels) {
    this.tweets(tweetViewModels);
    this.pageNumber = ko.observable(1);
    this.searchString = searchText;
  };
}

Updating the state of the bound view model removes the need for re-binding. The same approach is used for navigating from the SearchResultsViewModel to the TweetViewModel, a single TweetViewModel instance is bound to the UI, with its state updated as required.

List binding

The above code all works quite nicely apart from one snag – lists. A knockout foreach can result in new elements being added to the DOM, each of which have to be processed. Typically a Knockout foreach would be used to populate a jQM listview, which performs all kinds of extra processing to ensure that each list item has the same height etc …

The approach I took is very similar to the one presented in this blog post, which adds a custom binding that ensures that the listview is made aware of new items being added. The one problem I found with the approach presented is that it worked for the initial population of the list, but subsequent updates did not update the custom binding. Combining this approach with a few tips I picked up from StackOverflow, I came up with this solution:

ko.virtualElements.allowedBindings.updateListviewOnChange = true;
ko.bindingHandlers.updateListviewOnChange = {
  update: function (element, valueAccessor) {
    ko.utils.unwrapObservable(valueAccessor());  //grab dependency
 
    // locate the listview
    var listview = $(element).parents()
                             .andSelf()
                             .filter("[data-role='listview']");
 
    if (listview) {
      try {
        $(listview).listview('refresh');
      } catch (e) {
        // if the listview is not initialised, the above call with throw an exception
        // there doe snot appear to be any way to easily test for this state, so
        // we just swallow the exception here.
      }
    }
  }
};

The above is a custom binding that peforms some special logic each time the binding source changes. It locates the jQM listview, which could be the element that the binding is defined upon, or some parent (via a pretty funky jQuery tree traversal ‘query’). When the listview is found, listview('refresh') is invoked, which causes it to re-process all the list items, adding the required jQM markup to any newly added elements.

The ko.virtualElements.allowedBindings line allows this approach to be used for containerless Knockout bindings.

You can see this approach used in practice on the front page of the application where it displays the recent search terms:

<ul data-role="listview"
    data-bind="foreach: recentSearches, updateTableEachTimeItChanges:recentSearches">
  <li>
    <a data-bind="click: $parent.recentSearchClicked">
      <span data-bind="text: $data"></span>
    </a>
  </li>
</ul>

And in the search results view where it is used in the containerless form:

<div data-role="content" data-theme="c">
  <!-- twitter search results -->
  <ul data-role="listview">
    <!-- ko foreach: tweets, updateListviewOnChange:tweets -->
    <li>
      <a data-bind="click: $parent.tweetClicked">
        <div class="thumbnail-container">
          <img data-bind="attr: { src: thumbnail }" />
        </div>
        <h3 class="author" data-bind="text: author"></h3>
        <p class="text" data-bind="text: text"></p>
      </a>
    </li>
      <!-- /ko -->
    <li>
      <a data-bind="click: loadMore" href="#">
        <h3 data-bind="visible: !isSearching()">Load more ...</h3>
        <h3 data-bind="visible: isSearching()">Loading ...</h3>
      </a>
    </li>
  </ul>
</div>

It’s a pretty simple solution, but it took a lot of trial and error to reach this point!

Finally, the iScroll integration, which I previously attempted myself is now achieved via the jquery-mobile-iscrollview plugin, which works like a charm. Just add data-iscroll to any page content and it will scroll.

The code for this blog post can be found on github. You can also try it out on your phone or browser.

Regards, Colin E.

Handling the back-stack in Windows Phone 7 PhoneGap applications

November 28th, 2011

Recently I have been researching the use of PhoneGap for creating HTML5 Windows Phone 7 applications. I have written an introductory post on the subject and also managed to have a HTML5-based application accepted into the marketplace.

The Windows Phone 7 execution model has a few unique features (when compared to Android and iOS) that need to be considered when developing HTML5 applications. The first one, tombstoning, is something I dealt with in an earlier blogpost. In this post I want to look at how to handle the back-button and the application back-stack.

With Windows Phone 7, as the user navigates from one page to the next, the latest page is added to the top of a stack (the back-stack). The back-button is used to navigate back through the pages within your application by pulling them from the top of this stack. When the back stack holds a single page, i.e. the initial entry point into your application, pressing the back-button should exit the application.

So, how do you support this with a HTML5 / PhoneGap application? The PhoneGap APIs expose the back-button as a JavaScript event. If you subscribe to this event, your JavaScript code is notified each time the user hits the back button. Also, if the JavaScript event is subscribed to, the ‘native’ (C#) event is cancelled, which means application back-stack remains unchanged and your PhoneGap application does not exit.

In order to emulate the back-stack functionality in a PhoneGap application you need to subscribe to the PhoneGap backbutton when back-navigation is possible within your application, and unsubscribe when it is not. This means that when the user hits the back-button at the entry-point of your application, the application is terminated as expected.

A JavaScript Back-Stack

In order to handle the PhoenGap backbutton event, I felt the easiest approach would be to structure my JavaScript application so that it more closely resembles a Windows Phone 7 application. I have already discussed in my blog post on tombstoning how this task was made easier by using KnockoutJS to structure my code using the Model-View-ViewModel (MVVM) pattern.

In this blog I’ll add further structure to the code, starting with an ApplicationViewModel. This view model contains a stack of view-models, with the one that is at the top of the stack, exposed via the currentViewModel observable property, being the one which is rendered on screen. The ApplicationViewModel also exposes functions for adding to and removing items from this stack:

function ApplicationViewModel() {
 
  var that = this;
 
  this.viewModelBackStack = ko.observableArray();
 
  this.backButtonRequired = ko.dependentObservable(function () {    
    return this.viewModelBackStack().length > 1;
  }, this);
 
  this.currentViewModel = ko.dependentObservable(function () {
    return this.viewModelBackStack()[this.viewModelBackStack().length-1];
  }, this);
 
  this.navigateTo = function (viewModel) {
    this.viewModelBackStack.push(viewModel);
  }
 
  this.back = function () {
    this.viewModelBackStack.pop();
   }
}

The currentViewModel is implemented as a dependent-observable, a really neat KnockoutJS feature where you create a new observable property based on one or more observable properties of a view model. In this case, KnockoutJS does some magic behind the scenes to deduce that
currentViewModel depends upon the viewModelBackStack observable array. Each time the array is modified, bindings that depend on currentViewModel are also updated. That’s pretty neat!

The UI code needs to be modified so that the view relating to the currentViewModel is rendered and bound to its respective view model. This is simply a matter of subscribing to changes in this observable property in our code:

// create the view model
application = new ApplicationViewModel();
 
application.currentViewModel.subscribe(function (viewModel) {
  if (viewModel !== undefined) {
    $("#app").empty();
    $("#" + viewModel.template).tmpl("").appendTo("#app");
    ko.applyBindings(viewModel);
  }
});
 
application.navigateTo(new TwitterSearchViewModel());

For the above code to work, each view model must expose a template property which identified the HTML template which is their corresponding view. The above code creates a view instance and appends it to the #app element.

The templates for the two view-models within this application are shown below:

<script type=text/x-jquery-tmpl" charset="utf-8" id="twitterSearchView">
  <div>
    <form data-bind="submit: search">
        <input data-bind="value: searchTerm, valueUpdate: 'afterkeydown'" />
        <button type="submit" data-bind="enable: searchTerm().length > 0 && isSearching() == false">Go</button>  
    </form>
    <ul data-bind="template: {name: 'tweetView', foreach: tweets}"> </ul>
  </div>
</script>
 
<script type="text/x-jquery-tmpl" charset="utf-8" id="tweetView">
  <li class="tweet"
    data-bind="click: select">
    <div class="thumbnailColumn">
      <img data-bind="attr: { src: thumbnail }" class="thumbnail"/>
    </div>
    <div class="detailsColumn">
      <div class="author" data-bind="text: author"/> 
      <div class="text" data-bind="text: text"/> 
      <div class="time" data-bind="text: time"/> 
    </div>
  </li>
</script>
 
<script type="text/x-jquery-tmpl" charset="utf-8" id="tweetDetailView">
  <div class="tweet">
    <div class="thumbnailColumn">
      <img data-bind="attr: { src: thumbnail }" class="thumbnail"/>
    </div>
    <div class="detailsColumn">
      <div class="author" data-bind="text: author"/> 
      <div class="text" data-bind="text: text"/> 
      <div class="time" data-bind="text: time"/> 
    </div>
  </div>
</script>
 
<h1 id="welcomeMsg">Twitter Search</h1>   
<div id="app" />

When a tweet is clicked on, the select function is invoked via the Knockout binding. This simply pushes a TweetViewModel instance onto the application stack:

this.select = function () {
  application.navigateTo(this);
};

Handling the Back-Button

The above code navigates to the given view model, this will cause the currentViewModel dependent observable to notify that a change has occurred and the view that relates to this view model will be rendered. The next step is to wire up the back-button.

The ApplicationViewModel has a boolean backButtonRequired dependent observable that indicates whether the JavaScript code needs to handle the back-button. This is true whenever there is more than one item in the back-stack:

this.backButtonRequired = ko.dependentObservable(function () {    
  return this.viewModelBackStack().length > 1;
}, this);

When the application is initially created, we handle changes to the observable as follows:

application.backButtonRequired.subscribe(function (backButtonRequired) {
  if (backButtonRequired) {
    document.addEventListener("backbutton", onBackButton, false);
  } else {
    document.removeEventListener("backbutton", onBackButton, false);
  }
});
 
function onBackButton() {
  application.back();
}

And that’s it! When the JavaScript back-stack has more than one item, the PhoneGap backbutton event is handled and the back function invoked on our application, popping the top item from the stack, with the view updating accordingly. When our JavaScript back-stack has a single item, we no longer handle the PhoneGap backbutton event, this results in the Silverlight container handling the back-button. The Silverlight application back-stack always has a single page, so our application will exit.

This technique for handling the back-stack is simple and elegant, this example has just two simple pages, however this same pattern should scale well for more complex HTML5 applications.

You can download the full sourcecode here: PhoneGapBackStack.zip

Regards, Colin E.