From a List to a Detail View using jQuery Mobile and Backbone.js

In my previous post I built a basic application to demonstrate the use of Backbone.js with jQueryMobile (JQM). The introduction can be found here, with a brief subsequent post on sorting collections here. In this post, I would like to add the capability to view the details of the items presented in the list view.

The first step is to create a new JQM page to display the details view. JQM makes it pretty easy to add pages to your application. I added the following code to the index.html file:

<div data-role="page" id="activity-details" data-add-back-btn="true">
  <div data-role="header">
    <a href="#" data-role="button" data-icon="arrow-d" id="edit-activity-button" class="ui-btn-right">Edit</a>
    <h1>Activity Details</h1>
  </div>
  <div data-role="content" id="activity-details-content">
      <!-- the contents of the list view will be rendered via the backbone view -->
  </div>
</div>

This will create the structure for the page, and Backbone.js will be used to fill in the content div based on the record tapped (or clicked) within the list view. The next step is to define the template for the details view. This is the pattern that I follow when developing using jQueryMobile and Backbone.js. The template can be inserted below the list item template in index.html.

    <script type="text/template" id="activity-details-template">    
        <h3><%= type %></h3>
        <ul data-role="listview" id="activitiy-fields" data-inset="true">
          <li>Date: <%= date %></li>
          <li>Minutes: <%= minutes %></li>
          <li>Distance: <%= distance %></li>
          <li>Comments: <%= comments %></li>
        </ul>
    </script>

I decided to embed the details in a read-only list view, this way jQueryMobile will provide some reasonable styling. Since the point here is to demonstrate Backbone and jQueryMobile playing together, I didn’t want to have to spend much time on style 🙂

Next, we need to define the Backbone view that will use the template to render the appropriate content. All this View needs to do is apply the model to the template and append it to the HTML container defined when the View is instantiated.

    exercise.ActivityDetailsView = Backbone.View.extend({
        //since this template will render inside a div, we don't need to specify a tagname
        initialize: function() {
            this.template = _.template($('#activity-details-template').html());
        },
        
        render: function() {
            var container = this.options.viewContainer,
                activity = this.model,
                renderedContent = this.template(this.model.toJSON());
                
            container.html(renderedContent);
            container.trigger('create');
            return this;
        }
    });

In order to retrieve the correct model to bind to the details view, we need to know what row in the list view was clicked (or tapped). To do this, we can bind to the click event of the item in the list view. This can be accomplished by modifying the ActivityListView render method. Here is the current version of the list view:

    exercise.ActivityListView = Backbone.View.extend({
        tagName: 'ul',
        id: 'activities-list',
        attributes: {"data-role": 'listview'},
        
        initialize: function() {
            this.collection.bind('add', this.add, this);
            this.template = _.template($('#activity-list-item-template').html());
        },
        
        render: function() {
            var container = this.options.viewContainer,
                activities = this.collection,
                template = this.template,
                listView = $(this.el);
                
            $(this.el).empty();
            activities.each(function(activity){
                listView.append(template(activity.toJSON()));
            });
            container.html($(this.el));
            container.trigger('create');
            return this;
        },
        
        add: function(item) {
            var activitiesList = $('#activities-list'),
                template = this.template;
                
            activitiesList.append(template(item.toJSON()));
            activitiesList.listview('refresh');
        }
    });

The key area to focus on here is lines 18-20, where the activity item HTML is rendered and appended to the list view. This is where the modification needs to occur. Each activity item HTML element needs to be bound to a click event. In this click event, the activity id will need to somehow be passed to the details view so the appropriate look up can occur. There are several ways to do this. Approaches I have used in the past include the use of jQuery.jqmData(...) or session local storage (assuming HTML5). In this example, we will use the jqmData method. The trick is to capture the id during the rendering of the list so that it can be used during the execution of the click event. Below are the required modifications to lines 18-20 of the previous code snippet.

