Showing posts with label mobile web. Show all posts
Showing posts with label mobile web. Show all posts

Friday, May 4, 2012

Camera Access from Android Browser

The browser in Android 4 is able to take pictures from a web page and display them without any plugins or server interaction. This demo uses the Device API and File API to take and display a picture on a phone using the browser.

Try it on your Android phone http://don.github.com/html-cam/.

I wrote this demo after reading David Calhoun’s post about Android implementing device APIs.

This works on my Google Nexus S running Ice Cream Sandwich (4.0.4). The Device API works on Samsung Galaxy Tab 10.1 with Honeycomb (3.2) but the File APIs do not. YMMV.

Loading images from the gallery is finicky. Images load from the device but fail to load from Picasa albums. I had to alter the image blob and change data:base64 to data:image/jpeg;base64 to get photos from galleries to display.

I kludge the photo orientation based on the device orientation, clearly there is a better solution.

It's possible to run the browser out of memory. I'm guessing this is because a huge photo is encoded into a base64 string. Maybe window.URL.createObjectURL(file) would work better than reading the image with FileReader?

Adjusting the input tag can control the input types:

<input type="file" />
Browser prompts for camera, camcorder, sound recorder, 
  music track, or gallery

<input type="file" accept="image/*" />          
Browser prompts for camera or gallery   

<input type="file" accept="image/*;capture=camera" />           
Browser goes directly to the camera

For more info see the github project.

UPDATE 2012-05-06
It looks like Facebook's mobile website allows camera access from a browser on Android.

Wednesday, February 22, 2012

Using jQueryMobile and Backbone.js for handling forms

Introduction

In this post, I continue the development of my basic "exercise app" that I started (and enhanced) in these posts:

Let's add the ability to create and edit exercise records.

Adding a new activity

Just like in the previous post, the first thing we need is a jQueryMobile page to hold the form content. Add the following to index.html:

    
Save

New Activity

In the code above, we see the typical jQueryMobile page. The main things of interest here are with respect to the anchor tag in the header div:

  • Specify data-rel="back" to navigate to the previous page once the save is complete. This way whether we enter the form page from "Add" or "Edit", navigation will be handled.
  • The class="ui-btn-right" moves the button to the right side of the header bar.
  • The data-theme="b" causes the save button to use the blue theme.
The next step is to define the template that the view will use to render the content for this page. Lets start with a simple template with an input for each attribute of an activity. To vary the UI slightly, we can use a drop down for the activity type. The basic template will look like this:

    

Notice that we are using the HTML 5 input types for some of the fields. Browser support for these will vary, but worst case they will deprecate to a text input type. For example, on iOS, the date will present a native date picker, whereas on Android (or Chrome, etc.) the field will behave as a standard text input.

Now we need to navigate to the form page. This requires modifying the "Add" button on the list page.

    

Activities

Add

In order to transition to the form page, the href attribute on the anchor tag (i.e. button) needs to be changed to the id of the form page, as shown in line 4 above. The final step in presenting the form page to support adding exercise activities is modifying the add button click handler.

    $('#add-button').live('click', function(){
        var activity = new exercise.Activity(),
            activityForm = $('#activity-form-form'),
            activityFormView;

        activityFormView = new exercise.ActivityFormView({model: activity, viewContainer: activityForm});
        activityFormView.render();
    });

Here we create an empty model, grab the form node, create a new exercise.ActivityFormView with these objects, and render the form. Testing at this point reveals a problem. The screen shot below doesn't look right ;)

Investigating the browsers console reveals an error: Uncaught ReferenceError: distance is not defined. This is due to the underscore template trying to access an undefined attribute of the model. The issue is described here with some work arounds. I found the easiest workaround is to provide defaults for the model, as shown below.

     exercise.Activity = Backbone.Model.extend({
        defaults: {
            date: '',
            type: '',
            distance: '',
            comments: '',
            minutes: ''
        }
     });

