C H A P T E R  32

Refactoring the Mobile Example: Part IV

In prior chapters in this part of the book, I introduced you to jQuery Mobile. In this chapter, I'll build a more complete example that uses the jQuery Mobile functionality. By its nature, jQuery Mobile is a lot simpler than jQuery UI, and there are a lot fewer design choices available. Your development efforts with jQuery Mobile are further constrained by the unique issues that face mobile device development.

Starting with the Basics

In Chapter 31, I showed you an example that used split lists. This example is the starting point for this chapter. We'll use it to build out some additional functionality. Listing 32-1 shows the initial document.

Listing 32-1. The starting point for this chapter

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.0.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.6.4.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.0.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding-top: 10px}
        .productData {float: right; padding: 10px; width: 60%}
        .cWrapper {text-align: center; margin: 20px}
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>

        <div id="container" style="padding: 20px">
            <ul data-role="listview" data-inset=true>
                <li><a href="#basket" class="buy" id="rose">Roses</a>
                    <a href="#roses">Roses</a></li>

                <li><a href="#basket" class="buy" id="orchid">Orchids</a>
                    <a href="#orchids">Orchids</a>  </li>
                <li><a href="#basket" class="buy" id="astor">Astors</a>
                    <a href="#astors">Astors</a>  </li>
            </ul>
        </div>
    </div>

    <div id="basket" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div class="cWrapper">
            Basket will go here
        </div>
        <div class="cWrapper">
            <a href="#" data-rel="back" data-role="button" data-inline=true
               data-direction="reverse">Back</a>
        </div>
    </div>

    <div id="roses" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Roses</h1>
        </div>
        <div>
            <div class="lcontainer">
                <img src="rose.png">
                <div><a href="#" data-rel="back" data-role="button"
                       data-inline=true data-direction="reverse">Back</a>
                </div>
            </div>
            <div class="productData">
                A rose is a woody perennial within the family Rosaceae.
                They form a group of erect shrubs, and climbing or trailing plants.
                <div><b>Price: $4.99</b></div>
            </div>
        </div>
    </div>

   <div id="orchids" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Orchids</h1>
        </div>
        <div>
            <div class="lcontainer">
                <img src="orchid.png">
                <div><a href="#" data-rel="back" data-role="button"
                       data-inline=true data-direction="reverse">Back</a>
                </div>
            </div>
            <div class="productData">
                The orchid family is a diverse and widespread family in the order
                Asparagales. It is one of the largest families of flowering plants.
                <div><b>Price: $10.99</b></div>
            </div>
        </div>
    </div>

   <div id="astors" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Astors</h1>
        </div>
        <div>
            <div class="lcontainer">
                <img src="astor.png">
                <div><a href="#" data-rel="back" data-role="button"
                       data-inline=true data-direction="reverse">Back</a>
                </div>
            </div>
            <div class="productData">
                The name Astor comes from the Ancient Greek word meaning "star",
                referring to the shape of the flower head.
                <div><b>Price: $2.99</b></div>
            </div>
        </div>
    </div>
</body>
</html>

Inserting Products Programmatically

The first thing I'll do is replace the static pages that describe each flower with some that are created dynamically. This change allows me to have a more compact document and to easily add some more flowers for the user to pick from without duplicating HTML elements. I will generate the pages using the data template plugin, which I described in Chapter 12. This plugin works with the core jQuery library, so it fits nicely into a jQuery Mobile application. I created a file called data.json that contains the data I need for the flowers. Listing 32-2 shows the contents of data.json.

Listing 32-2. The content of the data.json file

[{  "name": "astor",
    "label": "Astors",
    "price": "$2.99",
    "text": "The name Astor comes from the Ancient Greek word meaning star..."
},{ "name": "carnation",
    "label": "Carnations",
    "price": "$1.99",
    "text": "Carnations require well-drained, neutral to slightly alkaline soil..."
},{ "name": "daffodil",
    "label": "Daffodils",
    "price": "$1.99",

    "text": "Daffodil is a common English name, sometimes used for all varieties..."
},{ "name": "rose",
    "label": "Roses",
    "price": "$4.99",
    "text":  "A rose is a woody perennial within the family Rosaceae. They form a..."
},{ "name": "orchid",
    "label": "Orchids",
    "price": "$10.99",
    "text": "The orchid family is a diverse and widespread family in the order..."
}]

