Persistent Navigation in JQuery Mobile

I have been doing quite a bit of work in mobile application development with JQM recently, and generally finding it a very useful product once you get your head around its idiosyncrasies.
One of the things I needed to do was to have a persistent navigation across the bottom of the page, to mimic the standard iPhone navbar.
Out of the box JQM offers a facility for persistent navbars. The documentation says…
“To tell the framework to apply the persistent behaviour, add a data-id attribute to the footer of all HTML pages in the navigation set to the same ID. It’s that simple: if the page you’re navigating to has a header or footer with the same data-id, the toolbars will appear fixed outside of the transition.”
And gives the following code example

<div data-role="footer" data-id="foo1" data-position="fixed">
 <div data-role="navbar">
 <ul>
 <li><a href="a.html">Friends</a></li>
 <li><a href="b.html">Albums</a></li>
 <li><a href="c.html">Emails</a></li>
 <li><a href="d.html" >Info</a></li>
 </ul>
 </div><!-- /navbar -->
</div><!-- /footer -->

And to set the current active button you use

<li><a href="d.html">Info</a></li>

This sounded a bit confusing, so I looked at the example code and it looked like they had simply pasted the same code into the bottom of every page and altered the active link. Thinking that I must be misreading the code I searched the internet for more information and it turns out that I wasn’t. The JQM solution to this problem was actually to mirror the footer on every page but with a different active link. The only way in which these were actually persistent was that if JQM saw that there was another footer on the incoming page with the same id then it would exclude that footer from the page transition.

The programmer in me would not let myself even consider that as a solution. Even early in development as I currently am, I have five files, some containing up to four pages. So that’s a minimum of ten places I’d have to maintain the navigation and that figure is only going to grow as development progresses. The thought of that made me actually feel sick. I simply wasn’t going to do it… simple as that.

Instead I decided to dynamically generate the navbar on first load and then inject into every page. The end solution to this is actually relatively straight forward.

In my index page I created a hidden div that contained the actual navbar html

<div id="hiddenFooter" style="display:none">
 <div data-role="footer" data-id="nav" data-position="fixed">
 <div data-role="navbar">
 <ul>
 <li><a href="page1.html">Page 1</a></li>
 <li><a href="page2.html">Page 2</a></li>
 <li><a href="page3.html">Page 3</a></li>
 <li><a href="page4.html">Page 4</a></li>
 <li><a href="page5.html">Page 5</a></li>
 </ul>
 </div><!-- /navbar -->
 </div><!-- /footer -->
</div>

The way the JQM works is to load any subsequent pages in using Ajax and keep them in a shared Javascript context. This means that events can be bound on the load of the first page and will apply to any subsequent pages called using JQM links.

Therefore I could read the contents on the hidden div into a Javascript variable

$("#index").live("pagebeforecreate", function () {
 Navigation.NavigationHtml = $("#hiddenFooter").html();
});

And this would be available to all subsequent pages loaded.

All that was needed then was to inject this html at the end of any pages that are created. To do that simply bind a pagebeforecreate event for all pages using the live rather than bind method (bind only binds events to items that exist at that time, live will bind to an item that comes into existence later).

$(":jqmData(role='page')").live("pagebeforecreate", function (event) {
 $("#" + event.target.id)).append(Navigation.NavigationHtml);
});

Now, when you navigate to any page (providing you have visited the index page first) it will display a fixed navigation bar at the foot of the page.
However there is one small problem…. the button you click is not correctly marked as the active tab. It is if you click it twice… but to me that is less than optimal, in fact that’s worse than it never being selected at all. The solution to this turned out to be trickier than expected.
The simple solution I came up with was to identify the url of the page and add the class “ui-btn-active” to the link. However getting the url of the current page was pretty tricky. Because JQM uses Ajax to load all the pages the current url (window.location) is always the first loaded page and because each file can contain multiple pages the JQM page object is only aware of those pages, not its parent file.

The solution I came up with was to use the pagebeforeload event which exposes a url, save that url and make it available to pagebeforecreate events defined earlier

$(document).bind("pagebeforeload", function (event, data) {
 Navigation.CurrentPageFilename = $.mobile.path.parseUrl(data.url).filename;
});

