jquery mobile refreshing dynamically loaded page

17,470

Short Answer

The short answer is that jQuery Mobile expects you to use the hash to represent the state of a single page:

  • /index.html - Page 1
  • /index.html#dashboard - The dashboard.

Instead, you are loading a completely new page when you should be just calling JavaScript to dynamically change the content on the first page to represent the second page.

If the short answer makes sense, great! If not, the long answer is incredibly detailed and describes two ways you can approach this problem.

Long Answer

What you are essentially asking is how to build a multiple page mobile website and have both pages be accessible by the URI that identifies the resource.

There are two methods you could use to tackle this problem.

Multi-Page Templates:

For jQuery mobile, this is accomplished with the jQuery Mobile Multi-page Template.

The general idea is that both of your pages are in the same HTML document, like so:

<head>

</head>

<!-- This is your FIRST page -->
<div data-role="page" id="one">

    <div data-role="header">
        <h1>Multi-page</h1>
    </div><!-- /header -->

    <div data-role="content" >  
        <h2>One</h2>

        <p>I have an id of "one" on my page container. I'm first in the source order so I'm shown when the page loads.</p>  

        <h3>Show internal pages:</h3>
        <p><a href="#two" data-role="button">Show page "two"</a></p>    
        <p><a href="#popup"data-role="button" data-rel="dialog" data-transition="pop">Show page "popup" (as a dialog)</a></p>
    </div><!-- /content -->

    <div data-role="footer" data-theme="d">
        <h4>Page Footer</h4>
    </div><!-- /footer -->
</div><!-- /page one -->

<!-- This is your SECOND page -->
<!-- Start of second page: #two -->
<div data-role="page" id="two" data-theme="a">

    <div data-role="header">
        <h1>Two</h1>
    </div><!-- /header -->

    <div data-role="content" data-theme="a">    
        <h2>Two</h2>
        <p>I have an id of "two" on my page container. I'm the second page container in this multi-page template.</p>   
        <p>Notice that the theme is different for this page because we've added a few <code>data-theme</code> swatch assigments here to show off how flexible it is. You can add any content or widget to these pages, but we're keeping these simple.</p>  
        <p><a href="#one" data-direction="reverse" data-role="button" data-theme="b">Back to page "one"</a></p> 

    </div><!-- /content -->

    <div data-role="footer">
        <h4>Page Footer</h4>
    </div><!-- /footer -->
</div><!-- /page two -->

</body>

Next, what jQuery Mobile essentially does is it uses CSS to hide the DIV element with the id="two" and only show the div with the id="one". When the user clicks on the hyperlink with the `href="#two", there is a listener that intercepts the hashChange event and fires some JavaScript code that hides the DIV with id="one" and shows the DIV with id="two".

This makes the page transitions appear extremely smooth and fast, without needing to make a trip to the server to get the HTML markup.

Dynamically Intected Content:

If your data is more dynamic, then another option is to use the jQuery Mobile Dynamic Page Injection. The general premise to this process is similar to the Multi-Page Templates in that the browser listens for the hashChange event, except in addition to changing pages, it also makes an AJAX request to the server to retrieve JSON content.

<div id="home" data-role="page">
  <div data-role="header"><h1>Categories</h1></div>
  <div data-role="content">
    <h2>Select a Category Below:</h2>
    <ul data-role="listview" data-inset="true">
        <li><a href="#category-items?category=animals">Animals</a></li>
        <li><a href="#category-items?category=colors">Colors</a></li>
        <li><a href="#category-items?category=vehicles">Vehicles</a></li>
    </ul>
  </div>

</div>

When the category "Animals" is clicked on, the id="home" DIV is hidden. Instead of reloading the page, in this example, the HTML is being dynamically generated and populated with the results of a JSON object.

Here is the code that handles displaying the correct content:

// Load the data for a specific category, based on
// the URL passed in. Generate markup for the items in the
// category, inject it into an embedded page, and then make
// that page the current active page.
function showCategory( urlObj, options )
{
    var categoryName = urlObj.hash.replace( /.*category=/, "" ),

        // Get the object that represents the category we
        // are interested in. Note, that at this point we could
        // instead fire off an ajax request to fetch the data, but
        // for the purposes of this sample, it's already in memory.
        category = categoryData[ categoryName ],

        // The pages we use to display our content are already in
        // the DOM. The id of the page we are going to write our
        // content into is specified in the hash before the '?'.
        pageSelector = urlObj.hash.replace( /\?.*$/, "" );

    if ( category ) {
        // Get the page we are going to dump our content into.
        var $page = $( pageSelector ),

            // Get the header for the page.
            $header = $page.children( ":jqmData(role=header)" ),

            // Get the content area element for the page.
            $content = $page.children( ":jqmData(role=content)" ),

            // The markup we are going to inject into the content
            // area of the page.
            markup = "<p>" + category.description + "</p><ul data-role='listview' data-inset='true'>",

            // The array of items for this category.
            cItems = category.items,

            // The number of items in the category.
            numItems = cItems.length;

        // Generate a list item for each item in the category
        // and add it to our markup.
        for ( var i = 0; i < numItems; i++ ) {
            markup += "<li>" + cItems[i].name + "</li>";
        }
        markup += "</ul>";

        // Find the h1 element in our header and inject the name of
        // the category into it.
        $header.find( "h1" ).html( category.name );

        // Inject the category items markup into the content element.
        $content.html( markup );

        // Pages are lazily enhanced. We call page() on the page
        // element to make sure it is always enhanced before we
        // attempt to enhance the listview markup we just injected.
        // Subsequent calls to page() are ignored since a page/widget
        // can only be enhanced once.
        $page.page();

        // Enhance the listview we just injected.
        $content.find( ":jqmData(role=listview)" ).listview();

        // We don't want the data-url of the page we just modified
        // to be the url that shows up in the browser's location field,
        // so set the dataUrl option to the URL for the category
        // we just loaded.
        options.dataUrl = urlObj.href;

        // Now call changePage() and tell it to switch to
        // the page we just modified.
        $.mobile.changePage( $page, options );
    }
}

Note that also, when looking at the URL for the Category page and the Animals page, you can see that the HTML document is the same each time. The difference is the hash value.

http://jquerymobile.com/demos/1.1.0/docs/pages/dynamic-samples/sample-reuse-page.html

http://jquerymobile.com/demos/1.1.0/docs/pages/dynamic-samples/sample-reuse-page.html#category-items?category=animals

When the page loads, the hash value is used to represent the state of the page. Since the browser is mostly stateless, the hash is one trick that is available to us to help determine what state a page should be displayed to the user.

Just compare your changePage method call with the URL's that are used in the menu in the Category page:

<li><a href="#category-items?category=animals">Animals</a></li>
<li><a href="#category-items?category=colors">Colors</a></li>
<li><a href="#category-items?category=vehicles">Vehicles</a></li>

Note how the only thing that changes when a user clicks a link is the hashvalue, the page is never reloaded. But in your example, you're actually loading an entirely new page:

// this is a new page, not a hash
$.mobile.changePage("dashboard.html", { transition : "slide" });

Logically speaking, you need to rethink your strategy for how you represent your pages. To make the most use of jQuery Mobile, think of your first HTML page as a frame for all of your CSS, JavaScript, and static content.

Afterwards, any resources you request should be identified by a URL with that same page followed by a hashvalue.

For example, if your static page is "index.html", then your dashboard might be "index.html#dashboard". You could also either include the dashboard HTML in an id="dashboard" DIV and dynamically populate it with data from the server, or you could load the HTML and data in via AJAX.

The second point is that anyone accessing your dashboard directly would need to visit "/index.html#dashboard", which would load your original page, fire some JavaScript that would check the hash, recognize it contains "dashboard", and then make the transition to the dashboard page by pulling back in the dynamic data.

For more information, see the jQuery Page Dynamic Documentation.

Share:
17,470
Danny
Author by

Danny

Updated on September 15, 2022

Comments

  • Danny
    Danny over 1 year

    I have page1 which is index.html, it a self-contained website includes header and scripts ect. I have a button on this page that when clicked will load page2 via ajax and insert page2 into page1. Page2 is just the div data-role="page" and html within it, its not a self-contained page. However, the url changes to page2.html and then if I "refresh" the page it doesn't load a complete page because page2 is not a full page, it was only ment to be injected into page1.

    I tried setting the data-url="index.html&dashboard" (dashboard is the main id on page2) and in this case the url looks correct as it is still index.html&dashboard so a page refresh reloads page1 even if you were on page2. But now, jquery freaks out and sits on a loading screen forever because it can't find "dashboard" because that content was only added after the user clicked a button.

    How do I work around this?

    Thanks.

    page1 index.html

    <!DOCTYPE HTML>
    <html>
    <head>
    <title>title</title>
    <script><script type="text/javascript" charset="utf-8" src="cordova-1.7.0.js"></script>
    <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
    <script type="text/javascript" charset="utf-8" src="js/jquery.mobile.js"></script>
    <script type="text/javascript" charset="utf-8" src="js/global.js"></script>
    <script type="text/javascript" charset="utf-8" src="js/login.js"></script>
    
    <link rel="stylesheet" type="text/css" href="css/jquery.mobile.css" />
    <link rel="stylesheet" type="text/css" href="css/style.css" />
    
    <script type="text/javascript" charset="utf-8" src="cordova-1.7.0.js"></script>
    <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
    <script type="text/javascript" charset="utf-8" src="js/jquery.mobile.js"></script>
    <script type="text/javascript" charset="utf-8" src="js/login.js"></script>
    
    <link rel="stylesheet" type="text/css" href="css/jquery.mobile.css" />
    <link rel="stylesheet" type="text/css" href="css/style.css" />
    
    </head>
    <body>
        <div data-role="page" data-theme="b">
    
            <div data-role="header" data-theme="b">
                <h1>Login</h1>
            </div>
            <!-- /header -->
    
            <div data-role="content">
                <div class="row">
                    <label for="login_name">Login Name:</label> <input
                        class="ui-input-text" type="text" name="login_name" id="login_name" />
                </div>
                <div class="row">
                    <label for="basic">Password:</label> <input class="ui-input-text"
                        type="password" name="password" id="password" />
                </div>
                <div class="row">
                    <input type="checkbox" name="remember" id="remember" class="custom"
                        data-mini="true" /> <label for="remember">Remember Me</label>
                </div>
                <div class="row">
                    <input type="submit" id="submit" name="submit" value="Submit" />
                </div>
            </div>
            <!-- /content -->
    
        </div>
        <!-- /page -->
    </body>
    </html>
    

    page2 dashboard.html

    <div id="dashboard" data-role="page" data-theme="b" data-url="index.html&dashboard">
    
    <div data-role="header" data-theme="b">
        <h1 id="page_title">Dashboard</h1>
    </div>
    

    login.js

    $(function() {
    
    
    $("#submit").click(function() {
    
        if ($("#login_name").val() == "") {
            alert("Please enter a login name");
            return false;
        }
    
        if ($("#password").val() == "") {
            alert("Please enter a password");
            return false;
        }
    
        $.post(URL + 'login', {
            'APIKEY' : APIKEY,
            'login' : $("#login_name").val(),
            'password' : $("#password").val()
        }, function(data) {
            if (data.error == "") {
    
                $.mobile.changePage("dashboard.html", { transition : "slide" });
            } else {
                alert(data.error);
            }
    
        }, "json");
    
    });
    

    });

  • Danny
    Danny almost 12 years
    Thanks I will look at this a little more to see if that method will work for me. My example was pretty simple in reality there's going to be around 20 different pages each with sets of custom javascript with a lot of functionality involved on each page.
  • Danny
    Danny almost 12 years
    I might have to just call each page separately and forget about the page transitions, I just like the slide effects between pages so I'll try something like above first but even with that working I need to figure out how to dynamically add/remove and execute javascript files based on that page was loaded as its going to be too large to keep stacking everything in the same dom (and preload all js files). Now if they have a way to do the page transitions on a completely different page that would solve everything I would just make each page self containing. I'll research some more :) Thanks.
  • jamesmortensen
    jamesmortensen almost 12 years
    Then you may be better off using the 2nd method. Create a single page template and each time you request another page, just swap out the main DIV in the page template with dynamic data. Think of your JS as a library of functions you preload.