5 (function( $, undefined ) {
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 = {};
12 $.widget( "mobile.listview", $.mobile.widget, {
21 initSelector: ":jqmData(role='listview')"
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 " : "" );
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";
39 li = li.add( li.find( ".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb" ) );
41 if ( which === "top" ) {
42 li.removeClass( top );
43 } else if ( which === "bottom" ) {
44 li.removeClass( bot );
46 li.removeClass( top + " " + bot );
50 _refreshCorners: function( create ) {
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" );
61 this._removeCorners( $li );
63 // Select the first visible li element
64 $topli = $visibleli.first()
65 .addClass( "ui-corner-top" );
67 $topli.add( $topli.find( ".ui-btn-inner" )
68 .not( ".ui-li-link-alt span:first-child" ) )
69 .addClass( "ui-corner-top" )
71 .find( ".ui-li-link-alt, .ui-li-link-alt span:first-child" )
72 .addClass( "ui-corner-tr" )
74 .find( ".ui-li-thumb" )
76 .addClass( "ui-corner-tl" );
78 // Select the last visible li element
79 $bottomli = $visibleli.last()
80 .addClass( "ui-corner-bottom" );
82 $bottomli.add( $bottomli.find( ".ui-btn-inner" ) )
83 .find( ".ui-li-link-alt" )
84 .addClass( "ui-corner-br" )
86 .find( ".ui-li-thumb" )
88 .addClass( "ui-corner-bl" );
91 this.element.trigger( "updatelayout" );
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 )
109 dict[ lcName ] = dict[ ucName ] = true;
111 if ( dict[ ele.nodeName ] ) {
114 ele = ele[ nextProp ];
118 _getChildrenByTagName: function( ele, lcName, ucName )
122 dict[ lcName ] = dict[ ucName ] = true;
123 ele = ele.firstChild;
125 if ( dict[ ele.nodeName ] ) {
128 ele = ele.nextSibling;
133 _addThumbClasses: function( containers )
135 var i, img, len = containers.length;
136 for ( i = 0; i < len; i++ ) {
137 img = $( this._findFirstElementByTagName( containers[ i ].firstChild, "nextSibling", "img", "IMG" ) );
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" );
145 refresh: function( create ) {
146 this.parentPage = this.element.closest( ".ui-page" );
147 this._createSubPages();
149 var o = this.options,
150 $list = this.element,
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,
158 item, itemClass, itemTheme,
159 a, last, splittheme, countParent, icon, imgParents, img;
162 $list.find( ".ui-li-dec" ).remove();
166 o.theme = $.mobile.getInheritedTheme( this.element, "c" );
169 for ( var pos = 0, numli = li.length; pos < numli; pos++ ) {
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" );
179 icon = item.jqmData("icon");
186 icon: a.length > 1 || icon === false ? false : icon || "arrow-r",
190 if ( ( icon != false ) && ( a.length == 1 ) ) {
191 item.addClass( "ui-li-has-arrow" );
194 a.first().addClass( "ui-link-inherit" );
196 if ( a.length > 1 ) {
197 itemClass += " ui-li-has-alt";
200 splittheme = listsplittheme || last.jqmData( "theme" ) || o.splitTheme;
203 .attr( "title", last.getEncodedText() )
204 .addClass( "ui-li-link-alt" )
213 .find( ".ui-btn-inner" )
215 $( document.createElement( "span" ) ).buttonMarkup({
220 icon: listspliticon || last.jqmData( "icon" ) || o.splitIcon
224 } else if ( item.jqmData( "role" ) === "list-divider" ) {
226 itemClass += " ui-li-divider ui-btn ui-bar-" + dividertheme;
227 item.attr( "role", "heading" );
229 //reset counter when a divider heading is encountered
235 itemClass += " ui-li-static ui-body-" + itemTheme;
239 if ( counter && itemClass.indexOf( "ui-li-divider" ) < 0 ) {
240 countParent = item.is( ".ui-li-static:first" ) ? item : item.find( ".ui-link-inherit" );
242 countParent.addClass( "ui-li-jsnumbering" )
243 .prepend( "<span class='ui-li-dec'>" + (counter++) + ". </span>" );
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.
251 if ( !itemClassDict[ itemClass ] ) {
252 itemClassDict[ itemClass ] = [];
255 itemClassDict[ itemClass ].push( item[ 0 ] );
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.
264 for ( itemClass in itemClassDict ) {
265 $( itemClassDict[ itemClass ] ).addClass( itemClass ).children( ".ui-btn-inner" ).addClass( itemClass );
268 $list.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" )
271 .find( "p, dl" ).addClass( "ui-li-desc" )
274 .find( ".ui-li-aside" ).each(function() {
276 $this.prependTo( $this.parent() ); //shift aside to front for css float
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" );
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:
289 // li.find(">img:eq(0), .ui-link-inherit>img:eq(0)").each( ... );
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.
296 this._addThumbClasses( li );
297 this._addThumbClasses( $list.find( ".ui-link-inherit" ) );
299 this._refreshCorners( create );
302 //create a string for ID/subpage url creation
303 _idStringEscape: function( str ) {
304 return str.replace(/[^a-zA-Z0-9]/g, '-');
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" ),
314 dns = "data-" + $.mobile.ns,
316 persistentFooterID = parentPage.find( ":jqmData(role='footer')" ).jqmData( "id" ),
319 if ( typeof listCountPerPage[ parentId ] === "undefined" ) {
320 listCountPerPage[ parentId ] = -1;
323 parentListId = parentListId || ++listCountPerPage[ parentId ];
325 $( parentList.find( "li>ul, li>ol" ).toArray().reverse() ).each(function( i ) {
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,
338 //define hasSubPages for use in later removal
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>" )
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 +"'>") : "" )
347 .appendTo( $.mobile.pageContainer );
351 anchor = parent.find('a:first');
353 if ( !anchor.length ) {
354 anchor = $( "<a/>" ).html( nodeEls || title ).prependTo( parent.empty() );
357 anchor.attr( "href", "#" + id );
361 // on pagehide, remove any nested pages along with the parent page, as long as they aren't active
362 // and aren't embedded
364 parentPage.is( ":jqmData(external-page='true')" ) &&
365 parentPage.data("page").options.domCache === false ) {
367 var newRemove = function( e, ui ){
368 var nextPage = ui.nextPage, npURL;
371 npURL = nextPage.jqmData( "url" );
372 if( npURL.indexOf( parentUrl + "&" + $.mobile.subPageUrlKey ) !== 0 ){
373 self.childPages().remove();
379 // unbind the original page remove and replace with our specialized version
381 .unbind( "pagehide.remove" )
382 .bind( "pagehide.remove", newRemove);
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" );
390 return $( ":jqmData(url^='"+ parentUrl + "&" + $.mobile.subPageUrlKey +"')");
394 //auto self-init widgets
395 $( document ).bind( "pagecreate create", function( e ){
396 $( $.mobile.listview.prototype.options.initSelector, e.target ).listview();