61
votes

I'm diving into writing a mobile app with jQuery Mobile/PhoneGap. I'm using this sample template to start from, which uses HTML/JS to create the pages. Rather than have all the <page> tags in one single html file, he's got it split up so it's easier to edit.

Since I will have a separate file for each page, what's the best way to include the tempated header/footer? I've only seen it where you need to copy and paste the whole footer->navbar code into each HTML page. This doesn't seem like it should be. For example, if you want to change one menu item, you need to go into each page and change it.

What's the solution that I'm missing?

Maybe I'm just not understanding jQuery Mobile. For example, their sidebar they use for their docs - is that sidebar code copy and pasted onto each page? That doesn't make sense. It's the same idea as the question I'm asking here about the footer.

http://jquerymobile.com/test/docs/pages/page-cache.html

This is what I've got that doesn't seem right (and $.live('pageinit') isn't working). This HTML is what goes on each HTML page:

<div id="mainFooter" data-position="fixed" data-id="mainFooter" data-role="footer" class="ui-footer ui-bar-a ui-footer-fixed fade ui-fixed-inline" role="contentinfo" style="top: 58px;">

And the JS

$.live('pageinit', function (event) {
    displayFooter();
});

function displayFooter() {
    $('#mainFooter').html('<div data-role="navbar" class="nav-glyphish-example" data-grid="d">' +
        '<ul>' +
        '<li><a href="#" id="menuitem1" data-icon="custom">Menu Item 1</a></li>' +
        '<li><a href="#" id="menuitem2" data-icon="custom">Menu Item 2</a></li>' +
        '<li><a href="#" id="menuitem3" data-icon="custom">Menu Item 3</a></li>' +
        '</ul>' +
        '</div>');
}
6
I don't know why this question doesn't have more up votes, I find it one of the real chinks in the armor of jquery mobile.rogermushroom
jQuery Mobile expects you to do the including/templating on the server side instead of using ajax to only load the navbar or content (it's not only used for phonegap you know). The JQM methods like .changePage change the entire HTML of the page. So if you only want to include the navbar or content with ajax I recommend you to take a look at my answer.sougonde
yeah, this is HUGE (and hugely frustrating) for me as well. JQM's default hijacking of URLs basically means either you 1) DRY - DO Repeat Yourself all over the place 2) use a server to do your includes, 3) write nasty hacks that break all over the place due to JQM's nearly incomprehensible markup model 4) try to incorporate an MVC framework over the top of JQM (I'm excited about Angular, but it's serious rocket science) 5) cryrmirabelle

6 Answers

22
votes

I've been trying to tackle this problem for a few days now and I've gotten really close to the solution. I use the following HTML:

<body>
    <div data-role="page" id="page">

        <div data-role="header">
            <h1 id="title">Title</h1>
        </div><!-- /header -->

        <div data-role="content" id="content">  
            <p>Loading...</p>
        </div><!-- /content -->

        <div data-role="footer" id="footer" data-position="fixed">
            <div data-role="navbar" id="navbar">
            </div>
        </div><!-- /footer -->
    </div><!-- /page -->
</body>

And I've created the following functions to load the menu/content using ajax:

$(document).on('pageinit', "#page", function() {
    // for example: displayFooter();
    loadContent("main");
    loadMenu("default");
});

function loadContent(location) {
    return $('#content').load("views/content/"+location+".html", function() { 
        $(this).trigger('create');
    });
}
function loadMenu(location) {
    $("#menu").remove();
    $("#navbar").remove();

    $("#footer").html('<div data-role="navbar" id="navbar">');
    $("#navbar").html('<ul id="menu"></ul>');

    $("#menu").load("views/menus/"+location+".html", function() { $("#menu").parent().navbar(); });

    return true;
}

The .trigger('create'); and .navbar(); are the methods required to keep the JQM styling correct, however, there's still one little bug. The position of the menu (which is set using css top: ...px) is sometimes not correct and moves itself to the correct position after the first tap. Really close though!

Edit: By setting #footer to position: fixed; the minor bug I mentioned above is gone. Also, it's easy to make a method that calculates the top (in px) for the #footer if the position caused by the top value by JQM is incorrect.