activities.each(function(activity){
   var renderedItem = template(activity.toJSON()),
       $renderedItem = $(renderedItem);  //convert the html into an jQuery object
   $renderedItem.jqmData('activityId', activity.get('id'));  //set the data on it for use in the click event
   $renderedItem.bind('click', function(){
         //set the activity id on the page element for use in the details pagebeforeshow event
        $('#activity-details').jqmData('activityId', $(this).jqmData('activityId'));  //'this' represents the element being clicked
   });
   listView.append($renderedItem);
});

The first thing is to capture the rendered activity item HTML in a variable and cast it to a jQuery object, as can be seen in lines 2-3. Line 4 is where the activity id data is attached to our activity item HTML element. Then, the bind event (lines 5-8) retrieves the attached data and sets it on the activity details HTML element, which is our activity details page HTML. Per the jQuery documentation, this within a bind method refers to the DOM element to which the event handler is bound. We can use that to get at the activity id data and attach it to the details view. This gives us the ability to pass the appropriate id at runtime to the details page.

Now we need to wire all this together. The typical pattern I follow is to use the jQueryMobile pagebeforeshow event to set up everything needed to render a complete page. This acts as my controller.

$('#activity-details').live('pagebeforeshow', function(){
    var activitiesDetailsContainer = $('#activity-details').find(":jqmData(role='content')"),
        activityDetailsView,
        activityId = $('#activity-details').jqmData('activityId'),
        activityModel = exercise.activities.get(activityId);
    
    activityDetailsView = new exercise.ActivityDetailsView({model: activityModel, viewContainer: activitiesDetailsContainer});
    activityDetailsView.render();
});

This code retrieves the id data attached to the activity details element, looks up the model using the Backbone API (line 5), instantiates the view with the model and the view container, and calls the render method to render the final HTML. The end result looks like this:

The source code for this post can be found here. Note that this is a branch of my repository for this sample app. Each blog post associated with this code base resides on a separate branch.

Related Posts:

This is a re-post of my original found here.

NSFetchedResultsController and Core Data Managed Object Updates

In the process of working on an iPhone project for a client, I came across an interesting problem. I was using Core Data to store domain related information. This data is being pulled from a server, parsed, and persisted on a background thread. The main thread of the application presents a list of information based on what is persisted. In this application, I chose to use an NSFetchedResultsController to manage querying the underlying Core Data store and implemented the standard UITableView delegate methods. I won’t go into detail on how to do that here, but there are many good write-ups out there, including this very good one: How to use NSFetchedResultsController.

I was updating the underlying data store in a background thread with its own managed object context, as is the standard practice. As a result, the NSFetchedResultsController’s managed object context was unaware of the changes to the data store. This was easily fixed by subscribing to the NSManagedObjectContextDidSaveNotification notification in my table view controller class.

[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(refreshData:) 
                                             name:NSManagedObjectContextDidSaveNotification 
                                           object:nil];

The notification that gets fired includes all the information about the changes to the underlying data store (Inserts, Updates, and Deletes). The nice thing here is that there is a convenience method on the managed object context for merging these changes in, as shown in the snippet below:

- (void) refreshData:(NSNotification *)notif {
    [[[self fetchedResultsController] managedObjectContext] mergeChangesFromContextDidSaveNotification:notif];
}

On the surface, this worked beautifully. Once the changes are merged in, the NSFetchedResultsController delegate methods start firing. This is where my struggle occurred. All the proper delegate methods fired correctly, but the underlying result set for the NSFetchedResultsController still had the old data. I combed through the documentation, code samples, etc. and it seemed that I was doing everything correct. I came up with some hack arounds, but they were too ugly…and just wrong. I knew there had to be something I was missing.

I could see that the object that I updated in my main thread’s managed object context was a faulted object (see Apple’s description of a Faulted object here if you aren’t familiar with this concept). What I didn’t realize is that when accessing attributes of a faulted object, the store will retrieve the underlying data from the previously fetched data first, then from the underlying data store. How long the previously fetched data is used is based off of the staleness interval attribute of the managed object context. BTW, this defaults to infinity 🙂

