Welcome to our third installment of Advanced jQuery Mobile tutorial, a new series where we talk about more advanced aspects of jQuery Mobile development. In the last article, we discussed jQuery Mobile client-server communication, and I showed you an easy example. Now I want to expand that topic and show you how to work with dynamically added content, and furthermore, how to even do it with jQuery Mobile.
 
 

Note: If this tutorial was helpful, need further clarification, something is not working or do you have a request for another Ionic post? Furthermore, if you don't like something about this blog, if something is bugging you, don't like how I'm doing stuff here, again leave me a comment below. I'm here to help you, I expect the same from you. Feel free to comment below, subscribe to my blog, mail me to dragan.gaic@gmail.com, or follow and mention me on twitter (@gajotres). Thanks and have a nice day!

PS. If you want my help, if possible (even if it takes you some time to do that), create a working example I can play with. Use Plunker for AngularJS based questions or jsFiddle for jQuery/jQuery Mobile based questions.


 

Table of Contents

 

Intro

 
Note: This tutorial is made for jQuery Mobile version 1.4 (possibly 1.5 if jQuery Mobile developers fulfill their promise and postpone depreciation of some methods) and below. I want to cover as many jQuery Mobile users as I can, so I made a decision to use now deprecated (as of jQuery Mobile 1.4) way of page handling. Pagecontainer object introduced in jQuery Mobile 1.4 is still in development, plus it's missing several key features we will use in some future articles. Another reason is that I fear some users won't understand changes made to page handling. If these series become successful, I will also write examples intended for new page handling.
 
Last time, I showed you why server-side (PHP, Java, ASP.NET) shouldn’t be used to generate jQuery Mobile client side code, at least if we are creating a mobile application. I also showed you how to establish simple client-server communication. Now we’ll use that same solution to gather some server side data and show it in our application. I will also show you how to generate new jQuery Mobile content, new pages and finally why is it crucial to plan everything before you start coding.
 
Everything I said about planning is related to my very first jQuery Mobile application. I must confess, it wasn’t beautiful, fast nor very usable. A Major factor for low application performance was my lack of understanding of what makes a good hybrid mobile application.
 
The Worst thing you can do while creating a hybrid mobile application is making it too large; that was my first and foremost mistake. I had too many pages, too much non-necessary content, it was clogging my mobile device memory, not to mention, a user experience was … bad.
 
I could have solved this if I only understand this:
 
  • All essential pages needs to be part of the first HTML file. That’s the file first loaded into the DOM. Every page loaded with it will permanently stay in memory so make sure you need them there. You can’t programmatically remove them from the DOM if AJAX is used as page handler.
  • Separate non-essential pages into other HTML pages (just remember the rule, one page per HTML file, only first HTML can hold more than one page).
  • Remove some pages completely, especially if we can generate them on our own. These are pages that will change over time, for example, pages holding listview that’s refreshed with new content every few minutes.
 
I will show you how to do this in a working example.
 

We will cover

 
  • How to dynamically add jQuery Mobile content
  • How to properly style dynamically added content
  • How to properly bind events to dynamically added content
 

Example 1 – Creating a weather application

 
The first thing we need to do is to create our skeleton application. This will be our initial HTML – CSS – JavaScript code, the just basic content we need for our application to work.
 
Let us create initial page, it should look something like this:
 
HTML:
 
<!DOCTYPE html>
<html>
    <head>
        <title>jQM Complex Demo</title>
        <meta name="viewport" content="initial-scale=1, maximum-scale=1"/>
        <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css" /> 
        <script src="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.js"></script>    
    </head>
    <body>
        <div data-role="page" id="index">
            <div data-theme="a" data-role="header">
                <h3>
                    City Search
                </h3>
            </div>        
            <div data-role="content">
                <div class="example-wrapper" data-iscroll>
                    <input name="city-search" id="city-search" value="" type="text" placeholder="Enter City Name"/>            
                    <a href="#" class="ui-btn" id="city-search-btn">Search</a>
                </div>
            </div>            
        </div> 
    </body>
 