The data describes five flowers. For each of them, I defined the product name, the label to display to the user, the price per unit, and a text description. I did not show the full text description in the listing, but it is included in the data.json file that is part of the source code download for this book (which you can get without charge from apress.com).

Now that you have the data, you can integrate it into the document. Listing 32-3 shows the changes from static to programmatically generated pages.

Listing 32-3. Adding pages dynamically

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.0.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.6.4.js"></script>
    <script type="text/javascript" src="jquery.tmpl.js"></script>
    <script type="text/javascript">
        $(document).ready(function() {
            $.getJSON("data.json", function(data) {
                $('#flowerTmpl').tmpl(data).appendTo("body");
                $('ul').append($('#liTmpl').tmpl(data)).listview("refresh")
            })
        })
    </script>
    <script type="text/javascript" src="jquery.mobile-1.0.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding-top: 10px}
        .productData {float: right; padding: 10px; width: 60%}
        .cWrapper {text-align: center}
    </style>
    <script id="flowerTmpl" type="text/x-jquery-tmpl">
       <div id="${name}" data-role="page" data-theme="b">
            <div data-role="header">
               <h1>${label}</h1>
            </div>
            <div>
                <div class="lcontainer">
                    <img src="${name}.png">
                    <div><a href="#" data-rel="back" data-role="button"

                           data-inline=true data-direction="reverse">Back</a>
                    </div>
                </div>
                <div class="productData">
                    ${text}
                    <div><b>Price: ${price}</b></div>
                </div>
            </div>
        </div>
    </script>
    <script id="liTmpl" type="text/x-jquery-tmpl">
        <li>
            <a href="#basket" class="buy" id="${name}">${label}</a>
            <a href="#${name}">${label}</a>
        </li>
    </script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div id="container" style="padding: 20px">
            <ul data-role="listview" data-inset=true></ul>
        </div>
    </div>

    <div id="basket" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div class="cWrapper">
            Basket will go here
        </div>
        <div class="cWrapper">
            <a href="#" data-rel="back" data-role="button" data-inline=true
               data-direction="reverse">Back</a>
        </div>
    </div>
</body>
</html>

I removed the per-flower pages and used the data template plugin to generate what I need from the data, which I obtain using the getJSON method (which I described in Chapter 14). The key to this change is the simple custom JavaScript code, as follows:

<script type="text/javascript">
    $(document).ready(function() {
        $.getJSON("data.json", function(data) {
            $('#flowerTmpl').tmpl(data).appendTo("body");
            $('ul').append($('#liTmpl').tmpl(data)).listview("refresh")
        })
    })
</script>

I deferred execution of this code until jQuery triggers the ready event, rather than waiting for the jQuery Mobile pageinit event. I want to load my JSON data only once, and the pageinit event is triggered too often to make it a sensible choice. When I obtain the data, I call the tmpl method to add the dynamically generated pages to the body element in the document.

I also use a template to generate the items for the main list of flowers. I tell jQuery Mobile that I have modified the contents of the list, which I do by calling the refresh method on the listview widget, like this:

$('ul').append($('#liTmpl').tmpl(data)).listview("refresh")

The data templates are pretty simple and use the standard techniques I described in Chapter 12. You can see the result in Figure 32-1—list whose items are generated programmatically and that link to pages that have been added to the document programmatically.

Image

Figure 32-1 Programmatically generated list items and pages

Reusing Pages

I like the data template approach because it shows how jQuery underpins such a wide range of functionality, allowing you to bring together features like templates with an interface toolkit like jQuery Mobile.

That said, you can adopt a more elegant approach for dealing with the per-flower pages. Rather than generate a set of elements for each flower you want to show, you can generate one set of elements and modify them to show the flower that the user has selected. Listing 32-4 shows the changes to the document that make this possible.

Image Tip This is an approach that is particularly suited to jQuery Mobile because multiple pages are contained within a single HTML document. As a rule, you want to keep your HTML documents as simple as possible because of the limitations inherent in mobile devices.

