Gajotres.net

jQuery Mobile and how to enhance the markup of dynamically added content

Share this article
maps
 
There are several ways of enhancing dynamically created content markup. It is just not enough to dynamically add new content to jQuery Mobile page, new content must be enhanced with classic jQuery Mobile styling.
 
Because this is rather processing heavy task there need to be some priorities, if possible jQuery Mobile needs to do as less enhancing as possible. Don’t enhance whole page if only one component need’s to be styled.
 
What does this all means? When page plugin dispatches a pageInit event, which most widgets use to auto-initialize themselves. it will automatically enhance any instances of the widgets it finds on the page.
 
However, if you generate new markup client-side or load in content via Ajax and inject it into a page, you can trigger the create event to handle the auto-initialization for all the plugins contained within the new markup. This can be triggered on any element (even the page div itself), saving you the task of manually initializing each plugin (listview button, select, etc.).
 
With this in mind lets discuss enhancement levels. There are three of them and they are sorted from the less resource demanding to higher ones:
 
  • Enhance a single component/widget
  • Enhance a page content
  • Enhance a full page content (header, content, footer)

 

Enhance a single component/widget:


 
 
Every jQuery Mobile widget can be enhanced dynamically:
 

1. Listview:

 
Markup enhancement:
 
$('#mylist').listview('refresh');
 
Removing listview elements:
 
$('#mylist li').eq(0).addClass('ui-screen-hidden'); 
 
Enhancement example: http://jsfiddle.net/Gajotres/LrAyE/
 
Method refresh() only affects new nodes appended to the list. This is done because of a performance reasons.
 
One of a listview high-points is a filtering functionality. Unfortunately, for some reason, jQuery Mobile will fail to dynamically add filter option to an existing listview. Fortunately there’s a workaround. If possible, remove current listview and add another one with a filer option turned on.
 
Here’s a working example: http://stackoverflow.com/a/15163984/1848600
 
$(document).on('pagebeforeshow', '#index', function(){       
    $('<ul>').attr({'id':'test-listview','data-role':'listview', 'data-filter':'true','data-filter-placeholder':'Search...'}).appendTo('#index [data-role="content"]');
    $('<li>').append('<a href="#">Audi</a>').appendTo('#test-listview');
    $('<li>').append('<a href="#">Mercedes</a>').appendTo('#test-listview');
    $('<li>').append('<a href="#">Opel</a>').appendTo('#test-listview');
    $('#test-listview').listview().listview('refresh');
});
 

2. Button

 
Markup enhancement:
 
$('[type="button"]').button();
 
Enhancement example: http://jsfiddle.net/Gajotres/m4rjZ/
 
One more thing, you don’t need to use a input element to create a button, it can be even done with a basic div, here’s an example: http://jsfiddle.net/Gajotres/L9xcN/
 

3. Navbar

 
Markup enhancement:
 
$('[data-role="navbar"]').navbar();
 
Enhancement example: http://jsfiddle.net/Gajotres/w4m2B/
 
Here’s a demo how to add dynamic navbar tab: http://jsfiddle.net/Gajotres/V6nHp/
 
And one more in pagebeforecreate event: http://jsfiddle.net/Gajotres/SJG8W/
 

4. Text inputs, Search inputs & Textareas

 
Markup enhancement:
 
$('[type="text"]').textinput();   
 
Enhancement example: http://jsfiddle.net/Gajotres/9UQ9k/
 

5. Sliders & Flip toggle switch

 
Markup enhancement:
 
$('[type="range"]').slider();  
 
Enhancement example: http://jsfiddle.net/Gajotres/caCsf/
 
Enhancement example during the pagebeforecreate event: http://jsfiddle.net/Gajotres/NwMLP/
 
Sliders are little bit buggy to dynamically create, read more about it here: http://stackoverflow.com/a/15708562/1848600
 

6. Checkbox & Radiobox

 
Markup enhancement:
 
$('[type="radio"]').checkboxradio();
 
or if you want to select/deselect another Radiobox/Checkbox element:
 
$("input[type='radio']").eq(0).attr("checked",false).checkboxradio("refresh");
 
or
 
$("input[type='radio']").eq(0).attr("checked",true).checkboxradio("refresh");
 
Enhancement example: http://jsfiddle.net/Gajotres/VAG6F/
 

7. Select menu

 
Markup enhancement:
 
$('select').selectmenu();  
 
Enhancement example: http://jsfiddle.net/Gajotres/dEXac/
 

8. Collapsible

 
Unfortunately collapsible element can’t be enhanced through some specific method, so trigger(‘create’) must be used instead.
 
Enhancement example: http://jsfiddle.net/Gajotres/ck6uK/
 

9. Table

 
Markup enhancement:
 
$(".selector").table("refresh");
 
While this is a standard way of table enhancement, at this point I can’t make it work. So instead use trigger(‘create’).
 
