Tag Archives: application

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

DynamoDB up to the usual high standards of AWS tools: A NoSQL comparison

This blog post was written by Intechnica’s Technical Director Andy Still. Watch Andy’s webinar “Designing Applications for the Cloud” on YouTube now.

With the pace of new product releases on Amazon AWS it is often not possible to get more than a quick look at any new development. One recent development that has really caught my interest however is the new DynamoDB offering.

Essentially this is Amazon’s offering for the highly scalable NoSQL database market.

Like everything else in AWS it is charged on a pay by use basis, however you have to specify in advance the level of read/write access you will require and Amazon will set aside that level of capacity guaranteed for you. This value can be set via the console or in real time via the API. To be honest I found the pricing model a bit confusing, I wasn’t 100% sure what happened if you fell above or below the levels you set or how the actually billing rate was calculated. Looking at the real time billing information in my account didn’t clear this up. I think if I decide to use DynamoDb in any sort of anger I will need to contact Amazon for clarification.

Most of my NoSQL experience has been using Azure Table Services so my initial reaction is to compare the two.

In essence they work in a similar fashion. They are large key/value datastores where defined tables allow for the storage of entities containing an arbitrary amount of attribute/value combinations that are stored against a key – called a hash key. Keys can be grouped in ranges, identified by a range key. If a range key is specified then the hash key/range key combination must be unique for that table, otherwise all hash keys for a table must be unique. For those familiar with Table Services, hash key is the equivalent of primary key, range key is partition key – however range key is optional in DynamoDb whereas partition key is compulsory in Table Services.

That’s about as much as detail as I want to go into about the actual service itself. For more details the Amazon literature is here: http://aws.amazon.com/dynamodb/

Let’s get instead into a bit of actual usage of this, particularly the AWS .net SDK interface into it. All API calls can be made direct via JSON based REST calls but I’ve been testing using the SDK direct from .net.

First step is to install the AWS Visual Studio Toolkit (http://aws.amazon.com/visualstudio/). This will install all the SDKs for the various AWS services, including Dynamo DB, as well as the AWS toolbar for Visual Studio and a new AWS project type that comes with the correct references already added.

The AWS toolbar is a useful tool giving you access to control some AWS services from within Visual Studio, including creating DynamoDb tables and viewing/editing their content.

A sample of the DynamoDB schema. Image: aws.amazon.com

I used the toolbar to create some initial test tables. A few points to mention about the creation procedure:

  1. At creation time you need to specify whether the table with have a range key or not. This cannot be changed later. This struck me a potential issue if you incorrectly judge how much a table will grow to and want to introduce ranging later on.
  2. At creation time you also have to specify the level of read/writes per second you want assigning to this table. As I mentioned earlier I found this slightly off-putting, primarily because I actually had no idea. As this was a test table it may require a few reads/writes per second very infrequently but I had to commit to a permanent fixed allocation (this can be changed at any point).
  3. Amazon doesn’t currently offer an offline version for development so any testing has to be done on the live AWS service. This could result in running up bills if doing full production scale tests. AWS does offer a generous free tier for DynamoDB but it is always something to be aware of when using AWS.

Having got my tables created I could now start looking at how to enter data into them. The system I was looking to build in DynamoDB was a remote offshoot of an existing system that held a read only subset of the data. All data would be pulled from REST API calls as JSON objects and stored within AWS until read and cached in memory.

DynamoDB sounded like a good candidate for this task as I thought I could just try and read the JSON objects into .net objects and store them direct in DynamoDB.

The first API example I looked at (http://docs.amazonwebservices.com/amazondynamodb/latest/developerguide/LoadData_dotNET.html) used a document object, assigned attributes to it then pushed it to the remote table

 Amazon.DynamoDB.DocumentModel.Table replyTable = Amazon.DynamoDB.DocumentModel.Table.LoadTable(client, "Reply");

&nbsp;

var thread1Reply1 = new Document();

thread1Reply1["Id"] = "Amazon DynamoDB#DynamoDB Thread 1"; // Hash attribute.

thread1Reply1["ReplyDateTime"] = DateTime.UtcNow.Subtract(new TimeSpan(21, 0, 0, 0)); // Range attribute.

thread1Reply1["Message"] = "DynamoDB Thread 1 Reply 1 text";

thread1Reply1["PostedBy"] = "User A";

&nbsp;

replyTable.PutItem(thread1Reply1);

Although this syntax is pretty straight forward I didn’t really like the look of it. To convert my existing objects would be either a manual process including a lot of magic strings or an overly complex process of mapping data attributes to document attributes.

Luckily AWS have provided a solution and implemented the second solution for you. They have created a number of attributes that you can decorate your class with to define it as a DynamoDB class. These can then be read directly to and from the db.

The minimum attributes you need to define on a table are:

[DynamoDBTable("TableName")]

public class ClassName

{

[DynamoDBHashKey]   // hash key

public int Id { get; set; }

This will then save all data to the table TableName with the hash key of Id. You can also use the DynamoDbProperty attribute to define that it should be read to and from the database and even use DynamoDbProperty(“dbFieldName”) to associate it with differently named attributes in the database table. DynamoDbIgnore will flag the field not to be transferred to the database.

Therefore the only code needed to store an object into a DynamoDB table is to define an object:

[DynamoDBTable("Fruit")]

public class tFruit : Fruit

{

[DynamoDBHashKey]

public new string Name { get; set; }

&nbsp;

public string Colour { get; set; }

}

And then create an instance of the object and save it to the DynamoDb context.

AmazonDynamoDBClient client = new AmazonDynamoDBClient();

DynamoDBContext context = new DynamoDBContext(client);

Fruit banana = new Fruit()

{

Name = "banana",

Colour = "yellow"

};

&nbsp;

context.Save(banana);

Getting it back out is even easier:

Fruit bananaFromDb = context.Load("banana");

This is a very nice and simple solution if you are defining your own classes.

I was impressed with the SDK and it looks a simple job to get data in and out of the database, so this could well be a viable option if a NoSQL solution is required; especially if Amazon’s claims about scalability hold up. There is an entity size limit of 64kb, which could be a limiting factor (Table Services allows 1mb) and the lack of an offline emulator for development could also cause issues.

All in all though, this is up to the usual high standard of tools that AWS produce.

Andy hosted a live webinar “Designing Applications for the Cloud” in March 2012. Watch it on YouTube or in the player embedded below.

1 Comment

Filed under Opinions