1 /* ***************************************************************************
2 * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 * ***************************************************************************
23 * Author: Wongi Lee <wongi11.lee@samsung.com>
24 * Youmin Ha <youmin.ha@samsung.com>
28 * Virtual List Widget for unlimited data.
29 * To support more then 1,000 items, special list widget developed.
30 * Fast initialize and light DOM tree.
31 * DB connection and works like DB cursor.
35 * data-role: virtuallistview
36 * data-template : jQuery.template ID that populate into virtual list
37 * data-row : Optional. Set number of <li> elements that are used for data handling.
39 * ID : <UL> element that has "data-role=virtuallist" must have ID attribute.
44 * itemData: function ( idx ) { return json_obj; },
45 * numItemData: number or function () { return number; },
46 * cacheItemData: function ( minIdx, maxIdx ) {}
48 * : Create a virtuallist widget. At this moment, _create method is called.
49 * args : A collection of options
50 * itemData: A function that returns JSON object for given index. Mandatory.
51 * numItemData: Total number of itemData. Mandatory.
52 * cacheItemData: Virtuallist will ask itemData between minIdx and maxIdx.
53 * Developers can implement this function for preparing data.
58 * touchstart : Temporary preventDefault applied on touchstart event to avoid broken screen.
62 * <script id="tmp-3-2-7" type="text/x-jquery-tmpl">
63 * <li class="ui-li-3-2-7">
64 * <span class="ui-li-text-main">${NAME}</span>
65 * <img src="00_winset_icon_favorite_on.png" class="ui-li-icon-sub">
66 * <span class="ui-li-text-sub">${ACTIVE}</span>
67 * <span class="ui-li-text-sub2">${FROM}</span>
71 * <ul id="virtuallist-normal_3_2_7_ul" data-role="virtuallistview" data-template="tmp-3-2-7" data-dbtable="JSON_DATA" data-row="100">
78 In the Web environment, it is challenging to display a large amount of data in a list, such as displaying a contact list of over 1000 list items. It takes time to display the entire list in HTML and the DOM manipulation is complex.
80 The virtual list widget is used to display a list of unlimited data elements on the screen for better performance. This widget provides easy access to databases to retrieve and display data. Virtual lists are based on the jQuery.template plugin as described in the jQuery documentation for jQuery.template plugin.
82 To add a virtual list widget to the application, use the following code:
84 <script id="tmp-3-2-7" type="text/x-jquery-tmpl">
85 <li class="ui-li-3-2-7">
86 <span class="ui-li-text-main">${NAME}</span>
87 <img src="00_winset_icon_favorite_on.png" class="ui-li-icon-sub"/>
88 <span class="ui-li-text-sub">${ACTIVE}</span>
89 <span class="ui-li-text-sub2">${FROM}</span>
92 <ul id="vlist" data-role="virtuallistview" data-template="tmp-3-2-7" data-dbtable="JSON_DATA" data-row="100"></ul>
95 @property {String} data-role
96 Creates the virtual list view. The value must be set to virtuallistview.
97 Only the >ul< element, which a id attribute defined, supports this option. Also, the vlLoadSuccess class attribute must be defined in the >ul< element to ensure that loading data from the database is complete.
100 @property {String} data-template
101 Defines the jQuery.template element ID.
102 The jQuery.template must be defined. The template style can use rem units to support scalability.
105 @property {Number} data-row
106 Defines the number of virtual list child elements.
107 The minimum value is 20 and the default value is 100. As the value gets higher, the loading time increases while the system performance improves. So you need to pick a value that provides the best performance without excessive loading time.
111 @param {function} itemData(index)
112 : function itemData(index) returns the JSON object matched with the given index. The index value is between 0 and numItemData-1.
113 @param {Number} numItemData
114 : number numItemData or function numItemData() defines or returns a static number of items.
115 @param {function} cacheItemData(minIndex, maxIndex)
116 : function cacheItemData(minIndex, maxIndex) prepares the JSON data. This method is called before calling the itemData() method with index values between minIndex and maxIndex.
119 (function ( $, undefined ) {
121 /* Code for Virtual List Demo */
122 var listCountPerPage = {}, /* 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 */
123 _NO_SCROLL = 0, /* ENUM */
124 _SCROLL_DOWN = 1, /* ENUM */
125 _SCROLL_UP = -1; /* ENUM */
127 $.widget( "tizen.virtuallistview", $.mobile.widget, {
133 splitIcon: "arrow-r",
136 id: "", /* Virtual list UL elemet's ID */
137 childSelector: " li", /* To support swipe list */
140 dbkey: false, /* Data's unique Key */
144 initSelector: ":jqmData(role='virtuallistview')"
147 _stylerMouseUp: function () {
148 $( this ).addClass( "ui-btn-up-s" );
149 $( this ).removeClass( "ui-btn-down-s" );
152 _stylerMouseDown: function () {
153 $( this ).addClass( "ui-btn-down-s" );
154 $( this ).removeClass( "ui-btn-up-s" );
157 _stylerMouseOver: function () {
158 $( this ).toggleClass( "ui-btn-hover-s" );
161 _stylerMouseOut: function () {
162 $( this ).toggleClass( "ui-btn-hover-s" );
163 $( this ).addClass( "ui-btn-up-s" );
164 $( this ).removeClass( "ui-btn-down-s" );
168 // this virtuallistview object
169 // @param[in] template template name(string)
170 _pushData: function ( template ) {
171 var o = this.options,
173 myTemplate = $( "#" + template ), // Get template object
174 // NOTE: o.row = # of rows handled at once. Default value is 100.
175 lastIndex = ( o.row > this._numItemData ? this._numItemData : o.row ), // last index of handled data
178 for ( i = 0; i < lastIndex; i++ ) {
179 htmlData = myTemplate.tmpl( this._itemData( i ) ); // Make rows with template,
180 $( o.id ).append( $( htmlData ).attr( 'id', o.itemIDPrefix + i ) ); // and append it to the vlist object
183 // After pushing data re-style virtuallist widget
184 $( o.id ).trigger( "create" );
187 // Set children <li> elements' position
189 // this: virtuallist element
190 // event: virtuallistview.options
191 // TODO: Why this arg name is 'event'? Not resonable.
192 // (this function is not called with event element as args!)
193 _reposition: function ( event ) {
203 if ( $( o.id + o.childSelector ).size() > 0 ) { // $("#vlistid li")
204 // first child's top position
205 // NOTE: the first element may not be '0'!!!
206 t._title_h = $( o.id + o.childSelector + ':first' ).position().top;
207 // first child's outer height (TODO: reuse selected items)
208 t._line_h = $( o.id + o.childSelector + ':first' ).outerHeight();
210 // container(vlist element)'s innerwidth
211 t._container_w = $( o.id ).innerWidth();
213 // get sum of container's left/right padding
214 padding = parseInt( $( o.id + o.childSelector ).css( "padding-left" ), 10 ) + parseInt( $( o.id + o.childSelector ).css( "padding-right" ), 10 );
216 // Add CSS to all <li> elements
217 // * absolute position
219 // * mouse up/down/over/out styles
220 $( o.id + ">" + o.childSelector )
221 .addClass( "position_absolute" )
222 .addClass( "ui-btn-up-s" )
223 .bind( "mouseup", t._stylerMouseUp )
224 .bind( "mousedown", t._stylerMouseDown )
225 .bind( "mouseover", t._stylerMouseOver )
226 .bind( "mouseout", t._stylerMouseOut );
229 // Set absolute top/left position of each <li>
230 $( o.id + ">" + o.childSelector ).each( function ( index ) {
231 $( this ).css( "top", t._title_h + t._line_h * index + 'px' )
232 .css( "width", t._container_w - padding );
235 // Set Max Listview Height
236 $( o.id ).height( t._numItemData * t._line_h );
239 _resize: function ( event ) {
250 t._container_w = $( o.id ).innerWidth();
252 padding = parseInt( $( o.id + o.childSelector ).css( "padding-left" ), 10 ) + parseInt( $( o.id + o.childSelector ).css( "padding-right" ), 10 );
254 $( o.id + o.childSelector ).each( function (index) {
255 $( this ).css( "width", t._container_w - padding );
259 // New scrollmove function supporting scrollTo
260 _scrollmove: function ( ev ) {
261 var t = ev.data, // vlist (JQM object)
262 o = t.options, // options
263 prevTopBufLen = t._num_top_items, // Previous(remembered) top buf length
270 viewTop: function ( ) {
271 var sv = $( o.id ).parentsUntil( ".ui-page" ).find( ".ui-scrollview-view" ),
272 svTrans = sv.css( "-webkit-transform" ),
273 svTransVal = "0,0,0,0,0,0";
275 svTransVal = svTrans.replace( /matrix\s*\((.*)\)/, "$1" ); // matrix(a,c,b,d,tx,ty)
277 return - parseInt( svTransVal.split(',')[5], 10 );
281 viewTop: function ( ) {
282 return $( window ).scrollTop( ); // TODO: - _line_h?
285 // Get current view top position
286 function viewTop ( ) {
287 return o.scrollview ? _scrollView.viewTop() : _normalScroll.viewTop();
289 // log function for debug
290 function log ( msg ) {
293 console.log( ">>virtualllist: " + msg );
297 // Timer interval function
298 // @param[in] vl virtuallist object (JQM object)
299 function timerMove ( vl, undefined ) {
300 var cy, // current y position
301 cti, // current top idx
302 cbi, // current bottom idx
303 oti = vl._first_index, // old top idx
304 obi = vl._last_index, // old botton idx
305 dti, // delta of top idx
307 toIdx, // index range to be moved
308 delta, // moveItem delta
309 rowLen = vl.options.row, // max. # of items handled at once
310 bufSize, // top/bottom buffer size. unit: # of items
313 // subroutine: Move itemContents in i2 into i1
314 function moveItemContents( vl, i1, i2 ) {
315 // TODO: Find a efficient way to replace data!
316 // Assumption: i1 and i2 has same children.
317 var NODETYPE = { ELEMENT_NODE: 1, TEXT_NODE: 3 },
318 c1, // child item 1 (old)
319 c2, // child item 2 (new)
324 $( i1 ).find( ".ui-li-text-main", ".ui-li-text-sub", ".ui-li-text-sub2", "ui-btn-text" ).each( function ( index ) {
326 newText = $( i2 ).find( ".ui-li-text-main", ".ui-li-text-sub", "ui-btn-text" ).eq( index ).text();
328 $( c1 ).contents().filter( function () {
329 return ( this.nodeType == NODETYPE.TEXT_NODE );
330 } ).get( 0 ).data = newText;
333 $( i1 ).find( "img" ).each( function ( imgIdx ) {
335 newImg = $( i2 ).find( "img" ).eq( imgIdx ).attr( "src" );
337 $( c1 ).attr( "src", newImg );
340 $( i1 ).removeData( ); // Clear old data
343 // subroutine: Move item
344 function moveItem( vl, fromIdx, toIdx ) {
345 var itemData, // data from itemData()
346 item, // item element
347 newItem, // new item element
350 log( ">> move item: " + fromIdx + " --> " + toIdx );
353 item = $( '#' + vl.options.itemIDPrefix + fromIdx ); // TODO: refactor ID generation!
359 tmpl = $( "#" + vl.options.template );
360 newItem = tmpl.tmpl( vl._itemData( toIdx ) );
362 // TODO: Consider touch block while moving?
364 // Move item contents
365 moveItemContents( vl, item, newItem );
367 // clean up temporary item
370 // Move position, and set id
371 item.css( 'top', toIdx * vl._line_h )
372 .attr( 'id' , vl.options.itemIDPrefix + toIdx ); // TODO: refactor ID generation!
374 // TODO: Apply jqmdata? check following old code;
375 // $( oldItem ).removeData( ); // Clear old data
376 // if (key) { $( oldItem ).data( key, $( newItem ).data( key ) ); }
382 // Get current view position
385 // Calculate bufSize: rowLen / 3
386 // NOTE: Assumption: total row length = visible items * 3 (upper+visible+lower)
387 bufSize = Math.ceil( rowLen / 3 );
389 // Calculate current top/bottom index (to be applied)
390 // top index = current position / line height
391 cti = Math.floor( cy / vl._line_h ) - bufSize; // TODO: consider buffer!
392 cbi = cti + rowLen - 1;
394 if ( cti < 0 ) { // Top boundary check
397 } else if ( cbi > ( vl._numItemData - 1 ) ) { // Bottom boundary check
398 cti -= ( cbi - ( vl._numItemData - 1 ) );
399 cbi = ( vl._numItemData - 1 );
404 log( "cy=" + cy + ", oti=" + oti + ", obi=" + obi + ", cti=" + cti + ", cbi=" + cbi + ", dti=" + dti );
406 // switch: dti = 0 --> timer stop condition: delta=0 or scrollstop event comes. END.
408 // Check timer runtime
409 vl.timerStillCount += 1;
410 if ( vl.timerStillCount < 12 ) { // check count ( TODO: test and adjust )
411 log("dti=0 " + vl.timerStillCount + " times");
412 vl.timerMoveID = setTimeout( timerMove, timerInterval, vl ); // run once more
416 log("dti=0 " + vl.timerStillCount + " times. End timer.");
417 vl.timerStillCount = 0;
419 if ( vl.timerMoveID ) {
420 clearTimeout( vl.timerMoveID );
421 vl.timerMoveID = null;
424 // switch: dti >= # of max elements --> total replace.
425 vl.timerStillCount = 0; // Reset still counter
427 if ( Math.abs( dti ) >= rowLen ) {
431 log( ">>> WHOLE CHANGE! delta=" + delta );
433 // switch: dti < # of max elements --> move t2b or b2t until new top/bottom idx is covered
436 toIdx = oti + dti - 1;
439 fromIdx = obi + dti + 1; // dti < 0
443 log( ">>> partial change. delta=" + delta );
447 for ( i = fromIdx; i <= toIdx; i++ ) {
448 moveItem( vl, i, i + delta ); // Change data and position
451 // Store current top/bottom idx into vl
452 vl._first_index = cti;
453 vl._last_index = cbi;
455 // Register timer to check again
456 vl.timerMoveID = setTimeout( timerMove, timerInterval, vl );
458 return; // End of function
461 // ==== function start ====
463 t.timerStillCount = 0; // Count do-nothing time. For behavior tuning.
465 // If a timer function is alive, clear it
466 if ( t.timerMoveID ) {
467 clearTimeout( t.timerMoveID );
468 t.timerMoveID = null;
474 _recreate: function ( newArray ) {
480 t._numItemData = newArray.length;
481 t._direction = _NO_SCROLL;
483 t._last_index = o.row - 1;
485 t._pushData( o.template );
487 if (o.childSelector == " ul" ) {
488 $( o.id + " ul" ).swipelist();
491 $( o.id ).virtuallistview();
498 // Init virtuallistview
499 // this virtuallistview object
500 _initList: function () {
504 /* After AJAX loading success */
506 // Put initial <li> elements
507 t._pushData( o.template );
509 // find a parent page, and run _reposition() at 'pageshow' event
510 // TODO: Consider replace parentsUntil().parent() to parent('.ui-page') ???
511 $( o.id ).parentsUntil( ".ui-page" ).parent().one( "pageshow", function () {
512 setTimeout( function () {
517 // Bind _scrollmove() at 'scrollstart.virtuallist' event
518 $( document ).bind( "scrollstart.virtuallist scrollstop.vrituallist", t, t._scrollmove );
520 // Bind _resize() at 'resize.virtuallist'
521 $( window ).bind( "resize.virtuallist", t._resize );
523 // when ul is a childselector, assume that this is also a swipelist,
524 // and run swipelist constructor
525 if ( o.childSelector == " ul" ) {
526 $( o.id + " ul" ).swipelist();
532 create: function () {
533 var o = this.options;
535 /* external API for AJAX callback */
536 this._create.apply( this, arguments );
538 // TODO: remove this line? _initList() calls reposition...
539 this._reposition( o );
542 _create: function ( args ) {
543 // Extend instance variables
545 _itemData : function ( idx ) { return null; },
547 _cacheItemData : function ( minIdx, maxIdx ) { },
551 _direction : _NO_SCROLL,
554 _num_top_items : 0 // By scroll move, number of hidden elements.
561 shortcutsContainer = $('<div class="ui-virtuallist"/>'),
562 shortcutsList = $('<ul></ul>'),
563 dividers = $el.find(':jqmData(role="virtuallistview" )'),
565 shortcutscroll = this,
570 // Add CSS classes to $el (=virtuallistview)
571 $el.addClass( function ( i, orig ) {
572 return orig + " ui-listview ui-virtual-list-container" + ( t.options.inset ? " ui-listview-inset ui-corner-all ui-shadow " : "" );
575 // keep the vlist's ID
576 o.itemIDPrefix = $el.attr( "id" ) + '_';
577 o.id = "#" + $el.attr( "id" );
579 // when page hides, empty all child elements
580 $( o.id ).bind( "pagehide", function ( e ) {
584 // Find if scrollview is used
585 if ( $( ".ui-scrollview-clip" ).size() > 0 ) {
588 o.scrollview = false;
591 // Calculate page buffer size
592 if ( $el.data( "row" ) ) {
593 o.row = $el.data( "row" );
595 if ( o.row < t._minimum_row ) {
596 o.row = t._minimum_row;
599 o.page_buf = parseInt( ( o.row / 2 ), 10 );
604 if ( args.itemData && typeof args.itemData == 'function' ) {
605 t._itemData = args.itemData;
609 if ( args.numItemData ) {
610 if ( typeof args.numItemData == 'function' ) {
611 t._numItemData = args.numItemData( );
612 } else if ( typeof args.numItemData == 'number' ) {
613 t._numItemData = args.numItemData;
620 } else { // No option is given
621 // Legacy support: dbtable
622 console.log("WARNING: The data interface of virtuallist is changed. \nOld data interface(data-dbtable) is still supported, but will be removed in next version. \nPlease fix your code soon!");
624 /* After DB Load complete, Init Vritual list */
625 if ( $( o.id ).hasClass( "vlLoadSuccess" ) ) {
626 dbtable_name = $el.jqmData('dbtable');
627 dbtable = window[ dbtable_name ];
635 t._itemData = function ( idx ) {
636 return dbtable[ idx ];
638 t._numItemData = dbtable.length;
640 return; // Do nothing
645 if ( $el.data( "template" ) ) {
646 o.template = $el.data( "template" );
648 /* to support swipe list, <li> or <ul> can be main node of virtual list. */
649 if ( $el.data( "swipelist" ) == true ) {
650 o.childSelector = " ul";
652 o.childSelector = " li";
656 // Set data's unique key
657 // NOTE: Unnecessary?
658 if ( $el.data( "dbkey" ) ) {
659 o.dbkey = $el.data( "dbkey" );
662 t._first_index = 0; // initial top idx of <li> element.
663 t._last_index = o.row - 1; // initial bottom idx of <li> element.
664 t._initList(); // NOTE: Called at here only!
667 destroy : function () {
668 var o = this.options;
670 $( document ).unbind( "scrollstop" );
672 $( window ).unbind( "resize.virtuallist" );
677 _itemApply: function ( $list, item ) {
678 var $countli = item.find( ".ui-li-count" );
680 if ( $countli.length ) {
681 item.addClass( "ui-li-has-count" );
684 $countli.addClass( "ui-btn-up-" + ( $list.jqmData( "counttheme" ) || this.options.countTheme ) + " ui-btn-corner-all" );
686 // TODO class has to be defined in markup
687 item.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" ).end()
688 .find( "p, dl" ).addClass( "ui-li-desc" ).end()
689 .find( ">img:eq(0), .ui-link-inherit>img:eq(0)" ).addClass( "ui-li-thumb" ).each( function () {
690 item.addClass( $( this ).is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" );
692 .find( ".ui-li-aside" ).each(function () {
693 var $this = $( this );
694 $this.prependTo( $this.parent() ); //shift aside to front for css float
698 _removeCorners: function ( li, which ) {
699 var top = "ui-corner-top ui-corner-tr ui-corner-tl",
700 bot = "ui-corner-bottom ui-corner-br ui-corner-bl";
702 li = li.add( li.find( ".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb" ) );
704 if ( which === "top" ) {
705 li.removeClass( top );
706 } else if ( which === "bottom" ) {
707 li.removeClass( bot );
709 li.removeClass( top + " " + bot );
713 _refreshCorners: function ( create ) {
719 if ( this.options.inset ) {
720 $li = this.element.children( "li" );
721 // at create time the li are not visible yet so we need to rely on .ui-screen-hidden
722 $visibleli = create ? $li.not( ".ui-screen-hidden" ) : $li.filter( ":visible" );
724 this._removeCorners( $li );
726 // Select the first visible li element
727 $topli = $visibleli.first()
728 .addClass( "ui-corner-top" );
730 $topli.add( $topli.find( ".ui-btn-inner" ) )
731 .find( ".ui-li-link-alt" )
732 .addClass( "ui-corner-tr" )
734 .find( ".ui-li-thumb" )
735 .not( ".ui-li-icon" )
736 .addClass( "ui-corner-tl" );
738 // Select the last visible li element
739 $bottomli = $visibleli.last()
740 .addClass( "ui-corner-bottom" );
742 $bottomli.add( $bottomli.find( ".ui-btn-inner" ) )
743 .find( ".ui-li-link-alt" )
744 .addClass( "ui-corner-br" )
746 .find( ".ui-li-thumb" )
747 .not( ".ui-li-icon" )
748 .addClass( "ui-corner-bl" );
752 // this virtuallistview object
753 refresh: function ( create ) {
754 this.parentPage = this.element.closest( ".ui-page" );
755 // Make sub page, and move the virtuallist into it...
756 // NOTE: check this subroutine.
757 this._createSubPages();
759 var o = this.options,
760 $list = this.element,
762 dividertheme = $list.jqmData( "dividertheme" ) || o.dividerTheme,
763 listsplittheme = $list.jqmData( "splittheme" ),
764 listspliticon = $list.jqmData( "spliticon" ),
765 li = $list.children( "li" ),
766 counter = $.support.cssPseudoElement || !$.nodeName( $list[ 0 ], "ol" ) ? 0 : 1,
781 $list.find( ".ui-li-dec" ).remove();
784 for ( pos = 0, numli = li.length; pos < numli; pos++ ) {
788 // If we're creating the element, we update it regardless
789 if ( create || !item.hasClass( "ui-li" ) ) {
790 itemTheme = item.jqmData( "theme" ) || o.theme;
791 a = item.children( "a" );
794 icon = item.jqmData( "icon" );
801 /* icon: a.length > 1 || icon === false ? false : icon || "arrow-r",*/
802 icon: false, /* Remove unnecessary arrow icon */
806 if ( ( icon != false ) && ( a.length == 1 ) ) {
807 item.addClass( "ui-li-has-arrow" );
810 a.first().addClass( "ui-link-inherit" );
812 if ( a.length > 1 ) {
813 itemClass += " ui-li-has-alt";
816 splittheme = listsplittheme || last.jqmData( "theme" ) || o.splitTheme;
819 .attr( "title", last.getEncodedText() )
820 .addClass( "ui-li-link-alt" )
829 .find( ".ui-btn-inner" )
831 $( "<span />" ).buttonMarkup({
836 icon: listspliticon || last.jqmData( "icon" ) || o.splitIcon
840 } else if ( item.jqmData( "role" ) === "list-divider" ) {
842 itemClass += " ui-li-divider ui-btn ui-bar-" + dividertheme;
843 item.attr( "role", "heading" );
845 //reset counter when a divider heading is encountered
851 itemClass += " ui-li-static ui-body-" + itemTheme;
855 if ( counter && itemClass.indexOf( "ui-li-divider" ) < 0 ) {
856 countParent = item.is( ".ui-li-static:first" ) ? item : item.find( ".ui-link-inherit" );
858 countParent.addClass( "ui-li-jsnumbering" )
859 .prepend( "<span class='ui-li-dec'>" + (counter++) + ". </span>" );
862 item.add( item.children( ".ui-btn-inner" ) ).addClass( itemClass );
864 self._itemApply( $list, item );
867 this._refreshCorners( create );
870 //create a string for ID/subpage url creation
871 _idStringEscape: function ( str ) {
872 return str.replace(/\W/g , "-");
876 // this virtuallistview object
877 _createSubPages: function () {
878 var parentList = this.element,
879 parentPage = parentList.closest( ".ui-page" ),
880 parentUrl = parentPage.jqmData( "url" ),
881 parentId = parentUrl || parentPage[ 0 ][ $.expando ],
882 parentListId = parentList.attr( "id" ),
884 dns = "data-" + $.mobile.ns,
886 persistentFooterID = parentPage.find( ":jqmData(role='footer')" ).jqmData( "id" ),
890 if ( typeof listCountPerPage[ parentId ] === "undefined" ) {
891 listCountPerPage[ parentId ] = -1;
894 parentListId = parentListId || ++listCountPerPage[ parentId ];
896 $( parentList.find( "li>ul, li>ol" ).toArray().reverse() ).each(function ( i ) {
899 listId = list.attr( "id" ) || parentListId + "-" + i,
900 parent = list.parent(),
902 title = nodeEls.first().getEncodedText(),//url limits to first 30 chars of text
903 id = ( parentUrl || "" ) + "&" + $.mobile.subPageUrlKey + "=" + listId,
904 theme = list.jqmData( "theme" ) || o.theme,
905 countTheme = list.jqmData( "counttheme" ) || parentList.jqmData( "counttheme" ) || o.countTheme,
909 nodeEls = $( list.prevAll().toArray().reverse() );
910 nodeEls = nodeEls.length ? nodeEls : $( "<span>" + $.trim( parent.contents()[ 0 ].nodeValue ) + "</span>" );
912 //define hasSubPages for use in later removal
915 newPage = list.detach()
916 .wrap( "<div " + dns + "role='page' " + dns + "url='" + id + "' " + dns + "theme='" + theme + "' " + dns + "count-theme='" + countTheme + "'><div " + dns + "role='content'></div></div>" )
918 .before( "<div " + dns + "role='header' " + dns + "theme='" + o.headerTheme + "'><div class='ui-title'>" + title + "</div></div>" )
919 .after( persistentFooterID ? $( "<div " + dns + "role='footer' " + dns + "id='" + persistentFooterID + "'>" ) : "" )
921 .appendTo( $.mobile.pageContainer );
925 anchor = parent.find('a:first');
927 if ( !anchor.length ) {
928 anchor = $( "<a/>" ).html( nodeEls || title ).prependTo( parent.empty() );
931 anchor.attr( "href", "#" + id );
933 }).virtuallistview();
935 // on pagehide, remove any nested pages along with the parent page, as long as they aren't active
936 // and aren't embedded
938 parentPage.is( ":jqmData(external-page='true')" ) &&
939 parentPage.data( "page" ).options.domCache === false ) {
941 newRemove = function ( e, ui ) {
942 var nextPage = ui.nextPage, npURL;
945 npURL = nextPage.jqmData( "url" );
946 if ( npURL.indexOf( parentUrl + "&" + $.mobile.subPageUrlKey ) !== 0 ) {
947 self.childPages().remove();
953 // unbind the original page remove and replace with our specialized version
955 .unbind( "pagehide.remove" )
956 .bind( "pagehide.remove", newRemove );
960 // TODO sort out a better way to track sub pages of the virtuallistview this is brittle
961 childPages: function () {
962 var parentUrl = this.parentPage.jqmData( "url" );
964 return $( ":jqmData(url^='" + parentUrl + "&" + $.mobile.subPageUrlKey + "')" );
968 //auto self-init widgets
969 $( document ).bind( "pagecreate create", function ( e ) {
970 $( $.tizen.virtuallistview.prototype.options.initSelector, e.target ).virtuallistview();