61

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>');
}
Darin Kolev
  • 3,401
  • 13
  • 31
  • 46
Luke Shaheen
  • 4,262
  • 12
  • 52
  • 82
  • 8
    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 Feb 08 '12 at 20:22
  • 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 Feb 12 '12 at 20:01
  • 1
    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) cry – rmirabelle Oct 23 '12 at 16:32

6 Answers6

22

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.

sougonde
  • 3,438
  • 3
  • 26
  • 35
  • Have you tried this on the pages you're linking to? It loads on my index page, but when I follow a link to one of my other pages, it loads the ` – Luke Shaheen Feb 13 '12 at 15:23
  • @John, the idea of this structure is that you never actually change page. You always stay at index.html and handle the link clicks with javascript. The menu/content html only contains the html that should be inside ul#menu or #content. To automatically run `loadContent` you have to do `$('#content').on('click', 'a', clickedLink);` and then `function clickedLink() { loadContent($(this).attr('href')); return false; }`. Point your href attributes of your links (``) to the location (for example `href='test'` will load `views/content/test.html`) – sougonde Feb 13 '12 at 15:37
  • By doing this you've created your own little framework in a way (it replaces the changePage function of JQM). Since JQM is rather slow I'm thinking of only using the CSS/markup of JQM and disabling the JQM javascript file. That requires some work though, but I like this structure, being able to load the menu/content dynamically. – sougonde Feb 13 '12 at 15:41
  • 1
    Great solution, thanks. One note that is a little cleaner from what I've read all over - use `.live()` instead of `(document).on`, and then use `$('div:jqmData(role="page")')` instead of using an ID (`#page`), and the same with footer and navbar. – Luke Shaheen Feb 13 '12 at 18:17
  • Thanks! We're basically bypassing a major part of JQM, like $.mobile.changePage. However, I think this is a pretty elegant solution. – sougonde Feb 14 '12 at 12:16
  • Another similar approach is this: https://www.ibm.com/developerworks/mydeveloperworks/blogs/94e7fded-7162-445e-8ceb-97a2140866a9/entry/dynamic_page_loading_for_phonegap1?lang=zh. I have myself dropped it, since you won't get the nice changePage loading effects, which makes an app look like an app. – Dofs Mar 24 '12 at 11:37
  • @Dofs That's cool, yes you have to create the changePage loading effects yourself, not that hard though! – sougonde Apr 02 '12 at 17:20
  • @bartolsthoorn you mentioned that it's not hard to create the changePage effects. Can you give us an example of how to do this when using the above methods? – JoshL Jun 25 '12 at 03:45
  • @JoshL Adding effects is easy because you just use the standard methods of jQuery. Replace the .load method with an ajax .get to get the new content but without loading it in the div straight away. Then have a second temporary, hidden and empty div around that you use to power the animation. Load the new content in the temporary div, fade it or slide it in, and when the animation is finished also set the same content for the main/normal content div, and hide and empty the temporary div again for the next animation! So just basic jQuery animating, the structure I presented is just the minimum. – sougonde Jul 15 '12 at 15:49
  • @bartolsthoorn thanks. Ive created my own framework off all this with transitions and what not but Ill give your method a shot. – JoshL Jul 16 '12 at 01:15
  • This used to be perfect, I remember studying a bit PhoneGap and jQuery Mobile 2 years ago and I had found this thread. I still have a code similar to this, but with jQuery Mobile 1.4 now it doesn't work properly. – Marco Aurélio Deleu Apr 16 '15 at 13:13
3

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.

bmurmistro
  • 1,070
  • 1
  • 16
  • 35
  • From what I've read, the problem with this is that jQuery Mobile doesn't apply all the stuff they would if the footer was loaded normally, no? – Luke Shaheen Feb 09 '12 at 04:06
  • This is what I'm using and all the jqm stuff appears to be applied. If I'm not mistaken, jqm markup occurs sometime after pagecreate. – bmurmistro Feb 09 '12 at 12:19
3

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

Yacine Filali
  • 1,762
  • 14
  • 17
2

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.

frequent
  • 27,643
  • 59
  • 181
  • 333
  • I just can't understand why it seems like this is one big hack. Why is does it appear that the "recommended" way is to copy and paste the footer into each page? It doesn't make sense. – Luke Shaheen Feb 05 '12 at 20:44
  • 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 – Luke Shaheen Feb 05 '12 at 20:50
  • 1
    maybe this is something you should handle from the server side. I'm thinking CFinclude in coldfusion for example, where you just insert the same navigation bar on every page. – frequent Feb 06 '12 at 07:48
  • I am optimizing for PhoneGap, which means I can't do a PHP include (or CFinclude) - PhoneGap doesn't allow for .php files, only .html and .js. – Luke Shaheen Feb 08 '12 at 23:29
  • Actually JQM has this function built in (check the source for the stickyFooter). I used stickyFooter to set up my own solution, which I'm still testing. Code is above. – frequent Feb 09 '12 at 06:49
2

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.

imaginethepoet
  • 864
  • 6
  • 14
  • It's a solution, but from what I've read, the problem with this is that jQuery Mobile doesn't apply all the stuff they would if the footer was loaded normally. – Luke Shaheen Feb 08 '12 at 23:28
  • I haven't tried but you should be able to call some sort of create function I'd imagine, just like we do for listviews/buttons, etc – Clarence Liu Feb 09 '12 at 23:24
2

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

Community
  • 1
  • 1
Clarence Liu
  • 3,874
  • 2
  • 25
  • 29
  • As I mentioned, I am optimizing for PhoneGap, which means I can't do a PHP include - PhoneGap doesn't allow for .php files, only .html and .js. – Luke Shaheen Feb 08 '12 at 23:28
  • Oh I missed that, then ya just insert it via JS - you could even use the jQuery official template plugin which is pretty nice: http://api.jquery.com/category/plugins/templates/ – Clarence Liu Feb 09 '12 at 23:23