Tizen 2.0 Release
[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                                 margin;
198
199                         if ( event.data ) {
200                                 o = event.data;
201                         } else {
202                                 o = event;
203                         }
204                         if ( $( o.id + o.childSelector ).size() > 0 ) { // $("#vlistid li")
205                                 // first child's top position
206                                 // NOTE: the first element may not be '0'!!!
207                                 t._title_h = $( o.id + o.childSelector + ':first' ).position().top;
208                                 // first child's outer height (TODO: reuse selected items)
209                                 t._line_h = $( o.id + o.childSelector + ':first' ).outerHeight();
210
211                                 // container(vlist element)'s innerwidth
212                                 t._container_w = $( o.id ).innerWidth();
213
214                                 // get sum of container's left/right padding
215                                 padding = parseInt( $( o.id + o.childSelector ).css( "padding-left" ), 10 )
216                                         + parseInt( $( o.id + o.childSelector ).css( "padding-right" ), 10 );
217
218                                 // Add CSS to all <li> elements
219                                 //      * absolute position
220                                 //      * btn-up
221                                 //      * mouse up/down/over/out styles
222                                 $( o.id + ">" + o.childSelector )
223                                         .addClass( "position_absolute" )
224                                         .addClass( "ui-btn-up-s" )
225                                         .bind( "mouseup", t._stylerMouseUp )
226                                         .bind( "mousedown", t._stylerMouseDown )
227                                         .bind( "mouseover", t._stylerMouseOver )
228                                         .bind( "mouseout", t._stylerMouseOut );
229                         }
230
231                         // Set absolute top/left position of each <li>
232                         $( o.id + ">" + o.childSelector ).each( function ( index ) {
233                                 margin = parseInt( $( this ).css( "margin-left" ), 10 )
234                                         + parseInt( $( this ).css( "margin-right" ), 10 );
235
236                                 $( this ).css( "top", t._title_h + t._line_h * index + 'px' )
237                                         .css( "width", t._container_w - padding - margin );
238                         } );
239
240                         // Set Max Listview Height
241                         $( o.id ).height( t._numItemData * t._line_h );
242                 },
243
244                 _resize: function ( event ) {
245                         var o,
246                                 t = this,
247                                 padding,
248                                 margin;
249
250                         if ( event.data ) {
251                                 o = event.data;
252                         } else {
253                                 o = event;
254                         }
255
256                         t._container_w = $( o.id ).innerWidth();
257
258                         padding = parseInt( $( o.id + o.childSelector ).css( "padding-left" ), 10 )
259                                 + parseInt( $( o.id + o.childSelector ).css( "padding-right" ), 10 );
260
261                         $( o.id + o.childSelector ).each( function (index) {
262                                 margin = parseInt( $( this ).css( "margin-left" ), 10 )
263                                         + parseInt( $( this ).css( "margin-right" ), 10 );
264                                 $( this ).css( "width", t._container_w - padding - margin );
265                         } );
266                 },
267
268                 // New scrollmove function supporting scrollTo
269                 _scrollmove: function ( ev ) {
270                         var t = ev.data,        // vlist (JQM object)
271                                 o = t.options,  // options
272                                 prevTopBufLen = t._num_top_items,       // Previous(remembered) top buf length
273                                 timerInterval = 100,
274                                 i,
275                                 _scrollView,
276                                 _normalScroll;
277
278                         _scrollView = {
279                                 viewTop: function ( ) {
280                                         var sv = $( o.id ).parentsUntil( ".ui-page" ).find( ".ui-scrollview-view" ),
281                                                 svTrans = sv.css( "-webkit-transform" ),
282                                                 svTransVal = "0,0,0,0,0,0";
283                                         if ( svTrans ) {
284                                                 svTransVal = svTrans.replace( /matrix\s*\((.*)\)/, "$1" );      // matrix(a,c,b,d,tx,ty)
285                                         }
286                                         return - parseInt( svTransVal.split(',')[5], 10 );
287                                 }
288                         };
289                         _normalScroll = {
290                                 viewTop: function ( ) {
291                                         return $( window ).scrollTop( );        // TODO: - _line_h?
292                                 }
293                         };
294                         // Get current view top position
295                         function viewTop ( ) {
296                                 return o.scrollview ? _scrollView.viewTop() : _normalScroll.viewTop();
297                         }
298                         // log function for debug
299                         function log ( msg ) {
300                                 var debug = false;
301                                 if ( debug ) {
302                                         console.log( ">>virtualllist: " + msg );
303                                 }
304                         }
305
306                         // Timer interval function
307                         // @param[in]   vl      virtuallist object (JQM object)
308                         function timerMove ( vl, undefined ) {
309                                 var cy,                         // current y position
310                                         cti,            // current top idx
311                                         cbi,            // current bottom idx
312                                         oti = vl._first_index,  // old top idx
313                                         obi = vl._last_index,   // old botton idx
314                                         dti,                    // delta of top idx
315                                         fromIdx,
316                                         toIdx,  // index range to be moved
317                                         delta,                  // moveItem delta
318                                         rowLen = vl.options.row,        // max. # of items handled at once
319                                         bufSize,                // top/bottom buffer size. unit: # of items
320                                         i;
321
322                                 // subroutine: Move itemContents in i2 into i1
323                                 function moveItemContents( vl, i1, i2 ) {
324                                         // TODO: Find a efficient way to replace data!
325                                         // Assumption: i1 and i2 has same children.
326                                         var NODETYPE = { ELEMENT_NODE: 1, TEXT_NODE: 3 },
327                                                 c1,     // child item 1 (old)
328                                                 c2,     // child item 2 (new)
329                                                 newText,
330                                                 newImg,
331                                                 i;
332
333                                         $( i1 ).find( ".ui-li-text-main", ".ui-li-text-sub", ".ui-li-text-sub2", "ui-btn-text" ).each( function ( index ) {
334                                                 c1 = $( this );
335                                                 newText = $( i2 ).find( ".ui-li-text-main", ".ui-li-text-sub", "ui-btn-text" ).eq( index ).text();
336
337                                                 $( c1 ).contents().filter( function () {
338                                                         return ( this.nodeType == NODETYPE.TEXT_NODE );
339                                                 } ).get( 0 ).data = newText;
340                                         } );
341
342                                         $( i1 ).find( "img" ).each( function ( imgIdx ) {
343                                                 var c1 = $( this );
344                                                 newImg = $( i2 ).find( "img" ).eq( imgIdx ).attr( "src" );
345
346                                                 $( c1 ).attr( "src", newImg );
347                                         } );
348
349                                         $( i1 ).removeData( );  // Clear old data
350                                 }
351
352                                 // subroutine: Move item
353                                 function moveItem( vl, fromIdx, toIdx ) {
354                                         var itemData,   // data from itemData()
355                                                 item,           // item element
356                                                 newItem,        // new item element
357                                                 tmpl;           // template
358
359                                         log( ">> move item: " + fromIdx + " --> " + toIdx );
360
361                                         // Find current item
362                                         item = $( '#' + vl.options.itemIDPrefix + fromIdx );    // TODO: refactor ID generation!
363                                         if ( ! item || ! item.length ) {
364                                                 return false;
365                                         }
366
367                                         // Get new item
368                                         tmpl = $( "#" + vl.options.template );
369                                         if ( tmpl ) {
370                                                 newItem = tmpl.tmpl( vl._itemData( toIdx ) );
371
372                                                 // TODO: Consider touch block while moving?
373
374                                                 // Move item contents
375                                                 moveItemContents( vl, item, newItem );
376
377                                                 // clean up temporary item
378                                                 newItem.remove();
379                                         }
380
381                                         // Move position, and set id
382                                         item.css( 'top', toIdx * vl._line_h )
383                                                 .attr( 'id' , vl.options.itemIDPrefix + toIdx );        // TODO: refactor ID generation!
384
385                                         // TODO: Apply jqmdata? check following old code;
386                                         // $( oldItem ).removeData( );  // Clear old data
387                                         // if (key) { $( oldItem ).data( key, $( newItem ).data( key ) ); }
388
389                                         return true;
390                                 }
391
392
393                                 // Get current view position
394                                 cy = viewTop();
395
396                                 // Calculate bufSize: rowLen / 3
397                                 // NOTE: Assumption: total row length = visible items * 3 (upper+visible+lower)
398                                 bufSize = Math.ceil( rowLen / 3 );
399
400                                 // Calculate current top/bottom index (to be applied)
401                                 // top index = current position / line height
402                                 cti = Math.floor( cy / vl._line_h ) - bufSize;  // TODO: consider buffer!
403                                 cbi = cti + rowLen - 1;
404
405                                 if ( cti < 0 ) {                // Top boundary check
406                                         cbi += ( - cti );
407                                         cti = 0;
408                                 } else if ( cbi > ( vl._numItemData - 1 ) ) {           // Bottom boundary check
409                                         cti -= ( cbi - ( vl._numItemData - 1 ) );
410                                         cbi = ( vl._numItemData - 1 );
411                                 }
412
413                                 // Calculate dti
414                                 dti = cti - oti;
415                                 log( "cy=" + cy + ", oti=" + oti + ", obi=" + obi + ", cti=" + cti + ", cbi=" + cbi + ", dti=" + dti );
416
417                                 // switch: dti = 0 --> timer stop condition: delta=0 or scrollstop event comes. END.
418                                 if ( 0 == dti ) {
419                                         // Check timer runtime
420                                         vl.timerStillCount += 1;
421                                         if ( vl.timerStillCount < 12 ) {        // check count ( TODO: test and adjust )
422                                                 log("dti=0 " + vl.timerStillCount + " times");
423                                                 vl.timerMoveID = setTimeout( timerMove, timerInterval, vl );    // run once more
424                                                 return;
425                                         }
426
427                                         log("dti=0 " + vl.timerStillCount + " times. End timer.");
428                                         vl.timerStillCount = 0;
429                                         // Stop timer
430                                         if ( vl.timerMoveID ) {
431                                                 clearTimeout( vl.timerMoveID );
432                                                 vl.timerMoveID = null;
433                                         }
434                                 } else {
435                                         // switch: dti >= # of max elements --> total replace.
436                                         vl.timerStillCount = 0;         // Reset still counter
437
438                                         if ( Math.abs( dti ) >= rowLen ) {
439                                                 fromIdx = oti;
440                                                 toIdx = obi;
441                                                 delta = dti;
442                                                 log( ">>> WHOLE CHANGE! delta=" + delta );
443                                         } else {
444                                                 // switch: dti < # of max elements --> move t2b or b2t until new top/bottom idx is covered
445                                                 if ( dti > 0 ) {
446                                                         fromIdx = oti;
447                                                         toIdx = oti + dti - 1;
448                                                         delta = rowLen;
449                                                 } else {
450                                                         fromIdx = obi + dti + 1;        // dti < 0
451                                                         toIdx = obi;
452                                                         delta = -rowLen;
453                                                 }
454                                                 log( ">>> partial change. delta=" + delta );
455                                         }
456
457                                         // Move items
458                                         for ( i = fromIdx; i <= toIdx; i++ ) {
459                                                 moveItem( vl, i, i + delta );           // Change data and position
460                                         }
461
462                                         // Store current top/bottom idx into vl
463                                         vl._first_index = cti;
464                                         vl._last_index = cbi;
465
466                                         // Register timer to check again
467                                         vl.timerMoveID = setTimeout( timerMove, timerInterval, vl );
468                                 }
469                                 return; // End of function
470                         }
471
472                         // ==== function start ====
473
474                         t.timerStillCount = 0;  // Count do-nothing time.       For behavior tuning.
475
476                         // If a timer function is alive, clear it
477                         if ( t.timerMoveID ) {
478                                 clearTimeout( t.timerMoveID );
479                                 t.timerMoveID = null;
480                         }
481                         // run TimerMove()
482                         timerMove( t );
483                 },
484
485                 _recreate: function ( newArray ) {
486                         var t = this,
487                                 o = this.options;
488
489                         $( o.id ).empty();
490
491                         t._numItemData = newArray.length;
492                         t._direction = _NO_SCROLL;
493                         t._first_index = 0;
494                         t._last_index = o.row - 1;
495
496                         t._pushData( o.template );
497
498                         if (o.childSelector == " ul" ) {
499                                 $( o.id + " ul" ).swipelist();
500                         }
501
502                         $( o.id ).virtuallistview();
503
504                         t.refresh( true );
505
506                         t._reposition( o );
507                 },
508
509                 // Init virtuallistview
510                 // this         virtuallistview object
511                 _initList: function () {
512                         var t = this,
513                                 o = this.options;
514
515                         /* After AJAX loading success */
516
517                         // Put initial <li> elements
518                         t._pushData( o.template );
519
520                         // find a parent page, and run _reposition() at 'pageshow' event
521                         // TODO: Consider replace parentsUntil().parent() to parent('.ui-page') ???
522                         $( o.id ).parentsUntil( ".ui-page" ).parent().one( "pageshow", function () {
523                                 setTimeout( function () {
524                                         t._reposition( o );
525                                 }, 0);
526                         });
527
528                         // Bind _scrollmove() at 'scrollstart.virtuallist' event
529                         $( document ).bind( "scrollstart.virtuallist scrollstop.vrituallist", t, t._scrollmove );
530
531                         // Bind _resize() at 'resize.virtuallist'
532                         $( window ).bind( "resize.virtuallist", t._resize );
533
534                         // when ul is a childselector, assume that this is also a swipelist,
535                         // and run swipelist constructor
536                         if ( o.childSelector == " ul" ) {
537                                 $( o.id + " ul" ).swipelist();
538                         }
539
540                         t.refresh( true );
541                 },
542
543                 create: function () {
544                         var o = this.options;
545
546                         /* external API for AJAX callback */
547                         this._create.apply( this, arguments );
548
549                         // TODO: remove this line? _initList() calls reposition...
550                         this._reposition( o );
551                 },
552
553                 _create: function ( args ) {
554                         // Extend instance variables
555                         $.extend( this, {
556                                 _itemData : function ( idx ) { return null; },
557                                 _numItemData : 0,
558                                 _cacheItemData : function ( minIdx, maxIdx ) { },
559                                 _title_h : 0,
560                                 _container_w : 0,
561                                 _minimum_row : 100,
562                                 _direction : _NO_SCROLL,
563                                 _first_index : 0,
564                                 _last_index : 0,
565                                 _num_top_items : 0      // By scroll move, number of hidden elements.
566                         } );
567
568                         // local variables
569                         var t = this,
570                                 o = this.options,
571                                 $el = this.element,
572                                 shortcutsContainer = $('<div class="ui-virtuallist"/>'),
573                                 shortcutsList = $('<ul></ul>'),
574                                 dividers = $el.find(':jqmData(role="virtuallistview" )'),
575                                 lastListItem = null,
576                                 shortcutscroll = this,
577                                 dbtable_name,
578                                 dbtable;
579
580
581                         // Add CSS classes to $el (=virtuallistview)
582                         $el.addClass( function ( i, orig ) {
583                                 return orig + " ui-listview ui-virtual-list-container" + ( t.options.inset ? " ui-listview-inset ui-corner-all ui-shadow " : "" );
584                         });
585
586                         // keep the vlist's ID
587                         o.itemIDPrefix = $el.attr( "id" ) + '_';
588                         o.id = "#" + $el.attr( "id" );
589
590                         // when page hides, empty all child elements
591                         $( o.id ).bind( "pagehide", function ( e ) {
592                                 $( o.id ).empty();
593                         });
594
595                         // Find if scrollview is used
596                         if ( $( ".ui-scrollview-clip" ).size() > 0 ) {
597                                 o.scrollview = true;
598                         } else {
599                                 o.scrollview = false;
600                         }
601
602                         // Calculate page buffer size
603                         if ( $el.data( "row" ) ) {
604                                 o.row = $el.data( "row" );
605
606                                 if ( o.row < t._minimum_row ) {
607                                         o.row = t._minimum_row;
608                                 }
609
610                                 o.page_buf = parseInt( ( o.row / 2 ), 10 );
611                         }
612
613                         // Get arguments
614                         if ( args ) {
615                                 if ( args.itemData && typeof args.itemData == 'function'  ) {
616                                         t._itemData = args.itemData;
617                                 } else {
618                                         return;
619                                 }
620                                 if ( args.numItemData ) {
621                                         if ( typeof args.numItemData == 'function' ) {
622                                                 t._numItemData = args.numItemData( );
623                                         } else if ( typeof args.numItemData == 'number' ) {
624                                                 t._numItemData = args.numItemData;
625                                         } else {
626                                                 return;
627                                         }
628                                 } else {
629                                         return;
630                                 }
631                         } else {        // No option is given
632                                 // Legacy support: dbtable
633                                 console.warn( "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!" );
634
635                                 /* After DB Load complete, Init Vritual list */
636                                 if ( $( o.id ).hasClass( "vlLoadSuccess" ) ) {
637                                         dbtable_name = $el.jqmData('dbtable');
638                                         dbtable = window[ dbtable_name ];
639
640                                         $( o.id ).empty();
641
642                                         if ( !dbtable ) {
643                                                 dbtable = { };
644                                         }
645
646                                         t._itemData = function ( idx ) {
647                                                 return dbtable[ idx ];
648                                         };
649                                         t._numItemData = dbtable.length;
650                                 } else {
651                                         return; // Do nothing
652                                 }
653                         }
654
655                         // Get template data
656                         if ( $el.data( "template" ) ) {
657                                 o.template = $el.data( "template" );
658
659                                 /* to support swipe list, <li> or <ul> can be main node of virtual list. */
660                                 if ( $el.data( "swipelist" ) == true ) {
661                                         o.childSelector = " ul";
662                                 } else {
663                                         o.childSelector = " li";
664                                 }
665                         }
666
667                         // Set data's unique key
668                         // NOTE: Unnecessary?
669                         if ( $el.data( "dbkey" ) ) {
670                                 o.dbkey = $el.data( "dbkey" );
671                         }
672
673                         t._first_index = 0;                     // initial top idx of <li> element.
674                         t._last_index = o.row - 1;              // initial bottom idx of <li> element.
675                         t._initList();  // NOTE: Called at here only!
676                 },
677
678                 destroy : function () {
679                         var o = this.options;
680
681                         $( document ).unbind( "scrollstop" );
682
683                         $( window ).unbind( "resize.virtuallist" );
684
685                         $( o.id ).empty();
686
687                         if ( this.timerMoveID ) {
688                                 clearTimeout( this.timerMoveID );
689                                 this.timerMoveID = null;
690                         }
691                 },
692
693                 _itemApply: function ( $list, item ) {
694                         var $countli = item.find( ".ui-li-count" );
695
696                         if ( $countli.length ) {
697                                 item.addClass( "ui-li-has-count" );
698                         }
699
700                         $countli.addClass( "ui-btn-up-" + ( $list.jqmData( "counttheme" ) || this.options.countTheme ) + " ui-btn-corner-all" );
701
702                         // TODO class has to be defined in markup
703                         item.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" ).end()
704                                 .find( "p, dl" ).addClass( "ui-li-desc" ).end()
705                                 .find( ">img:eq(0), .ui-link-inherit>img:eq(0)" ).addClass( "ui-li-thumb" ).each( function () {
706                                         item.addClass( $( this ).is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" );
707                                 }).end()
708                                 .find( ".ui-li-aside" ).each(function () {
709                                         var $this = $( this );
710                                         $this.prependTo( $this.parent() ); //shift aside to front for css float
711                                 } );
712                 },
713
714                 _removeCorners: function ( li, which ) {
715                         var top = "ui-corner-top ui-corner-tr ui-corner-tl",
716                                 bot = "ui-corner-bottom ui-corner-br ui-corner-bl";
717
718                         li = li.add( li.find( ".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb" ) );
719
720                         if ( which === "top" ) {
721                                 li.removeClass( top );
722                         } else if ( which === "bottom" ) {
723                                 li.removeClass( bot );
724                         } else {
725                                 li.removeClass( top + " " + bot );
726                         }
727                 },
728
729                 _refreshCorners: function ( create ) {
730                         var $li,
731                                 $visibleli,
732                                 $topli,
733                                 $bottomli;
734
735                         if ( this.options.inset ) {
736                                 $li = this.element.children( "li" );
737                                 // at create time the li are not visible yet so we need to rely on .ui-screen-hidden
738                                 $visibleli = create ? $li.not( ".ui-screen-hidden" ) : $li.filter( ":visible" );
739
740                                 this._removeCorners( $li );
741
742                                 // Select the first visible li element
743                                 $topli = $visibleli.first()
744                                         .addClass( "ui-corner-top" );
745
746                                 $topli.add( $topli.find( ".ui-btn-inner" ) )
747                                         .find( ".ui-li-link-alt" )
748                                                 .addClass( "ui-corner-tr" )
749                                         .end()
750                                         .find( ".ui-li-thumb" )
751                                                 .not( ".ui-li-icon" )
752                                                 .addClass( "ui-corner-tl" );
753
754                                 // Select the last visible li element
755                                 $bottomli = $visibleli.last()
756                                         .addClass( "ui-corner-bottom" );
757
758                                 $bottomli.add( $bottomli.find( ".ui-btn-inner" ) )
759                                         .find( ".ui-li-link-alt" )
760                                                 .addClass( "ui-corner-br" )
761                                         .end()
762                                         .find( ".ui-li-thumb" )
763                                                 .not( ".ui-li-icon" )
764                                                 .addClass( "ui-corner-bl" );
765                         }
766                 },
767
768                 // this         virtuallistview object
769                 refresh: function ( create ) {
770                         this.parentPage = this.element.closest( ".ui-page" );
771                         // Make sub page, and move the virtuallist into it...
772                         // NOTE: check this subroutine.
773                         this._createSubPages();
774
775                         var o = this.options,
776                                 $list = this.element,
777                                 self = this,
778                                 dividertheme = $list.jqmData( "dividertheme" ) || o.dividerTheme,
779                                 listsplittheme = $list.jqmData( "splittheme" ),
780                                 listspliticon = $list.jqmData( "spliticon" ),
781                                 li = $list.children( "li" ),
782                                 counter = $.support.cssPseudoElement || !$.nodeName( $list[ 0 ], "ol" ) ? 0 : 1,
783                                 item,
784                                 itemClass,
785                                 temTheme,
786                                 a,
787                                 last,
788                                 splittheme,
789                                 countParent,
790                                 icon,
791                                 pos,
792                                 numli,
793                                 itemTheme;
794
795                         // TODO: ?
796                         if ( counter ) {
797                                 $list.find( ".ui-li-dec" ).remove();
798                         }
799
800                         for ( pos = 0, numli = li.length; pos < numli; pos++ ) {
801                                 item = li.eq( pos );
802                                 itemClass = "ui-li";
803
804                                 // If we're creating the element, we update it regardless
805                                 if ( create || !item.hasClass( "ui-li" ) ) {
806                                         itemTheme = item.jqmData( "theme" ) || o.theme;
807                                         a = item.children( "a" );
808
809                                         if ( a.length ) {
810                                                 icon = item.jqmData( "icon" );
811
812                                                 item.buttonMarkup({
813                                                         wrapperEls: "div",
814                                                         shadow: false,
815                                                         corners: false,
816                                                         iconpos: "right",
817                                                         /* icon: a.length > 1 || icon === false ? false : icon || "arrow-r",*/
818                                                         icon: false,    /* Remove unnecessary arrow icon */
819                                                         theme: itemTheme
820                                                 });
821
822                                                 if ( ( icon != false ) && ( a.length == 1 ) ) {
823                                                         item.addClass( "ui-li-has-arrow" );
824                                                 }
825
826                                                 a.first().addClass( "ui-link-inherit" );
827
828                                                 if ( a.length > 1 ) {
829                                                         itemClass += " ui-li-has-alt";
830
831                                                         last = a.last();
832                                                         splittheme = listsplittheme || last.jqmData( "theme" ) || o.splitTheme;
833
834                                                         last.appendTo(item)
835                                                                 .attr( "title", last.getEncodedText() )
836                                                                 .addClass( "ui-li-link-alt" )
837                                                                 .empty()
838                                                                 .buttonMarkup({
839                                                                         shadow: false,
840                                                                         corners: false,
841                                                                         theme: itemTheme,
842                                                                         icon: false,
843                                                                         iconpos: false
844                                                                 })
845                                                                 .find( ".ui-btn-inner" )
846                                                                 .append(
847                                                                         $( "<span />" ).buttonMarkup({
848                                                                                 shadow: true,
849                                                                                 corners: true,
850                                                                                 theme: splittheme,
851                                                                                 iconpos: "notext",
852                                                                                 icon: listspliticon || last.jqmData( "icon" ) || o.splitIcon
853                                                                         })
854                                                                 );
855                                                 }
856                                         } else if ( item.jqmData( "role" ) === "list-divider" ) {
857
858                                                 itemClass += " ui-li-divider ui-btn ui-bar-" + dividertheme;
859                                                 item.attr( "role", "heading" );
860
861                                                 //reset counter when a divider heading is encountered
862                                                 if ( counter ) {
863                                                         counter = 1;
864                                                 }
865
866                                         } else {
867                                                 itemClass += " ui-li-static ui-body-" + itemTheme;
868                                         }
869                                 }
870
871                                 if ( counter && itemClass.indexOf( "ui-li-divider" ) < 0 ) {
872                                         countParent = item.is( ".ui-li-static:first" ) ? item : item.find( ".ui-link-inherit" );
873
874                                         countParent.addClass( "ui-li-jsnumbering" )
875                                                 .prepend( "<span class='ui-li-dec'>" + (counter++) + ". </span>" );
876                                 }
877
878                                 item.add( item.children( ".ui-btn-inner" ) ).addClass( itemClass );
879
880                                 self._itemApply( $list, item );
881                         }
882
883                         this._refreshCorners( create );
884                 },
885
886                 //create a string for ID/subpage url creation
887                 _idStringEscape: function ( str ) {
888                         return str.replace(/\W/g , "-");
889                 },
890
891                 // ?
892                 // this         virtuallistview object
893                 _createSubPages: function () {
894                         var parentList = this.element,
895                                 parentPage = parentList.closest( ".ui-page" ),
896                                 parentUrl = parentPage.jqmData( "url" ),
897                                 parentId = parentUrl || parentPage[ 0 ][ $.expando ],
898                                 parentListId = parentList.attr( "id" ),
899                                 o = this.options,
900                                 dns = "data-" + $.mobile.ns,
901                                 self = this,
902                                 persistentFooterID = parentPage.find( ":jqmData(role='footer')" ).jqmData( "id" ),
903                                 hasSubPages,
904                                 newRemove;
905
906                         if ( typeof listCountPerPage[ parentId ] === "undefined" ) {
907                                 listCountPerPage[ parentId ] = -1;
908                         }
909
910                         parentListId = parentListId || ++listCountPerPage[ parentId ];
911
912                         $( parentList.find( "li>ul, li>ol" ).toArray().reverse() ).each(function ( i ) {
913                                 var self = this,
914                                         list = $( this ),
915                                         listId = list.attr( "id" ) || parentListId + "-" + i,
916                                         parent = list.parent(),
917                                         nodeEls,
918                                         title = nodeEls.first().getEncodedText(),//url limits to first 30 chars of text
919                                         id = ( parentUrl || "" ) + "&" + $.mobile.subPageUrlKey + "=" + listId,
920                                         theme = list.jqmData( "theme" ) || o.theme,
921                                         countTheme = list.jqmData( "counttheme" ) || parentList.jqmData( "counttheme" ) || o.countTheme,
922                                         newPage,
923                                         anchor;
924
925                                 nodeEls = $( list.prevAll().toArray().reverse() );
926                                 nodeEls = nodeEls.length ? nodeEls : $( "<span>" + $.trim( parent.contents()[ 0 ].nodeValue ) + "</span>" );
927
928                                 //define hasSubPages for use in later removal
929                                 hasSubPages = true;
930
931                                 newPage = list.detach()
932                                                         .wrap( "<div " + dns + "role='page' " + dns + "url='" + id + "' " + dns + "theme='" + theme + "' " + dns + "count-theme='" + countTheme + "'><div " + dns + "role='content'></div></div>" )
933                                                         .parent()
934                                                                 .before( "<div " + dns + "role='header' " + dns + "theme='" + o.headerTheme + "'><div class='ui-title'>" + title + "</div></div>" )
935                                                                 .after( persistentFooterID ? $( "<div " + dns + "role='footer' " + dns + "id='" + persistentFooterID + "'>" ) : "" )
936                                                                 .parent()
937                                                                 .appendTo( $.mobile.pageContainer );
938
939                                 newPage.page();
940
941                                 anchor = parent.find('a:first');
942
943                                 if ( !anchor.length ) {
944                                         anchor = $( "<a/>" ).html( nodeEls || title ).prependTo( parent.empty() );
945                                 }
946
947                                 anchor.attr( "href", "#" + id );
948
949                         }).virtuallistview();
950
951                         // on pagehide, remove any nested pages along with the parent page, as long as they aren't active
952                         // and aren't embedded
953                         if ( hasSubPages &&
954                                                 parentPage.is( ":jqmData(external-page='true')" ) &&
955                                                 parentPage.data( "page" ).options.domCache === false ) {
956
957                                 newRemove = function ( e, ui ) {
958                                         var nextPage = ui.nextPage, npURL;
959
960                                         if ( ui.nextPage ) {
961                                                 npURL = nextPage.jqmData( "url" );
962                                                 if ( npURL.indexOf( parentUrl + "&" + $.mobile.subPageUrlKey ) !== 0 ) {
963                                                         self.childPages().remove();
964                                                         parentPage.remove();
965                                                 }
966                                         }
967                                 };
968
969                                 // unbind the original page remove and replace with our specialized version
970                                 parentPage
971                                         .unbind( "pagehide.remove" )
972                                         .bind( "pagehide.remove", newRemove );
973                         }
974                 },
975
976                 // TODO sort out a better way to track sub pages of the virtuallistview this is brittle
977                 childPages: function () {
978                         var parentUrl = this.parentPage.jqmData( "url" );
979
980                         return $( ":jqmData(url^='" +  parentUrl + "&" + $.mobile.subPageUrlKey + "')" );
981                 }
982         });
983
984         //auto self-init widgets
985         $( document ).bind( "pagecreate create", function ( e ) {
986                 $( $.tizen.virtuallistview.prototype.options.initSelector, e.target ).virtuallistview();
987         });
988
989 } ( jQuery ) );