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