Enhancement example: http://jsfiddle.net/Gajotres/Zqy4n/
 

10. Panels

 
Panel Markup enhancement:
 
$('.selector').trigger('pagecreate');
 
Markup enhancement of content dynamically added to Panel:
 
$('.selector').trigger('pagecreate');
 
Example: http://jsfiddle.net/Palestinian/PRC8W/
 

Enhance a page content:


 
 
In case we are generating/rebuilding whole page content it is best to do it all at once and it can be done with this:
 
$('#index').trigger('create');
 
Enhancement example: http://jsfiddle.net/Gajotres/426NU/
 
 

Enhance a full page content (header, content, footer):


 
 
Unfortunately for us trigger(‘create’) can not enhance header and footer markup. In that case we need big guns:
 
$('#index').trigger('pagecreate');
 
Enhancement example: http://jsfiddle.net/Gajotres/DGZcr/
 
This is almost a mystic method because I can’t find it in official jQuery Mobile documentation. Still it is easily found in jQuery Mobile bug tracker with a warning not to use it unless it is really really necessary.
 
Note, **.trigger(‘pagecreate’);** can suppose be used only once per page refresh, I found it to be untrue: http://jsfiddle.net/Gajotres/5rzxJ/
 

3rd party enhancement plugins


 
 
There are several 3rd party enhancement plugins. Some are made as an update to an existing method and some are made to fix broken jQM functionalities. Button text change
 
Unfortunately cant found the developer of this plugin. Original SO source.
 
(function($) {
	/*
	 * Changes the displayed text for a jquery mobile button.
	 * Encapsulates the idiosyncracies of how jquery re-arranges the DOM
	 * to display a button for either an <a> link or <input type="button">
	 */
	$.fn.changeButtonText = function(newText) {
		return this.each(function() {
			$this = $(this);
			if( $this.is('a') ) {
				$('span.ui-btn-text',$this).text(newText);
				return;
			}
			if( $this.is('input') ) {
				$this.val(newText);
				// go up the tree
				var ctx = $this.closest('.ui-btn');
				$('span.ui-btn-text',ctx).text(newText);
				return;
			}
		});
	};
})(jQuery);
 
Working example: http://jsfiddle.net/Gajotres/mwB22/
 

Get correct maximum content height


 
 
In case page header and footer has a constant height content div can be easily set to cover full available space with a little css trick:
 
#content {
	padding: 0;
	position : absolute !important; 
	top : 40px !important;  
	right : 0; 
	bottom : 40px !important;  
	left : 0 !important;     
}
And here’s a working example with Google maps api3 demo: http://jsfiddle.net/Gajotres/7kGdE/
 
This method can be used to get correct maximum content height, and it must be used with a pageshow event.
 
function getRealContentHeight() {
	var header = $.mobile.activePage.find("div[data-role='header']:visible");
	var footer = $.mobile.activePage.find("div[data-role='footer']:visible");
	var content = $.mobile.activePage.find("div[data-role='content']:visible:visible");
	var viewport_height = $(window).height();

	var content_height = viewport_height - header.outerHeight() - footer.outerHeight();
	if((content.outerHeight() - header.outerHeight() - footer.outerHeight()) <= viewport_height) {
		content_height -= (content.outerHeight() - content.height());
	} 
	return content_height;
}
 
And here’s a live jsFiddle example: http://jsfiddle.net/Gajotres/nVs9J/
 
There’s one thing to remember. This function will correctly get you maximum available content height and at the same time it can be used to stretch that same content. Unfortunately it cant be used to stretch img to full content height, img tag has an overhead of 3px.
 

Methods of markup enhancement prevention:


 
 
This can be done in few ways, sometimes you will need to combine them to achieve a desired result.
 

Method 1:

 
It can do it by adding this attribute:
 
data-enhance="false"
 
to the header, content, footer container.
 
This also needs to be turned in the app loading phase:
 
$(document).one("mobileinit", function () {
    $.mobile.ignoreContentEnabled=true;
});
 
Initialize it before jquery-mobile.js is initialized (look at the example below).
 
More about this can be found here.
 
Example: http://jsfiddle.net/Gajotres/UZwpj/
 
To recreate a page again use this:
 
$('#index').live('pagebeforeshow', function (event) {
	$.mobile.ignoreContentEnabled = false;
	$(this).attr('data-enhance','true');
	$(this).trigger("pagecreate")
});
 

Method 2:

 
Second option is to do it manually with this line:
 
data-role="none"
 
Example: http://jsfiddle.net/Gajotres/LqDke/
 

Method 3:

 
Certain HTML elements can be prevented from markup enhancement:
 
$(document).bind('mobileinit',function(){
    $.mobile.page.prototype.options.keepNative = "select, input";
});
 
Example: http://jsfiddle.net/Gajotres/gAGtS/
 