3
votes

Something like this:

function getText() {
    //return footer text here
}


$("div:jqmData(role='page')").live("pagebeforecreate",function() {
    // get find the footer of the page
    var $footer =$(this).page().find('div:jqmData(role="footer")')
    // insert it where you want it... 
}

you could just define the role=footer in the getText and just check to see if it's defined... if not then add it.

3
votes

If you really want to do this properly, you need to look into a js framework like Thorax or JavascriptMVC.

In a JMVC app, you could do this from any view:

<div data-role="page">
    <%== $.View('./header.ejs') %>
    <div data-role="content">
    ...
    </div>
    <%== $.View('./footer.ejs') %>
</div>

jQuery Mobile is really only useful for progressive markup enhancement and styling for mobile devices. For the actual MVC part you need an MVC framework.

The examples for jQuery Mobile are also mostly geared for building mobile web sites which since they fetch pages from a server assume your code reuse is happening server side. A phonegap app is a whole nother beast since you will be generating these pages on the fly or from local static files. This is were the MVC framework comes in as you would be building your pages using controllers, views, view helpers, and model classes. You tie all that into jQuery mobile by listening to the pagebeforeshow event as shown on the jQm documentation page on scripting pages

2
votes

We haven't found a good way to do this yet either. So we are actually handling this dynamically in our custom js file. Looking for the closing container - and then dynamically appending the footer after the last content div closes.

2
votes

Depends how you load the respective pages.

If you use rel="external" - no AJAX, each page will be reloaded completely.

If you use regular JQM - AJAX, JQM will grab the page and attach it to your existing DOM. Think of your first page as your "anchor" page, which all subsequent pages are added/removed.

In the 2nd case you could specify a data-append-to-all-pages="true" attribute on elements you want to have on every page.

Then just listen to the respective event (pagebeforeshow?) and add elements with the above label to the new page before it's shown. This way elements would only have to be set on the first page and then automatically be added to any subsequent page being pulled in and added to the DOM.

I'm looking into this right now with a login form, which I need on every page outside the login and have to avoid ending up with duplicate input#someID.

EDIT - possible solution

Put the respective element on the first page and add unique attributes, like so:

 <div data-role="navbar" data-unique="true" data-unique-id="log">
     // full navbar
 </div>

All other pages just get the unique element container

<div data-role="navbar" data-unique="true" data-unique-id="log"></div>

Then use this script:

$('div:jqmData(role="page")').live('pagebeforehide', function(e, data) {

    //  check page you are leaving for unique items 
    $(this).find('div:jqmData(unique="true")').each(function(){

         // more or less 1:1 stickyFooter
         var uniqueID = $(this).jqmData("unique-id"),
             nextPage = data.nextPage,
             nextUnique = nextPage && nextPage.find( "div:jqmData(unique-id='"+uniqueID+"')" ),
             nextMatches = nextUnique.length && nextUnique.jqmData('unique-id') === uniqueID;

         // if unique items on previous and new page are matching
         if ( uniqueID && nextMatches ) {
            nextUnique.replaceWith( uniqueID );
            $(this).jqmData("unique-id").empty();
            }
         });
     }); 

Of course this would mean you need the unique container on every page. But then you would just carry the content of it along as you traverse through the app. Should work for me and avoid having the same login form 2+ times inside the DOM.

Plus you would be free to edit it's contents for example adding active class.

2
votes

Don't use pageinit, the events that you should listen for are pagecreate and pageshow, pagecreate gets called the first time a page is loaded, but not when you navigate back to the page, e.g. on back button. Whereas pageshow fires everytime a page is shown, so just bind a live listener to the pageshow event for every page where you add your footer (assuming your footer may change, otherwise use pagecreate).

I have a longer example that shows you how to bind to the event properly in another question: https://stackoverflow.com/a/9085014/737023

And yes a great solution is just to use a PHP/CF include, which is what I use instead of JS.


EDIT My bad, seems pageinit is now used instead of pagecreate (see here), but it still stands that pageshow fires every time a page is shown - so you can use those as you need