| //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); |
| //>>description: Applies listview styling of various types (standard, numbered, split button, etc.) |
| //>>label: Listview |
| //>>group: Widgets |
| //>>css.structure: ../css/structure/jquery.mobile.listview.css |
| //>>css.theme: ../css/themes/default/jquery.mobile.theme.css |
| |
| define( [ "jquery", "../jquery.mobile.widget", "../jquery.mobile.buttonMarkup", "./page", "./page.sections" ], function( jQuery ) { |
| //>>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, |
| listviewClasses = ""; |
| |
| listviewClasses += t.options.inset ? " ui-listview-inset ui-corner-all ui-shadow " : ""; |
| |
| // create listview markup |
| t.element.addClass(function( i, orig ) { |
| return orig + " ui-listview " + listviewClasses; |
| }); |
| |
| 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; |
| |
| $li = this.element.children( "li" ); |
| // At create time and when autodividers calls refresh the li are not visible yet so we need to rely on .ui-screen-hidden |
| $visibleli = create || $li.filter( ":visible" ).length === 0 ? $li.not( ".ui-screen-hidden" ) : $li.filter( ":visible" ); |
| |
| // ui-li-last is used for setting border-bottom on the last li |
| $li.filter( ".ui-li-last" ).removeClass( "ui-li-last" ); |
| |
| if ( this.options.inset ) { |
| 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 ui-li-last" ); |
| |
| $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" ); |
| } else { |
| $visibleli.last().addClass( "ui-li-last" ); |
| } |
| 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" ).attr( { |
| "role" : "", |
| "aria-label" : "icon" |
| }); |
| $( this._findFirstElementByTagName( img[ 0 ].parentNode, "parentNode", "li", "LI" ) ).addClass( img.is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" ); |
| } |
| } |
| }, |
| |
| _addCheckboxRadioClasses: function( containers ) |
| { |
| var i, inputAttr, len = containers.length; |
| for ( i = 0; i < len; i++ ) { |
| inputAttr = $( containers[ i ] ).find( "input" ); |
| if ( inputAttr.attr( "type" ) == "checkbox" ) { |
| $( containers[ i ] ).addClass( "ui-li-has-checkbox" ); |
| } else if ( inputAttr.attr( "type" ) == "radio" ) { |
| $( containers[ i ] ).addClass( "ui-li-has-radio" ); |
| } |
| } |
| }, |
| |
| _addRightBtnClasses: function( containers ) |
| { |
| var i, btnAttr, len = containers.length; |
| for ( i = 0; i < len; i++ ) { |
| btnAttr = $( containers[ i ] ).find( ":jqmData(role='button'),input[type='button'],select:jqmData(role='slider')" ); |
| if ( btnAttr.length ) { |
| if ( btnAttr.jqmData( "style" ) == "circle" ) { |
| $( containers[ i ] ).addClass( "ui-li-has-right-circle-btn" ); |
| } else { |
| $( containers[ i ] ).addClass( "ui-li-has-right-btn" ); |
| } |
| } |
| } |
| }, |
| |
| refresh: function( create ) { |
| this.parentPage = this.element.closest( ".ui-page" ); |
| this._createSubPages(); |
| |
| var o = this.options, |
| $list = this.element, |
| self = this, |
| dividertheme = $.mobile.getAttrFixed( $list[0], "data-" + $.mobile.ns + "dividertheme" ) || o.dividerTheme, |
| listsplittheme = $.mobile.getAttrFixed( $list[0], "data-" + $.mobile.ns + "splittheme" ), |
| listspliticon = $.mobile.getAttrFixed( $list[0], "data-" + $.mobile.ns + "spliticon" ), |
| li = this._getChildrenByTagName( $list[ 0 ], "li", "LI" ), |
| ol = !!$.nodeName( $list[ 0 ], "ol" ), |
| jsCount = !$.support.cssPseudoElement, |
| start = $list.attr( "start" ), |
| itemClassDict = {}, |
| item, itemClass, itemTheme, |
| a, last, splittheme, counter, startCount, newStartCount, countParent, icon, imgParents, img, linkIcon; |
| |
| if ( ol && jsCount ) { |
| $list.find( ".ui-li-dec" ).remove(); |
| } |
| |
| if ( ol ) { |
| // Check if a start attribute has been set while taking a value of 0 into account |
| if ( start || start === 0 ) { |
| if ( !jsCount ) { |
| startCount = parseFloat( start ) - 1; |
| $list.css( "counter-reset", "listnumbering " + startCount ); |
| } else { |
| counter = parseFloat( start ); |
| } |
| } else if ( jsCount ) { |
| counter = 1; |
| } |
| } |
| |
| 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 = $.mobile.getAttrFixed( item[0], "data-" + $.mobile.ns + "theme" ) || o.theme; |
| a = this._getChildrenByTagName( item[ 0 ], "a", "A" ).attr( { |
| "role": "", |
| "tabindex": "0" |
| }); |
| var isDivider = ( $.mobile.getAttrFixed( item[0], "data-" + $.mobile.ns + "role" ) === "list-divider" ); |
| |
| if ( item.hasClass( "ui-li-has-checkbox" ) || item.hasClass( "ui-li-has-radio" ) ) { |
| item.on( "vclick", function ( e ) { |
| var targetItem = $( e.target ); |
| var checkboxradio = targetItem.find( ".ui-checkbox" ); |
| if ( !checkboxradio.length ) { |
| checkboxradio = targetItem.find( ".ui-radio" ); |
| } |
| |
| if ( checkboxradio.length ) { |
| checkboxradio.children( "label" ).trigger( "vclick" ); |
| } |
| }); |
| } |
| |
| if ( a.length && !isDivider ) { |
| icon = $.mobile.getAttrFixed( item[0], "data-" + $.mobile.ns + "icon" ); |
| |
| /* Remove auto populated right-arrow button. */ |
| if ( icon === undefined ) { |
| icon = false; |
| } |
| |
| 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 || $.mobile.getAttrFixed( last[0], "data-" + $.mobile.ns + "theme" ) || o.splitTheme; |
| linkIcon = $.mobile.getAttrFixed( last[0], "data-" + $.mobile.ns + "icon" ); |
| |
| last.appendTo( item ) |
| .attr( "title", last.getEncodedText() ) |
| .addClass( "ui-li-link-alt" ) |
| .empty() |
| .buttonMarkup({ |
| shadow: false, |
| corners: false, |
| theme: itemTheme, |
| icon: false, |
| iconpos: "notext" |
| }) |
| .find( ".ui-btn-inner" ) |
| .append( |
| $( document.createElement( "span" ) ).buttonMarkup({ |
| shadow: true, |
| corners: true, |
| theme: splittheme, |
| iconpos: "notext", |
| // link icon overrides list item icon overrides ul element overrides options |
| icon: linkIcon || icon || listspliticon || o.splitIcon |
| }) |
| ); |
| } |
| } else if ( isDivider ) { |
| |
| itemClass += " ui-li-divider ui-bar-" + dividertheme; |
| item.attr( { "role": "heading", "tabindex": "0" } ); |
| |
| if ( ol ) { |
| //reset counter when a divider heading is encountered |
| if ( start || start === 0 ) { |
| if ( !jsCount ) { |
| newStartCount = parseFloat( start ) - 1; |
| item.css( "counter-reset", "listnumbering " + newStartCount ); |
| } else { |
| counter = parseFloat( start ); |
| } |
| } else if ( jsCount ) { |
| counter = 1; |
| } |
| } |
| |
| } else { |
| itemClass += " ui-li-static ui-btn-up-" + itemTheme; |
| item.attr( "tabindex", "0" ); |
| } |
| } |
| |
| if ( ol && jsCount && itemClass.indexOf( "ui-li-divider" ) < 0 ) { |
| countParent = itemClass.indexOf( "ui-li-static" ) > 0 ? item : item.find( ".ui-link-inherit" ); |
| |
| countParent.addClass( "ui-li-jsnumbering" ) |
| .prepend( "<span class='ui-li-dec'>" + ( counter++ ) + ". </span>" ); |
| } |
| |
| // 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-" + ( $.mobile.getAttrFixed( $list[0], "data-" + $.mobile.ns + "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._addCheckboxRadioClasses( li ); |
| this._addCheckboxRadioClasses( $list.find( ".ui-link-inherit" ) ); |
| |
| this._addRightBtnClasses( li ); |
| this._addRightBtnClasses( $list.find( ".ui-link-inherit" ) ); |
| |
| this._refreshCorners( create ); |
| |
| // autodividers binds to this to redraw dividers after the listview refresh |
| this._trigger( "afterrefresh" ); |
| }, |
| |
| //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(), |
| nodeElsFull = $( list.prevAll().toArray().reverse() ), |
| nodeEls = nodeElsFull.length ? nodeElsFull : $( "<span>" + $.trim(parent.contents()[ 0 ].nodeValue) + "</span>" ), |
| title = nodeEls.first().getEncodedText(),//url limits to first 30 chars of text |
| id = ( parentUrl || "" ) + "&" + $.mobile.subPageUrlKey + "=" + listId, |
| theme = $.mobile.getAttrFixed( list[0], "data-" + $.mobile.ns + "theme" ) || o.theme, |
| countTheme = $.mobile.getAttrFixed( list[0], "data-" + $.mobile.ns + "counttheme" ) || $.mobile.getAttrFixed( parentList[0], "data-" + $.mobile.ns + "counttheme" ) || o.countTheme, |
| newPage, anchor; |
| |
| //define hasSubPages for use in later removal |
| hasSubPages = true; |
| |
| newPage = list.detach() |
| .wrap( "<div " + dns + "role='page' " + dns + "url='" + id + "' " + dns + "theme='" + theme + "' " + dns + "count-theme='" + countTheme + "'><div " + dns + "role='content'></div></div>" ) |
| .parent() |
| .before( "<div " + dns + "role='header' " + dns + "theme='" + o.headerTheme + "'><div class='ui-title'>" + title + "</div></div>" ) |
| .after( persistentFooterID ? $( "<div " + dns + "role='footer' " + dns + "id='"+ persistentFooterID +"'>" ) : "" ) |
| .parent() |
| .appendTo( $.mobile.pageContainer ); |
| |
| newPage.page(); |
| |
| anchor = parent.find( 'a:first' ); |
| |
| if ( !anchor.length ) { |
| anchor = $( "<a/>" ).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, |
| prEvent = new $.Event( "pageremove" ); |
| |
| if ( ui.nextPage ) { |
| npURL = nextPage.jqmData( "url" ); |
| if ( npURL.indexOf( parentUrl + "&" + $.mobile.subPageUrlKey ) !== 0 ) { |
| self.childPages().remove(); |
| parentPage.trigger( prEvent ); |
| if ( !prEvent.isDefaultPrevented() ) { |
| parentPage.removeWithDependents(); |
| } |
| } |
| } |
| }; |
| |
| // unbind the original page remove and replace with our specialized version |
| parentPage |
| .unbind( "pagehide.remove" ) |
| .bind( "pagehide.remove", newRemove); |
| } |
| }, |
| |
| addItem : function( listitem , idx ) { |
| var $item = $(listitem), |
| $li, |
| _self = this; |
| |
| $li = _self.element.children( 'li' ); |
| $item.css( { 'opacity' : 0, |
| 'display' : 'none' } ); |
| if( $li.length == 0 |
| || $li.length <= idx) |
| { |
| $( _self.element ).append( $item ); |
| } else { |
| $( $li.get( idx ) ).before( $item ); |
| } |
| $(_self.element).trigger("create") |
| .listview( 'refresh' ); |
| |
| $item.css( 'min-height' , '0px' ); |
| |
| $item.slideDown( 'fast' , function( ){ |
| $item.addClass("addli"); |
| $item.css( { 'opacity' : 1 } ); |
| } ); |
| }, |
| |
| removeItem : function( idx ) { |
| var $item, |
| $li, |
| _self = this; |
| |
| $li = _self.element.children( 'li' ); |
| if( $li.length <= 0 || |
| $li.length < idx ) { |
| return ; |
| } |
| $item = $( $li.get( idx ) ); |
| $item.addClass("removeli"); |
| $item.slideUp('normal', |
| function( ) { |
| $(this).remove(); |
| }); |
| }, |
| |
| // 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 + "')" ); |
| } |
| }); |
| |
| //delegate auto self-init widgets |
| $.delegateSelfInitWithSingleSelector( $.mobile.listview ); |
| |
| })( jQuery ); |
| //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); |
| }); |
| //>>excludeEnd("jqmBuildExclude"); |