virtuallist: fix jslint errors
[platform/framework/web/web-ui-fw.git] / src / widgets / virtuallist / js / jquery.mobile.tizen.virtuallistview.js
1 /* ***************************************************************************
2  * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
3  *
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:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
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  * ***************************************************************************
22  *
23  *      Author: Wongi Lee <wongi11.lee@samsung.com>
24  *              Youmin Ha <youmin.ha@samsung.com>
25  */
26
27 /**
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.
32  * 
33  * HTML Attributes:
34  *
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. 
38  *              
39  *              ID : <UL> element that has "data-role=virtuallist" must have ID attribute.
40  *
41  * * APIs:
42  *
43  *              create ( {
44  *                              itemData: function ( idx ) { return json_obj; },
45  *                              numItemData: number or function () { return number; },
46  *                              cacheItemData: function ( minIdx, maxIdx ) {}
47  *                              } )
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.
54  *                                             Optional.
55  *
56  * Events:
57  *
58  *              touchstart : Temporary preventDefault applied on touchstart event to avoid broken screen.
59  *
60  * Examples:
61  *
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>
68  *                      </li>
69  *              </script>
70  *
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">
72  *              </ul>
73  *
74  */
75
76 /**
77         @class VirtualList
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.
79
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.
81
82         To add a virtual list widget to the application, use the following code:
83
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>
90                         </li>
91                 </script>
92                 <ul id="vlist" data-role="virtuallistview" data-template="tmp-3-2-7" data-dbtable="JSON_DATA" data-row="100"></ul>
93 */
94 /**
95         @property {String} data-role
96         Creates the virtual list view. The value must be set to virtuallistview.
97         Only the &gt;ul&lt; element, which a id attribute defined, supports this option. Also, the vlLoadSuccess class attribute must be defined in the &gt;ul&lt; element to ensure that loading data from the database is complete.
98 */
99 /**
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.
103 */
104 /**
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.
108 */
109 /**
110         @method create
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.
117 */
118
119 (function ( $, undefined ) {
120
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 */
126
127         $.widget( "tizen.virtuallistview", $.mobile.widget, {
128                 options: {
129                         theme: "s",
130                         countTheme: "s",
131                         headerTheme: "s",
132                         dividerTheme: "s",
133                         splitIcon: "arrow-r",
134                         splitTheme: "s",
135                         inset: false,
136                         id:     "",                                     /* Virtual list UL elemet's ID */
137                         childSelector: " li",   /* To support swipe list */
138                         dbtable: "",
139                         template : "",
140                         dbkey: false,                   /* Data's unique Key */
141                         scrollview: false,
142                         row: 100,
143                         page_buf: 30,
144                         initSelector: ":jqmData(role='virtuallistview')"
145                 },
146
147                 _stylerMouseUp: function () {
148                         $( this ).addClass( "ui-btn-up-s" );
149                         $( this ).removeClass( "ui-btn-down-s" );
150                 },
151
152                 _stylerMouseDown: function () {
153                         $( this ).addClass( "ui-btn-down-s" );
154                         $( this ).removeClass( "ui-btn-up-s" );
155                 },
156
157                 _stylerMouseOver: function () {
158                         $( this ).toggleClass( "ui-btn-hover-s" );
159                 },
160
161                 _stylerMouseOut: function () {
162                         $( this ).toggleClass( "ui-btn-hover-s" );
163                         $( this ).addClass( "ui-btn-up-s" );
164                         $( this ).removeClass( "ui-btn-down-s" );
165                 },
166
167                 // ?
168                 // this         virtuallistview object
169                 // @param[in]   template        template name(string)
170                 _pushData: function ( template ) {
171                         var o = this.options,
172                                 i,
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
176                                 htmlData;
177
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
181                         }
182
183                         // After pushing data re-style virtuallist widget
184                         $( o.id ).trigger( "create" );
185                 },
186
187                 // Set children <li> elements' position
188                 //
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 ) {
194                         var o,
195                                 t = this,
196                                 padding;
197
198                         if ( event.data ) {
199                                 o = event.data;
200                         } else {
201                                 o = event;
202                         }
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();
209
210                                 // container(vlist element)'s innerwidth
211                                 t._container_w = $( o.id ).innerWidth();
212
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 );
215
216                                 // Add CSS to all <li> elements
217                                 //      * absolute position
218                                 //      * btn-up
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 );
227                         }
228
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 );
233                         } );
234
235                         // Set Max Listview Height
236                         $( o.id ).height( t._numItemData * t._line_h );
237                 },
238
239                 _resize: function ( event ) {
240                         var o,
241                                 t = this,
242                                 padding;
243
244                         if ( event.data ) {
245                                 o = event.data;
246                         } else {
247                                 o = event;
248                         }
249
250                         t._container_w = $( o.id ).innerWidth();
251
252                         padding = parseInt( $( o.id + o.childSelector ).css( "padding-left" ), 10 ) + parseInt( $( o.id + o.childSelector ).css( "padding-right" ), 10 );
253
254                         $( o.id + o.childSelector ).each( function (index) {
255                                 $( this ).css( "width", t._container_w - padding );
256                         } );
257                 },
258
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
264                                 timerInterval = 100,
265                                 i,
266                                 _scrollView,
267                                 _normalScroll;
268
269                         _scrollView = {
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";
274                                         if ( svTrans ) {
275                                                 svTransVal = svTrans.replace( /matrix\s*\((.*)\)/, "$1" );      // matrix(a,c,b,d,tx,ty)
276                                         }
277                                         return - parseInt( svTransVal.split(',')[5], 10 );
278                                 }
279                         };
280                         _normalScroll = {
281                                 viewTop: function ( ) {
282                                         return $( window ).scrollTop( );        // TODO: - _line_h?
283                                 }
284                         };
285                         // Get current view top position
286                         function viewTop ( ) {
287                                 return o.scrollview ? _scrollView.viewTop() : _normalScroll.viewTop();
288                         }
289                         // log function for debug
290                         function log ( msg ) {
291                                 var debug = false;
292                                 if ( debug ) {
293                                         console.log( ">>virtualllist: " + msg );
294                                 }
295                         }
296
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
306                                         fromIdx,
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
311                                         i;
312
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)
320                                                 newText,
321                                                 newImg,
322                                                 i;
323
324                                         $( i1 ).find( ".ui-li-text-main", ".ui-li-text-sub", ".ui-li-text-sub2", "ui-btn-text" ).each( function ( index ) {
325                                                 c1 = $( this );
326                                                 newText = $( i2 ).find( ".ui-li-text-main", ".ui-li-text-sub", "ui-btn-text" ).eq( index ).text();
327
328                                                 $( c1 ).contents().filter( function () {
329                                                         return ( this.nodeType == NODETYPE.TEXT_NODE );
330                                                 } ).get( 0 ).data = newText;
331                                         } );
332
333                                         $( i1 ).find( "img" ).each( function ( imgIdx ) {
334                                                 var c1 = $( this );
335                                                 newImg = $( i2 ).find( "img" ).eq( imgIdx ).attr( "src" );
336
337                                                 $( c1 ).attr( "src", newImg );
338                                         } );
339
340                                         $( i1 ).removeData( );  // Clear old data
341                                 }
342
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
348                                                 tmpl;           // template
349
350                                         log( ">> move item: " + fromIdx + " --> " + toIdx );
351
352                                         // Find current item
353                                         item = $( '#' + vl.options.itemIDPrefix + fromIdx );    // TODO: refactor ID generation!
354                                         if ( ! item ) {
355                                                 return false;
356                                         }
357
358                                         // Get new item
359                                         tmpl = $( "#" + vl.options.template );
360                                         newItem = tmpl.tmpl( vl._itemData( toIdx ) );
361
362                                         // TODO: Consider touch block while moving?
363
364                                         // Move item contents
365                                         moveItemContents( vl, item, newItem );
366
367                                         // clean up temporary item
368                                         newItem.remove();
369
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!
373
374                                         // TODO: Apply jqmdata? check following old code;
375                                         // $( oldItem ).removeData( );  // Clear old data
376                                         // if (key) { $( oldItem ).data( key, $( newItem ).data( key ) ); }
377
378                                         return true;
379                                 }
380
381
382                                 // Get current view position
383                                 cy = viewTop();
384
385                                 // Calculate bufSize: rowLen / 3
386                                 // NOTE: Assumption: total row length = visible items * 3 (upper+visible+lower)
387                                 bufSize = Math.ceil( rowLen / 3 );
388
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;
393
394                                 if ( cti < 0 ) {                // Top boundary check
395                                         cbi += ( - cti );
396                                         cti = 0;
397                                 } else if ( cbi > ( vl._numItemData - 1 ) ) {           // Bottom boundary check
398                                         cti -= ( cbi - ( vl._numItemData - 1 ) );
399                                         cbi = ( vl._numItemData - 1 );
400                                 }
401
402                                 // Calculate dti
403                                 dti = cti - oti;
404                                 log( "cy=" + cy + ", oti=" + oti + ", obi=" + obi + ", cti=" + cti + ", cbi=" + cbi + ", dti=" + dti );
405
406                                 // switch: dti = 0 --> timer stop condition: delta=0 or scrollstop event comes. END.
407                                 if ( 0 == dti ) {
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
413                                                 return;
414                                         }
415
416                                         log("dti=0 " + vl.timerStillCount + " times. End timer.");
417                                         vl.timerStillCount = 0;
418                                         // Stop timer
419                                         if ( vl.timerMoveID ) {
420                                                 clearTimeout( vl.timerMoveID );
421                                                 vl.timerMoveID = null;
422                                         }
423                                 } else {
424                                         // switch: dti >= # of max elements --> total replace.
425                                         vl.timerStillCount = 0;         // Reset still counter
426
427                                         if ( Math.abs( dti ) >= rowLen ) {
428                                                 fromIdx = oti;
429                                                 toIdx = obi;
430                                                 delta = dti;
431                                                 log( ">>> WHOLE CHANGE! delta=" + delta );
432                                         } else {
433                                                 // switch: dti < # of max elements --> move t2b or b2t until new top/bottom idx is covered
434                                                 if ( dti > 0 ) {
435                                                         fromIdx = oti;
436                                                         toIdx = oti + dti - 1;
437                                                         delta = rowLen;
438                                                 } else {
439                                                         fromIdx = obi + dti + 1;        // dti < 0
440                                                         toIdx = obi;
441                                                         delta = -rowLen;
442                                                 }
443                                                 log( ">>> partial change. delta=" + delta );
444                                         }
445
446                                         // Move items
447                                         for ( i = fromIdx; i <= toIdx; i++ ) {
448                                                 moveItem( vl, i, i + delta );           // Change data and position
449                                         }
450
451                                         // Store current top/bottom idx into vl
452                                         vl._first_index = cti;
453                                         vl._last_index = cbi;
454
455                                         // Register timer to check again
456                                         vl.timerMoveID = setTimeout( timerMove, timerInterval, vl );
457                                 }
458                                 return; // End of function
459                         }
460
461                         // ==== function start ====
462
463                         t.timerStillCount = 0;  // Count do-nothing time.       For behavior tuning.
464
465                         // If a timer function is alive, clear it
466                         if ( t.timerMoveID ) {
467                                 clearTimeout( t.timerMoveID );
468                                 t.timerMoveID = null;
469                         }
470                         // run TimerMove()
471                         timerMove( t );
472                 },
473
474                 _recreate: function ( newArray ) {
475                         var t = this,
476                                 o = this.options;
477
478                         $( o.id ).empty();
479
480                         t._numItemData = newArray.length;
481                         t._direction = _NO_SCROLL;
482                         t._first_index = 0;
483                         t._last_index = o.row - 1;
484
485                         t._pushData( o.template );
486
487                         if (o.childSelector == " ul" ) {
488                                 $( o.id + " ul" ).swipelist();
489                         }
490
491                         $( o.id ).virtuallistview();
492
493                         t.refresh( true );
494
495                         t._reposition( o );
496                 },
497
498                 // Init virtuallistview
499                 // this         virtuallistview object
500                 _initList: function () {
501                         var t = this,
502                                 o = this.options;
503
504                         /* After AJAX loading success */
505
506                         // Put initial <li> elements
507                         t._pushData( o.template );
508
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 () {
513                                         t._reposition( o );
514                                 }, 0);
515                         });
516
517                         // Bind _scrollmove() at 'scrollstart.virtuallist' event
518                         $( document ).bind( "scrollstart.virtuallist scrollstop.vrituallist", t, t._scrollmove );
519
520                         // Bind _resize() at 'resize.virtuallist'
521                         $( window ).bind( "resize.virtuallist", t._resize );
522
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();
527                         }
528
529                         t.refresh( true );
530                 },
531
532                 create: function () {
533                         var o = this.options;
534
535                         /* external API for AJAX callback */
536                         this._create.apply( this, arguments );
537
538                         // TODO: remove this line? _initList() calls reposition...
539                         this._reposition( o );
540                 },
541
542                 _create: function ( args ) {
543                         // Extend instance variables
544                         $.extend( this, {
545                                 _itemData : function ( idx ) { return null; },
546                                 _numItemData : 0,
547                                 _cacheItemData : function ( minIdx, maxIdx ) { },
548                                 _title_h : 0,
549                                 _container_w : 0,
550                                 _minimum_row : 100,
551                                 _direction : _NO_SCROLL,
552                                 _first_index : 0,
553                                 _last_index : 0,
554                                 _num_top_items : 0      // By scroll move, number of hidden elements.
555                         } );
556
557                         // local variables
558                         var t = this,
559                                 o = this.options,
560                                 $el = this.element,
561                                 shortcutsContainer = $('<div class="ui-virtuallist"/>'),
562                                 shortcutsList = $('<ul></ul>'),
563                                 dividers = $el.find(':jqmData(role="virtuallistview" )'),
564                                 lastListItem = null,
565                                 shortcutscroll = this,
566                                 dbtable_name,
567                                 dbtable;
568
569
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 " : "" );
573                         });
574
575                         // keep the vlist's ID
576                         o.itemIDPrefix = $el.attr( "id" ) + '_';
577                         o.id = "#" + $el.attr( "id" );
578
579                         // when page hides, empty all child elements
580                         $( o.id ).bind( "pagehide", function ( e ) {
581                                 $( o.id ).empty();
582                         });
583
584                         // Find if scrollview is used
585                         if ( $( ".ui-scrollview-clip" ).size() > 0 ) {
586                                 o.scrollview = true;
587                         } else {
588                                 o.scrollview = false;
589                         }
590
591                         // Calculate page buffer size
592                         if ( $el.data( "row" ) ) {
593                                 o.row = $el.data( "row" );
594
595                                 if ( o.row < t._minimum_row ) {
596                                         o.row = t._minimum_row;
597                                 }
598
599                                 o.page_buf = parseInt( ( o.row / 2 ), 10 );
600                         }
601
602                         // Get arguments
603                         if ( args ) {
604                                 if ( args.itemData && typeof args.itemData == 'function'  ) {
605                                         t._itemData = args.itemData;
606                                 } else {
607                                         return;
608                                 }
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;
614                                         } else {
615                                                 return;
616                                         }
617                                 } else {
618                                         return;
619                                 }
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!");
623
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 ];
628
629                                         $( o.id ).empty();
630
631                                         if ( !dbtable ) {
632                                                 dbtable = { };
633                                         }
634
635                                         t._itemData = function ( idx ) {
636                                                 return dbtable[ idx ];
637                                         };
638                                         t._numItemData = dbtable.length;
639                                 } else {
640                                         return; // Do nothing
641                                 }
642                         }
643
644                         // Get template data
645                         if ( $el.data( "template" ) ) {
646                                 o.template = $el.data( "template" );
647
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";
651                                 } else {
652                                         o.childSelector = " li";
653                                 }
654                         }
655
656                         // Set data's unique key
657                         // NOTE: Unnecessary?
658                         if ( $el.data( "dbkey" ) ) {
659                                 o.dbkey = $el.data( "dbkey" );
660                         }
661
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!
665                 },
666
667                 destroy : function () {
668                         var o = this.options;
669
670                         $( document ).unbind( "scrollstop" );
671
672                         $( window ).unbind( "resize.virtuallist" );
673
674                         $( o.id ).empty();
675                 },
676
677                 _itemApply: function ( $list, item ) {
678                         var $countli = item.find( ".ui-li-count" );
679
680                         if ( $countli.length ) {
681                                 item.addClass( "ui-li-has-count" );
682                         }
683
684                         $countli.addClass( "ui-btn-up-" + ( $list.jqmData( "counttheme" ) || this.options.countTheme ) + " ui-btn-corner-all" );
685
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" );
691                                 }).end()
692                                 .find( ".ui-li-aside" ).each(function () {
693                                         var $this = $( this );
694                                         $this.prependTo( $this.parent() ); //shift aside to front for css float
695                                 } );
696                 },
697
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";
701
702                         li = li.add( li.find( ".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb" ) );
703
704                         if ( which === "top" ) {
705                                 li.removeClass( top );
706                         } else if ( which === "bottom" ) {
707                                 li.removeClass( bot );
708                         } else {
709                                 li.removeClass( top + " " + bot );
710                         }
711                 },
712
713                 _refreshCorners: function ( create ) {
714                         var $li,
715                                 $visibleli,
716                                 $topli,
717                                 $bottomli;
718
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" );
723
724                                 this._removeCorners( $li );
725
726                                 // Select the first visible li element
727                                 $topli = $visibleli.first()
728                                         .addClass( "ui-corner-top" );
729
730                                 $topli.add( $topli.find( ".ui-btn-inner" ) )
731                                         .find( ".ui-li-link-alt" )
732                                                 .addClass( "ui-corner-tr" )
733                                         .end()
734                                         .find( ".ui-li-thumb" )
735                                                 .not( ".ui-li-icon" )
736                                                 .addClass( "ui-corner-tl" );
737
738                                 // Select the last visible li element
739                                 $bottomli = $visibleli.last()
740                                         .addClass( "ui-corner-bottom" );
741
742                                 $bottomli.add( $bottomli.find( ".ui-btn-inner" ) )
743                                         .find( ".ui-li-link-alt" )
744                                                 .addClass( "ui-corner-br" )
745                                         .end()
746                                         .find( ".ui-li-thumb" )
747                                                 .not( ".ui-li-icon" )
748                                                 .addClass( "ui-corner-bl" );
749                         }
750                 },
751
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();
758
759                         var o = this.options,
760                                 $list = this.element,
761                                 self = this,
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,
767                                 item,
768                                 itemClass,
769                                 temTheme,
770                                 a,
771                                 last,
772                                 splittheme,
773                                 countParent,
774                                 icon,
775                                 pos,
776                                 numli,
777                                 itemTheme;
778
779                         // TODO: ?
780                         if ( counter ) {
781                                 $list.find( ".ui-li-dec" ).remove();
782                         }
783
784                         for ( pos = 0, numli = li.length; pos < numli; pos++ ) {
785                                 item = li.eq( pos );
786                                 itemClass = "ui-li";
787
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" );
792
793                                         if ( a.length ) {
794                                                 icon = item.jqmData( "icon" );
795
796                                                 item.buttonMarkup({
797                                                         wrapperEls: "div",
798                                                         shadow: false,
799                                                         corners: false,
800                                                         iconpos: "right",
801                                                         /* icon: a.length > 1 || icon === false ? false : icon || "arrow-r",*/
802                                                         icon: false,    /* Remove unnecessary arrow icon */
803                                                         theme: itemTheme
804                                                 });
805
806                                                 if ( ( icon != false ) && ( a.length == 1 ) ) {
807                                                         item.addClass( "ui-li-has-arrow" );
808                                                 }
809
810                                                 a.first().addClass( "ui-link-inherit" );
811
812                                                 if ( a.length > 1 ) {
813                                                         itemClass += " ui-li-has-alt";
814
815                                                         last = a.last();
816                                                         splittheme = listsplittheme || last.jqmData( "theme" ) || o.splitTheme;
817
818                                                         last.appendTo(item)
819                                                                 .attr( "title", last.getEncodedText() )
820                                                                 .addClass( "ui-li-link-alt" )
821                                                                 .empty()
822                                                                 .buttonMarkup({
823                                                                         shadow: false,
824                                                                         corners: false,
825                                                                         theme: itemTheme,
826                                                                         icon: false,
827                                                                         iconpos: false
828                                                                 })
829                                                                 .find( ".ui-btn-inner" )
830                                                                 .append(
831                                                                         $( "<span />" ).buttonMarkup({
832                                                                                 shadow: true,
833                                                                                 corners: true,
834                                                                                 theme: splittheme,
835                                                                                 iconpos: "notext",
836                                                                                 icon: listspliticon || last.jqmData( "icon" ) || o.splitIcon
837                                                                         })
838                                                                 );
839                                                 }
840                                         } else if ( item.jqmData( "role" ) === "list-divider" ) {
841
842                                                 itemClass += " ui-li-divider ui-btn ui-bar-" + dividertheme;
843                                                 item.attr( "role", "heading" );
844
845                                                 //reset counter when a divider heading is encountered
846                                                 if ( counter ) {
847                                                         counter = 1;
848                                                 }
849
850                                         } else {
851                                                 itemClass += " ui-li-static ui-body-" + itemTheme;
852                                         }
853                                 }
854
855                                 if ( counter && itemClass.indexOf( "ui-li-divider" ) < 0 ) {
856                                         countParent = item.is( ".ui-li-static:first" ) ? item : item.find( ".ui-link-inherit" );
857
858                                         countParent.addClass( "ui-li-jsnumbering" )
859                                                 .prepend( "<span class='ui-li-dec'>" + (counter++) + ". </span>" );
860                                 }
861
862                                 item.add( item.children( ".ui-btn-inner" ) ).addClass( itemClass );
863
864                                 self._itemApply( $list, item );
865                         }
866
867                         this._refreshCorners( create );
868                 },
869
870                 //create a string for ID/subpage url creation
871                 _idStringEscape: function ( str ) {
872                         return str.replace(/\W/g , "-");
873                 },
874
875                 // ?
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" ),
883                                 o = this.options,
884                                 dns = "data-" + $.mobile.ns,
885                                 self = this,
886                                 persistentFooterID = parentPage.find( ":jqmData(role='footer')" ).jqmData( "id" ),
887                                 hasSubPages,
888                                 newRemove;
889
890                         if ( typeof listCountPerPage[ parentId ] === "undefined" ) {
891                                 listCountPerPage[ parentId ] = -1;
892                         }
893
894                         parentListId = parentListId || ++listCountPerPage[ parentId ];
895
896                         $( parentList.find( "li>ul, li>ol" ).toArray().reverse() ).each(function ( i ) {
897                                 var self = this,
898                                         list = $( this ),
899                                         listId = list.attr( "id" ) || parentListId + "-" + i,
900                                         parent = list.parent(),
901                                         nodeEls,
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,
906                                         newPage,
907                                         anchor;
908
909                                 nodeEls = $( list.prevAll().toArray().reverse() );
910                                 nodeEls = nodeEls.length ? nodeEls : $( "<span>" + $.trim( parent.contents()[ 0 ].nodeValue ) + "</span>" );
911
912                                 //define hasSubPages for use in later removal
913                                 hasSubPages = true;
914
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>" )
917                                                         .parent()
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 + "'>" ) : "" )
920                                                                 .parent()
921                                                                 .appendTo( $.mobile.pageContainer );
922
923                                 newPage.page();
924
925                                 anchor = parent.find('a:first');
926
927                                 if ( !anchor.length ) {
928                                         anchor = $( "<a/>" ).html( nodeEls || title ).prependTo( parent.empty() );
929                                 }
930
931                                 anchor.attr( "href", "#" + id );
932
933                         }).virtuallistview();
934
935                         // on pagehide, remove any nested pages along with the parent page, as long as they aren't active
936                         // and aren't embedded
937                         if ( hasSubPages &&
938                                                 parentPage.is( ":jqmData(external-page='true')" ) &&
939                                                 parentPage.data( "page" ).options.domCache === false ) {
940
941                                 newRemove = function ( e, ui ) {
942                                         var nextPage = ui.nextPage, npURL;
943
944                                         if ( ui.nextPage ) {
945                                                 npURL = nextPage.jqmData( "url" );
946                                                 if ( npURL.indexOf( parentUrl + "&" + $.mobile.subPageUrlKey ) !== 0 ) {
947                                                         self.childPages().remove();
948                                                         parentPage.remove();
949                                                 }
950                                         }
951                                 };
952
953                                 // unbind the original page remove and replace with our specialized version
954                                 parentPage
955                                         .unbind( "pagehide.remove" )
956                                         .bind( "pagehide.remove", newRemove );
957                         }
958                 },
959
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" );
963
964                         return $( ":jqmData(url^='" +  parentUrl + "&" + $.mobile.subPageUrlKey + "')" );
965                 }
966         });
967
968         //auto self-init widgets
969         $( document ).bind( "pagecreate create", function ( e ) {
970                 $( $.tizen.virtuallistview.prototype.options.initSelector, e.target ).virtuallistview();
971         });
972
973 } ( jQuery ) );