Listing 32-4. Reusing a single page for multiple products

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.0.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.6.4.js"></script>
    <script type="text/javascript" src="jquery.tmpl.js"></script>
    <script type="text/javascript">
        $(document).ready(function() {
            $.getJSON("data.json", function(data) {
                $('ul').append($('#liTmpl').tmpl(data)).listview("refresh")
                $("a.productLink").bind("tap", function() {
                    var targetFlower = $(this).attr("data-flower");
                    for (var i = 0; i < data.length; i++) {
                        if (data[i].name == targetFlower) {
                            var page = $('#productPage'),
                            page.find("#header").text(data[i].label);
                            page.find("#image").attr("src", data[i].name + ".png");
                            page.find("#description").text(data[i].text);
                            page.find("#price").text(data[i].price);

                            $.mobile.changePage("#productPage");
                            break;
                        }
                    }
                })
            })
        })
    </script>
    <script type="text/javascript" src="jquery.mobile-1.0.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding-top: 10px}
        .productData {float: right; padding: 10px; width: 60%}
        .cWrapper {text-align: center}
    </style>
    <script id="liTmpl" type="text/x-jquery-tmpl">
        <li>
            <a href="#basket" class="buy" id="${name}">${label}</a>
            <a class="productLink" data-flower="${name}" href="#">${label}</a>
        </li>
    </script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div id="container" style="padding: 20px">
            <ul data-role="listview" data-inset=true>

            </ul>
        </div>
    </div>

    <div id="productPage" data-role="page" data-theme="b">
         <div data-role="header">
            <h1 id="header"></h1>
         </div>
         <div>
             <div class="lcontainer">
                 <img id="image" src="">
                 <div><a href="#" data-rel="back" data-role="button"
                        data-inline=true data-direction="reverse">Back</a>
                 </div>
             </div>
             <div class="productData">
                 <span id="description"></span>
                 <div><b>Price: <span id="price"></span></b></div>
             </div>
         </div>
     </div>

    <div id="basket" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div class="cWrapper">
            Basket will go here
        </div>
        <div class="cWrapper">
            <a href="#" data-rel="back" data-role="button" data-inline=true
               data-direction="reverse">Back</a>
        </div>
    </div>
</body>
</html>

I removed one of the data templates from the document and added a new page (whose id is productPage) that I use for each flower. I modified the template used to generate the list items so that there is no target page in the href attribute and to add my own data attribute so that I know which flower any given link relates to. When the data has been retrieved from JSON, the revised script selects all of the per-product links from the list elements I just created using the template and binds to the tap event. When a list item is tapped, I find the appropriate data item and use its properties to configure the productPage page, setting the text and image to display to the user, as follows:

<script type="text/javascript">
    $(document).ready(function() {
        $.getJSON("data.json", function(data) {
            $('ul').append($('#liTmpl').tmpl(data)).listview("refresh")
            $("a.productLink").bind("tap", function() {
                var targetFlower = $(this).attr("data-flower");
                for (var i = 0; i < data.length; i++) {
                    if (data[i].name == targetFlower) {
                        var page = $('#productPage'),
                        page.find("#header").text(data[i].label);
                        page.find("#image").attr("src", data[i].name + ".png");
                        page.find("#description").text(data[i].text);
                        page.find("#price").text(data[i].price);

                        $.mobile.changePage("#productPage");
                        break;
                    }
                }
            })
        })
    })
</script>

After I configure the page, I use the changePage method to trigger the navigation. There is no change in the appearance of the example, but you have a smaller set of elements for the mobile browser to manage, and it's a nice example of how you can manipulate the page structure of a jQuery Mobile document.

Creating the Shopping Basket

I am using a split list in this example, and the left side of the list item leads to the basket page. In this section, I'll define the elements for the page and add some JavaScript so that there is a simple basket in place. Listing 32-5 shows the changes to the document.

