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