Rip's Domain

Pitfalls with jQuery Mobile and how to over come them

Posted in Jquery, jquery mobile by rip747 on April 19, 2012

At work I’ve been tasked with creating a mobile app using jQuery Mobile and Phonegap. Needless to say, I’ve had nothing but issues. Below are some of the pitfalls that I’ve been running into and how to over come them. This post is a work in progress and I will add to it as things come up.

1) When creating additional pages, ONLY include the `data-role=”page”`.

First, let me explain what I mean by data-role=”page”. When you create your index.html for your jQuery Mobile project, you set it up like so:

<!– index.html –>
<!DOCTYPE html>
<html>
<head>
<title>My Project</title>
<meta name=”viewport” content=”width=device-width, initial-scale=1″/>
<link rel=”stylesheet” href=”css/jquery.mobile-1.1.0.min.css” />
<script type=”text/javascript” charset=”utf-8″ src=”js/jquery-1.7.1.min.js”></script>
<script type=”text/javascript” charset=”utf-8″ src=”js/jquery.mobile-1.1.0.min.js”></script>
<script type=”text/javascript” charset=”utf-8″ src=”js/phonegap-1.4.1.js”></script>
</head>
<body>

<div data-role=”page” id=”home”>

<div data-role=”header”>
<h1>Your header</h1>
</div>

<div data-role=”content”>
<p>The content goes here</p>
</div>

<div data-role=”footer”>
<p>The footer goes here</p>
</div>

</div>
</body>
</html>

Notice that we have our opening html tag, our head section with all our stylesheets and javascript includes, our opening body tag, our page div (containing the head, content and footer of the page), our closing body tag and closing html tag. This is standard when creating a html page and something we’re all use to.

Now let’s say you want to create another page called search.html. You would think that you would need to copy the head  section, body and html tags to the search.html page, but THIS IS WRONG! All the search.html page would contain is the page div like so:

<!– search.html –>

<div data-role=”page” id=”search”>

<div data-role=”header”>
<h1>Search</h1>
</div>

<div data-role=”content”>
<p>The form used to search</p>
</div>

<div data-role=”footer”>
<p>The footer goes here</p>
</div>

</div>

The reason for this is that jQuery Mobile will inject the search.html page’s content into the index.html via ajax. Because of this, if you put the html, head, and body tags into your search.html page, they will get duplicated. This can cause all sorts of issues that I won’t even go into. Just remember that you only need the html, head and body tags on the index.html page.

2) Disable ajax caching globally

Supposedly in jQuery Mobile disabled ajax caching of pages in 1.1.0. However, I was still having issue with the pages being cached so this was making development a real pain. Luckily you can disable ajax caching altogether by doing:

<script>
$.ajaxSetup ({
// Disable caching of AJAX responses
cache: false
});
</script>

Just put that in your head section on your index.html page and you should be golden.

3) Multiple events firing

This was a HUGE pain in the ass. You normally see this problem when submitting a form from one page to another. On the calling page, you might have some javascript that is bound to an event (like pageshow) that generates some dynamic content after doing an ajax call. If you look in your Net tab in firebug, you’ll notice that the ajax call will increment with each visit. So on the first visit it fires once, the second visit it fires twice and so on. The reason for this is because, again, of the way jQuery Mobile pulls in pages via ajax. Because it will pull the page in for each visit, it will continually add the code you want to run to the event stack on each visit.

Now I’ve seen people try to solve this by placing the code in the `pageinit` event since that only fires the first time the page in pulled via ajax, but this doesn’t work when you’re having to create dynamic content based on a search string.

The solution is quite simple once you think about it, just put your code on the index.html and delegate using the on() method. so for instance, if you have page with an id of `search` and you want to run an ajax request to get the results, you would do:

$(document).on(‘pageshow’, ‘#search’, function(){
// add code to get the search results via ajax here
});

Personally I would recommend that you put all the javascript code for your app in a js file and include that on your index.html page.

4) When inserting dynamic content into the DOM you must call trigger(‘create’) in order for the framework to apply the styling.

For my app, I have a function that automatically add pagination buttons (previous and next) to the footer. In my template the footer is just defined plainly:

<div data-role=”footer”></div>

In my javascript code, I add the pagination by calling the html() method on the footer object (code is summarized):

var loc = {};
loc.self = $(this);
loc.footer = $(‘[data-role=”footer”]’, loc.self);
loc.footer.html(pagination(loc.params.page, loc.results.PAGES));

The issue was that none of the styling was taking affect on the footer and the prev and next button were showing up as just links. I found in the jQuery Mobile form that you should call page() on the object after altering it, but this really didn’t work. After some more search I found that what I really need to do was call trigger(‘create’) on the object instead. the nice thing is that you can chain this after the html() call and it still works:

loc.footer.html(pagination(loc.params.page, loc.results.PAGES)).trigger(‘create’);

5) When creating a dynamic listview and inserting content into it, you need to call listview() and then listview(‘refresh’) to reapply the styling.

This is basically the same problem as #4 only this time we’re dynamically creating a listview an inserting content into it. In my app, I use Liquid as a templating language as it’s just making life soooo much easier then concatenating javascript strings. So to create my list view, I have it defined in my Liquid template like so:

<script type=”text/liquid” id=”arrestSearchResults-markup”>
{% if RESULTS.size %}
<ul id=”arrestSearchResults-listview” data-role=”listview”>
{% for item in RESULTS %}
<li>
<a href='{{LINK}}&jms_number={{item.JMS_NUMBER}}’ class=’arrestSearch-details’>
<img src='{{item.PHOTO_THUMB}}’ />
<h3>{{item.LAST_NAME}}, {{item.FIRST_NAME}} {{item.MIDDLE_NAME}}</h3>
<p>{{item.JAIL}}</p>
</a>
</li>
{% endfor %}
</ul>
{% else %}
<h3>No records found</h3>
{% endif %}
</script>

The target content div is defined plainly, just like my footer is in #4:

<div data-role=”content”></div>

to compile the template and insert it into the content div, I do the following in my code (code is summarized):

var loc = {};
loc.self = $(this);
loc.markup = $(“#arrestSearchResults-markup”).html();
loc.target = $(‘[data-role=”content”]’, loc.self);

// render the markup to the listview
loc.target.html(Liquid.Template.parse(loc.markup).render(assigns));
// refresh the listview
loc.target.find(‘ul’).listview();
loc.target.find(‘ul’).listview(‘refresh’);

Basically what I’m doing is getting the template markup and the content div and putting them into a variables. I then compile the Liquid template and pass in the assigns object that contains the information to render the template.

The key to all of this is the next two lines which finds the unsorted list (‘ul’) which contains the listview a just injected into the content div and calls listview() on it, this tell jQuery Mobile to treat the ul as a listview object. I then call listview(‘refresh’) to have the framework apply the styling to it.

6) When performing validation on a form, the form will still submit.

Here is the setup. You have a form and you’re trying to perform some sort of validation on it when the form is submitted and show the visitor some errors. You tie your validation to the form’s submit event using submit() and include event.preventDefault() good measure when any error occur. However, the form still submits even though errors are through, the event.preventDefault() does prevent the form from not submitting. Heck, you even throw in `return false` hoping the form won’t submit, but it still does.

The issue is that the form is being submitting via ajax and you can’t stop the ajax submission from happening through standard means. The only thing you can do is turn off ajax and submit the form yourself. Now in older versions of the framework, you could turn off ajax for form submissions separately, however in the latest version (1.1.0) you can only turn off ajax globally by setting `ajaxEnabled` to false. This sucks as you most likely want all the ajax goodness, just not on form submission.

The way around the is to add `data-ajax=”false”` to the form:

<form id=”myform” action=”somepage.html” method=”get” data-ajax=”false”>

The will prevent the form from being submitted via ajax. Now the fun part if how in the world are you going to submit the form data to the action page using ajax so you get that nice ajax spinner thingy when you’ve turn ajax off? The answer is manually submit the form data by serializing it and appending it to form’s action attribute. Then use `$.mobile.changePage()` to submit the data via ajax. Below is a little helper function I wrote to do this:

submitForm = function(formid){
var form = $(“#” + formid);
var page = [];
page.push(form.attr(‘action’));
page.push(form.serialize());
$.mobile.changePage(page.join(‘?’));
}

To use, just call submitForm(‘your form id’) and it will handle the submission for you:

submitForm(“myform”);

7) Calling trigger(‘create’) on date-role=”header” has no effect

Though you need to call trigger(‘create’) on the data-role=”content” when adding dynamic content for it to style properly, this doesn’t hold true for data-role=”header”. The solution is to call trigger(‘pagecreate’) instead.

————————–

I will continue to add to this post as more issues come up. Please let me know if you have any tips in the comments below.

7 Responses

Subscribe to comments with RSS.

  1. Eugene said, on June 13, 2012 at 10:40 am

    Thanks, I am start developing for PhoneGap too. keep writing about it.

  2. Index said, on June 26, 2012 at 9:22 am

    #1 is wrong: JQM ignores repeat headers and only includes it once. Check the documentation.
    Your solution works – but only until a user hits refresh. Granted this is implausible on a PhoneGap deployment, but most prefer to be able to deploy their PhoneGap -app on a mobile web site as well.

  3. wra said, on July 22, 2012 at 5:04 am

    Really helpful in my current dealing with phonegap & jquery mobile. Thanks!

  4. Steven said, on August 29, 2012 at 11:35 am

    I agree with Index, I wouldn’t follow the advice on #1. If the user ever gets to the page without first loading index.html as well as refreshing once there then it just won’t work.

  5. Peter said, on September 5, 2012 at 8:49 am

    Try to use chaining:

    loc.target
    .html(…)
    .find(‘ul’)
    .listview()
    .listview(‘refresh’);

  6. Jerry said, on October 17, 2012 at 12:05 pm

    On the issue with “Form Still Submits” I have a question:
    – If I use JQM with Dreamweaver’s builtin SPRY Validation, the Form fields go red but then it submits anyway?
    – Will your solution help in this case when using SPRY Validation?
    – Here is a link with more details: http://forums.adobe.com/message/4620296 (see 3rd entry)

  7. Larry said, on October 17, 2012 at 5:26 pm

    This worked perfectly, while 200 other suggestions did not. Thank you — no one else mentioned listview() then the refresh….


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

%d bloggers like this: