16 Jan

JMVC + JQM improved dispatch

In my previous example, I used an eval to load a page controller from the jQuery.fn  name space. Here is a much better version of that code which has the added benefit of allowing access to the instance of my controller:

If ( typeof MyApp.Pages[pageName] === 'function') {
    // don't really need the element passed here per this example's controller, will check if needed.
    pageInstance  = new MyApp.Pages[pageName]($('body'));
    pageInstance.view.done( function ( htmlOutput ) {
        $('body').append(htmlOutput);
        $('#' + pageName).page();
        $.mobile.changePage( $('#' + pageName) );
    }

On the controller side, the init method becomes much simpler than I had originally written:

/**
 * @class MyApp.Pages.Frontpage
 */
$.Controller('MyApp.Pages.Frontpage',
    /** @Static */
    {
        defaults : {
            itemsPerPage : 20,
            currentPage : 1
        },
        pluginName: 'frontpage'
    },
    /** @Prototype */
    {
        /**
         * Downloads and renders a full page of blog posts
         */
        init : function() {
            this.view = $.View("my_app/pages/views/frontpage.ejs", {
                items: MyApp.Models.BlogPost.findAll({
                    start : (this.options.currentPage-1)
                            * this.options.itemsPerPage,
                    count : this.options.itemsPerPage
                })
            });
        }
    }
); // End of controller

My full bootstrap method now looks like this:

function() {
    // 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]+)$/; // TODO $.route

            if (u.hash.search(re) !== -1 && $(u.hash).length == 0) {
                var pageName = re.exec(u.hash);
                pageName = pageName[1];
                if(pageName === 'splah') {
                    return false;
                }
                // ucwords - ignore as it will be replaced by using $.route
                controllerName = pageName.replace(/^([a-z])|\s+([a-z])/g, function (str) {
                    return str.toUpperCase();
                });
                if (typeof MyApp.Pages[controllerName] === 'function') {
                    var $instance = new MyApp.Pages[controllerName]($('body'));
                    $instance.view.done(function (htmlOutput){
                        // add the rendered output to the body (could also be done in the controllers instead)
                        $('body').append(htmlOutput);
                        $('#' + pageName).page(); // jQuery Mobile magic decoration
                        // passing an object skips the dispatch and just shows the page
                        $.mobile.changePage($('#' + pageName));
                    });
                    e.preventDefault();
                }
            }
        }
    });
    $('#splash').fadeIn(500);
    setTimeout(function () {
        $.mobile.changePage('#frontpage');
    }, 500);
});

I’ll flesh out the full updated integration when I get home, but i am now close to my ideal setup and my full app is working as expected from start to finish. Among my recent changes are the use of partials, routes, pagination, API service auth (with caveats) and of course testing and documentation.

Stay tuned!

4 thoughts on “JMVC + JQM improved dispatch

  1. I agree with Johannes, it is a great approach!
    Unfortunately I am in trouble with the routes. Similar to you I want to use $.route to get the right page controllers. However, when I steal “jquery/controller/route” and the app calls $.mobile.changePage($(‘#’ + pageName)); in the “pagebeforechange”-callback,
    it triggers a new “pagebeforechange”-event caused by the new hash-value. Therefore the requested page will be replaced again.
    Any hints on what I’m doing wrong?

  2. Hi,
    thanks a lot for this guide.
    But I get some problems while loading the page.
    If I use $instance.view.done(function (htmlOutput) there’s an error because is not a function.
    If I do the dom manipulation in my controller and load the page after that I get following message and the content is hidden:
    Error: cannot call methods on loader prior to initialization; attempted to call method ‘hide’

    There is my JS snippet:
    if (typeof Inventory.Pages[controllerName] === ‘function’) {
    //loading content in controller an put it into the body tag
    var $instance = new Inventory.Pages[controllerName]($(‘body’));
    $(‘#’ + pageName).page(); // jQuery Mobile magic decoration
    // passing an object skips the dispatch and just shows the page0
    $.mobile.changePage($(‘#’ + pageName));
    e.preventDefault();
    }
    I would be very happy if you could give me a hint, what’s my mistake
    Sebtra

Comments are closed.