Again initialize it before jquery-mobile.js is initialized.
 

Markup enhancement problems:


 
 
Sometimes when creating a component from scratch (like listview) this error will occur:
 
cannot call methods on listview prior to initialization
 
It can be prevented with component initialization prior to markup enhancement, this is how you can fix this:
 
 
$('#mylist').listview().listview('refresh');
 

Markup overriding problems:


 
 
If for some reason default jQuery Mobile CSS needs to be changed it must be done with !important override. Without it default jQuery Mobile CSS styles are not going to be changed.
 
	 
#navbar li {
    background: red !important;
}
 
jsFiddle example: http://jsfiddle.net/Gajotres/vTBGa/
 
 

15 COMMENTS ON THIS POST To “jQuery Mobile and how to enhance the markup of dynamically added content”

  • Omar

    June 4, 2013 at 09:30

    Great job Dragan!! You’re talented, keep it up!

  • Anonmous Change

    June 6, 2013 at 11:02

    Great information Gajotres

  • Tomislav

    July 2, 2013 at 07:49

    Very good job Dragan. Keep it up.

  • Michael

    August 3, 2013 at 12:52

    If I am adding Jquery from JSON/AJAX where do add these enhancements? Do I put them in the html file that is being brought in via json? Or, do I put the enhancements in the original html file?

    Thanks so much.

  • Leo

    October 31, 2013 at 11:11

    Hi, thanks for your post.

    Do you know if this is working with edge versions: jQuery 2.03 and jQuery Mobile 1.3.2?

    As far as I can tell it is not working: http://jsfiddle.net/ljcorreia/m4rjZ/51/

    Ok, it may be something I set incorrectly on jsFiddle, because in my project on Visual Studio it does add the buttons. However it does not add the elements to the DOM on IE9 and most importantly on iPad.

    Can you shed some light on this, please?

    Thanks

  • Leo

    October 31, 2013 at 12:51

    Ok, I realize the issue with my jsFiddler: http://jsfiddle.net/ljcorreia/m4rjZ/53/

    Unfortunately I still could not get to the bottom of what is the issue on my project.

    Cheers.

  • Markus

    December 16, 2013 at 14:22

    Is it somehow possible to enhance the markup off-dom (e.g. with DocumentFragments)?

    We are using Mustache.js & jQueryMobile in combination and the rendering gets slow because we have a lot of steps todo
    - load a mustache-tpl via ajax
    - let Muchstache parse the template and interpolate some values
    - take the given string and put it into the DOM
    - enhance the resulting page

    we would like to enhance the rendered Mustache with jQueryMobile off the DOM though the browser doesn’t have to do this expensive reflows+repaints 2 times (1st while html “plain” html is inserted into the sites’ DOM; 2nd after/while jQueryMobile enhance)..

  • oglegendre

    December 30, 2013 at 16:43

    Hi !
    Thanks for this really useful blog post !
    Unfortunately, the jsfiddle example does not work with jquery 1.4 (it works fine with 1.2). It seems that the pagecreate trigger does not work anymore. I was hoping to use this feature to load each page div from a local file containing only the HTML. All this to avoid putting ALL the pages definition in the same html file (which may be fine on a small project…but not quite maintainable on a real project).
    Speaking of which, it is such a pity there is no way to do that directly (see http://stackoverflow.com/questions/6875404/why-does-html5-not-include-a-way-of-loading-local-html-into-the-document)

  • simon

    January 4, 2014 at 12:45

    Your notes about radio button enhancment were very useful to me…thanks.
    I think they need updating because I have discovered that as of jQuery 1.9 the ‘.attr(“checked”,false)’ method no longer works and must the substituted with ‘.prop(“checked”,false)’

  • Kiran

    February 4, 2014 at 11:59

    Very nice tutorial.

    I would like to know, how can i pass data from the one to another in jquery mobile.

    What i am trying to achieve is , i need to pass an ID from list item to detailed view page, please guide me how to do this.

    Thanks

    • Dragan Gaić

      Dragan Gaić

      February 7, 2014 at 14:13

      It can be done easily. You will need to prevent list from changing pages on its own, have list element href attribute empty.

      And onClick event to list elements. Each time list is clicked store clicked list element to localstorage and manually change the page. Before detailed page is shown, read id form localstorage abd build page content.

  • Aravinth

    February 19, 2014 at 06:14

    Nice tutorial but i cant find find what i need..look at this one …http://jsfiddle.net/aravinth/Ad22d/90/…… in this fiddle cloned the rows using add row then add expenses shows the popup it is also cloned.. now i click the first row add expense button, 3 rows cloned in popup when click second row add expenses it shows three rows but need to show only one column …Suggest some solutions. Thank you..

  • Robert Broen

    February 19, 2014 at 12:39

    Thank you, this was very helpful.

Leave a Reply


+ two = four