jQTouch dynamic menus

Finally a little bit of time to learn something new and put some code up about it! I’ve been playing with jQtouch and PhoneGap for mobile development. Just as all my other stuff, this has an enterprise feel to it. I’ve been trying to come up with some good ways to communicate with a backend database, utilize local storage, and keep many lists of objects without code getting too out of control. Local storage and the backend communication is going to be part two of this. To start, here is how I’m trying to keep pages up to date without too much headache.

The idea is to catch hashchange events, and trigger a load and unload every time we get a hashchange event. Some browsers support this event(chrome, ie8), and others do not(mobile safari). So I use Ben Alman’s hashchange plugin for jQuery. If the browser supports hashchange, then you get the browser support. Otherwise it triggers the event for you.

I catch the hashchange event, and trigger two new events, load and unload

 Javascript |  copy code |? 
1
// global
2
var last_hash;
3
 
4
$(window).bind( 'hashchange',
5
                                function(){
6
                                       $(window).trigger('unload' + last_hash);
7
                                       last_hash = location.hash;
8
                                       $(window).trigger('load' + location.hash);
9
                                });

Now I can bind to these new events and find out when individual sections of my page are being loaded by jQtouch.
For example, you have a simple menu:

 HTML |  copy code |? 
1
<div id="usersMenu">
2
   <div class="toolbar"><h1>System Users</h1>
3
           <a href="#" class="button back">Back</a>
4
   </div>
5
   <ul id="usersMenuContent"></ul>
6
</div>

This menu has dynamic content that will change from time to time, so we have to load it with an ajax call. You may have a lot of users, and when you select one user, you don’t want all the other menu items taking up memory while you deal with that user.

What we need here is a way to load/unload those user items. What this method does is bind to the load#usersMenu and unload#usersMenu events, and either load data for #usersMenuContent from a data source, or remove the elements from #usersMenuContent.

 Javascript |  copy code |? 
01
02
$(window).bind('unload#usersMenu', 
03
                        function() { 
04
                               $('#usersMenuContent > *').remove();
05
                        });
06
 
07
$(window).bind('load#usersMenu', 
08
                        function() {
09
                                var dataArray = loadUsersData();
10
                                for( var i in dataArray ) {
11
                                      $('<li>' + dataArray[i] + '</li>').appendTo($('#usersMenuContent'));
12
                                }
13
                        });

Theres some psuedo code there to change depending on just what you are loading into the users menu of course. The idea is when your Users Menu loads, it will populate with data, and when you navigate away, the data is removed.

Here is an example we can use. You can view it here.
A simple page that has 3 menus: A primary menu, and two submenus. Each item in the submenus can be clicked on, and loaded into a form for modification(although we don’t implement modification here). Each menu has to load its items, and also remove its items when unloaded.

First, it is important to see the HTML template we will be working with.

 HTML |  copy code |? 
01
    <body>
02
        <div id="jqt">
03
            <div id="start">
04
                <div class="toolbar"><h1>Produce</h1>
05
                </div>
06
                <ul>
07
                   <li><a href="#fruitMenu">Fruits</a></li>
08
                   <li><a href="#vegetableMenu">Vegetables</a></li>
09
                </ul>
10
            </div>
11
 
12
            <div id="fruitMenu">
13
                <div class="toolbar"><h1>Fruits</h1>
14
                    <a href="#" class="button back">Back</a>
15
                </div>
16
                <ul id="fruitMenuContent"></ul>
17
            </div>
18
 
19
            <div id="vegetableMenu">
20
                <div class="toolbar"><h1>Vegetables</h1>
21
                    <a href="#" class="button back">Back</a>
22
                </div>
23
                <ul id="vegMenuContent"></ul>
24
            </div>
25
 
26
            <div id="fruitClickThru">
27
                <div class="toolbar"><h1 name="title"></h1>
28
                    <a href="#" class="button back">Back</a>
29
                </div>
30
                <ul class="form">
31
                    <label>Name</label><li><input name="name"></li>
32
                    <label>Other</label><li><input name="other"></li>
33
                </ul>
34
            </div>
35
 
36
            <div id="vegetableClickThru">