Listing 32-5. Implementing the shopping basket

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.0.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.6.4.js"></script>
    <script type="text/javascript" src="jquery.tmpl.js"></script>
    <script type="text/javascript">
        $(document).ready(function() {
            $.getJSON("data.json", function(data) {
                $('ul').append($('#liTmpl').tmpl(data)).listview("refresh")

                $("a.productLink").bind("tap", function() {
                    var targetFlower = $(this).attr("data-flower");
                    for (var i = 0; i < data.length; i++) {
                        if (data[i].name == targetFlower) {
                            var page = $('#productPage'),
                            page.find("#header").text(data[i].label);
                            page.find("#image").attr("src", data[i].name + ".png");
                            page.find("#description").text(data[i].text);
                            page.find("#price").text(data[i].price);

                            $.mobile.changePage("#productPage");
                            break;
                        }
                    }
                })

                $('a.buy').bind("tap", function() {
                    var targetFlower = this.id;
                    var row = $("#basketTable tbody #" + targetFlower);
                    if (row.length > 0) {
                        var countCell = row.find("#count");
                        countCell.text(Number(countCell.text()) + 1);
                    } else {
                        for (var i = 0; i < data.length; i++) {
                            if (data[i].name == targetFlower) {
                                $('#trTmpl').tmpl(data[i]).appendTo("#basketTable tbody")
                                break;
                            }
                        }
                    }
                    calculateTotals();
                    $.mobile.changePage("#basket")
                })
            })
        })

        function calculateTotals() {
            var total = 0;
            $('#basketTable tbody').children().each(function(index, elem) {
                var count = Number($(elem).find("#count").text())
                var price = Number($(elem).attr("data-price").slice(1))
                var subtotal = count * price;
                $(elem).find("#subtotal").text("$"+ subtotal.toFixed(2));
                total += subtotal;
            })
            $('#total').text("$" + total.toFixed(2))
        }
    </script>
    <script type="text/javascript" src="jquery.mobile-1.0.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding-top: 10px}
        .productData {float: right; padding: 10px; width: 60%}
        .cWrapper {text-align: center}
        table {display: inline-block; margin: auto; margin-top: 20px; text-align: left;
            border-collapse: collapse}
        td {min-width: 100px}
        th, td {text-align: right}
        th:nth-child(1), td:nth-child(1) {text-align: left}
    </style>
    <script id="liTmpl" type="text/x-jquery-tmpl">
        <li>
            <a href="#" class="buy" id="${name}">${label}</a>
            <a class="productLink" data-flower="${name}" href="#">${label}</a>
        </li>
    </script>
    <script id="trTmpl" type="text/x-jquery-tmpl">
        <tr data-price="${price}" id="${name}"><td>${label}</td><td id="count">1</td>
            <td id="subtotal">0</td></tr>
    </script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div id="container" style="padding: 20px">
            <ul data-role="listview" data-inset=true></ul>
        </div>
    </div>

    <div id="productPage" data-role="page" data-theme="b">
         <div data-role="header">
            <h1 id="header"></h1>
         </div>
         <div>
             <div class="lcontainer">
                 <img id="image" src="">
                 <div><a href="#" data-rel="back" data-role="button"
                        data-inline=true data-direction="reverse">Back</a>
                 </div>
             </div>
             <div class="productData">
                 <span id="description"></span>
                 <div><b>Price: <span id="price"></span></b></div>
             </div>
         </div>
     </div>

    <div id="basket" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div class="cWrapper">
            <table id="basketTable" border=0>
                <thead>
                    <tr><th>Flower</th><th>Quantity</th><th>Subtotal</th></tr>
                </thead>
                <tbody></tbody>
                <tfoot>
                    <tr><th colspan=2>Total:</th><td id="total"></td></tr>
                </tfoot>
            </table>
        </div>
        <div class="cWrapper">
            <a href="#" data-rel="back" data-role="button" data-inline=true
               data-direction="reverse">Back</a>
            <button data-inline="true">Checkout</button>
        </div>
    </div>

</body>
</html>

I added a table to the basket page, which shows one row for each selected product. Each row shows the name of the product, the quantity, and the subtotal. There is a footer in the table that shows the overall total. I bound to the tap event so that when the user clicks on the left side of the split button, either a new row is added to the table or the quantity is incremented if there is already a row for this product in the table. New rows are generated using another data template, and everything else is handled by reading the contents of elements in the document.

I determine and maintain the entire state of the customer's basket using the DOM itself. I could have created a JavaScript object to model the order and driven the contents of the table from the object, but in a book about jQuery, I like to take every opportunity to work with the document itself. The result is a very simple basket, which is shown in Figure 32-2.

Image

Figure 32-2 The basket page

Adding for Quantity Changes

The basket is functional, but if the user wants two roses, for example, she has to tap the Rose list item, tap the Back button, and then tap the Rose item again. This process is pretty ridiculous, so to make it easier to change the quantity of a product, I added some input elements to the table. You can see the changes in Listing 32-6.