Now the form renders properly. With the help of some CSS, we can tighten things up a little and end up with the version on the right (the CSS is part of the source code, see the links at the end of this post).

Editing an existing activity

With the template and the view already implemented, adding edit functionality is pretty straightforward. The first step is to update the href attribute of the edit button on the activity-details page.

    
Edit

Activity Details

As we did with the add button, this should reference the activity-form page. Now we need to add the click event handler for the edit button.

    $('#edit-activity-button').live('click', function() {
        var activityId = $('#activity-details').jqmData('activityId'),
            activityModel = exercise.activities.get(activityId),
            activityForm = $('#activity-form-form'),
            activityFormView;
        
        activityFormView = new exercise.ActivityFormView({model: activityModel, viewContainer: activityForm});
        activityFormView.render();
    });

If you recall, the activityId is passed to the details page view the list item click event handler. We reuse that fact here to retrieve the activity from the collection.

Time to test. Selecting an activity renders the detail page. Clicking the edit button renders the form, pre-filled with the activity details. Everything looks great.

But wait.....that screenshot was from the browser on which I am testing. We should always test on devices as well. Earlier I mentioned that we are using some of the HTML 5 input types (i.e. date). The nice thing is that iOS 5 will render a nice date picker for date types. The challenge is that the date needs to be in a specific format for that to work. The image below shows what the form page looks like on an iOS device.


Fixing the Date

No date value is showing up on our iOS device. This is a known issue. So, for iOS we need the date format as yyyy-mm-dd, while for other platforms, I prefer the date to be shown as mm/dd/yyyy. I do realize that date formats should be localized, but for the purpose of this example I would like to keep it straightforward and specify mm/dd/yyyy as the display date format. There are many options that could be pursued here, but to demonstrate some more capabilities of Backbone.js, we will modify our model to help meet our date requirements.

First, the date attribute in our JSON feed is a string. Lets convert this to a date when the data is retrieved from the server, then when we need the date, we can just manipulate it as needed. This can be accomplished several ways.

  • Implement a parse method on the collection that converts the String to a Date as the data is being fetched from the server.
  • Implement a method on the model that converts the date String to a Date and sets it on the model. This would then require the calling code to know to call this method depending upon the circumstance.
  • Override the set method on the model, look for the date attribute, then convert it to a date as needed.
The last option is the most seamless approach. Add the following method to the Activity model.

        set: function(attributes, options) {
            var aDate;
            if (attributes.date){
                //TODO future version - make sure date is valid format during input
                aDate = new Date(attributes.date);
                if ( Object.prototype.toString.call(aDate) === "[object Date]" && !isNaN(aDate.getTime()) ){
                    attributes.date = aDate;
                }
            }
            Backbone.Model.prototype.set.call(this, attributes, options);
        }

Also, update the defaults so that the date attribute is now a Date instead of a String.

        defaults: {
            date: new Date(),
            type: '',
            distance: '',
            comments: '',
            minutes: ''
        },

While this clearly isn't the most robust date handling, it is fine for now. In a future version, this should be improved.

Next, add attributes to our model that will format the date in the ways we need it (i.e. mm/dd/yyyy and yyyy-mm-dd).

        dateInputType: function(){
            return exercise.formatDate(this.get('date'), "yyyy-mm-dd"); //https://github.com/jquery/jquery-mobile/issues/2755
        },
        
        displayDate: function(){
            return exercise.formatDate(this.get('date'), "mm/dd/yyyy");
        }

The formatDate function is a simple date formatter to meet our specific needs and can be found in the source code that accompanied this post (see link at the bottom of the post).

Now, how do we use these new methods in our view. The first thing to realize is that we pass the template JSON. The default implementation of the toJSON method of a Backbone.js model will not include these functions. Therefore, we need to override the toJSON method.

        toJSON: function(){
            var json = Backbone.Model.prototype.toJSON.call(this);
            return _.extend(json, {dateInputType : this.dateInputType(), displayDate: this.displayDate()});
        }

