mirror of
https://github.com/Hopiu/jquery-mobile.git
synced 2026-03-16 22:10:25 +00:00
399 lines
12 KiB
JavaScript
399 lines
12 KiB
JavaScript
/*
|
|
* "listview" plugin
|
|
*/
|
|
|
|
(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().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( "<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-" + ( $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 : $( "<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 = 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( "<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;
|
|
|
|
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 );
|