Listing 32-6. Adding range sliders to the basket table

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.0.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.6.4.js"></script>
    <script type="text/javascript" src="jquery.tmpl.js"></script>
    <script type="text/javascript">
        $(document).ready(function() {
            $.getJSON("data.json", function(data) {
                $('ul').append($('#liTmpl').tmpl(data)).listview("refresh")

                $("a.productLink").bind("tap", function() {
                    var targetFlower = $(this).attr("data-flower");
                    for (var i = 0; i < data.length; i++) {
                        if (data[i].name == targetFlower) {
                            var page = $('#productPage'),
                            page.find("#header").text(data[i].label);
                            page.find("#image").attr("src", data[i].name + ".png");
                            page.find("#description").text(data[i].text);
                            page.find("#price").text(data[i].price);

                            $.mobile.changePage("#productPage");
                            break;
                        }
                    }
                })

                $('a.buy').bind("tap", function() {
                    var targetFlower = this.id;
                    var row = $("#basketTable tbody #" + targetFlower);
                    if (row.length > 0) {
                        var countCell = row.find("#count input");
                        countCell.val(Number(countCell.val()) + 1);
                    } else {
                        for (var i = 0; i < data.length; i++) {
                            if (data[i].name == targetFlower) {
                                $('#trTmpl').tmpl(data[i]).appendTo("#basketTable tbody")
                                    .find("input").textinput()


                                break;
                            }
                        }
                    }
                    calculateTotals();
                    $.mobile.changePage("#basket")
                })

                $('input').live("change click", function(event) {
                    calculateTotals();
                })
            })
        })

        function calculateTotals() {
            var total = 0;
            $('#basketTable tbody').children().each(function(index, elem) {
                var count = Number($(elem).find("#count input").val())
                var price = Number($(elem).attr("data-price").slice(1))
                var subtotal = count * price;
                $(elem).find("#subtotal").text("$"+ subtotal.toFixed(2));
                total += subtotal;
            })
            $('#total').text("$" + total.toFixed(2))
        }

    </script>
    <script type="text/javascript" src="jquery.mobile-1.0.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding-top: 10px}
        .productData {float: right; padding: 10px; width: 60%}
        .cWrapper {text-align: center}
        table {display: inline-block; margin: auto; margin-top: 20px; text-align: left;
            border-collapse: collapse}
        td {min-width: 100px; padding-bottom: 10px}
        td:nth-child(2) {min-width: 75px; width: 75px}
        th, td {text-align: right}
        th:nth-child(1), td:nth-child(1) {text-align: left}
        input[type=number] {background-color: white}
        tfoot tr {border-top: medium solid black}
        tfoot tr td {padding-top: 10px}
    </style>
    <script id="liTmpl" type="text/x-jquery-tmpl">
        <li>
            <a href="#" class="buy" id="${name}">${label}</a>
            <a class="productLink" data-flower="${name}" href="#">${label}</a>
        </li>
    </script>
    <script id="trTmpl" type="text/x-jquery-tmpl">
        <tr data-theme="b" data-price="${price}" id="${name}"><td>${label}</td>
            <td id="count"><input type=number value=1 min=0 max=10></td>

            <td id="subtotal">0</td>
        </tr>
    </script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div id="container" style="padding: 20px">
            <ul data-role="listview" data-inset=true></ul>
        </div>
    </div>

    <div id="productPage" data-role="page" data-theme="b">
         <div data-role="header">
            <h1 id="header"></h1>
         </div>
         <div>
             <div class="lcontainer">
                 <img id="image" src="">
                 <div><a href="#" data-rel="back" data-role="button"
                        data-inline=true data-direction="reverse">Back</a>
                 </div>
             </div>
             <div class="productData">
                 <span id="description"></span>
                 <div><b>Price: <span id="price"></span></b></div>
             </div>
         </div>
     </div>

    <div id="basket" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div class="cWrapper">
            <table id="basketTable" border=0>
                <thead>
                    <tr><th>Flower</th><th>Quantity</th><th>Subtotal</th></tr>
                </thead>
                <tbody></tbody>
                <tfoot>
                    <tr><th colspan=2>Total:</th><td id="total"></td></tr>
                </tfoot>
            </table>
        </div>
        <div class="cWrapper">
            <a href="#" data-rel="back" data-role="button" data-inline=true
               data-direction="reverse">Back</a>
            <button data-inline="true">Checkout</button>

        </div>
    </div>