Here, we are using the Underscore.js extend to add our attributes to the standard Backbone JSON. Now, we need to modify our view templates to use the appropriate JSON attributes.

    
    
    
    
    

Notice that lines 16-21 include some conditional logic. This is a very basic device detection check to determine which date format to use. There are plug-ins,etc. that provide more robust alternatives, but to keep things clear, this will serve our needs. The screen shots below show the desktop browser and the iOS versions of the form with the appropriate date handling.

Saving

The final step is to implement the save functionality.

    $('#save-activity-button').live('click', function(){
        var activityId = $('#activity-details').jqmData('activityId'),
            activity,
            dateComponents,
            formJSON = $('#activity-form-form').formParams();
        
        //if we are on iOS and we have a date...convert it from yyyy-mm-dd back to mm/dd/yyyy
        //TODO future version - for non-iOS, we would need to validate the date is in the expected format (mm/dd/yyyy)
        if (formJSON.date && ((navigator.userAgent.indexOf('iPhone') >= 0 || navigator.userAgent.indexOf('iPad') >= 0)) ){
            dateComponents = formJSON.date.split("-");
            formJSON.date = dateComponents[1] + "/" + dateComponents[2] + "/" + dateComponents[0];
        }
        
        if (activityId){
            //editing
            activity = exercise.activities.get(activityId);
            activity.set(formJSON); //not calling save since we have no REST backend...save in memory
        }else{
            //new (since we have no REST backend, create a new model and add to collection to prevent Backbone making REST calls)
            activity = new exercise.Activity(formJSON);
            activity.set({'id': new Date().getTime()});  //create some identifier
            exercise.activities.add(activity);
        }
    });

I used the JavascriptMVC formParams jQuery plugin (found here) to convert my HTML form into a JSON object. Then, the date is converted into the appropriate format. One thing to note here is that since we have no real back end server for Backbone to interface with, we don't call save on the model or create on the collection. These methods would cause the appropriate REST calls to the server.

Since the click event on the list view passes the activityId to the details page, we can use this to determine if we are adding or editing. One thing to consider here is that the user may click an existing activity, then go back to the list, then click add. In this case, the activityId from the previously selected activity is still attached to the activity-details view. This will cause our save implementation to think we are editing. To prevent this, we should remove the activityId from the activity-details page when adding a new activity. This can be accomplished by adding the highlighted line below to the add handler.

    $('#add-button').live('click', function(){
        var activity = new exercise.Activity(),
            activityForm = $('#activity-form-form'),
            activityFormView;
    
        //clear any existing id attribute from the form page
        $('#activity-details').jqmRemoveData('activityId');
        activityFormView = new exercise.ActivityFormView({model: activity, viewContainer: activityForm});
        activityFormView.render();
    });

The final step is to make sure our views are updated accordingly when we add or modify activities. Since the details view retrieves the activity before the page loads, no change is required here. But our list view only renders during the initial page load. This is where we can take advantage of Backbone event binding. A few small additions to the ActivityListView and things will be handled.

        initialize: function() {
            this.collection.bind('add', this.render, this);
            this.collection.bind('change', this.changeItem, this);
            this.collection.bind('reset', this.render, this);
            this.template = _.template($('#activity-list-item-template').html());
        },

        ...

        changeItem: function(item){
            this.collection.sort();
        }

The change event will bubble up from the model and fires as a result of the set being called. In the changeItem method, we sort the collection to handle any changes in the date attribute. The sort method call will cause the reset event to fire, which then results in the render method invocation, causing the list to be re-rendered. This keeps everything in the correct order. The add event will be fired when we add a new activity to the collection. Since we implemented the comparator method on the collection, models added to the collection will be added in the appropriate order.

We can now add and edit activities. Start exercising :)

The source code for this post can be found here

