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
268 viewTop: function ( ) {
269 var sv = $( o.id ).parentsUntil( ".ui-page" ).find( ".ui-scrollview-view" ),
270 svTrans = sv.css( "-webkit-transform" ),
271 svTransVal = "0,0,0,0,0,0";
273 svTransVal = svTrans.replace( /matrix\s*\((.*)\)/, "$1" ); // matrix(a,c,b,d,tx,ty)
275 return - parseInt( svTransVal.split(',')[5] );
279 viewTop: function ( ) {
280 return $( window ).scrollTop( ); // TODO: - _line_h?
283 // Get current view top position
284 function viewTop ( ) {
285 return o.scrollview ? _scrollView.viewTop() : _normalScroll.viewTop();
287 // log function for debug
288 function log ( msg ) {
291 console.log( ">>virtualllist: " + msg );
295 // Timer interval function
296 // @param[in] vl virtuallist object (JQM object)
297 function timerMove ( vl, undefined ) {
298 var cy, // current y position
299 cti, cbi, // current top/bottom idx
300 oti = vl._first_index, obi = vl._last_index, // old top/botton idx
301 dti, // delta of top idx
302 fromIdx, toIdx, // index range to be moved
303 delta, // moveItem delta
304 rowLen = vl.options.row, // max. # of items handled at once
305 bufSize, // top/bottom buffer size. unit: # of items
308 // Get current view position
311 // Calculate bufSize: rowLen / 3
312 // NOTE: Assumption: total row length = visible items * 3 (upper+visible+lower)
313 bufSize = Math.ceil( rowLen / 3 );
315 // Calculate current top/bottom index (to be applied)
316 // top index = current position / line height
317 cti = Math.floor( cy / vl._line_h ) - bufSize; // TODO: consider buffer!
318 cbi = cti + rowLen - 1;
320 if ( cti < 0 ) { // Top boundary check
323 } else if ( cbi > ( vl._numItemData - 1 ) ) { // Bottom boundary check
324 cti -= ( cbi - ( vl._numItemData - 1 ) );
325 cbi = ( vl._numItemData - 1 );
330 log("cy="+cy+", oti="+oti+", obi="+obi+", cti="+cti+", cbi="+cbi+", dti="+dti);
332 // switch: dti = 0 --> timer stop condition: delta=0 or scrollstop event comes. END.
334 // Check timer runtime
335 vl.timerStillCount += 1;
336 if ( vl.timerStillCount < 12 ) { // check count ( TODO: test and adjust )
337 log("dti=0 " + vl.timerStillCount + " times");
338 vl.timerMoveID = setTimeout( timerMove, timerInterval, vl ); // run once more
342 log("dti=0 " + vl.timerStillCount + " times. End timer.");
343 vl.timerStillCount = 0;
345 if ( vl.timerMoveID ) {
346 clearTimeout( vl.timerMoveID );
347 vl.timerMoveID = null;
349 return; // End timerMove()
352 // switch: dti >= # of max elements --> total replace.
354 vl.timerStillCount = 0; // Reset still counter
356 if ( Math.abs( dti ) >= rowLen ) {
360 log(">>> WHOLE CHANGE! delta="+delta);
363 // switch: dti < # of max elements --> move t2b or b2t until new top/bottom idx is covered
367 toIdx = oti + dti - 1;
370 fromIdx = obi + dti + 1; // dti < 0
374 log(">>> partial change. delta="+delta);
378 for( i = fromIdx; i <= toIdx; i++ ) {
379 moveItem( vl, i, i + delta ); // Change data and position
382 // Store current top/bottom idx into vl
383 vl._first_index = cti;
384 vl._last_index = cbi;
386 // Register timer to check again
387 vl.timerMoveID = setTimeout( timerMove, timerInterval, vl );
389 return; // End of function
391 // Move itemContents in i2 into i1
392 function moveItemContents( vl, i1, i2 ) {
393 // TODO: Find a efficient way to replace data!
394 // Assumption: i1 and i2 has same children.
395 var NODETYPE={ ELEMENT_NODE:1, TEXT_NODE:3 },
396 c1, c2, // child item
400 $( i1 ).find( ".ui-li-text-main", ".ui-li-text-sub", ".ui-li-text-sub2", "ui-btn-text" ).each( function ( index ) {
402 newText = $( i2 ).find( ".ui-li-text-main", ".ui-li-text-sub", "ui-btn-text" ).eq( index ).text();
404 $( c1 ).contents().filter( function () {
405 return ( this.nodeType == NODETYPE.TEXT_NODE );
406 } ).get( 0 ).data = newText;
409 $( i1 ).find( "img" ).each( function ( imgIdx ) {
411 newImg = $( i2 ).find( "img" ).eq( imgIdx ).attr( "src" );
413 $( c1 ).attr( "src", newImg );
416 $( i1 ).removeData( ); // Clear old data
420 function moveItem( vl, fromIdx, toIdx ) {
425 log(">> move item: "+fromIdx + " --> "+toIdx);
428 item = $( '#' + vl.options.itemIDPrefix + fromIdx ); // TODO: refactor ID generation!
434 tmpl = $( "#" + vl.options.template );
435 newItem = tmpl.tmpl( vl._itemData( toIdx ) );
437 // TODO: Consider touch block while moving?
439 // Move item contents
440 moveItemContents( vl, item, newItem );
442 // clean up temporary item
445 // Move position, and set id
446 item.css( 'top', toIdx * vl._line_h )
447 .attr( 'id' , vl.options.itemIDPrefix + toIdx ); // TODO: refactor ID generation!
449 // TODO: Apply jqmdata? check following old code;
450 // $( oldItem ).removeData( ); // Clear old data
451 // if (key) { $( oldItem ).data( key, $( newItem ).data( key ) ); }
457 // ==== function start ====
459 t.timerStillCount = 0; // Count do-nothing time. For behavior tuning.
461 // If a timer function is alive, clear it
462 if ( t.timerMoveID ) {
463 clearTimeout( t.timerMoveID );
464 t.timerMoveID = null;
470 _recreate: function ( newArray ) {
476 t._numItemData = newArray.length;
477 t._direction = _NO_SCROLL;
479 t._last_index = o.row - 1;
481 t._pushData( o.template );
483 if (o.childSelector == " ul" ) {
484 $( o.id + " ul" ).swipelist();
487 $( o.id ).virtuallistview();
494 // Init virtuallistview
495 // this virtuallistview object
496 _initList: function () {
500 /* After AJAX loading success */
502 // Put initial <li> elements
503 t._pushData( o.template );
505 // find a parent page, and run _reposition() at 'pageshow' event
506 // TODO: Consider replace parentsUntil().parent() to parent('.ui-page') ???
507 $( o.id ).parentsUntil( ".ui-page" ).parent().one( "pageshow", function () {
508 setTimeout( function () {
513 // Bind _scrollmove() at 'scrollstart.virtuallist' event
514 $( document ).bind( "scrollstart.virtuallist scrollstop.vrituallist", t, t._scrollmove );
516 // Bind _resize() at 'resize.virtuallist'
517 $( window ).bind( "resize.virtuallist", t._resize );
519 // when ul is a childselector, assume that this is also a swipelist,
520 // and run swipelist constructor
521 if ( o.childSelector == " ul" ) {
522 $( o.id + " ul" ).swipelist();
528 create: function () {
529 var o = this.options;
531 /* external API for AJAX callback */
532 this._create.apply( this, arguments );
534 // TODO: remove this line? _initList() calls reposition...
535 this._reposition( o );
538 _create: function ( args ) {
539 // Extend instance variables
541 _itemData : function ( idx ) { return null; },
543 _cacheItemData : function ( minIdx, maxIdx ) { },
547 _direction : _NO_SCROLL,
550 _num_top_items : 0 // By scroll move, number of hidden elements.
557 shortcutsContainer = $('<div class="ui-virtuallist"/>'),
558 shortcutsList = $('<ul></ul>'),
559 dividers = $el.find(':jqmData(role="virtuallistview" )'),
561 shortcutscroll = this,
566 // Add CSS classes to $el (=virtuallistview)
567 $el.addClass( function ( i, orig ) {
568 return orig + " ui-listview ui-virtual-list-container" + ( t.options.inset ? " ui-listview-inset ui-corner-all ui-shadow " : "" );
571 // keep the vlist's ID
572 o.itemIDPrefix = $el.attr( "id" ) + '_';
573 o.id = "#" + $el.attr( "id" );
575 // when page hides, empty all child elements
576 $( o.id ).bind( "pagehide", function ( e ) {
580 // Find if scrollview is used
581 if ( $( ".ui-scrollview-clip" ).size() > 0 ) {
584 o.scrollview = false;
587 // Calculate page buffer size
588 if ( $el.data( "row" ) ) {
589 o.row = $el.data( "row" );
591 if ( o.row < t._minimum_row ) {
592 o.row = t._minimum_row;
595 o.page_buf = parseInt( ( o.row / 2 ), 10 );
600 if ( args.itemData && typeof args.itemData == 'function' ) {
601 t._itemData = args.itemData;
605 if ( args.numItemData ) {
606 if ( typeof args.numItemData == 'function' ) {
607 t._numItemData = args.numItemData( );
608 } else if ( typeof args.numItemData == 'number' ) {
609 t._numItemData = args.numItemData;
616 } else { // No option is given
617 // Legacy support: dbtable
618 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!");
620 /* After DB Load complete, Init Vritual list */
621 if ( $( o.id ).hasClass( "vlLoadSuccess" ) ) {
622 dbtable_name = $el.jqmData('dbtable');
623 dbtable = window[ dbtable_name ];
631 t._itemData = function ( idx ) {
632 return dbtable[ idx ];
634 t._numItemData = dbtable.length;
636 return; // Do nothing
641 if ( $el.data( "template" ) ) {
642 o.template = $el.data( "template" );
644 /* to support swipe list, <li> or <ul> can be main node of virtual list. */
645 if ( $el.data( "swipelist" ) == true ) {
646 o.childSelector = " ul";
648 o.childSelector = " li";
652 // Set data's unique key
653 // NOTE: Unnecessary?
654 if ( $el.data( "dbkey" ) ) {
655 o.dbkey = $el.data( "dbkey" );
658 t._first_index = 0; // initial top idx of <li> element.
659 t._last_index = o.row - 1; // initial bottom idx of <li> element.
660 t._initList(); // NOTE: Called at here only!
663 destroy : function () {
664 var o = this.options;
666 $( document ).unbind( "scrollstop" );
668 $( window ).unbind( "resize.virtuallist" );
673 _itemApply: function ( $list, item ) {
674 var $countli = item.find( ".ui-li-count" );
676 if ( $countli.length ) {
677 item.addClass( "ui-li-has-count" );
680 $countli.addClass( "ui-btn-up-" + ( $list.jqmData( "counttheme" ) || this.options.countTheme ) + " ui-btn-corner-all" );
682 // TODO class has to be defined in markup
683 item.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" ).end()
684 .find( "p, dl" ).addClass( "ui-li-desc" ).end()
685 .find( ">img:eq(0), .ui-link-inherit>img:eq(0)" ).addClass( "ui-li-thumb" ).each( function () {
686 item.addClass( $( this ).is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" );
688 .find( ".ui-li-aside" ).each(function () {
689 var $this = $( this );
690 $this.prependTo( $this.parent() ); //shift aside to front for css float
694 _removeCorners: function ( li, which ) {
695 var top = "ui-corner-top ui-corner-tr ui-corner-tl",
696 bot = "ui-corner-bottom ui-corner-br ui-corner-bl";
698 li = li.add( li.find( ".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb" ) );
700 if ( which === "top" ) {
701 li.removeClass( top );
702 } else if ( which === "bottom" ) {
703 li.removeClass( bot );
705 li.removeClass( top + " " + bot );
709 _refreshCorners: function ( create ) {
715 if ( this.options.inset ) {
716 $li = this.element.children( "li" );
717 // at create time the li are not visible yet so we need to rely on .ui-screen-hidden
718 $visibleli = create ? $li.not( ".ui-screen-hidden" ) : $li.filter( ":visible" );
720 this._removeCorners( $li );
722 // Select the first visible li element
723 $topli = $visibleli.first()
724 .addClass( "ui-corner-top" );
726 $topli.add( $topli.find( ".ui-btn-inner" ) )
727 .find( ".ui-li-link-alt" )
728 .addClass( "ui-corner-tr" )
730 .find( ".ui-li-thumb" )
731 .not( ".ui-li-icon" )
732 .addClass( "ui-corner-tl" );
734 // Select the last visible li element
735 $bottomli = $visibleli.last()
736 .addClass( "ui-corner-bottom" );
738 $bottomli.add( $bottomli.find( ".ui-btn-inner" ) )
739 .find( ".ui-li-link-alt" )
740 .addClass( "ui-corner-br" )
742 .find( ".ui-li-thumb" )
743 .not( ".ui-li-icon" )
744 .addClass( "ui-corner-bl" );
748 // this virtuallistview object
749 refresh: function ( create ) {
750 this.parentPage = this.element.closest( ".ui-page" );
751 // Make sub page, and move the virtuallist into it...
752 // NOTE: check this subroutine.
753 this._createSubPages();
755 var o = this.options,
756 $list = this.element,
758 dividertheme = $list.jqmData( "dividertheme" ) || o.dividerTheme,
759 listsplittheme = $list.jqmData( "splittheme" ),
760 listspliticon = $list.jqmData( "spliticon" ),
761 li = $list.children( "li" ),
762 counter = $.support.cssPseudoElement || !$.nodeName( $list[ 0 ], "ol" ) ? 0 : 1,
777 $list.find( ".ui-li-dec" ).remove();
780 for ( pos = 0, numli = li.length; pos < numli; pos++ ) {
784 // If we're creating the element, we update it regardless
785 if ( create || !item.hasClass( "ui-li" ) ) {
786 itemTheme = item.jqmData( "theme" ) || o.theme;
787 a = item.children( "a" );
790 icon = item.jqmData( "icon" );
797 /* icon: a.length > 1 || icon === false ? false : icon || "arrow-r",*/
798 icon: false, /* Remove unnecessary arrow icon */
802 if ( ( icon != false ) && ( a.length == 1 ) ) {
803 item.addClass( "ui-li-has-arrow" );
806 a.first().addClass( "ui-link-inherit" );
808 if ( a.length > 1 ) {
809 itemClass += " ui-li-has-alt";
812 splittheme = listsplittheme || last.jqmData( "theme" ) || o.splitTheme;
815 .attr( "title", last.getEncodedText() )
816 .addClass( "ui-li-link-alt" )
825 .find( ".ui-btn-inner" )
827 $( "<span />" ).buttonMarkup({
832 icon: listspliticon || last.jqmData( "icon" ) || o.splitIcon
836 } else if ( item.jqmData( "role" ) === "list-divider" ) {
838 itemClass += " ui-li-divider ui-btn ui-bar-" + dividertheme;
839 item.attr( "role", "heading" );
841 //reset counter when a divider heading is encountered
847 itemClass += " ui-li-static ui-body-" + itemTheme;
851 if ( counter && itemClass.indexOf( "ui-li-divider" ) < 0 ) {
852 countParent = item.is( ".ui-li-static:first" ) ? item : item.find( ".ui-link-inherit" );
854 countParent.addClass( "ui-li-jsnumbering" )
855 .prepend( "<span class='ui-li-dec'>" + (counter++) + ". </span>" );
858 item.add( item.children( ".ui-btn-inner" ) ).addClass( itemClass );
860 self._itemApply( $list, item );
863 this._refreshCorners( create );
866 //create a string for ID/subpage url creation
867 _idStringEscape: function ( str ) {
868 return str.replace(/\W/g , "-");
872 // this virtuallistview object
873 _createSubPages: function () {
874 var parentList = this.element,
875 parentPage = parentList.closest( ".ui-page" ),
876 parentUrl = parentPage.jqmData( "url" ),
877 parentId = parentUrl || parentPage[ 0 ][ $.expando ],
878 parentListId = parentList.attr( "id" ),
880 dns = "data-" + $.mobile.ns,
882 persistentFooterID = parentPage.find( ":jqmData(role='footer')" ).jqmData( "id" ),
886 if ( typeof listCountPerPage[ parentId ] === "undefined" ) {
887 listCountPerPage[ parentId ] = -1;
890 parentListId = parentListId || ++listCountPerPage[ parentId ];
892 $( parentList.find( "li>ul, li>ol" ).toArray().reverse() ).each(function ( i ) {
895 listId = list.attr( "id" ) || parentListId + "-" + i,
896 parent = list.parent(),
898 title = nodeEls.first().getEncodedText(),//url limits to first 30 chars of text
899 id = ( parentUrl || "" ) + "&" + $.mobile.subPageUrlKey + "=" + listId,
900 theme = list.jqmData( "theme" ) || o.theme,
901 countTheme = list.jqmData( "counttheme" ) || parentList.jqmData( "counttheme" ) || o.countTheme,
905 nodeEls = $( list.prevAll().toArray().reverse() );
906 nodeEls = nodeEls.length ? nodeEls : $( "<span>" + $.trim( parent.contents()[ 0 ].nodeValue ) + "</span>" );
908 //define hasSubPages for use in later removal
911 newPage = list.detach()
912 .wrap( "<div " + dns + "role='page' " + dns + "url='" + id + "' " + dns + "theme='" + theme + "' " + dns + "count-theme='" + countTheme + "'><div " + dns + "role='content'></div></div>" )
914 .before( "<div " + dns + "role='header' " + dns + "theme='" + o.headerTheme + "'><div class='ui-title'>" + title + "</div></div>" )
915 .after( persistentFooterID ? $( "<div " + dns + "role='footer' " + dns + "id='" + persistentFooterID + "'>" ) : "" )
917 .appendTo( $.mobile.pageContainer );
921 anchor = parent.find('a:first');
923 if ( !anchor.length ) {
924 anchor = $( "<a/>" ).html( nodeEls || title ).prependTo( parent.empty() );
927 anchor.attr( "href", "#" + id );
929 }).virtuallistview();
931 // on pagehide, remove any nested pages along with the parent page, as long as they aren't active
932 // and aren't embedded
934 parentPage.is( ":jqmData(external-page='true')" ) &&
935 parentPage.data( "page" ).options.domCache === false ) {
937 newRemove = function ( e, ui ) {
938 var nextPage = ui.nextPage, npURL;
941 npURL = nextPage.jqmData( "url" );
942 if ( npURL.indexOf( parentUrl + "&" + $.mobile.subPageUrlKey ) !== 0 ) {
943 self.childPages().remove();
949 // unbind the original page remove and replace with our specialized version
951 .unbind( "pagehide.remove" )
952 .bind( "pagehide.remove", newRemove );
956 // TODO sort out a better way to track sub pages of the virtuallistview this is brittle
957 childPages: function () {
958 var parentUrl = this.parentPage.jqmData( "url" );
960 return $( ":jqmData(url^='" + parentUrl + "&" + $.mobile.subPageUrlKey + "')" );
964 //auto self-init widgets
965 $( document ).bind( "pagecreate create", function ( e ) {
966 $( $.tizen.virtuallistview.prototype.options.initSelector, e.target ).virtuallistview();