</body>
</html>

I inserted an input element into the quantity cell in the template, which is used to generate rows for the table. The type of this input element is number, which causes some browsers to insert small up and down buttons alongside the text entry area. These buttons are too small to be useful for touch, but the browser will also filter the characters to discard anything that isn't appropriate for a number. Although it's acceptable for this chapter, this isn't a perfect approach for real projects because floating point numbers are supported, which means that the user can input fractions of products.

I call the textinput method when I add input elements to the document after the pages have been enhanced by jQuery Mobile:

$('#trTmpl').tmpl(data[i]).appendTo("#basketTable tbody").find("input").textinput()

If I do not add this method call, the browser displays the native input element. Calling the textinput method causes jQuery Mobile to enhance the element, although it doesn't properly assign the swatch. So I defined a style for the input element to set the background color consistently:

input[type=number] {background-color: white}

I need to calculate the subtotals and totals more frequently now that the user can change the quantity of a product in the basket page. Because I add input elements to the document throughout the life of the application, I use the jQuery live method to handle events. The live method is described in Chapter 9. Here is the event handler code:

$('input').live("change click", function(event) {
    calculateTotals();
})

I use the live method to associate my handler function with both the change and click events. The browsers that add up and down buttons to numeric input elements trigger the click event when those buttons are pressed, so I need to handle this event in addition to the more expected change event. My handler function simply calls the calculateTotals function when either event is triggered. You can see how the basket looks in Figure 32-3.

Image

Figure 32-3 Adding input elements to the basket page

Adding a Button to the Information Page

The product information describes the flower the user has selected, but it doesn't provide any way for the user to add it to the basket. To round out the basic basket functionality, I added a button to the product page that adds the item to the basket. Listing 32-7 shows the changes to the product page.

Listing 32-7. Adding a button to the product page

...
<div id="productPage" data-role="page" data-theme="b">
     <div data-role="header">
        <h1 id="header"></h1>
     </div>
     <div>
         <div class="lcontainer">
             <img id="image" src="">
             <div><a href="#" data-rel="back" data-role="button"
                    data-inline=true data-direction="reverse">Back</a>
             </div>
         </div>
         <div class="productData">
             <span id="description"></span>
             <div>
                <b>Price: <span id="price"></span></b>
                <a href="#" id="buybutton" data-flower="" data-role="button"
                   data-inline=true>Buy</a>
             </div>
         </div>
     </div>
 </div>
...

I defined an a element that jQuery Mobile will transform into a button widget. I added a data attribute (data-flower) so that I can keep track of which flower is being displayed when the user taps the button. To support this button, I made some changes to the script. These changes are shown in Listing 32-8.

Listing 32-8. Adding support for the buy button in the script

...
<script type="text/javascript">
    $(document).ready(function() {
        $.getJSON("data.json", function(data) {
            $('ul').append($('#liTmpl').tmpl(data)).listview("refresh")

            $("a.productLink").bind("tap", function() {
                var targetFlower = $(this).attr("data-flower");
                for (var i = 0; i < data.length; i++) {
                    if (data[i].name == targetFlower) {
                        var page = $('#productPage'),
                        page.find("#header").text(data[i].label);
                        page.find("#image").attr("src", data[i].name + ".png");
                        page.find("#description").text(data[i].text);
                        page.find("#price").text(data[i].price);
                        page.find("#buybutton").attr("data-flower", data[i].name);

                        $.mobile.changePage("#productPage");
                        break;
                    }
                }
            })

            $('#buybutton').bind("tap", function() {
                addProduct($(this).attr("data-flower"));
            .})

            $('a.buy').bind("tap", function() {
                addProduct(this.id);
            })

            function addProduct(targetFlower) {
                var row = $("#basketTable tbody #" + targetFlower);
                if (row.length > 0) {
                    var countCell = row.find("#count input");
                    countCell.val(Number(countCell.val()) + 1);
                } else {
                    for (var i = 0; i < data.length; i++) {
                        if (data[i].name == targetFlower) {
                            $('#trTmpl').tmpl(data[i]).appendTo("#basketTable tbody")
                                .find("input").textinput()

                            break;
                        }
                    }
                }
                calculateTotals();
                $.mobile.changePage("#basket")
            }

            $('input').live("change click", function(event) {
                calculateTotals();
            })

        })
    })

    function calculateTotals() {
        var total = 0;
        $('#basketTable tbody').children().each(function(index, elem) {
            var count = Number($(elem).find("#count input").val())
            var price = Number($(elem).attr("data-price").slice(1))
            var subtotal = count * price;
            $(elem).find("#subtotal").text("$"+ subtotal.toFixed(2));
            total += subtotal;
        })
        $('#total').text("$" + total.toFixed(2))
    }