Wednesday, February 15, 2012

Sencha Touch 2 - NavigationView, Templates and Forms

This post will build on the example application from the Introduction to Sencha Touch post. We will look at sorting, templates, navigation and forms.

Sorting

In the existing code new items appear at the bottom of the list. I’d prefer to see the newest items first. After a Sorter is added to the Store, Sencha displays records in the proper order.
var store = Ext.create('Ext.data.Store', {
    storeId: "activityStore",
    model: "Activity",
    proxy: {
        type: 'ajax',
        url: 'exercise.json'
    },
    sorters: [
        {
            property: 'date',
            direction: 'DESC'
        }
    ],
    autoLoad: true
});

Detail View

The user should able to drill into an Activity on the list to see the details.

Template

We will create an XTemplate that will be populated with the data from the Model. Templates can be created in Javascript or externalized. I prefer to put more complex templates in HTML rather than inline Javascript. Sencha recommends external templates are defined in a hidden textarea.
<textarea id="detail-template" style="display:none">
  <div>
    Date: {date:date("m/d/Y")} <br/>
    Type: {type} <br/>
    Distance: {distance} <br/>
    Time: {minutes} minutes <br/> 
    Notes: {comments} <br/>
  </div>    
</textarea>
The template is created once in code.
var template = Ext.XTemplate.from(Ext.get('detail-template'));

List item disclosure

Sencha displays a disclosure icon for each row when a function is assigned to the onItemDisclosure property of the list. Clicking on this icon allows the user to drill into the details for a row.
The onItemDisclosure function
  • receives the selected record as an argument
  • populates the template with the model’s data
  • creates a new panel
  • pushes the panel onto the navigation view
   {
       xtype: 'list',
       title: 'Activities',
       itemTpl: '{date:date("m/d/Y")} - {type}',
       store: store,
       onItemDisclosure: function (record, btn, index) {
           view.push({
               xtype: 'panel',
               title: 'Activity',
               html: template.apply(record.data),
               styleHtmlContent: true
           });
       }
   }

Pushing the panel onto the NavigationView handles a lot of stuff. The view becomes visible, the navigation bar title is set, a back button is displayed, navigation is handled automatically. This is a nice new feature in Sencha Touch 2.0.

Disclosure icon Record detail

Showing and hiding buttons

