58a2078cb587df03075b31c8aa3275d6a2a20b9e
[platform/framework/web/web-ui-fw.git] / libs / js / jquery-mobile-1.0.1pre / js / jquery.mobile.listview.js
1 /*
2 * "listview" plugin
3 */
4
5 (function( $, undefined ) {
6
7 //Keeps track of the number of lists per page UID
8 //This allows support for multiple nested list in the same page
9 //https://github.com/jquery/jquery-mobile/issues/1617
10 var listCountPerPage = {};
11
12 $.widget( "mobile.listview", $.mobile.widget, {
13         options: {
14                 theme: null,
15                 countTheme: "c",
16                 headerTheme: "b",
17                 dividerTheme: "b",
18                 splitIcon: "arrow-r",
19                 splitTheme: "b",
20                 inset: false,
21                 initSelector: ":jqmData(role='listview')"
22         },
23
24         _create: function() {
25                 var t = this;
26
27                 // create listview markup
28                 t.element.addClass(function( i, orig ) {
29                         return orig + " ui-listview " + ( t.options.inset ? " ui-listview-inset ui-corner-all ui-shadow " : "" );
30                 });
31
32                 t.refresh( true );
33         },
34
35         _removeCorners: function( li, which ) {
36                 var top = "ui-corner-top ui-corner-tr ui-corner-tl",
37                         bot = "ui-corner-bottom ui-corner-br ui-corner-bl";
38
39                 li = li.add( li.find( ".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb" ) );
40
41                 if ( which === "top" ) {
42                         li.removeClass( top );
43                 } else if ( which === "bottom" ) {
44                         li.removeClass( bot );
45                 } else {
46                         li.removeClass( top + " " + bot );
47                 }
48         },
49
50         _refreshCorners: function( create ) {
51                 var $li,
52                         $visibleli,
53                         $topli,
54                         $bottomli;
55
56                 if ( this.options.inset ) {
57                         $li = this.element.children( "li" );
58                         // at create time the li are not visible yet so we need to rely on .ui-screen-hidden
59                         $visibleli = create?$li.not( ".ui-screen-hidden" ):$li.filter( ":visible" );
60
61                         this._removeCorners( $li );
62
63                         // Select the first visible li element
64                         $topli = $visibleli.first()
65                                 .addClass( "ui-corner-top" );
66
67                         $topli.add( $topli.find( ".ui-btn-inner" )
68                                         .not( ".ui-li-link-alt span:first-child" ) )
69                                 .addClass( "ui-corner-top" )
70                                 .end()
71                                 .find( ".ui-li-link-alt, .ui-li-link-alt span:first-child" )
72                                         .addClass( "ui-corner-tr" )
73                                 .end()
74                                 .find( ".ui-li-thumb" )
75                                         .not(".ui-li-icon")
76                                         .addClass( "ui-corner-tl" );
77
78                         // Select the last visible li element
79                         $bottomli = $visibleli.last()
80                                 .addClass( "ui-corner-bottom" );
81
82                         $bottomli.add( $bottomli.find( ".ui-btn-inner" ) )
83                                 .find( ".ui-li-link-alt" )
84                                         .addClass( "ui-corner-br" )
85                                 .end()
86                                 .find( ".ui-li-thumb" )
87                                         .not(".ui-li-icon")
88                                         .addClass( "ui-corner-bl" );
89                 }
90                 if ( !create ) {
91                         this.element.trigger( "updatelayout" );
92                 }
93         },
94
95         // This is a generic utility method for finding the first
96         // node with a given nodeName. It uses basic DOM traversal
97         // to be fast and is meant to be a substitute for simple
98         // $.fn.closest() and $.fn.children() calls on a single
99         // element. Note that callers must pass both the lowerCase
100         // and upperCase version of the nodeName they are looking for.
101         // The main reason for this is that this function will be
102         // called many times and we want to avoid having to lowercase
103         // the nodeName from the element every time to ensure we have
104         // a match. Note that this function lives here for now, but may
105         // be moved into $.mobile if other components need a similar method.
106         _findFirstElementByTagName: function( ele, nextProp, lcName, ucName )
107         {
108                 var dict = {};
109                 dict[ lcName ] = dict[ ucName ] = true;
110                 while ( ele ) {
111                         if ( dict[ ele.nodeName ] ) {
112                                 return ele;
113                         }
114                         ele = ele[ nextProp ];
115                 }
116                 return null;
117         },
118         _getChildrenByTagName: function( ele, lcName, ucName )
119         {
120                 var results = [],
121                         dict = {};
122                 dict[ lcName ] = dict[ ucName ] = true;
123                 ele = ele.firstChild;
124                 while ( ele ) {
125                         if ( dict[ ele.nodeName ] ) {
126                                 results.push( ele );
127                         }
128                         ele = ele.nextSibling;
129                 }
130                 return $( results );
131         },
132
133         _addThumbClasses: function( containers )
134         {
135                 var i, img, len = containers.length;
136                 for ( i = 0; i < len; i++ ) {
137                         img = $( this._findFirstElementByTagName( containers[ i ].firstChild, "nextSibling", "img", "IMG" ) );
138                         if ( img.length ) {
139                                 img.addClass( "ui-li-thumb" );
140                                 $( this._findFirstElementByTagName( img[ 0 ].parentNode, "parentNode", "li", "LI" ) ).addClass( img.is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" );
141                         }
142                 }
143         },
144
145         refresh: function( create ) {
146                 this.parentPage = this.element.closest( ".ui-page" );
147                 this._createSubPages();
148
149                 var o = this.options,
150                         $list = this.element,
151                         self = this,
152                         dividertheme = $list.jqmData( "dividertheme" ) || o.dividerTheme,
153                         listsplittheme = $list.jqmData( "splittheme" ),
154                         listspliticon = $list.jqmData( "spliticon" ),
155                         li = this._getChildrenByTagName( $list[ 0 ], "li", "LI" ),
156                         counter = $.support.cssPseudoElement || !$.nodeName( $list[ 0 ], "ol" ) ? 0 : 1,
157                         itemClassDict = {},
158                         item, itemClass, itemTheme,
159                         a, last, splittheme, countParent, icon, imgParents, img;
160
161                 if ( counter ) {
162                         $list.find( ".ui-li-dec" ).remove();
163                 }
164                 
165                 if ( !o.theme ) {
166                         o.theme = $.mobile.getInheritedTheme( this.element, "c" );
167                 }
168
169                 for ( var pos = 0, numli = li.length; pos < numli; pos++ ) {
170                         item = li.eq( pos );
171                         itemClass = "ui-li";
172
173                         // If we're creating the element, we update it regardless
174                         if ( create || !item.hasClass( "ui-li" ) ) {
175                                 itemTheme = item.jqmData("theme") || o.theme;
176                                 a = this._getChildrenByTagName( item[ 0 ], "a", "A" );
177
178                                 if ( a.length ) {
179                                         icon = item.jqmData("icon");
180
181                                         item.buttonMarkup({
182                                                 wrapperEls: "div",
183                                                 shadow: false,
184                                                 corners: false,
185                                                 iconpos: "right",
186                                                 icon: a.length > 1 || icon === false ? false : icon || "arrow-r",
187                                                 theme: itemTheme
188                                         });
189
190                                         if ( ( icon != false ) && ( a.length == 1 ) ) {
191                                                 item.addClass( "ui-li-has-arrow" );
192                                         }
193
194                                         a.first().addClass( "ui-link-inherit" );
195
196                                         if ( a.length > 1 ) {
197                                                 itemClass += " ui-li-has-alt";
198
199                                                 last = a.last();
200                                                 splittheme = listsplittheme || last.jqmData( "theme" ) || o.splitTheme;
201
202                                                 last.appendTo(item)
203                                                         .attr( "title", last.getEncodedText() )
204                                                         .addClass( "ui-li-link-alt" )
205                                                         .empty()
206                                                         .buttonMarkup({
207                                                                 shadow: false,
208                                                                 corners: false,
209                                                                 theme: itemTheme,
210                                                                 icon: false,
211                                                                 iconpos: false
212                                                         })
213                                                         .find( ".ui-btn-inner" )
214                                                                 .append(
215                                                                         $( document.createElement( "span" ) ).buttonMarkup({
216                                                                                 shadow: true,
217                                                                                 corners: true,
218                                                                                 theme: splittheme,
219                                                                                 iconpos: "notext",
220                                                                                 icon: listspliticon || last.jqmData( "icon" ) || o.splitIcon
221                                                                         })
222                                                                 );
223                                         }
224                                 } else if ( item.jqmData( "role" ) === "list-divider" ) {
225
226                                         itemClass += " ui-li-divider ui-btn ui-bar-" + dividertheme;
227                                         item.attr( "role", "heading" );
228
229                                         //reset counter when a divider heading is encountered
230                                         if ( counter ) {
231                                                 counter = 1;
232                                         }
233
234                                 } else {
235                                         itemClass += " ui-li-static ui-body-" + itemTheme;
236                                 }
237                         }
238
239                         if ( counter && itemClass.indexOf( "ui-li-divider" ) < 0 ) {
240                                 countParent = item.is( ".ui-li-static:first" ) ? item : item.find( ".ui-link-inherit" );
241
242                                 countParent.addClass( "ui-li-jsnumbering" )
243                                         .prepend( "<span class='ui-li-dec'>" + (counter++) + ". </span>" );
244                         }
245
246                         // Instead of setting item class directly on the list item and its
247                         // btn-inner at this point in time, push the item into a dictionary
248                         // that tells us what class to set on it so we can do this after this
249                         // processing loop is finished.
250
251                         if ( !itemClassDict[ itemClass ] ) {
252                                 itemClassDict[ itemClass ] = [];
253                         }
254
255                         itemClassDict[ itemClass ].push( item[ 0 ] );
256                 }
257
258                 // Set the appropriate listview item classes on each list item
259                 // and their btn-inner elements. The main reason we didn't do this
260                 // in the for-loop above is because we can eliminate per-item function overhead
261                 // by calling addClass() and children() once or twice afterwards. This
262                 // can give us a significant boost on platforms like WP7.5.
263
264                 for ( itemClass in itemClassDict ) {
265                         $( itemClassDict[ itemClass ] ).addClass( itemClass ).children( ".ui-btn-inner" ).addClass( itemClass );
266                 }
267
268                 $list.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" )
269                         .end()
270
271                         .find( "p, dl" ).addClass( "ui-li-desc" )
272                         .end()
273
274                         .find( ".ui-li-aside" ).each(function() {
275                                         var $this = $(this);
276                                         $this.prependTo( $this.parent() ); //shift aside to front for css float
277                                 })
278                         .end()
279
280                         .find( ".ui-li-count" ).each( function() {
281                                         $( this ).closest( "li" ).addClass( "ui-li-has-count" );
282                                 }).addClass( "ui-btn-up-" + ( $list.jqmData( "counttheme" ) || this.options.countTheme) + " ui-btn-corner-all" );
283
284                 // The idea here is to look at the first image in the list item
285                 // itself, and any .ui-link-inherit element it may contain, so we
286                 // can place the appropriate classes on the image and list item.
287                 // Note that we used to use something like:
288                 //
289                 //    li.find(">img:eq(0), .ui-link-inherit>img:eq(0)").each( ... );
290                 //
291                 // But executing a find() like that on Windows Phone 7.5 took a
292                 // really long time. Walking things manually with the code below
293                 // allows the 400 listview item page to load in about 3 seconds as
294                 // opposed to 30 seconds.
295
296                 this._addThumbClasses( li );
297                 this._addThumbClasses( $list.find( ".ui-link-inherit" ) );
298
299                 this._refreshCorners( create );
300         },
301
302         //create a string for ID/subpage url creation
303         _idStringEscape: function( str ) {
304                 return str.replace(/[^a-zA-Z0-9]/g, '-');
305         },
306
307         _createSubPages: function() {
308                 var parentList = this.element,
309                         parentPage = parentList.closest( ".ui-page" ),
310                         parentUrl = parentPage.jqmData( "url" ),
311                         parentId = parentUrl || parentPage[ 0 ][ $.expando ],
312                         parentListId = parentList.attr( "id" ),
313                         o = this.options,
314                         dns = "data-" + $.mobile.ns,
315                         self = this,
316                         persistentFooterID = parentPage.find( ":jqmData(role='footer')" ).jqmData( "id" ),
317                         hasSubPages;
318
319                 if ( typeof listCountPerPage[ parentId ] === "undefined" ) {
320                         listCountPerPage[ parentId ] = -1;
321                 }
322
323                 parentListId = parentListId || ++listCountPerPage[ parentId ];
324
325                 $( parentList.find( "li>ul, li>ol" ).toArray().reverse() ).each(function( i ) {
326                         var self = this,
327                                 list = $( this ),
328                                 listId = list.attr( "id" ) || parentListId + "-" + i,
329                                 parent = list.parent(),
330                                 nodeEls = $( list.prevAll().toArray().reverse() ),
331                                 nodeEls = nodeEls.length ? nodeEls : $( "<span>" + $.trim(parent.contents()[ 0 ].nodeValue) + "</span>" ),
332                                 title = nodeEls.first().getEncodedText(),//url limits to first 30 chars of text
333                                 id = ( parentUrl || "" ) + "&" + $.mobile.subPageUrlKey + "=" + listId,
334                                 theme = list.jqmData( "theme" ) || o.theme,
335                                 countTheme = list.jqmData( "counttheme" ) || parentList.jqmData( "counttheme" ) || o.countTheme,
336                                 newPage, anchor;
337
338                         //define hasSubPages for use in later removal
339                         hasSubPages = true;
340
341                         newPage = list.detach()
342                                                 .wrap( "<div " + dns + "role='page' " + dns + "url='" + id + "' " + dns + "theme='" + theme + "' " + dns + "count-theme='" + countTheme + "'><div " + dns + "role='content'></div></div>" )
343                                                 .parent()
344                                                         .before( "<div " + dns + "role='header' " + dns + "theme='" + o.headerTheme + "'><div class='ui-title'>" + title + "</div></div>" )
345                                                         .after( persistentFooterID ? $( "<div " + dns + "role='footer' " + dns + "id='"+ persistentFooterID +"'>") : "" )
346                                                         .parent()
347                                                                 .appendTo( $.mobile.pageContainer );
348
349                         newPage.page();
350
351                         anchor = parent.find('a:first');
352
353                         if ( !anchor.length ) {
354                                 anchor = $( "<a/>" ).html( nodeEls || title ).prependTo( parent.empty() );
355                         }
356
357                         anchor.attr( "href", "#" + id );
358
359                 }).listview();
360
361                 // on pagehide, remove any nested pages along with the parent page, as long as they aren't active
362                 // and aren't embedded
363                 if( hasSubPages &&
364                         parentPage.is( ":jqmData(external-page='true')" ) &&
365                         parentPage.data("page").options.domCache === false ) {
366
367                         var newRemove = function( e, ui ){
368                                 var nextPage = ui.nextPage, npURL;
369
370                                 if( ui.nextPage ){
371                                         npURL = nextPage.jqmData( "url" );
372                                         if( npURL.indexOf( parentUrl + "&" + $.mobile.subPageUrlKey ) !== 0 ){
373                                                 self.childPages().remove();
374                                                 parentPage.remove();
375                                         }
376                                 }
377                         };
378
379                         // unbind the original page remove and replace with our specialized version
380                         parentPage
381                                 .unbind( "pagehide.remove" )
382                                 .bind( "pagehide.remove", newRemove);
383                 }
384         },
385
386         // TODO sort out a better way to track sub pages of the listview this is brittle
387         childPages: function(){
388                 var parentUrl = this.parentPage.jqmData( "url" );
389
390                 return $( ":jqmData(url^='"+  parentUrl + "&" + $.mobile.subPageUrlKey +"')");
391         }
392 });
393
394 //auto self-init widgets
395 $( document ).bind( "pagecreate create", function( e ){
396         $( $.mobile.listview.prototype.options.initSelector, e.target ).listview();
397 });
398
399 })( jQuery );