37
                <div class="toolbar"><h1 name="title"></h1>
38
                    <a href="#" class="button back">Back</a>
39
                </div>
40
                <ul class="form">
41
                    <label>Name</label><li><input name="name"></li>
42
                    <label>Other</label><li><input name="other"></li>
43
                </ul>
44
            </div>
45
        </div>
46
    </body>
47
</html>
48

Here is the javascript and HTML put together. I trigger a load and unload event every time the hash changes. I bind to the load and unload events for the menus to update, and clean themselves up after use.

 Javascript |  copy code |? 
001
<!doctype html>
002
<html>
003
    <head>
004
        <meta charset="UTF-8" />
005
 
006
        <title>Dynamic Menus using jQTouch</title>
007
        <style type="text/css" media="screen">@import "jqtouch/jqtouch.css";</style>
008
        <style type="text/css" media="screen">@import "themes/jqt/theme.css";</style>
009
        <script src="jqtouch/jquery-1.4.2.min.js" type="text/javascript" charset="utf-8"></script>
010
        <script src="jqtouch/jqtouch.js" type="application/x-javascript" charset="utf-8"></script>
011
        <script src="jquery.ba-hashchange.js" type="text/javascript" charset="utf-8"></script>
012
        <script type="text/javascript" charset="utf-8">
013
 
014
            var jQT = new $.jQTouch({
015
                icon: 'jqtouch.png',
016
                addGlossToIcon: false,
017
                startupScreen: 'jqt_startup.png',
018
                statusBar: 'black'
019
            });
020
 
021
            /* For this example I think globals are fine. 
022
               For real though, the selected item should be stored
023
               inside the object controlling that item type */
024
 
025
            var activefruit;
026
            var activeveg;
027
            var last_hash;
028
 
029
 
030
            $(function(){
031
                /* Setup our load events for page changes. The idea is that
032
                   we trigger an unload<hash> and load<hash> event whenever
033
                   the page location hash changes. The event handlers can
034
                   do whatever they want with that information. */
035
                $(window).bind( 'hashchange', 
036
                                function(){
037
                                       console.log('unloading ' + last_hash);
038
                                       $(window).trigger('unload' + last_hash);
039
 
040
                                       /* Handle the previous hash ourselves because some browsers won't support the
041
                                          hash change event, and others will. So we don't want to modify the plugin
042
                                          because it may not always be handling the events */
043
                                       last_hash = location.hash;
044
 
045
                                       console.log('loading ' + location.hash);
046
                                       $(window).trigger('load' + location.hash);
047
                                });
048
 
049
 
050
                /* Handle our fruit menu
051
                   Our fruit menu is dynamic. In this example it really isn't,
052
                   but real world it would be loaded through an ajax call. Since
053
                   the fruit menu is dynamic, we want to bind to the 
054
                   unload#fruitMenu event, and erase the menu contents when we
055
                   navigate away from the page. This makes it easier to keep the
056
                   menu free from duplicates, but it also keeps us from having
057
                   a lot of DOM objects lying around. */
058
                $(window).bind('unload#fruitMenu', function() { $('#fruitMenuContent > *').remove(); });
059
                $(window).bind('load#fruitMenu', 
060
                           function() { 
061
                                console.log('Fruit menu loaded');
062
 
063
                                // On a touch event, we set the activefruit global, and link to the fruit click thru
064
                                $('<li onclick="activefruit=1;"><a href="#fruitClickThru">Apple</a></li>').appendTo($('#fruitMenuContent'));
065
                                $('<li onclick="activefruit=2;"><a href="#fruitClickThru">Banana</a></li>').appendTo($('#fruitMenuContent'));
066
                           });
067
 
068
                /* Handle our vegetable menu
069
                   Same as for the fruits. Clean up when the menu is unloaded. */
070
                $(window).bind('unload#vegetableMenu', function() { $('#vegMenuContent > *').remove(); });
071
                $(window).bind('load#vegetableMenu', 
072
                           function() { 
073
                                console.log('Vegetable menu loaded');
074
                                $('<li onclick="activevegetable=1;"><a href="#vegetableClickThru">Carrots</a></li>').appendTo($('#vegMenuContent'));
075
                                $('<li onclick="activevegetable=2;"><a href="#vegetableClickThru">Broccolli</a></li>').appendTo($('#vegMenuContent'));
076
                           });
077
 
078
 
079
 
080
                /* Fill our fruit click thru
081
                   Once this section is loaded, we access the activefruit global to determine
082
                   what information to populate the form with. Again, in the real world
083
                   this info would be grabbed from a database, or an ajax call. */
084
                $(window).bind('load#fruitClickThru', 
085
                                function() { 
086
                                     if( activefruit == 1 ) {
087
                                         $('#fruitClickThru > .form > li > input[name=name]').val('Apple');
088
                                         $('#fruitClickThru > .form > li > input[name=other]').val('Red');
089
                                         $('#fruitClickThru > .toolbar > :header').html('Apple');
090
                                     } else if( activefruit == 2 ) {
091
                                         $('#fruitClickThru > .form > li > input[name=name]').val('Banana');
092
                                         $('#fruitClickThru > .form > li > input[name=other]').val('Yellow');
093
                                         $('#fruitClickThru > .toolbar > :header').html('Banana');
094
                                     }
095
                                 });
096
 
097
                /* Fill out vegetable click thru
098
                   Same as the fruit sections. We aren't bother to bind to the unload
099
                   event here because the form fields are static and we replace their
100
                   values during each load event anyway. */
101
                $(window).bind('load#vegetableClickThru',
102
                                function() { 
103
                                     if( activevegetable == 1 ) {
104
                                         $('#vegetableClickThru > .form > li > input[name=name]').val('Carrots');
105
                                         $('#vegetableClickThru > .form > li > input[name=other]').val('Orange');
106
                                         $('#vegetableClickThru > .toolbar > :header').html('Carrots');
107
                                     } else if( activevegetable == 2 ) {
108
                                         $('#vegetableClickThru > .form > li > input[name=name]').val('Broccolli');
109
                                         $('#vegetableClickThru > .form > li > input[name=other]').val('Green');
110
                                         $('#vegetableClickThru > .toolbar > :header').html('Broccolli');
111
                                     }
112
                                 });
113
            });