When the user is viewing the details, the Add button should disappear. We can manually hide the button in the onItemDisclosure before pushing the view.
onItemDisclosure: function (record, btn, index) {
                      view.down('#addButton').hide();
                      view.push({
                      // ...
The Add button needs to be shown again when the user navigates from the detail back to the main screen. Since the NavigationView handles the navigation automatically, we listen for the pop event and make the Add button is visible.
var onNavigationPop = function(v, item) {
   v.down('#addButton').show();
};

var view = Ext.create("Ext.NavigationView", {
   fullscreen: true,
   items: [
      // ...
   ],
   listeners: {
       pop: onNavigationPop
   }
});
Similarly, we can add onNavigationPush to hide the Add button whenever we push a new view on the stack. This is better than hiding the button in onItemDisclosure.
var onNavigationPush = function(v, item) {
    v.down('#addButton').hide();
};

var view = Ext.create("Ext.NavigationView", {
   fullscreen: true,
   items: [
      // ...
   ],
   listeners: {
       pop: onNavigationPop,
       push: onNavigationPush
   }
});

Record detail with button Record detail without button

Finally, instead of word Add on the button, I think it looks better with an icon.
view.getNavigationBar().add([
  {
      xtype: 'button',
      id: 'addButton',
      align: 'right',
      ui: 'action',
      iconCls: 'compose',
      iconMask: true, 
      handler: addNewRow
  }
]);

Add button text Add button icon

Adding new records with a form

The current implementation puts a fake row into the store when the Add button is pressed. We want to display a form to collect the user input.
We are going to change the implementation of addNewRow to create a FormPanel and push it onto the view. The NavigationView destroys components as they are popped, so we recreate the form every time. Alternately we could set autoDestroy to false on the NavigationView.
var addNewRow = function() {
    view.push(Ext.create('Ext.form.Panel', formBase));
};
The formBase object contains the form fields.
var formBase = {
    title: "Activity",
    itemId: "form",
    items: [
        {
            xtype: 'datepickerfield',
            name: 'date',
            label: 'Date',
            value: new Date()
        },
        {
            xtype: 'selectfield',
            name: 'type',
            label: 'Type',
            options: [
                { text: "" },
                { text: "Run", value: "Run" },
                { text: "Bike", value: "Bike" },
                { text: "Swim", value: "Swim" },
                { text: "Walk", value: "Walk" }
            ]
        },
        {
            xtype: 'textfield',
            name: 'distance',
            label: 'Distance'
        },
        {
            xtype: 'numberfield',
            name: 'minutes',
            label: 'Minutes'
        },
        {
            xtype: 'textfield',
            name: 'comments',
            label: 'Notes'
        }
    ]
};

Saving data

The navigation bar needs a save button. Initially this button will be hidden.
{
    xtype: 'button',
    id: 'saveButton',
    text: 'Save',
    align: 'right',
    ui: 'action',
    handler: save,
    hidden: true
}
The addNewRow function makes the save button visible.
var addNewRow = function() {
    view.push(Ext.create('Ext.form.Panel', formBase));
    view.down('#saveButton').show();
};
The save button is hidden in onNavigationPop.
var onNavigationPop = function(v, item) {
    v.down('#addButton').show();
    v.down('#saveButton').hide();
};
The save function
  • gets the form using the item id
  • creates an new Activity model from the form data
  • adds the new model to the store
  • navigates back to the list view
var save = function() {
   var form = view.down('#form');
   var record = Ext.create('Activity', form.getValues());
   store.add(record);
   // TODO validation and error handling
   view.pop();
};

Note that Sencha automatically displays the new data in the list without any effort on our part.

Form New row

Additional enhancements to improve this example could include things like validating form data, editing existing rows, sending data back to the server, converting to MVC.

The source code is available on github.

Monday, January 30, 2012

From a List to a Details View using jQueryMobile 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:

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.

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.

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:

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.

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.

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:

Monday, January 23, 2012

Introduction to Sencha Touch

Steve Smith recently looked at jQuery Mobile and Backbone using a simple application that allowed a user to track their daily exercise. I'm going to duplicate his example using Sencha Touch.

Sencha Touch is a framework for building mobile applications using HTML5, CSS3, and Javascript.  This example uses the Developer Preview of Sencha Touch 2.0.

Here's what our project looks like.

A Sencha Touch application is loaded from an HTML page. The HTML loads CSS and Javascript. Sencha Touch applications typically build the UI using Javascript so the index.html is very simple.

The touch/ directory contains the Sencha Javascript and CSS from the SDK.  Since this is an example, all if the application code is in one file, app.js. Larger applications will break the code up into more files and directories.

Sencha Application

Sencha Touch has a global namespace, Ext, that holds the framework and application code. The Ext.application helper function takes a configuration object where we define our application.

Ext.application({
    launch: function() {
        // define our app here
    }
});

Model

We extend Ext.data.Model to define a Model object describing our data. The data is supplied by the server in a JSON file, exercise.json. The fields in the model, match the properties in the JSON file.

Ext.define("Activity", {
    extend: "Ext.data.Model",
    fields: [
        {name: 'id', type: 'int'},
        {name: 'date', type: 'date'},    
        {name: 'type', type: 'string'},    
        {name: 'distance', type: 'string'},    
        {name: 'minutes', type: 'int'},  
        {name: 'comments', type: 'string'}
    ]
});
/* exercise.json */
[
    {
        "id":3,
        "date": "12/10/2011",
        "type": "Walk",
        "distance": "2.5 miles",
        "comments": "Shouldn't have taken the dog",
        "minutes": 45
    },
    ...
]  

Store

A Store is used to load and manage data. The Activity model we created is associated with the store. We define a proxy to load the JSON data from a URL. Finally, we tell the store to automatically load the data when the store is created.

var store = Ext.create('Ext.data.Store', {
    storeId: "activityStore",
    model: "Activity",
    proxy: {
        type: 'ajax',
        url: 'exercise.json'
    },
    autoLoad: true
});

Once the store is created, we can use the Javascript console to interact with the data.

User Interface

Now we need to display the data. A NavigationView shows a navigation bar and holds the List component. The list is defined in the code using the object literal syntax. The data store we created is assigned to the list so it can display the records. The itemTmp property defines how to display each item of data. This can be a template or a string.

var view = Ext.create("Ext.NavigationView", {
    fullscreen: true,
    items: [
        {
            xtype: 'list',
            title: 'Activities',
            itemTpl: '{date} - {type}',
            store: store
        }
    ]
});
Unformatted Date

This works but the date doesn't display very well. A format can be added to the template definition. {date} in the itemTpl becomes {date:date("m/d/Y")}.

var view = Ext.create("Ext.NavigationView", {
    fullscreen: true,
    items: [
        {
            xtype: 'list',
            title: 'Activities',
            itemTpl: '{date:date("m/d/Y")} - {type}',
            store: store
        }
    ]
});
Formatted Date

Adding New Data

Let's add a button to the header to add a new row of data.

view.getNavigationBar().add({
    xtype: 'button',
    text: 'Add',
    align: 'right',
    handler: addNewRow
});

The handler property of the button defines the function that is called whenever the button is pressed. Eventually we need a form so the user can add new data. For now, let's define a function addNewRow that will create a new row and add it to the store.

var addNewRow = function() {
    // future versions should display a form for adding a record
    var fakeRecord = Ext.create('Activity', {
        date: new Date(), 
        type: 'Walk', 
        distance: '2 miles', 
        minutes: 28, 
        comments: 'Auto generated record.'
    });

    store.add(fakeRecord);
};

Now when the button is pressed a new row appears in the UI.  The store notifies the list, so rows appear as records are added to the store.

Add Button

New Row

That should be enough to get you started. In future posts we'll look at features like sorting, detail views, navigation, and forms. The source code is available on github.

Tuesday, January 3, 2012

Introduction to Backbone.js with jQuery Mobile

If you are working on a JavaScript heavy application (think jQuery Mobile, etc.), you probably will want to look at some JavaScript libraries to help add structure, consistency and convenience to your applications. One of the JavaScript libraries I've used lately is Backbone.js. To quote Backbone themselves, it provides "models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing application over a RESTful JSON interface.". This is really a great summary of what Backbone can provide to your application.

As an example of how to apply Backbone, I'll create a small jQuery Mobile app and use a Backbone model, collection and view to render the user interface. Through the use of Backbone's event model, the user interface will update as the model changes or new models are created.

I decided to use jQuery Mobile as the UI framework mainly because I am a mobile guy and it will provide the HTML with some reasonable style with no additional effort on my part. I know, lazy...but it works for this blog post :) Continuing on the lazy path, I will refer to jQuery Mobile as JQM from this point forward.

The following assumes you are at least a little familiar with jQuery and JQM. If not, take a look at the JQM website (http://jquerymobile.com/). They have some great documentation and even a template for creating a basic app. All of the code in this post will be available in my github repository (https://github.com/stevenpsmith/Exercise/tree/BackboneIntro).

The application we are going to make will allow a user to log exercise they have done on any given day. Please remember this is a very basic app for the sole purpose of demonstrating Backbone.js. The piece that will be developed for this post will involve pulling exercise data from a server and presenting it in a list view, a very common paradigm for mobile apps. In the end, our app will look like this:

The JSON we will be dealing with will look like this:

I like to organize my application structure a little differently than the JQM introduction shows, mainly for organizational reasons. My application structure looks like this:

All of our work will be done in index.html and app.js. In real applications I usually break down my JS files even further, but it really isn't necessary here. The list page of the application is defined within index.html. It should look like a typical JQM page definition with the only the content portion defined. We will use the Backbone View to create the list view:

If we view this in the browser, we get an empty page with an add button in the header. Since we want to populate our list view with data, we should define a Backbone model and collection in order to manipulate and store the data via JavaScript. A basic model and its collection are easily defined:

The great thing about the collection is it provides a RESTful access back to your server for data retrieval and persistence. To keep this simple, our data will be served from a static JSON file with the structure referenced earlier. Since we have no real server side in this example, we can't really save our changes anywhere but in the client's memory. But the collection will fetch the data from the server and can be used to help render our user interface. The following code will create an instance of the collection in memory, retrieve the data from the server, and parse it into a collection of models.

The exercise.initData() function can be called during initialization of the application (or wherever else might be appropriate) to load the data. Now it is time to render the view. Backbone views depend upon Underscore.js. Underscore is a utility library that includes a lot of great features, one of which is view templates. Backbone can be used with many view template frameworks (e.g. Mustache, Handlbars, etc.), but here we will stick with Underscore templates as they meet our needs. For our exercise activity items, we need a very basic template like this:

Giving the template a unique id will let us access it using typical jQuery selectors. The data driven parts of the template are surrounded by

<%= %>
As you can see in line 2, we are using the id, date, and type attributes of whatever model is supplied to the template to fill in the HTML accordingly. While the "identifier" attribute is not a real HTML attribute and not required by any of the frameworks, it would be used to determine what activity the user selected in the list so the appropriate data could be retrieved and displayed on a subsequent page.

The next step is to define the Backbone View that will render the list view. Backbone views can be defined and set up numerous ways, and the documentation does a great job explaining the options, etc. I like to define my views so that they are as self contained as possible. So, rather than allowing Backbone to create the HTML within a DIV (the default behavior), I define the appropriate properties to create a JQM list view as shown in lines 2-4 below:

From the Backbone site: "The default implementation of render is a no-op. Override this function with your code that renders the view template from model data, and updates this.el with the new HTML. A good convention is to return this at the end of render to enable chained calls". The $(this.el) refers to the containing element, in this case the ul element. There are standard attributes that are accessible from within the Backbone View including collection and model. Lines 17-20 iterate over the collection of activities, fills in the template placeholders with the data from the model, and appends the resulting HTML to the containing element.

If you need to pass arbitrary data to your view, it can be accessed via the options object as shown on line 11 of the code snippet. As mentioned earlier, I like my Backbone Views to be as self contained as possible, so I provide the containing HTML element for this view (as a jQuery object) when constructing the View instance. Line 20 sets the HTML of the containing element to the Backbone View and line 21 tells JQM to style things appropriately.

The next step is to instantiate and render the view with the appropriate collection and HTML container. Since this is the "home" page in our application, this is done after JQM initializes the page.

Now we have a list view that renders a JQM list based on data in a Backbone collection. To take advantage of the real power of Backbone models/collections, we can add listeners for changes to the collection that will update the view based on those changes. The beauty of this is the additional code to accomplish this is minimal. By adding

this.collection.bind('add', this.add, this);
to the initialize method of our Backbone View and providing an add() method.

Here we are just grabbing the list view and appending the new model to the view (and refreshing it so JQM does its magic). By adding a click handler to the "Add" button, we can test that adding a model to the collection updates the view.

There are other methods on which to bind, but this provides a quick example on how Backbone can be used to assist with view rendering and data handling in JavaScript based client applications.

The entire code base is available here. It can be deployed on any web server. For OS X, I use the built in web server, but feel free to use the server of your choice.

Related Posts: