Sunday, April 22, 2012

jQuery Mobile: Removing a Page from the DOM

By default jQuery Mobile loads pages via Ajax, allowing it to animate the page transitions. This behavior can sometimes cause a problem, especially if JavaScript is being used on the pages.

For example, if a website has two pages that contain elements with the same id attributes and a jQuery selector is used that refers to the elements by their ids, the jQuery may select the wrong element.

Page 1

<!DOCTYPE html>

<html>

       <head>

       <title>Page 1</title>

       <meta name="viewport" content="width=device-width, initial-scale=1">

       <link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css" />

       <script type="text/javascript" src="http://code.jquery.com/jquery-1.7.1.min.js"></script>

       <script type="text/javascript" src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js"></script>

    <script type="text/javascript" src="UnloadExample.js"></script>

</head>

<body>

 

<div id="Page" data-role="page" data-theme="b">

 

       <div data-role="header" data-theme="b">

              <h1 id="header">Page 1</h1>

        <a href="UnloadPage2.htm" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">Page 2</a>

       </div><!-- /header -->

 

       <div data-role="content"> 

              <p>This is page 1.</p>

        <a id="ShowButton" data-role="button">Show</a>

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

 

</div><!-- /page -->

 

</body>

</html>

 

Page 2

<!DOCTYPE html>

<html>

       <head>

       <title>Page 2</title>

       <meta name="viewport" content="width=device-width, initial-scale=1">

       <link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css" />

       <script type="text/javascript" src="http://code.jquery.com/jquery-1.7.1.min.js"></script>

       <script type="text/javascript" src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js"></script>

    <script type="text/javascript" src="UnloadExample.js"></script>

</head>

<body>

 

<div id="Page" data-role="page" data-theme="b">

 

       <div data-role="header" data-theme="b">

        <a href="UnloadPage1.htm" data-icon="arrow-l" data-iconpos="left">Page 1</a>

              <h1 id="header">Page 2</h1>

       </div><!-- /header -->

 

       <div data-role="content"> 

              <p>This is page 2.</p>

        <a id="ShowButton" data-role="button">Show</a>

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

 

</div><!-- /page -->

 

</body>

</html>

 

UnloadExample.js

$(document).delegate("#ShowButton", "click", function () {

    alert($("#header").text());

});

If the Show button is clicked on the first page, an alert box is shown with the message "Page 1".

Page1

Then if the "Page 2" button is clicked, Page 2 is displayed. If the Show button is clicked on the second page, an alert box is shown with the message "Page 1", not "Page 2".

Page2Before

Since Page 1 is still in the DOM, jQuery selects the "#header" element on the hidden page (Page 1) instead of the one that is actually visible (the one on Page 2).

This issue can be avoided by removing the hidden page from the DOM after Page 2 is shown. The following JavaScript can be added to the UnloadExample.js page to accomplish this.

$('#Page').live('pagehide', function () {

    $(this).remove();

});

Now when the Show button on Page 2 is clicked, the alert box message will be “Page 2”.

Page2After

Monday, April 16, 2012

jQuery Mobile: Finding the “Real” Collapse Event

I have found that if you have a group of jQuery Mobile (Version 1.1.0) collapsible blocks (e.g. <div data-role="collapsible">) in a collapsible set (e.g. <div data-role="collapsible-set">) the collapsible blocks’ “collapse” event seems to fire more often than it should.

For example, if a page contains a collapsible set containing five collapsible blocks that are currently collapsed and one of the blocks is expand, a “collapse” event will be raised for the other four blocks in the set, even though they were already collapsed. I would not have expected to see any “collapse” events in that situation.

Then, if one of the other collapsed blocks is expanded, I would expect to only see a “collapse” event raised for the one block that was expanded, but a “collapse” event is raised for all of the blocks in the collapsible set (except for the one that was just expanded).

For my purposes I, only want to know about the “collapse” events that occur when a collapsible block is truly collapsed (i.e. the collapsible block went from an expanded state to a collapsed state). To accomplish this, I add a new attribute to the collapsible block, called data-previous-state. On the “expand” event for the collapsible block, I set this attribute to “expanded”. On the “collapse” event, I check to see if the data-previous-state attribute is set to “expanded”. If it is, I perform my collapse logic and set the data-previous-state attribute to “collapsed”. If the data-previous-state attribute is set to “collapsed”, I ignore it.

The following is the code for a page that demonstrates this issue. The collapse events highlighted in yellow are the “real” collapse events.

<!DOCTYPE html>

<html>

<head>

    <title>jQuery Mobile Collapsible Content Events</title>

    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css" />

    <script type="text/javascript" src="http://code.jquery.com/jquery-1.7.1.min.js"></script>

    <script type="text/javascript" src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js"></script>

    <style>

        #EventOutput

        {

            border-top: 1px dotted Grey;

        }

        #EventOutput div

        {

            border-bottom: 1px dotted Grey;

        }

        .highlight

        {

            background-color: Yellow;

        }

    </style>

</head>

<body>

    <div id="CollapsibleContentEventsPage" data-role="page">

    <div data-role="header" data-position="fixed">

        <h1>

            Collapsible Content Events

        </h1>

    </div>

    <div data-role="content">

      <div data-role="collapsible-set">

        <div class="section" data-section="1" data-role="collapsible" data-content-theme="d">

          <h3>

            Section 1

          </h3>

          <p>

            I'm the collapsible set content for section 1.

          </p>

        </div>

        <div class="section" data-section="2" data-role="collapsible" data-content-theme="d">

          <h3>

            Section 2

          </h3>

          <p>

            I'm the collapsible set content for section 2.

          </p>

        </div>

        <div class="section" data-section="3" data-role="collapsible" data-content-theme="d">

          <h3>

            Section 3

          </h3>

          <p>

            I'm the collapsible set content for section 3.

          </p>

        </div>

        <div class="section" data-section="4" data-role="collapsible" data-content-theme="d">

          <h3>

            Section 4

          </h3>

          <p>

            I'm the collapsible set content for section 4.

          </p>

        </div>

        <div class="section" data-section="5" data-role="collapsible" data-content-theme="d">

          <h3>

            Section 5

          </h3>

          <p>

            I'm the collapsible set content for section 5.

          </p>

        </div>

      </div>

    </div>

    <div data-role="content" data-theme="d">

      <h3>

        Event Details

      </h3>

      <div id="EventOutput">

      </div>

    </div>

    </div>

 

<script type="text/javascript">

 

    $("#CollapsibleContentEventsPage").live("pageinit", function (event) {

 

        $("#EventOutput").prepend("<div>" + (new Date()).getTime() + ": pageinit</div>");

 

        $("div.section").on("expand", function () {

            $(this).data("previous-state", "expanded");

        });

 

        $("div.section").on("collapse", function () {

 

            var eventInfo;

 

            if ($(this).data("previous-state") == "expanded") {

                eventInfo = "<div class='highlight'>"

            }

            else {

                eventInfo = "<div>"

            }

 

            eventInfo += (new Date()).getTime()

                + ": collapse - Section "

                + $(this).data("section")

                + "</div>";

 

            $("#EventOutput").prepend(eventInfo);

 

            $(this).data("previous-state", "collapsed");

        });

    });

</script>

</body>

</html>