10 Jan

PhoneGap + JavascriptMVC + jQuery Mobile

I started a new mobile app project and decided to go with HTML5 wrapped with PhoneGap. The reasons are many and this post isn’t about why, but how.

Before you start, here are the libraries/frameworks I am using:
jQuery Mobile – a “Touch-Optimized Web Framework for Smartphones & Tablets”
Javascript MVC – a Javascript MVC Framework (and more)
PhoneGap – “HTML5 app platform”/embedded cross platform browser

I will be posting code snippets as I go along that may be helpful to someone just starting out with a similar project.

Here is my directory structure so far:

  • blocks
    • controllers
    • views
    • blocks.js
  • pages
    • controllers
    • views
  • models
    • models.js
    • blog_post.js
  • css
  • images
  • platforms // platform projects (iOS XCode project, Android eclipse project, etc.)
  • myapp.js
  • myapp.html
  • build.xml

I also chose to go with EJS for the templating system which allows my view to look very similar to a PHP/Zend Framework view:

<%== blocks('header') %>
<div data-role="content" id="frontpageContent">
    <ul data-role="listview" data-theme="c">
        <% if (items) for(var i=0; i < items.length; i++) { %>
            <li data-icon="tcc2-arrow-right">
                <a href="#">
                    <img src="<%= items&#91;i&#93;.media.m %>"/>
                    <h3><%= items&#91;i&#93;.title %></h3>
                </a>
            </li>
        <% } %>
    </ul>
</div>
<%== blocks('footer') %>

The first piece of code that makes life a little easier on large projects is my blocks view helper. This way I can modularize and reuse:

    $.EJS.Helpers.prototype.blocks = function (blockName) {
        if (typeof MyApp.Blocks[blockName] === 'function') {
            blockInstance = new MyApp.Blocks[blockName]($('<div/>'));
            return blockInstance.view;
        }
        throw "No such block controller MyApp.Blocks." + blockName;
    }

You could also just call

<%== $.View('someview.ejs') %>

if you have no need for a controller. Block controllers look like this:

$.Controller('MyApp.Blocks.Header',
    /** @Static */
    {
        defaults: {},
        pluginName: 'header'
    },
    /** @Prototype */
    {
        init : function(){
            this.view = $.View("my_app/blocks/views/header.ejs",{});
        }
    }
); // Header controller

Now on to more substantial jQuery Mobile related code. We need jqm navigation to tie into our MVC controllers and vice versa. In other words, when we navigate to #frontpage, we want a new page injected into our dom using the frontpage controller, which in turns will render a view (frontpage.ejs, or init.ejs if you stick to the generated naming).

The answer to this problem comes straight from the jQm manual:
From myapp.js (or whatever you called your app)

steal(
    'phonegap-1.3.0.js',
    'jquery/jquery.js',
    './css/jquery.mobile.structure-1.0.min.css' // jQuery Mobile structure CSS
    )
.then(
    './css/global.css', // Our global css style and jqm theme
    'jquery/controller',
    'jquery/view/ejs'
    )
.then(
    './models/models.js', // all models
    './pages/pages.js', // all pages
    './blocks/blocks.js', // all blocks and Blocks view helper
     function () { // Init some jqm settings and fadeIn splash page
        $(document).bind("mobileinit", function(){
            $.mobile.touchOverflowEnabled = true;
            $.mobile.allowCrossDomainPages = true;
        });
    })
.then('//library/jquery.mobile-1.0.min.js') // jQuery Mobile library
.then(
    // Run the frontpage controller
    function() {
        $('#splash').fadeIn(500);
        // intercept changePage events to inject our controllers as needed (dispatch)
        $(document).bind( "pagebeforechange", function( e, data ) {
            // we only handle url changes
            if ( typeof data.toPage === "string" ) {
                // We are being asked to load a page by URL
                var u = $.mobile.path.parseUrl( data.toPage ),
                    re = /^\#([a-zA-Z]+)$/;

                if (u.hash.search(re) !== -1 ) {
                    var $page = re.exec(u.hash);
                    $page = $page[1];
                    console.log('Loading page ' + $page);
                    // We're being asked to display the items for a specific page
                    // Call our internal controller
                    if (typeof MyApp.Pages[$page] === 'function') {
                        var controller = new MyApp.Pages[$page]( $('body') );
                        controller.view.done(function (htmlOutput) {
                            $(htmlOutput).appendTo( $('body') ).page();
                            $.mobile.changePage( $(htmlOutput) );
                        });
                    }
                }
            }
        });
        setTimeout(function () {
            $.mobile.changePage('#frontpage');
        }, 500);
    });

This is of course a very early draft, but I hope it is enough to fill in the gaps between the docs of JMVC and jQm to get someone started. There is much you need to add to make this nicer. For example you could have an routes object defining each hash and params and which controller to send them to, then use that during the event above (look at jquery page params for adding hash params to jQuery Mobile).

4 thoughts on “PhoneGap + JavascriptMVC + jQuery Mobile

  1. Thanks for the post – very helpful. Maybe a silly question, but how did you get PhoneGap to recognize a main page other than index.html? Thanks!

  2. You can change this:
    super.loadUrl(“file:///android_asset/www/index.html”);

    for this:

    super.loadUrl(“file:///android_asset/www/myapp.html”);

    public class MainActivity extends DroidGap
    {
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    super.loadUrl(“file:///android_asset/www/index.html”);
    }
    }

Comments are closed.