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