114
        </script>
115
    </head>
116
    <body>
117
        <div id="jqt">
118
            <div id="start">
119
                <div class="toolbar"><h1>Produce</h1>
120
                </div>
121
                <ul>
122
                   <li><a href="#fruitMenu">Fruits</a></li>
123
                   <li><a href="#vegetableMenu">Vegetables</a></li>
124
                </ul>
125
            </div>
126
 
127
            <div id="fruitMenu">
128
                <div class="toolbar"><h1>Fruits</h1>
129
                    <a href="#" class="button back">Back</a>
130
                </div>
131
                <ul id="fruitMenuContent"></ul>
132
            </div>
133
 
134
            <div id="vegetableMenu">
135
                <div class="toolbar"><h1>Vegetables</h1>
136
                    <a href="#" class="button back">Back</a>
137
                </div>
138
                <ul id="vegMenuContent"></ul>
139
            </div>
140
 
141
            <div id="fruitClickThru">
142
                <div class="toolbar"><h1 name="title"></h1>
143
                    <a href="#" class="button back">Back</a>
144
                </div>
145
                <ul class="form">
146
                    <label>Name</label><li><input name="name"></li>
147
                    <label>Other</label><li><input name="other"></li>
148
                </ul>
149
            </div>
150
 
151
            <div id="vegetableClickThru">
152
                <div class="toolbar"><h1 name="title"></h1>
153
                    <a href="#" class="button back">Back</a>
154
                </div>
155
                <ul class="form">
156
                    <label>Name</label><li><input name="name"></li>
157
                    <label>Other</label><li><input name="other"></li>
158
                </ul>
159
            </div>
160
        </div>
161
    </body>
162
</html>
163

Moving forward
The next step is to have the menus load from the local cache. If the cache doesn’t have the requested information, then it should be retrieved. This way when you load a menu, you can always show a spinner and if the cache has been invalidated, new information will automatically be populated. But that is for next time.

Posted Sunday, April 25th, 2010 under jqTouch.

One comment so far

Leave a Reply