So, the addition of 1 line of code when creating my NSFetchedResultsController to set the staleness time interval to 0 fixed everything.

[context setStalenessInterval:0];

Now my NSFetchedResultsController delegate methods were able to update the table view with the updated data as expected.

For those of you following the tutorial referenced above, one change is necessary if you use a sectioned table view by setting the sectionNameKeyPath when initializing the NSFetchedResultsController.

In the didChangeObject delegate method, the NSFetchedResultsChangeMove case should look like this:

case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] 
                             withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] 
                             withRowAnimation:UITableViewRowAnimationFade];
            break;

Otherwise you will get an error under certain use cases (e.g. changing/removing the last item in a section so the section gets deleted).

Trying out Adobe Flex for Mobile Development

Let me start by saying I have never developed using Flex or any Adobe tools.

I’ve heard a lot of mentions of Adobe’s tools for Android and iOS development and decided to give them a try. The first thing I thought I needed to do was download some tools and SDKs. Was it Flash, Flex, Flash Builder for making Flex apps, AIR…..Adobe doesn’t make it easy. In the end, I found a nice step-by-step tutorial here: http://www.adobe.com/devnet/flex/articles/twitter-trends.html.

Tools
Turns out I needed Flash Builder 4.5, which allows you to develop Flex apps, and somehow AIR fits in here….beyond breathing of course. Actually, it provides the runtime for Android and ends up being packaged as part of the iOS app, acting as “captive runtime” (according to Adobe docs). I guess if you are familiar with Adobe tools, this is all obvious….but as I mentioned, my background is mobile development, not flex/flash development.

It was time to install the IDE. Flash Builder 4.5 is based on Eclipse….OK, maybe I can install an Eclipse plug-in instead. Turns out they do have an Eclipse plug-in, but you have to install their full version of Eclipse first, then you can install the plug-in into your version of Eclipse….WHAT?. So I downloaded a trial version of Flash Builder and installed it.

Following the Tutorial
The tutorial was well written and easy to follow. The only correction needed was the trends URL for twitter, which is this: http://api.twitter.com/trends.json. There is a lot of clicking and code templates that come into play…and I am certainly no fan of coding in XML….I kind of feel dirty doing it. But the tools do provide some nice abstractions, like making it really easy to connect to an HTTP service and make REST calls and handle JSON or XML. They also have a pseudo emulator that you can use during development. It is incredibly basic, but you can do things like simulate rotation, let it act like different OS’s….but it really just looks like a headless browser window.

Next up, the part of the tutorial where you get to deploy the app to an actual device. This is the part I was waiting for. How would it perform? Can I debug? How does it work on iOS? All these questions were ready to be answered.

Running/Debugging
Running and debugging on an Android device was simple and worked fine. It was a different story for iOS. First you had to get your developer certificate in p12 format….hopefully you remember your password, if not you will need to re-export it from keychain. You also need you provisioning profile. Once you get this configured, you have to build the app for iOS, copy it into iTunes, sync your device so the app gets loaded, then run the app. Debugging involves all these steps, plus your development machine needs to be on the same network as your device and debugging occurs over WiFi. Really. And every time you make a change….delete the app from the device, rinse, and repeat. Really, really. It took me several IDE restarts and iOS device disconnect/reconnect cycles to get the debugging to actually work….but once it did…(insert sarcasm) well, it worked (end sarcasm).

Conclusion
On the devices, the app didn’t really perform that well. Scrolling was bit jerky and transitions seemed slow. Oh, and the tutorial neglected to give you a back button for iOS devices to be able to pop the view off the navigation stack. I’ll post a little work around/additional code for that separately.

In the end, the tools are OK. The language (MXML/ActionScript) is not my cup of tea. The performance on the devices was really mediocre. Not being a Flex developer, I am not sure if that can be improved…it was only a tutorial. I think for simple apps, this might be an alternative. If you are a Flex developer, this is definitely and option to allow you to create mobile applications. Will I use it, doubtful. I’ll stick to either native app or mobile web development.