</script>
...

These changes are pretty straightforward. When the user selects a product from the main list, I set the value of the data-flower attribute on the a element. I register a function to handle the tap event for the button and use the value data-flower to call the addProduct function, which contains code I extracted from another handler function. With these changes, the user can add products to the basket from the main list (by tapping on the left side of the split list item) or from the information page (by tapping the Buy button). Figure 32-4 shows the addition of the Buy button to the page.

Image

Figure 32-4 Adding a button to the product information page

Implementing the Checkout Process

To round out this example, I'll demonstrate gathering the data from the various jQuery Mobile pages in a form that can be used to make an Ajax request. I will not make the request itself or implement the server. jQuery Mobile uses the core jQuery support for Ajax, which I described in Chapters 14 and 15. Listing 32-9 shows the addition of a page that is shown to the user when the Checkout button is tapped and the handler function that gathers the data.

Listing 32-9. Implementing the checkout process

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.0.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.6.4.js"></script>
    <script type="text/javascript" src="jquery.tmpl.js"></script>
    <script type="text/javascript">

        $(document).ready(function() {
            $.getJSON("data.json", function(data) {
                $('ul').append($('#liTmpl').tmpl(data)).listview("refresh")

                $("a.productLink").bind("tap", function() {
                    var targetFlower = $(this).attr("data-flower");
                    for (var i = 0; i < data.length; i++) {
                        if (data[i].name == targetFlower) {
                            var page = $('#productPage'),
                            page.find("#header").text(data[i].label);
                            page.find("#image").attr("src", data[i].name + ".png");
                            page.find("#description").text(data[i].text);
                            page.find("#price").text(data[i].price);
                            page.find("#buybutton").attr("data-flower", data[i].name);

                            $.mobile.changePage("#productPage");
                            break;
                        }
                    }
                })

                $('#buybutton').bind("tap", function() {
                    addProduct($(this).attr("data-flower"));
                })

                $('a.buy').bind("tap", function() {
                    addProduct(this.id);
                })

                function addProduct(targetFlower) {
                    var row = $("#basketTable tbody #" + targetFlower);
                    if (row.length > 0) {
                        var countCell = row.find("#count input");
                        countCell.val(Number(countCell.val()) + 1);
                    } else {
                        for (var i = 0; i < data.length; i++) {
                            if (data[i].name == targetFlower) {
                                $('#trTmpl').tmpl(data[i]).appendTo("#basketTable tbody")
                                    .find("input").textinput()

                                break;
                            }
                        }
                    }
                    calculateTotals();
                    $.mobile.changePage("#basket")
                }

                $('input').live("change click", function(event) {
                    calculateTotals();
                })

                $('#submit').bind("tap", function() {
                    var dataObject = new Object();
                    $('#basketTable tbody').children().each(function(index, elem) {
                        dataObject[elem.id] = $(elem).find("#count input").val();
                    })
                    dataObject["name"] = $('#name').val();
                    dataObject["wrap"] = $('option:selected').val();
                    dataObject["shipping"] = $('input:checked').attr("id")

                    console.log("DATA: " + JSON.stringify(dataObject))
                })

            })
        })

        function calculateTotals() {
            var total = 0;
            $('#basketTable tbody').children().each(function(index, elem) {
                var count = Number($(elem).find("#count input").val())
                var price = Number($(elem).attr("data-price").slice(1))
                var subtotal = count * price;
                $(elem).find("#subtotal").text("$"+ subtotal.toFixed(2));
                total += subtotal;
            })
            $('#total').text("$" + total.toFixed(2))
        }

    </script>
    <script type="text/javascript" src="jquery.mobile-1.0.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding: 10px}
        .productData {float: right; padding: 10px; width: 60%}
        .cWrapper {text-align: center}
        table {display: inline-block; margin: auto; margin-top: 20px; text-align: left;
            border-collapse: collapse}
        td {min-width: 100px; padding-bottom: 10px}
        td:nth-child(2) {min-width: 75px; width: 75px}
        th, td {text-align: right}
        th:nth-child(1), td:nth-child(1) {text-align: left}
        input[type=number] {background-color: white}
        tfoot tr {border-top: medium solid black}
        tfoot tr td {padding-top: 10px}
    </style>
    <script id="liTmpl" type="text/x-jquery-tmpl">
        <li>
            <a href="#" class="buy" id="${name}">${label}</a>
            <a class="productLink" data-flower="${name}" href="#">${label}</a>
        </li>
    </script>
    <script id="trTmpl" type="text/x-jquery-tmpl">
        <tr data-theme="b" data-price="${price}" id="${name}"><td>${label}</td>
            <td id="count"><input type=number value=1 min=0 max=10></td>
            <td id="subtotal">0</td>
        </tr>
    </script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div id="container" style="padding: 20px">
            <ul data-role="listview" data-inset=true></ul>
        </div>
    </div>

    <div id="productPage" data-role="page" data-theme="b">
         <div data-role="header">
            <h1 id="header"></h1>
         </div>
         <div>
             <div class="lcontainer">
                 <img id="image" src="">
                 <div><a href="#" data-rel="back" data-role="button"
                        data-inline=true data-direction="reverse">Back</a>
                 </div>
             </div>
             <div class="productData">
                 <span id="description"></span>
                 <div>
                    <b>Price: <span id="price"></span></b>
                    <a href="#" id="buybutton" data-flower="" data-role="button"
                       data-inline=true>Buy</a>
                 </div>
             </div>
         </div>
     </div>

    <div id="basket" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div class="cWrapper">
            <table id="basketTable" border=0>
                <thead>
                    <tr><th>Flower</th><th>Quantity</th><th>Subtotal</th></tr>
                </thead>
                <tbody></tbody>
                <tfoot>
                    <tr><th colspan=2>Total:</th><td id="total"></td></tr>
                </tfoot>
            </table>
        </div>
        <div class="cWrapper">
            <a href="#" data-rel="back" data-role="button" data-inline=true
               data-direction="reverse">Back</a>
            <a href="#checkout" data-role="button" data-inline="true">Checkout</a>
        </div>
    </div>

   <div id="checkout" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">

            <label for="name">Name: </label>
            <input id="name" placeholder="Your Name">

            <label for="wrap"><span>Gift Wrap: </span></label>
            <select id="wrap" name="wrap" data-role="slider">
                <option value="yes" selected>Yes</option>
                <option value="no">No</option>
            </select>

            <fieldset data-role="controlgroup">
                <legend>Shipping:</legend>
                <input type="radio" name="ship" id="overnight" checked />
                <label for="overnight">Overnight</label>
                <input type="radio" name="ship" id="23day"/>
                <label for="23day">2-3 days</label>
                <input type="radio" name="ship" id="710day"/>
                <label for="710day">7-10 days</label>
            </fieldset>

            <div class="cWrapper">
                <a href="#" data-rel="back" data-role="button" data-inline="true"
                   data-direction="reverse">Back</a>
                <a href="#" id="submit" data-role="button"
                   data-inline=true">Submit Order</a>
            </div>

        </div>
    </div>

</body>
</html>

The new page is called checkout. I kept this form very simple, prompting the user for a name and providing choices for giftwrapping and the shipping method. You can see how the page appears in Figure 32-5. I have shown the page in the portrait orientation because it allows me to display all of the elements without having to scroll.

Image

Figure 32-5 The checkout page

When the user taps the Submit Order button, I gather the data from the different pages in the HTML document and write the result as a JSON string to the console. The following is an example of such a string:


{"carnation":"3","rose":"1","orchid":"1",
    "name":"Adam Freeman","wrap":"yes","shipping":"23day"}

Summary

In this chapter, I took some of the core features of jQuery Mobile and combined them to create a simple mobile implementation of the flower shop example. By its very nature, jQuery Mobile is a lot simpler than jQuery UI. The main challenge is to design an approach that gives the user the information he require within the confines of a small screen.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.74.231