3 * jQuery Mobile Widget @VERSION - listview autodividers
5 * This software is licensed under the MIT licence (as defined by the OSI at
6 * http://www.opensource.org/licenses/mit-license.php)
8 * ***************************************************************************
9 * Copyright (C) 2011 by Intel Corporation Ltd.
11 * Permission is hereby granted, free of charge, to any person obtaining a
12 * copy of this software and associated documentation files (the "Software"),
13 * to deal in the Software without restriction, including without limitation
14 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 * and/or sell copies of the Software, and to permit persons to whom the
16 * Software is furnished to do so, subject to the following conditions:
18 * The above copyright notice and this permission notice shall be included in
19 * all copies or substantial portions of the Software.
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 * DEALINGS IN THE SOFTWARE.
28 * ***************************************************************************
30 * Authors: Elliot Smith <elliot.smith@intel.com>
33 // Applies dividers automatically to a listview, using link text
34 // (for link lists) or text (for readonly lists) as the basis for the
37 // Apply using autodividers({type: 'X'}) on a <ul> with
38 // data-role="listview", or with data-autodividers="true", where X
39 // is the type of divider to create. The default divider type is 'alpha',
40 // meaning first characters of list item text, upper-cased.
42 // The element used to derive the text for the auto dividers defaults
43 // to the first link inside the li; failing that, the text directly inside
44 // the li element is used. This can be overridden with the
45 // data-autodividers-selector attribute or via options; the selector
46 // will use each li element as its context.
48 // Any time a new li element is added to the list, or an li element is
49 // removed, this extension will update the dividers in the listview
52 // Note that if a listview already has dividers, applying this
53 // extension will remove all the existing dividers and replace them
54 // with new, generated ones.
56 // Also note that this extension doesn't sort the list: it only creates
57 // dividers based on text inside list items. So if your list isn't
58 // alphabetically-sorted, you may get duplicate dividers.
60 // So, for example, this markup:
62 // <ul id="has-no-dividers" data-role="listview" data-autodividers="alpha">
71 // will produce dividers like this:
73 // <ul data-role="listview" data-autodividers="alpha">
74 // <li data-role="list-divider">B</li>
76 // <li data-role="list-divider">C</li>
78 // <li data-role="list-divider">B</li>
80 // <li data-role="list-divider">H</li>
82 // <li data-role="list-divider">C</li>
84 // <li data-role="list-divider">H</li>
88 // with each divider occuring twice.
92 // selector: The jQuery selector to use to find text for the
93 // generated dividers. Default is to use the first 'a'
94 // (link) element. If this selector doesn't find any
95 // text, the widget automatically falls back to the text
96 // inside the li (for read-only lists). Can be set to a custom
97 // selector via data-autodividers-selector="..." or the 'selector'
100 // type: 'alpha' (default) sets the auto divider type to "uppercased
101 // first character of text selected from each item"; "full" sets
102 // it to the unmodified text selected from each item. Set via
103 // the data-autodividers="<type>" attribute on the listview or
104 // the 'type' option.
108 // updatelayout: Triggered if the dividers in the list change;
109 // this happens if list items are added to the listview,
110 // which causes the autodividers to be regenerated.
112 (function( $, undefined ) {
114 var autodividers = function(options) {
115 var list = $( this );
116 options = options || {};
118 var listview = list.data( 'listview' );
120 var dividerType = options.type || list.jqmData( 'autodividers' ) || 'alpha';
122 var textSelector = options.selector || list.jqmData( 'autodividers-selector' ) || 'a';
124 var getDividerText = function( elt ) {
125 // look for some text in the item
126 var text = elt.find( textSelector ).text() || elt.text() || null;
132 // create the text for the divider
133 if ( dividerType === 'alpha' ) {
134 text = text.slice( 0, 1 ).toUpperCase();
140 var mergeDividers = function() {
141 var dividersChanged = false;
143 // any dividers which are following siblings of a divider, where
144 // there are no dividers with different text inbetween, can be removed
145 list.find( 'li.ui-li-divider' ).each(function() {
146 var divider = $( this );
147 var dividerText = divider.text();
148 var selector = '.ui-li-divider:not(:contains(' + dividerText + '))';
149 var nextDividers = divider.nextUntil( selector );
150 nextDividers = nextDividers.filter( '.ui-li-divider:contains(' + dividerText + ')' );
152 if (nextDividers.length > 0) {
153 nextDividers.remove();
154 dividersChanged = true;
158 if (dividersChanged) {
159 list.trigger( 'updatelayout' );
163 // check that elt is a non-divider li element
164 var isNonDividerLi = function( elt ) {
165 return elt.is('li') &&
166 elt.jqmData( 'role' ) !== 'list-divider';
169 // li element inserted, so check whether it needs a divider
170 var liAdded = function( li ) {
171 var dividerText = getDividerText( li );
173 if ( !dividerText ) {
178 // add expected divider for this li if it doesn't exist
179 var existingDividers = li.prevAll( '.ui-li-divider:first:contains(' + dividerText + ')' );
181 if ( existingDividers.length === 0 ) {
182 var divider = $( '<li>' + dividerText + '</li>' );
183 divider.attr( 'data-' + $.mobile.ns + 'role', 'list-divider' );
184 li.before( divider );
195 // li element removed, so check whether its divider should go
196 var liRemoved = function( li ) {
198 var dividerText = getDividerText( li );
200 if ( !dividerText ) {
205 // remove divider for this li if there are no other
206 // li items for the divider before or after this li item
207 var precedingItems = li.prevUntil( '.ui-li-divider:contains(' + dividerText + ')' );
208 var nextItems = li.nextUntil( '.ui-li-divider' );
210 if ( precedingItems.length === 0 && nextItems.length === 0 ) {
211 li.prevAll( '.ui-li-divider:contains(' + dividerText + '):first' ).remove();
222 // set up the dividers on first create
223 list.find( 'li' ).each( function() {
226 // remove existing dividers
227 if ( li.jqmData( 'role' ) === 'list-divider' ) {
230 // make new dividers for list items
236 // bind to DOM events to keep list up to date
237 list.bind( 'DOMNodeInserted', function( e ) {
238 var elt = $( e.target );
240 if ( !isNonDividerLi( elt ) ) {
247 list.bind( 'DOMNodeRemoved', function( e ) {
248 var elt = $( e.target );
250 if ( !isNonDividerLi( elt ) ) {
258 $.fn.autodividers = autodividers;
260 $( ":jqmData(role=listview)" ).live( "listviewcreate", function() {
261 var list = $( this );
263 if ( list.is( ':jqmData(autodividers)' ) ) {