//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); //>>description: Adds a filtering textfield to listviews //>>label: Listview Filter define( [ "jquery", "./jquery.mobile.widget", "./jquery.mobile.buttonMarkup", "./jquery.mobile.page", "./jquery.mobile.page.sections" ], function( $ ) { //>>excludeEnd("jqmBuildExclude"); (function( $, undefined ) { //Keeps track of the number of lists per page UID //This allows support for multiple nested list in the same page //https://github.com/jquery/jquery-mobile/issues/1617 var listCountPerPage = {}; $.widget( "mobile.listview", $.mobile.widget, { options: { theme: null, countTheme: "c", headerTheme: "b", dividerTheme: "b", splitIcon: "arrow-r", splitTheme: "b", inset: false, initSelector: ":jqmData(role='listview')" }, _create: function() { var t = this; // create listview markup t.element.addClass(function( i, orig ) { return orig + " ui-listview " + ( t.options.inset ? " ui-listview-inset ui-corner-all ui-shadow " : "" ); }); t.refresh( true ); }, _removeCorners: function( li, which ) { var top = "ui-corner-top ui-corner-tr ui-corner-tl", bot = "ui-corner-bottom ui-corner-br ui-corner-bl"; li = li.add( li.find( ".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb" ) ); if ( which === "top" ) { li.removeClass( top ); } else if ( which === "bottom" ) { li.removeClass( bot ); } else { li.removeClass( top + " " + bot ); } }, _refreshCorners: function( create ) { var $li, $visibleli, $topli, $bottomli; if ( this.options.inset ) { $li = this.element.children( "li" ); // at create time the li are not visible yet so we need to rely on .ui-screen-hidden $visibleli = create?$li.not( ".ui-screen-hidden" ):$li.filter( ":visible" ); this._removeCorners( $li ); // Select the first visible li element $topli = $visibleli.first() .addClass( "ui-corner-top" ); $topli.add( $topli.find( ".ui-btn-inner" ) .not( ".ui-li-link-alt span:first-child" ) ) .addClass( "ui-corner-top" ) .end() .find( ".ui-li-link-alt, .ui-li-link-alt span:first-child" ) .addClass( "ui-corner-tr" ) .end() .find( ".ui-li-thumb" ) .not(".ui-li-icon") .addClass( "ui-corner-tl" ); // Select the last visible li element $bottomli = $visibleli.last() .addClass( "ui-corner-bottom" ); $bottomli.add( $bottomli.find( ".ui-btn-inner" ) ) .find( ".ui-li-link-alt" ) .addClass( "ui-corner-br" ) .end() .find( ".ui-li-thumb" ) .not(".ui-li-icon") .addClass( "ui-corner-bl" ); } if ( !create ) { this.element.trigger( "updatelayout" ); } }, // This is a generic utility method for finding the first // node with a given nodeName. It uses basic DOM traversal // to be fast and is meant to be a substitute for simple // $.fn.closest() and $.fn.children() calls on a single // element. Note that callers must pass both the lowerCase // and upperCase version of the nodeName they are looking for. // The main reason for this is that this function will be // called many times and we want to avoid having to lowercase // the nodeName from the element every time to ensure we have // a match. Note that this function lives here for now, but may // be moved into $.mobile if other components need a similar method. _findFirstElementByTagName: function( ele, nextProp, lcName, ucName ) { var dict = {}; dict[ lcName ] = dict[ ucName ] = true; while ( ele ) { if ( dict[ ele.nodeName ] ) { return ele; } ele = ele[ nextProp ]; } return null; }, _getChildrenByTagName: function( ele, lcName, ucName ) { var results = [], dict = {}; dict[ lcName ] = dict[ ucName ] = true; ele = ele.firstChild; while ( ele ) { if ( dict[ ele.nodeName ] ) { results.push( ele ); } ele = ele.nextSibling; } return $( results ); }, _addThumbClasses: function( containers ) { var i, img, len = containers.length; for ( i = 0; i < len; i++ ) { img = $( this._findFirstElementByTagName( containers[ i ].firstChild, "nextSibling", "img", "IMG" ) ); if ( img.length ) { img.addClass( "ui-li-thumb" ); $( this._findFirstElementByTagName( img[ 0 ].parentNode, "parentNode", "li", "LI" ) ).addClass( img.is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" ); } } }, refresh: function( create ) { this.parentPage = this.element.closest( ".ui-page" ); this._createSubPages(); var o = this.options, $list = this.element, self = this, dividertheme = $list.jqmData( "dividertheme" ) || o.dividerTheme, listsplittheme = $list.jqmData( "splittheme" ), listspliticon = $list.jqmData( "spliticon" ), li = this._getChildrenByTagName( $list[ 0 ], "li", "LI" ), counter = $.support.cssPseudoElement || !$.nodeName( $list[ 0 ], "ol" ) ? 0 : 1, itemClassDict = {}, item, itemClass, itemTheme, a, last, splittheme, countParent, icon, imgParents, img; if ( counter ) { $list.find( ".ui-li-dec" ).remove(); } if ( !o.theme ) { o.theme = $.mobile.getInheritedTheme( this.element, "c" ); } for ( var pos = 0, numli = li.length; pos < numli; pos++ ) { item = li.eq( pos ); itemClass = "ui-li"; // If we're creating the element, we update it regardless if ( create || !item.hasClass( "ui-li" ) ) { itemTheme = item.jqmData("theme") || o.theme; a = this._getChildrenByTagName( item[ 0 ], "a", "A" ); if ( a.length ) { icon = item.jqmData("icon"); item.buttonMarkup({ wrapperEls: "div", shadow: false, corners: false, iconpos: "right", icon: a.length > 1 || icon === false ? false : icon || "arrow-r", theme: itemTheme }); if ( ( icon != false ) && ( a.length == 1 ) ) { item.addClass( "ui-li-has-arrow" ); } a.first().removeClass( "ui-link" ).addClass( "ui-link-inherit" ); if ( a.length > 1 ) { itemClass += " ui-li-has-alt"; last = a.last(); splittheme = listsplittheme || last.jqmData( "theme" ) || o.splitTheme; last.appendTo(item) .attr( "title", last.getEncodedText() ) .addClass( "ui-li-link-alt" ) .empty() .buttonMarkup({ shadow: false, corners: false, theme: itemTheme, icon: false, iconpos: false }) .find( ".ui-btn-inner" ) .append( $( document.createElement( "span" ) ).buttonMarkup({ shadow: true, corners: true, theme: splittheme, iconpos: "notext", icon: listspliticon || last.jqmData( "icon" ) || o.splitIcon }) ); } } else if ( item.jqmData( "role" ) === "list-divider" ) { itemClass += " ui-li-divider ui-btn ui-bar-" + dividertheme; item.attr( "role", "heading" ); //reset counter when a divider heading is encountered if ( counter ) { counter = 1; } } else { itemClass += " ui-li-static ui-body-" + itemTheme; } } if ( counter && itemClass.indexOf( "ui-li-divider" ) < 0 ) { countParent = item.is( ".ui-li-static:first" ) ? item : item.find( ".ui-link-inherit" ); countParent.addClass( "ui-li-jsnumbering" ) .prepend( "" + (counter++) + ". " ); } // Instead of setting item class directly on the list item and its // btn-inner at this point in time, push the item into a dictionary // that tells us what class to set on it so we can do this after this // processing loop is finished. if ( !itemClassDict[ itemClass ] ) { itemClassDict[ itemClass ] = []; } itemClassDict[ itemClass ].push( item[ 0 ] ); } // Set the appropriate listview item classes on each list item // and their btn-inner elements. The main reason we didn't do this // in the for-loop above is because we can eliminate per-item function overhead // by calling addClass() and children() once or twice afterwards. This // can give us a significant boost on platforms like WP7.5. for ( itemClass in itemClassDict ) { $( itemClassDict[ itemClass ] ).addClass( itemClass ).children( ".ui-btn-inner" ).addClass( itemClass ); } $list.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" ) .end() .find( "p, dl" ).addClass( "ui-li-desc" ) .end() .find( ".ui-li-aside" ).each(function() { var $this = $(this); $this.prependTo( $this.parent() ); //shift aside to front for css float }) .end() .find( ".ui-li-count" ).each( function() { $( this ).closest( "li" ).addClass( "ui-li-has-count" ); }).addClass( "ui-btn-up-" + ( $list.jqmData( "counttheme" ) || this.options.countTheme) + " ui-btn-corner-all" ); // The idea here is to look at the first image in the list item // itself, and any .ui-link-inherit element it may contain, so we // can place the appropriate classes on the image and list item. // Note that we used to use something like: // // li.find(">img:eq(0), .ui-link-inherit>img:eq(0)").each( ... ); // // But executing a find() like that on Windows Phone 7.5 took a // really long time. Walking things manually with the code below // allows the 400 listview item page to load in about 3 seconds as // opposed to 30 seconds. this._addThumbClasses( li ); this._addThumbClasses( $list.find( ".ui-link-inherit" ) ); this._refreshCorners( create ); }, //create a string for ID/subpage url creation _idStringEscape: function( str ) { return str.replace(/[^a-zA-Z0-9]/g, '-'); }, _createSubPages: function() { var parentList = this.element, parentPage = parentList.closest( ".ui-page" ), parentUrl = parentPage.jqmData( "url" ), parentId = parentUrl || parentPage[ 0 ][ $.expando ], parentListId = parentList.attr( "id" ), o = this.options, dns = "data-" + $.mobile.ns, self = this, persistentFooterID = parentPage.find( ":jqmData(role='footer')" ).jqmData( "id" ), hasSubPages; if ( typeof listCountPerPage[ parentId ] === "undefined" ) { listCountPerPage[ parentId ] = -1; } parentListId = parentListId || ++listCountPerPage[ parentId ]; $( parentList.find( "li>ul, li>ol" ).toArray().reverse() ).each(function( i ) { var self = this, list = $( this ), listId = list.attr( "id" ) || parentListId + "-" + i, parent = list.parent(), nodeEls = $( list.prevAll().toArray().reverse() ), nodeEls = nodeEls.length ? nodeEls : $( "" + $.trim(parent.contents()[ 0 ].nodeValue) + "" ), title = nodeEls.first().getEncodedText(),//url limits to first 30 chars of text id = ( parentUrl || "" ) + "&" + $.mobile.subPageUrlKey + "=" + listId, theme = list.jqmData( "theme" ) || o.theme, countTheme = list.jqmData( "counttheme" ) || parentList.jqmData( "counttheme" ) || o.countTheme, newPage, anchor; //define hasSubPages for use in later removal hasSubPages = true; newPage = list.detach() .wrap( "
" ) .parent() .before( "
" + title + "
" ) .after( persistentFooterID ? $( "
") : "" ) .parent() .appendTo( $.mobile.pageContainer ); newPage.page(); anchor = parent.find('a:first'); if ( !anchor.length ) { anchor = $( "" ).html( nodeEls || title ).prependTo( parent.empty() ); } anchor.attr( "href", "#" + id ); }).listview(); // on pagehide, remove any nested pages along with the parent page, as long as they aren't active // and aren't embedded if( hasSubPages && parentPage.is( ":jqmData(external-page='true')" ) && parentPage.data("page").options.domCache === false ) { var newRemove = function( e, ui ){ var nextPage = ui.nextPage, npURL; if( ui.nextPage ){ npURL = nextPage.jqmData( "url" ); if( npURL.indexOf( parentUrl + "&" + $.mobile.subPageUrlKey ) !== 0 ){ self.childPages().remove(); parentPage.remove(); } } }; // unbind the original page remove and replace with our specialized version parentPage .unbind( "pagehide.remove" ) .bind( "pagehide.remove", newRemove); } }, // TODO sort out a better way to track sub pages of the listview this is brittle childPages: function(){ var parentUrl = this.parentPage.jqmData( "url" ); return $( ":jqmData(url^='"+ parentUrl + "&" + $.mobile.subPageUrlKey +"')"); } }); //auto self-init widgets $( document ).bind( "pagecreate create", function( e ){ $( $.mobile.listview.prototype.options.initSelector, e.target ).listview(); }); })( jQuery ); //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); }); //>>excludeEnd("jqmBuildExclude");