This meant that we needed to amend our earlier event to

$(":jqmData(role='page')").live("pagebeforecreate", function (event) {
 $("#" + event.target.id)).append(Navigation.NavigationHtml);
 $('a[href="' + Navigation.CurrentPageFilename + '"]').addClass("ui-btn-active");
});

I wrapped it al up in one Javascript object

Navigation = {
Navigation = {
 CurrentPageFilename: "",
 NavigationHtml: "",
 Initialise: function () {
 $("#index").live("pagebeforecreate", function () {
 Navigation.NavigationHtml = $("#hiddenFooter").html();
 });
 $(":jqmData(role='page')").live("pagebeforecreate", function (event) {
 $("#" + event.target.id).append(Navigation.NavigationHtml);
 $('a[href="' + Navigation.CurrentPageFilename + '"]').addClass("ui-btn-active");
 });

 $(document).bind("pagebeforeload", function (event, data) {
 Navigation.CurrentPageFilename = $.mobile.path.parseUrl(data.url).filename;
 });
 }
};

Then just needed to call

Navigation.Initialise();

From my index page.

As mentioned this solution only works if you go to the index page first. This was ok for my app as that should always happen so all I did for the sake of completeness was add the following code to every other page

if (typeof (Navigation) === 'undefined') {
 window.location = ("index.html");
}

To me this is an infinitely better solution that the original JQM suggested solution as it means I am managing my navigation is one place. It also gives me scope to dynamically manage the navigation contents if needed in future.

One last Gotcha on this subject. Be careful that you don’t have any pages across multiple files that have the same id. JQM keeps the DOM of some previous pages within its current DOM and duplicate page Ids can confuse it.

Advertisements

8 Comments

Filed under Code

8 responses to “Persistent Navigation in JQuery Mobile

  1. Thanks for this article, I thought I was misreading their example with duplicated footer too…

    Also, I think your one-line example following “And to set the current active button you use” misses something. Maybe you wanted to add ” class=”ui-btn-active” to the link?

  2. Arthur

    Same here 😉 Thought it was me missing something. Did more or less the same thing, although your solution is more elegant than mine.

  3. Mike Hedman

    I’m glad I’m not the only one! I even considered using a PHP code generator, just so I wouldn’t have to manually muck with persistent page sections.

    Nice solution, elegant implementation, and a nice write-up…Thanks!

  4. Steve

    Hi Andy!

    I like your solution and I’d like to apply it for a navigation bar in the header-section of my app. There are only two ”challenges”:

    1. Within the header-section, I have a heading (Page title) that needs to be changed (dynamically) according to the actual page to be displayed.

    2. In a few pages, I need a header that is different from the ”standard”-header.

    Could you please give me a hint, how I could achieve that?

    My app is self-contained, i. e. it isn’t connected to a webserver. I only use JQM and Phonegap for the implementation (and HTML5 and Javascript of course! 😉 .

    Best regards
    Steve

  5. Hi Andy,

    I am building an app using PhoneGap and JQuery Mobile (JQM).

    I like your solution and it works great for simple tabs. But, per tab’s content I have more links that lead to inner pages (which belong to the same tab). Keeping track of this requires a more complex version of what you have written since every link = a new page which requires the header and footer injected, tab highlighted etc…. You see a flicker depending how fast the mobile is since the whole UI updates.

    My question is, since I am not using the JQM themes or icons and the navigation is annoying, do you think I can just use JQuery (without JQM), have fixed header and footer and use simple AJAX methods for updating only the relevant part of the UI?

    The content transitions are handy but I can also use a JQ library for this and test on mobiles as I go?

    Many thanks for your time
    Jeremy

  6. Hi Andy,
    As of jQuery 1.7, the .live() method is deprecated. Use .on() to attach event handlers.
    Maybe you want to update your post?

  7. Pingback: Helpful Developer Resources - Part 1 « Development AdvantageDevelopment Advantage

  8. Just a note here incase people stumble across this, that all this workaround is no longer needed for persistent navigation it is no a built in feature of jQuery mobile as of 1.4.0 you can have true persistent navigation by just using the footer outside the page. http://demos.jquerymobile.com/1.4.2/toolbar-fixed-external/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s