Next JavaScript snippet will acquire our data:
 
$(document).on('pageinit', '#index', function(){        
    $(document).on('click', '#city-search-btn', function(){ 
        var cityName = $('#city-search').val();
        if(cityName.length > 0) {
            var url = 'http://api.openweathermap.org/data/2.5/weather?q='+cityName+'&amp;units=metric';    
            $.ajax({
                url: url,
                dataType: "jsonp",
                async: true,
                beforeSend: function() {
                    // This callback function will trigger before data is sent
                    $.mobile.loading('show', {theme:"a", text:"Please wait...", textonly:true, textVisible: true}); // This will show AJAX spinner
                },
                complete: function() {
                    // This callback function will trigger on data sent/received complete
                    $.mobile.loading('hide'); // This will hide AJAX spinner
                },                
                success: function (result) {
                    ajax.parseJSONP(result);
                },
                error: function (request,error) {
                    alert('Network error has occurred please try again!');
                }
            });          
        } else {
            alert('Please enter city name!');
        }       
    });        
});
 
At this point, we have everything we need to get some raw data. I have selected openweathermap.org as a source; they have more then enough information to make our application look like something. If we start this application and for example enter London, when we hit Search button we should receive something like this:
 
{"coord":{"lon":-81.23,"lat":42.98},"sys":{"message":0.0076,"country":"CA","sunrise":1400061712,"sunset":1400114440},"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"base":"cmc stations","main":{"temp":14.89,"humidity":77,"pressure":1014,"temp_min":12,"temp_max":19.44},"wind":{"speed":3.08,"gust":3.08,"deg":67},"rain":{"3h":0},"clouds":{"all":80},"dt":1400076515,"id":6058560,"name":"London","cod":200}
 
This is everything we need. We will use this data to create new jQuery Mobile page, holding a Google map API v3 city overview, plus some weather data. Of course we will need to generate everything from scratch. Here’s a code we will use to create our new page:
 
$(document).on('pagehide', '#map', function(){   
    $(this).remove();
});

$(document).on('pageshow', '#map',function(e,data){   
    var minZoomLevel = 12;
    
    var myLatLng = new google.maps.LatLng(weatherData.response.coord.lat, weatherData.response.coord.lon);
    
    var map = new google.maps.Map(document.getElementById('map_canvas'), {
        zoom: minZoomLevel,
        center: myLatLng,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    });
    
    var image = {
        url: 'http://openweathermap.org/img/w/'+weatherData.response.weather[0].icon+'.png'
    };
    
    infoWindow = new google.maps.InfoWindow();
    infoWindow.setOptions({
        content: "<div class='info-window'><div class='icon-holder'><img src='http://openweathermap.org/img/w/"+weatherData.response.weather[0].icon+".png'/></div><div class='info-holder'><span class='info-text'>City:</span><br/>"+weatherData.response.name+"<br/><span class='info-text'>Min. Temp:</span><br/>"+weatherData.response.main.temp_min+" °C<br/><span class='info-text'>Temp:</span><br/>"+weatherData.response.main.temp+" °C<br/><span class='info-text'>Max. Temp:</span><br/>"+weatherData.response.main.temp_max+" °C</div></div>",
        position: myLatLng,
    });
    infoWindow.open(map);     
    
});

var ajax = {  
    parseJSONP:function(result){  
        weatherData.response = result;
        var mapPage    =   $('<div>').attr({'id':'map','data-role':'page'}).appendTo('body');
        var mapHeader  = $('<div>').attr({'data-role':'header', 'data-theme' : 'b','id':'map-header'}).appendTo(mapPage);
        $('<h3>').html(weatherData.response.name + ' weather').appendTo(mapHeader);
        $('<a>').attr({'href':'#index', 'class' : 'ui-btn-righ'}).html('Back').appendTo(mapHeader);
        var mapContent = $('<div>').attr({'data-role':'content'}).appendTo(mapPage);
        $('<div>').attr({'id':'map_canvas', 'style':'height:100%'}).appendTo(mapContent);
        $.mobile.changePage( "#map", { transition: "slide"});
    }
}

var weatherData = {
    response : null
}
 
Let us discuss this code. The first thing we are doing after successful AJAX call is storing JSON response into a JavaScript object, this way we can easily access it anytime we want. Next part is most important, at least for this article, we need to create dynamically page that’s going to hold our weather data:
 
var mapPage    = $('<div>').attr({'id':'map','data-role':'page'}).appendTo('body');
var mapHeader  = $('<div>').attr({'data-role':'header', 'data-theme' : 'b','id':'map-header'}).appendTo(mapPage);
$('<h3>').html(weatherData.response.name + ' weather').appendTo(mapHeader);
$('<a>').attr({'href':'#index', 'class' : 'ui-btn-righ'}).html('Back').appendTo(mapHeader);
var mapContent = $('<div>').attr({'data-role':'content'}).appendTo(mapPage);
$('<div>').attr({'id':'map_canvas', 'style':'height:100%'}).appendTo(mapContent);
 
As you can see everything is done dynamically, we are even changing page header text using city name from our JSON response. Of course we could have done this a little bit different. Instead of removing a whole page we could only empty content div thus skipping whole page generation. We will see this kind of content generation in our second example. This example is done from scratch so that you can see that there are several solutions to any problem.
 
At some point a dynamic page is created inside the DOM, now we can use a changePage method and show newly generated content. At this point, things are becoming a little bit complicated, we still need to cover two-page events bound to the second page. Take a look at this code:
 
$(document).on('pagehide', '#map', function(){   
.
.
.
$(document).on('pageshow', '#map',function(e,data){   
.
.
.
 
Those events (pagehide and pageshow) are bind in such way that they don’t care if page #map exist inside the DOM. This is all thanks to on() method which works for both current and future DOM elements (like a new element created by a script). Thanks to this way of event handling we can create/remove elements (pages) from DOM as much as we want, and in the end, mentioned events will always work on newly created content. You can use on() method for other kind of events, not just page events, remember this.
 
Code executed inside a pageshow event prepares and shows acquired data on Google map. I won’t cover this part of example because you can find it in my other article. Last but not least take a look at this code snippet:
 
$(document).on('pagehide', '#map', function(){   
    $(this).remove();
});
 
We are using this code to remove page #map during our return to first page. Let me show you a working jsFiddle example, find it here: http://jsfiddle.net/Gajotres/frSsS/ or play with it directly:
 
 
In the end, our code will look like this (I will show you everything in case jsFiddle stops working at some point):
 
Continue Reading

  • tony

    Hi Dragan

    very nice article thank you. It gave me some clues about dynamically building mobile web pages, particularly collapsibles and also my header will be dynamic.

    One of your links are broken here: “If you want to learn how to work with specific dynamic widgets take a look at this article. ”

    Best, Tony

  • Abdi

    How do you implement using your same code to pull and display the weather data of multiple cities. What I mean with clicking anything you wan to pull data of multiple cities and display.

    Thank it is an excellent tutorial

    Abdi

  • Abdi

    Dragan,

    It all makes sense, I thank you very much. You are good coach for coding. I like you how you keep things simple.Happy new year my friend.

    Put some Java/Android tutorial given that you have experience in that. I am craving for that ?

    Kind Regards,

    Abdi

    • Hi Abdi, thank you.

      I will write a Java version of this tutorial, I was planning to do it for some time. I will contact you when I finis it.

  • sharon

    I’m working on a jQuery mobile website. Would you recommend to combine it with backbone.js/require.js/ember.js to get a MVC structure? Do you have any articles about it?

  • Chener

    Can not say more thank YOU to you !

    One question, what if the app has a login page, should it be inclued as a page in the first html ?
    Or I just need to put all the essential pages into the second html after login page ?

    Thank you !

    • Initial page should always be part of a first HTML file, even if it is uses only once.