Export 0.1.63
[platform/framework/web/web-ui-fw.git] / src / widgets / virtualgrid / js / jquery.mobile.tizen.virtualgrid.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: Kangsik Kim <kangsik81.kim@samsung.com>
24  *                      Youmin Ha <youmin.ha@samsung.com>
25 */
26
27 /**
28  * In the web environment, it is challenging to display a large amount of data in a grid.
29  * When an application needs to show, for example, image gallery with over 1,000 images,
30  * the same enormous data must be inserted into a HTML document.
31  * It takes a long time to display the data and manipulating DOM is complex.
32  * The virtual grid widget supports storing unlimited data without performance issues
33  * by reusing a limited number of grid elements.
34  * The virtual grid widget is based on the jQuery.template plug-in 
35  * For more information, see jQuery.template.
36  *
37  * HTML Attributes:
38  *
39  *              data-role:  virtualgrid
40  *              data-template : Has the ID of the jQuery.template element.
41  *                                              jQuery.template for a virtual grid must be defined.
42  *                                              Style for template would use rem unit to support scalability.
43  *              data-itemcount : Number of column elements. (Default : 1)
44  *                                              User can select a numeric type or 'auto'.
45  *                                              If value of attribute is 'auto', Number of column is dependent on screen size.
46  *                                              If value of attribute is numeric type, number of column is always fixed.
47  *              data-direction : This option define the direction of the scroll.
48  *                                              You must choose one of the 'x' and 'y' (Default : y)
49  *              data-rotation : This option defines whether or not the circulation of the data.
50  *                                              If option is 'true' and scroll is reached the last data,
51  *                                              Widget will present the first data on the screen.
52  *                                              If option is ‘false’, Widget will operate like a scrollview.
53  *
54  *              ID : <DIV> element that has "data-role=virtualgrid" must have ID attribute.
55  *
56  * APIs:
57  *
58  *              create ( {
59  *                              itemData: function ( idx ) { return json_obj; },
60  *                              numItemData: number or function () { return number; },
61  *                              cacheItemData: function ( minIdx, maxIdx ) {}
62  *                              } )
63  *                      : Create VirtualGrid widget. At this moment, _create method is called.
64  *                      args : A collection of options
65  *                              itemData: A function that returns JSON object for given index. Mandatory.
66  *                              numItemData: Total number of itemData. Mandatory.
67  *                              cacheItemData: Virtuallist will ask itemData between minIdx and maxIdx.
68  *                              Developers can implement this function for preparing data.
69  *                              Optional.
70  *
71  *              centerTo ( String )
72  *                      : Find a DOM Element with the given class name.
73  *                      This element will be centered on the screen.
74  *                      Serveral elements were found, the first element is displayed.
75  *
76  * Events:
77  *              scrollstart : : This event triggers when a user begin to move the scroll on VirtualGrid.
78  *              scrollupdate : : This event triggers while a user moves the scroll on VirtualGrid.
79  *              scrollstop : This event triggers when a user stop the scroll on VirtualGrid.
80  *              select : This event triggers when a cell is selected.
81  *
82  * Examples:
83  *
84  *                      <script id="tizen-demo-namecard" type="text/x-jquery-tmpl">
85  *                              <div class="ui-demo-namecard">
86  *                                      <div class="ui-demo-namecard-pic">
87  *                                              <img class="ui-demo-namecard-pic-img" src="${TEAM_LOGO}" />
88  *                                      </div>
89  *                                      <div class="ui-demo-namecard-contents">
90  *                                              <span class="name ui-li-text-main">${NAME}</span>
91  *                                              <span class="active ui-li-text-sub">${ACTIVE}</span>
92  *                                              <span class="from ui-li-text-sub">${FROM}</span>
93  *                                      </div>
94  *                              </div>
95  *                      </script>
96  *                      <div id="virtualgrid-demo" data-role="virtualgrid" data-itemcount="3" data-template="tizen-demo-namecard" >
97  *                      </div>
98  *
99  */
100
101 // most of following codes are derived from jquery.mobile.scrollview.js
102
103 /**
104         @class VirtualGrid
105         In the Web environment, it is challenging to display 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.
106
107         The virtual grid widget is used to display a list of unlimited data elements on the screen for better performance. This widget displays the data in the grid format by reusing the existing grid control space. Virtual grids are based on the jQuery.template plugin as described in the jQuery documentation for jQuery.template plugin.
108
109         To add a virtual grid widget to the application, use the following code:
110
111                 <script id="tizen-demo-namecard" type="text/x-jquery-tmpl">
112                         <div class="ui-demo-namecard">
113                                 <div class="ui-demo-namecard-pic">
114                                         <img class="ui-demo-namecard-pic-img" src="${TEAM_LOGO}" />
115                                 </div>
116                                 <div class="ui-demo-namecard-contents">
117                                 <span class="name ui-li-text-main">${NAME}</span>
118                                 </div>
119                         </div>
120                 </script>
121                 <div id="virtualgrid-demo" data-role="virtualgrid" data-itemcount="1" data-template="tizen-demo-namecard">
122                 </div>
123 */
124 /**
125         @property {String} data-template
126         Specifies the jQuery.template element ID.
127         The jQuery.template must be defined. The template style can use rem units to support scalability.
128 */
129 /**
130         @property {String} data-direction
131         Defines the scroll direction. The direction options are x (horizontal) and y (vertical).
132         The default value is y.
133 */
134 /**
135         @property {Boolean} data-rotation
136         Defines whether the data elements are displayed from the beginning of the list again once the end of file is reached.
137         The default value is false.
138 */
139 /**
140         @property {String} data-itemcount
141         Sets the number of column elements displayed in one row. The value can be a number (when the column number is fixed and displayed as the input value) or auto (when the column number varies and the columns are arranged according to the screen size).
142 The default value is 1.
143 */
144 /**
145         @event scrollstart
146         The scrollstart event is fired when the user starts scrolling through the grid:
147
148                 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
149                 // Option 01
150                 $(".selector").virtualgrid
151                 ({
152                         scrollstart: function(event, ui)
153                         {
154                         // Handle the scrollstart event
155                         }
156                 });
157                 // Option 02
158                 $(".selector").bind("scrollstart", function(event, ui)
159                 {
160                 // Handle the scrollstart event
161                 });
162 */
163 /**
164         @event scrollupdate
165         The scrollupdate event is fired when the user moves the scroll bar in the grid:
166
167                 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
168                 // Option 01
169                 $(".selector").virtualgrid
170                 ({
171                         scrollupdate: function(event, ui)
172                         {
173                         // Handle the scrollupdate event
174                         }
175                 });
176                 // Option 02
177                 $(".selector").bind("scrollupdate", function(event, ui)
178                 {
179                 // Handle the scrollupdate event
180                 });
181 */
182 /**
183         @event scrollstop
184         The scrollstop event is fired when the user stops scrolling:
185
186                 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
187                 // Option 01
188                 $(".selector").virtualgrid
189                 ({
190                         scrollstop: function(event, ui)
191                         {
192                         // Handle the scrollstop event
193                         }
194                 });
195                 // Option 02
196                 $(".selector").bind("scrollstop", function(event, ui)
197                 {
198                 // Handle the scrollstop event
199                 });
200 */
201 /**
202         @event select
203         The select event is fired when a virtual grid cell is selected:
204
205                 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
206                 // Option 01
207                 $(".selector").virtualgrid
208                 ({
209                         select: function(event, ui)
210                         {
211                         // Handle the select event
212                         }
213                 });
214                 // Option 02
215                 $(".selector").bind("select", function(event, ui)
216                 {
217                 // Handle the select event
218                 });
219 */
220 /**
221         @method create
222         @param {function} itemData(index)
223         @param {Number} numItemData
224         @param {function} cacheItemData(minIndex, maxIndex)
225         The create method is used to call the jQuery _create method. In the method parameters:
226
227         function itemData(index) returns the JSON object matched with the given index. The index value is between 0 and numItemData-1.<br/>
228         number numItemData or function numItemData() defines or returns a static number of items.<br/>
229         function cacheItemData(minIndex, maxIndex) prepares the JSON data. This method is called before calling the itemData() method with index values between minIndex and maxIndex.<br/>
230
231                 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
232                         function itemData(idx)
233                         {
234                                 return DATA[idx];
235                         }
236                         function cacheItemData(minIdx, maxIdx)
237                         {
238                         // Prepare JSON data between minIdx and maxIdx
239                         }
240                         var numItemData = DATA.length;
241                         $(".selector").virtualgrid("create",
242                         {
243                                 itemData, numItemData, cacheItemData
244                         });
245 */
246 /**
247         @method centerTo
248         The centerTo method is used to search for the DOM element with a specified class name. The retrieved element is placed at the center of the virtual grid. If multiple elements are retrieved, the first element from the result list is placed at the center of the virtual grid.
249
250                 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
251                 $(".selector").virtualgrid("centerTo", "selector");
252 */
253 /**
254         @method resize
255         The resize method is used to rearrange the DOM elements to fit a new screen size when the screen is resized:
256
257                 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
258                 ".selector").virtualgrid("resize");
259
260         @since Tizen2.0
261 */
262
263 ( function ($, window, document, undefined) {
264
265         function circularNum (num, total) {
266                 var n = num % total;
267                 if (n < 0) {
268                         n = total + n;
269                 }
270                 return n;
271         }
272
273         function MomentumTracker (options) {
274                 this.options = $.extend({}, options);
275                 this.easing = "easeOutQuad";
276                 this.reset();
277         }
278
279         var tstates = {
280                 scrolling : 0,
281                 done : 1
282         };
283
284         function getCurrentTime () {
285                 return Date.now();
286         }
287
288         $.extend (MomentumTracker.prototype, {
289                 start : function (pos, speed, duration) {
290                         this.state = (speed !== 0 ) ? tstates.scrolling : tstates.done;
291                         this.pos = pos;
292                         this.speed = speed;
293                         this.duration = duration;
294
295                         this.fromPos = 0;
296                         this.toPos = 0;
297
298                         this.startTime = getCurrentTime();
299                 },
300
301                 reset : function () {
302                         this.state = tstates.done;
303                         this.pos = 0;
304                         this.speed = 0;
305                         this.duration = 0;
306                 },
307
308                 update : function () {
309                         var state = this.state, duration, elapsed, dx, x;
310
311                         if (state == tstates.done) {
312                                 return this.pos;
313                         }
314                         duration = this.duration;
315                         elapsed = getCurrentTime () - this.startTime;
316                         elapsed = elapsed > duration ? duration : elapsed;
317                         dx = this.speed * (1 - $.easing[this.easing] (elapsed / duration, elapsed, 0, 1, duration) );
318                         x = this.pos + dx;
319                         this.pos = x;
320
321                         if (elapsed >= duration) {
322                                 this.state = tstates.done;
323                         }
324                         return this.pos;
325                 },
326
327                 done : function () {
328                         return this.state == tstates.done;
329                 },
330
331                 getPosition : function () {
332                         return this.pos;
333                 }
334         });
335
336         jQuery.widget ("mobile.virtualgrid", jQuery.mobile.widget, {
337                 options : {
338                         // virtualgrid option
339                         template : "",
340                         direction : "y",
341                         rotation : false,
342                         itemcount : 1
343                 },
344
345                 create : function () {
346                         this._create.apply( this, arguments );
347                 },
348
349                 _create : function ( args ) {
350                         $.extend(this, {
351                                 // view
352                                 _$view : null,
353                                 _$clip : null,
354                                 _$rows : null,
355                                 _tracker : null,
356                                 _viewSize : 0,
357                                 _clipSize : 0,
358                                 _cellSize : undefined,
359                                 _currentItemCount : 0,
360                                 _itemCount : 1,
361                                 _isAuto : false,
362
363                                 // timer
364                                 _timerInterval : 0,
365                                 _timerID : 0,
366                                 _timerCB : null,
367                                 _lastMove : null,
368
369                                 // Data
370                                 _itemData : function ( idx ) { return null; },
371                                 _numItemData : 0,
372                                 _cacheItemData : function ( minIdx, maxIdx ) { },
373                                 _totalRowCnt : 0,
374                                 _template : null,
375                                 _maxViewSize : 0,
376                                 _modifyViewPos : 0,
377                                 _maxSize : 0,
378
379                                 // axis - ( true : x , false : y )
380                                 _direction : false,
381                                 _didDrag : true,
382                                 _reservedPos : 0,
383                                 _scalableSize : 0,
384                                 _eventPos : 0,
385                                 _nextPos : 0,
386                                 _movePos : 0,
387                                 _lastY : 0,
388                                 _speedY : 0,
389                                 _lastX : 0,
390                                 _speedX : 0,
391                                 _rowsPerView : 0,
392
393                                 _filterRatio : 0.9
394                         });
395
396                         var self = this,
397                                 $dom = $(self.element),
398                                 opts = self.options,
399                                 $item = null;
400
401                         // itemData
402                         // If mandatory options are not given, Do nothing.
403                         if ( !args ) {
404                                 return ;
405                         }
406
407                         if ( !self._loadData(args) ) {
408                                 return;
409                         }
410
411                         // set a scroll direction.
412                         self._direction = opts.direction === 'x' ? true : false;
413
414                         // itemcount is assigned 'auto'
415                         if ( typeof opts.itemcount === "string" && opts.itemcount.toUpperCase() == "AUTO" ) {
416                                 self._isAuto = true;
417                         }
418
419                         // make view layer
420                         self._$clip = $(self.element).addClass("ui-scrollview-clip").addClass("ui-virtualgrid-view");
421                         $item = $(document.createElement("div")).addClass("ui-scrollview-view");
422                         self._clipSize =  self._calculateClipSize();
423                         self._$clip.append($item);
424                         self._$view = $item;
425                         self._$clip.css("overflow", "hidden");
426                         self._$view.css("overflow", "hidden");
427
428                         // inherit from scrollview widget.
429                         self._scrollView = $.tizen.scrollview.prototype;
430                         self._initScrollView();
431
432                         // create tracker.
433                         self._createTracker();
434                         self._makePositioned(self._$clip);
435                         self._timerInterval = 1000 / self.options.fps;
436
437                         self._timerID = 0;
438                         self._timerCB = function () {
439                                 self._handleMomentumScroll();
440                         };
441                         $dom.closest(".ui-content").addClass("ui-virtualgrid-content").css("overflow", "hidden");
442
443                         // add event handler.
444                         self._addBehaviors();
445
446                         self._currentItemCount = 0;
447                         self._createScrollBar();
448                         self.refresh();
449                 },
450
451                 // The argument is checked for compliance with the specified format.
452                 // @param args   : Object
453                 // @return boolean
454                 _loadData : function ( args ) {
455                         var self = this;
456
457                         if ( args.itemData && typeof args.itemData == 'function'  ) {
458                                 self._itemData = args.itemData;
459                         } else {
460                                 return false;
461                         }
462                         if ( args.numItemData ) {
463                                 if ( typeof args.numItemData == 'function' ) {
464                                         self._numItemData = args.numItemData( );
465                                 } else if ( typeof args.numItemData == 'number' ) {
466                                         self._numItemData = args.numItemData;
467                                 } else {
468                                         return false;
469                                 }
470                         } else {
471                                 return false;
472                         }
473                         return true;
474                 },
475
476                 // Make up the first screen.
477                 _initLayout: function () {
478                         var self = this,
479                                 opts = self.options,
480                                 i,
481                                 $row;
482
483                         for ( i = -1; i < self._rowsPerView + 1; i += 1 ) {
484                                 $row = self._$rows[ circularNum( i, self._$rows.length ) ];
485                                 self._$view.append( $row );
486                         }
487                         self._setElementTransform( -self._cellSize );
488
489                         self._replaceRow(self._$view.children().first(), self._totalRowCnt - 1);
490                         if ( opts.rotation && self._rowsPerView >= self._totalRowCnt ) {
491                                 self._replaceRow(self._$view.children().last(), 0);
492                         }
493                         self._setViewSize();
494                 },
495
496                 _setViewSize : function () {
497                         var self = this,
498                                 height = 0,
499                                 width = 0;
500
501                         if ( self._direction ) {
502                                 width = self._cellSize * ( self._rowsPerView + 2 );
503                                 width = parseInt(width, 10) + 1;
504                                 self._$view.width( width );
505                                 self._viewSize = self._$view.width();
506                         } else {
507                                 self._$view.height( self._cellSize * ( self._rowsPerView + 2 ) );
508                                 self._$clip.height( self._clipSize );
509                                 self._viewSize = self._$view.height();
510                         }
511                 },
512
513                 _getViewHeight : function () {
514                         var self = this;
515                         return self._$view.height();
516                 },
517
518                 refresh : function () {
519                         var self = this,
520                                 opts = self.options,
521                                 width = 0,
522                                 height = 0;
523
524                         self._template = $( "#" + opts.template );
525                         if ( !self._template ) {
526                                 return ;
527                         }
528
529                         width = self._calculateClipWidth();
530                         height = self._calculateClipHeight();
531                         self._$view.width(width).height(height);
532                         self._$clip.width(width).height(height);
533
534                         self._clipSize = self._calculateClipSize();
535                         self._calculateColumnSize();
536                         self._initPageProperty();
537                         self._setScrollBarSize( );
538                 },
539
540                 _initPageProperty : function () {
541                         var self = this,
542                                 rowsPerView = 0,
543                                 $child,
544                                 columnCount = 0,
545                                 totalRowCnt = 0,
546                                 attributeName = self._direction ? "width" : "height";
547
548                         if ( self._isAuto ) {
549                                 columnCount = self._calculateColumnCount();
550                         } else {
551                                 columnCount = self.options.itemcount;
552                         }
553                         totalRowCnt = parseInt(self._numItemData / columnCount , 10 );
554                         self._totalRowCnt = self._numItemData % columnCount === 0 ? totalRowCnt : totalRowCnt + 1;
555                         self._itemCount = columnCount;
556
557                         if ( self._cellSize <= 0) {
558                                 return ;
559                         }
560
561                         rowsPerView = self._clipSize / self._cellSize;
562                         rowsPerView = Math.ceil( rowsPerView );
563                         self._rowsPerView = parseInt( rowsPerView, 10);
564
565                         $child = self._makeRows( rowsPerView + 2 );
566                         $(self._$view).append($child.children());
567                         self._$view.children().css(attributeName, self._cellSize + "px");
568                         self._$rows = self._$view.children().detach();
569
570                         self._reservedPos = -self._cellSize;
571                         self._scalableSize = -self._cellSize;
572
573                         self._initLayout();
574
575                         self._blockScroll = self._rowsPerView > self._totalRowCnt;
576                         self._maxSize = ( self._totalRowCnt - self._rowsPerView ) * self._cellSize;
577                         self._maxViewSize = ( self._rowsPerView ) * self._cellSize;
578                         self._modifyViewPos = -self._cellSize;
579                         if ( self._clipSize < self._maxViewSize ) {
580                                 self._modifyViewPos = (-self._cellSize) + ( self._clipSize - self._maxViewSize );
581                         }
582                 },
583
584                 resize : function ( ) {
585                         var self = this,
586                                 rowsPerView = 0,
587                                 itemCount = 0,
588                                 totalRowCnt = 0,
589                                 diffRowCnt = 0,
590                                 clipSize = 0,
591                                 prevcnt = 0,
592                                 clipPosition = 0;
593
594                         itemCount = self._calculateColumnCount();
595                         if ( self._isAuto && itemCount != self._itemCount ) {
596                                 totalRowCnt = parseInt(self._numItemData / itemCount , 10 );
597                                 self._totalRowCnt = self._numItemData % itemCount === 0 ? totalRowCnt : totalRowCnt + 1;
598                                 prevcnt = self._itemCount;
599                                 self._itemCount = itemCount;
600                                 clipPosition = self._getClipPosition();
601                                 self._$view.hide();
602
603                                 diffRowCnt = self._replaceRows(itemCount, prevcnt, self._totalRowCnt, clipPosition);
604                                 self._maxSize = ( self._totalRowCnt - self._rowsPerView ) * self._cellSize;
605                                 self._scalableSize += (-diffRowCnt) * self._cellSize;
606                                 self._reservedPos  += (-diffRowCnt) * self._cellSize;
607                                 self._setScrollBarSize();
608                                 self._setScrollBarPosition(diffRowCnt);
609
610                                 self._$view.show();
611                         }
612
613                         clipSize = self._calculateClipSize();
614                         if ( clipSize !== self._clipSize ) {
615                                 rowsPerView = clipSize / self._cellSize;
616                                 rowsPerView = parseInt( Math.ceil( rowsPerView ), 10 );
617
618                                 if ( rowsPerView > self._rowsPerView ) {
619                                         // increase row.
620                                         self._increaseRow( rowsPerView - self._rowsPerView );
621                                 } else if ( rowsPerView < self._rowsPerView ) {
622                                         // decrease row.
623                                         self._decreaseRow( self._rowsPerView - rowsPerView );
624                                 }
625                                 self._rowsPerView = rowsPerView;
626                                 self._clipSize = clipSize;
627                                 self._blockScroll = self._rowsPerView > self._totalRowCnt;
628                                 self._maxSize = ( self._totalRowCnt - self._rowsPerView ) * self._cellSize;
629                                 self._maxViewSize = ( self._rowsPerView ) * self._cellSize;
630                                 if ( self._clipSize < self._maxViewSize ) {
631                                         self._modifyViewPos = (-self._cellSize) + ( self._clipSize - self._maxViewSize );
632                                 }
633                                 if ( self._direction ) {
634                                         self._$clip.width(self._clipSize);
635                                 } else {
636                                         self._$clip.height(self._clipSize);
637                                 }
638                                 self._setScrollBarSize();
639                                 self._setScrollBarPosition(0);
640                                 self._setViewSize();
641                         }
642                 },
643
644                 _initScrollView : function () {
645                         var self = this;
646                         $.extend(self.options, self._scrollView.options);
647                         self.options.moveThreshold = 10;
648                         self.options.showScrollBars = false;
649                         self._getScrollHierarchy = self._scrollView._getScrollHierarchy;
650                         self._makePositioned =  self._scrollView._makePositioned;
651                         self._set_scrollbar_size = self._scrollView._set_scrollbar_size;
652                         self._setStyleTransform = self._scrollView._setElementTransform;
653                         self._hideOverflowIndicator = self._scrollView._hideOverflowIndicator;
654                         self._showOverflowIndicator = self._scrollView._showOverflowIndicator;
655                         self._setGestureScroll = self._scrollView._setGestureScroll;
656                 },
657
658                 _createTracker : function () {
659                         var self = this;
660
661                         self._tracker = new MomentumTracker(self.options);
662                         if ( self._direction ) {
663                                 self._hTracker = self._tracker;
664                                 self._$clip.width(self._clipSize);
665                         } else {
666                                 self._vTracker = self._tracker;
667                                 self._$clip.height(self._clipSize);
668                         }
669                 },
670
671                 //----------------------------------------------------//
672                 //              Scrollbar               //
673                 //----------------------------------------------------//
674                 _createScrollBar : function () {
675                         var self = this,
676                                 prefix = "<div class=\"ui-scrollbar ui-scrollbar-",
677                                 suffix = "\"><div class=\"ui-scrollbar-track\"><div class=\"ui-scrollbar-thumb\"></div></div></div>";
678
679                         if ( self.options.rotation ) {
680                                 return ;
681                         }
682
683                         if ( self._direction ) {
684                                 self._$clip.append( prefix + "x" + suffix );
685                                 self._hScrollBar = $(self._$clip.children(".ui-scrollbar-x"));
686                                 self._hScrollBar.find(".ui-scrollbar-thumb").addClass("ui-scrollbar-thumb-x");
687                         } else {
688                                 self._$clip.append( prefix + "y" + suffix );
689                                 self._vScrollBar = $(self._$clip.children(".ui-scrollbar-y"));
690                                 self._vScrollBar.find(".ui-scrollbar-thumb").addClass("ui-scrollbar-thumb-y");
691                         }
692                 },
693
694                 _setScrollBarSize: function () {
695                         var self = this,
696                                 scrollBarSize = 0,
697                                 currentSize = 0,
698                                 $scrollBar,
699                                 attrName,
700                                 className;
701
702                         if ( self.options.rotation ) {
703                                 return ;
704                         }
705
706                         scrollBarSize = parseInt( self._maxViewSize / self._clipSize , 10);
707                         if ( self._direction ) {
708                                 $scrollBar = self._hScrollBar.find(".ui-scrollbar-thumb");
709                                 attrName = "width";
710                                 currentSize = $scrollBar.width();
711                                 className = "ui-scrollbar-thumb-x";
712                                 self._hScrollBar.css("width", self._clipSize);
713                         } else {
714                                 $scrollBar = self._vScrollBar.find(".ui-scrollbar-thumb");
715                                 attrName = "height";
716                                 className = "ui-scrollbar-thumb-y";
717                                 currentSize = $scrollBar.height();
718                                 self._vScrollBar.css("height", self._clipSize);
719                         }
720
721                         if ( scrollBarSize > currentSize ) {
722                                 $scrollBar.removeClass(className);
723                                 $scrollBar.css(attrName, scrollBarSize);
724                         } else {
725                                 scrollBarSize = currentSize;
726                         }
727
728                         self._itemScrollSize = parseFloat( ( self._clipSize - scrollBarSize ) / ( self._totalRowCnt - self._rowsPerView ) );
729                         self._itemScrollSize = Math.round(self._itemScrollSize * 100) / 100;
730                 },
731
732                 _setScrollBarPosition : function ( di, duration ) {
733                         var self = this,
734                                 $sbt = null,
735                                 x = "0px",
736                                 y = "0px";
737
738                         if ( self.options.rotation ) {
739                                 return ;
740                         }
741
742                         self._currentItemCount = self._currentItemCount + di;
743                         if ( self._vScrollBar ) {
744                                 $sbt = self._vScrollBar .find(".ui-scrollbar-thumb");
745                                 y = ( self._currentItemCount * self._itemScrollSize ) + "px";
746                         } else {
747                                 $sbt = self._hScrollBar .find(".ui-scrollbar-thumb");
748                                 x = ( self._currentItemCount * self._itemScrollSize ) + "px";
749                         }
750                         self._setStyleTransform( $sbt, x, y, duration );
751                 },
752
753                 _hideScrollBars : function () {
754                         var self = this,
755                                 vclass = "ui-scrollbar-visible";
756
757                         if ( self.options.rotation ) {
758                                 return ;
759                         }
760
761                         if ( self._vScrollBar ) {
762                                 self._vScrollBar.removeClass( vclass );
763                         } else {
764                                 self._hScrollBar.removeClass( vclass );
765                         }
766                 },
767
768                 _showScrollBars : function () {
769                         var self = this,
770                                 vclass = "ui-scrollbar-visible";
771
772                         if ( self.options.rotation ) {
773                                 return ;
774                         }
775
776                         if ( self._vScrollBar ) {
777                                 self._vScrollBar.addClass( vclass );
778                         } else {
779                                 self._hScrollBar.addClass( vclass );
780                         }
781                 },
782
783                 //----------------------------------------------------//
784                 //              scroll process          //
785                 //----------------------------------------------------//
786                 centerTo: function ( selector ) {
787                         var self = this,
788                                 i,
789                                 newX = 0,
790                                 newY = 0;
791
792                         if ( !self.options.rotation ) {
793                                 return;
794                         }
795
796                         for ( i = 0; i < self._$rows.length; i++ ) {
797                                 if ( $( self._$rows[i]).hasClass( selector ) ) {
798                                         if ( self._direction ) {
799                                                 newX = -( i * self._cellSize - self._clipSize / 2 + self._cellSize * 2 );
800                                         } else {
801                                                 newY = -( i * self._cellSize - self._clipSize / 2 + self._cellSize * 2 );
802                                         }
803                                         self.scrollTo( newX, newY );
804                                         return;
805                                 }
806                         }
807                 },
808
809                 scrollTo: function ( x, y, duration ) {
810                         var self = this;
811                         if ( self._direction ) {
812                                 self._sx = self._reservedPos;
813                                 self._reservedPos = x;
814                         } else {
815                                 self._sy = self._reservedPos;
816                                 self._reservedPos = y;
817                         }
818                         self._scrollView.scrollTo.apply( this, [ x, y, duration ] );
819                 },
820
821                 getScrollPosition: function () {
822                         if ( this.direction ) {
823                                 return { x: -this._ry, y: 0 };
824                         }
825                         return { x: 0, y: -this._ry };
826                 },
827
828                 _setScrollPosition: function ( x, y ) {
829                         var self = this,
830                                 sy = self._scalableSize,
831                                 distance = self._direction ? x : y,
832                                 dy = distance - sy,
833                                 di = parseInt( dy / self._cellSize, 10 ),
834                                 i = 0,
835                                 idx = 0,
836                                 $row = null;
837
838                         if ( self._blockScroll ) {
839                                 return ;
840                         }
841
842                         if ( ! self.options.rotation ) {
843                                 if ( dy > 0 && distance >= -self._cellSize && self._scalableSize >= -self._cellSize ) {
844                                         // top
845                                         self._stopMScroll();
846                                         self._scalableSize = -self._cellSize;
847                                         self._setElementTransform( -self._cellSize );
848                                         return;
849                                 }
850                                 if ( (dy < 0 && self._scalableSize <= -(self._maxSize + self._cellSize) )) {
851                                         // bottom
852                                         self._stopMScroll();
853                                         self._scalableSize = -(self._maxSize + self._cellSize);
854                                         self._setElementTransform( self._modifyViewPos );
855                                         return;
856                                 }
857                         }
858
859                         if ( di > 0 ) { // scroll up
860                                 for ( i = 0; i < di; i++ ) {
861                                         idx = -parseInt( ( sy / self._cellSize ) + i + 3, 10 );
862                                         $row = self._$view.children( ).last( ).detach( );
863                                         self._replaceRow( $row, circularNum( idx, self._totalRowCnt ) );
864                                         self._$view.prepend( $row );
865                                         self._setScrollBarPosition(-1);
866                                 }
867                         } else if ( di < 0 ) { // scroll down
868                                 for ( i = 0; i > di; i-- ) {
869                                         idx = self._rowsPerView - parseInt( ( sy / self._cellSize ) + i, 10 );
870                                         $row = self._$view.children().first().detach();
871                                         self._replaceRow($row, circularNum( idx, self._totalRowCnt ) );
872                                         self._$view.append( $row );
873                                         self._setScrollBarPosition(1);
874                                 }
875                         }
876                         self._scalableSize += di * self._cellSize;
877                         self._setElementTransform( distance - self._scalableSize - self._cellSize );
878                 },
879
880                 _setElementTransform : function ( value ) {
881                         var self = this,
882                                 x = 0,
883                                 y = 0;
884
885                         if ( self._direction ) {
886                                 x = value + "px";
887                         } else {
888                                 y = value + "px";
889                         }
890                         self._setStyleTransform(self._$view, x, y );
891                 },
892
893                 //----------------------------------------------------//
894                 //              Event handler           //
895                 //----------------------------------------------------//
896                 _handleMomentumScroll: function () {
897                         var self = this,
898                                 opts = self.options,
899                                 keepGoing = false,
900                                 v = this._$view,
901                                 x = 0,
902                                 y = 0,
903                                 t = self._tracker;
904
905                         if ( t ) {
906                                 t.update();
907                                 if ( self._direction ) {
908                                         x = t.getPosition();
909                                 } else {
910                                         y = t.getPosition();
911                                 }
912                                 keepGoing = !t.done();
913                         }
914
915                         self._setScrollPosition( x, y );
916                         if ( !opts.rotation ) {
917                                 keepGoing = !t.done();
918                                 self._reservedPos = self._direction ? x : y;
919                                 // bottom
920                                 self._reservedPos = self._reservedPos <= (-(self._maxSize - self._modifyViewPos)) ? ( - ( self._maxSize + self._cellSize) ) : self._reservedPos;
921                                 // top
922                                 self._reservedPos = self._reservedPos > -self._cellSize ? -self._cellSize : self._reservedPos;
923                         } else {
924                                 self._reservedPos = self._direction ? x : y;
925                         }
926                         self._$clip.trigger( self.options.updateEventName, [ { x: x, y: y } ] );
927
928                         if ( keepGoing ) {
929                                 self._timerID = setTimeout( self._timerCB, self._timerInterval );
930                         } else {
931                                 self._stopMScroll();
932                         }
933                 },
934
935                 _startMScroll: function ( speedX, speedY ) {
936                         var self = this;
937                         if ( self._direction  ) {
938                                 self._sx = self._reservedPos;
939                         } else {
940                                 self._sy = self._reservedPos;
941                         }
942                         self._scrollView._startMScroll.apply(self, [speedX, speedY]);
943                 },
944
945                 _stopMScroll: function () {
946                         this._scrollView._stopMScroll.apply(this);
947                 },
948
949                 _enableTracking: function () {
950                         $(document).bind( this._dragMoveEvt, this._dragMoveCB );
951                         $(document).bind( this._dragStopEvt, this._dragStopCB );
952                 },
953
954                 _disableTracking: function () {
955                         $(document).unbind( this._dragMoveEvt, this._dragMoveCB );
956                         $(document).unbind( this._dragStopEvt, this._dragStopCB );
957                 },
958
959                 _handleDragStart: function ( e, ex, ey ) {
960                         var self = this;
961                         self._scrollView._handleDragStart.apply( this, [ e, ex, ey ] );
962                         self._eventPos = self._direction ? ex : ey;
963                         self._nextPos = self._reservedPos;
964                 },
965
966                 _handleDragMove: function ( e, ex, ey ) {
967                         var self = this,
968                                 dx = ex - self._lastX,
969                                 dy = ey - self._lastY,
970                                 x = 0,
971                                 y = 0;
972
973                         self._lastMove = getCurrentTime();
974                         self._speedX = dx;
975                         self._speedY = dy;
976
977                         self._didDrag = true;
978
979                         self._lastX = ex;
980                         self._lastY = ey;
981
982                         if ( self._direction ) {
983                                 self._movePos = ex - self._eventPos;
984                                 x = self._nextPos + self._movePos;
985                         } else {
986                                 self._movePos = ey - self._eventPos;
987                                 y = self._nextPos + self._movePos;
988                         }
989                         self._showScrollBars();
990                         self._setScrollPosition( x, y );
991                         return false;
992                 },
993
994                 _handleDragStop: function ( e ) {
995                         var self = this;
996
997                         self._reservedPos = self._movePos ? self._nextPos + self._movePos : self._reservedPos;
998                         self._scrollView._handleDragStop.apply( this, [ e ] );
999                         return self._didDrag ? false : undefined;
1000                 },
1001
1002                 _addBehaviors: function () {
1003                         var self = this;
1004
1005                         // scroll event handler.
1006                         if ( self.options.eventType === "mouse" ) {
1007                                 self._dragStartEvt = "mousedown";
1008                                 self._dragStartCB = function ( e ) {
1009                                         return self._handleDragStart( e, e.clientX, e.clientY );
1010                                 };
1011
1012                                 self._dragMoveEvt = "mousemove";
1013                                 self._dragMoveCB = function ( e ) {
1014                                         return self._handleDragMove( e, e.clientX, e.clientY );
1015                                 };
1016
1017                                 self._dragStopEvt = "mouseup";
1018                                 self._dragStopCB = function ( e ) {
1019                                         return self._handleDragStop( e, e.clientX, e.clientY );
1020                                 };
1021
1022                                 self._$view.bind( "vclick", function (e) {
1023                                         return !self._didDrag;
1024                                 } );
1025                         } else { //touch
1026                                 self._dragStartEvt = "touchstart";
1027                                 self._dragStartCB = function ( e ) {
1028                                         var t = e.originalEvent.targetTouches[0];
1029                                         return self._handleDragStart(e, t.pageX, t.pageY );
1030                                 };
1031
1032                                 self._dragMoveEvt = "touchmove";
1033                                 self._dragMoveCB = function ( e ) {
1034                                         var t = e.originalEvent.targetTouches[0];
1035                                         return self._handleDragMove(e, t.pageX, t.pageY );
1036                                 };
1037
1038                                 self._dragStopEvt = "touchend";
1039                                 self._dragStopCB = function ( e ) {
1040                                         return self._handleDragStop( e );
1041                                 };
1042                         }
1043                         self._$view.bind( self._dragStartEvt, self._dragStartCB );
1044
1045                         // other events.
1046                         self._$view.delegate(".virtualgrid-item", "click", function (event) {
1047                                 var $selectedItem = $(this);
1048                                 $selectedItem.trigger("select", this);
1049                         });
1050
1051                         $( window ).bind("resize", function ( e ) {
1052                                 var height = 0,
1053                                         $virtualgrid = $(".ui-virtualgrid-view");
1054                                 if ( $virtualgrid.length !== 0 ) {
1055                                         if ( self._direction ) {
1056                                                 height = self._calculateClipHeight();
1057                                                 self._$view.height(height);
1058                                                 self._$clip.height(height);
1059                                         } else {
1060                                                 height = self._calculateClipWidth();
1061                                                 self._$view.width(height);
1062                                                 self._$clip.width(height);
1063                                         }
1064                                         self.resize( );
1065                                 }
1066                         });
1067
1068                         $(document).one("pageshow", function (event) {
1069                                 var $page = $(self.element).parents(".ui-page"),
1070                                         $header = $page.find( ":jqmData(role='header')" ),
1071                                         $footer = $page.find( ":jqmData(role='footer')" ),
1072                                         $content = $page.find( ":jqmData(role='content')" ),
1073                                         footerHeight = $footer ? $footer.height() : 0,
1074                                         headerHeight = $header ? $header.height() : 0;
1075
1076                                 if ( $page && $content ) {
1077                                         $content.height(window.innerHeight - headerHeight - footerHeight).css("overflow", "hidden");
1078                                         $content.addClass("ui-virtualgrid-content");
1079                                 }
1080                         });
1081                 },
1082
1083                 //----------------------------------------------------//
1084                 //              Calculate size about dom element.               //
1085                 //----------------------------------------------------//
1086                 _calculateClipSize : function () {
1087                         var self = this,
1088                                 clipSize = 0;
1089
1090                         if ( self._direction ) {
1091                                 clipSize = self._calculateClipWidth();
1092                         } else {
1093                                 clipSize = self._calculateClipHeight();
1094                         }
1095                         return clipSize;
1096                 },
1097
1098                 _calculateClipWidth : function () {
1099                         var self = this,
1100                                 view = $(self.element),
1101                                 $parent = $(self.element).parent(),
1102                                 paddingValue = 0,
1103                                 clipSize = $(window).width();
1104                         if ( $parent.hasClass("ui-content") ) {
1105                                 paddingValue = parseInt($parent.css("padding-left"), 10);
1106                                 clipSize = clipSize - ( paddingValue || 0 );
1107                                 paddingValue = parseInt($parent.css("padding-right"), 10);
1108                                 clipSize = clipSize - ( paddingValue || 0);
1109                         } else {
1110                                 clipSize = view.width();
1111                         }
1112                         return clipSize;
1113                 },
1114
1115                 _calculateClipHeight : function () {
1116                         var self = this,
1117                                 view = $(self.element),
1118                                 $parent = $(self.element).parent(),
1119                                 header = null,
1120                                 footer = null,
1121                                 paddingValue = 0,
1122                                 clipSize = $(window).height();
1123                         if ( $parent.hasClass("ui-content") ) {
1124                                 paddingValue = parseInt($parent.css("padding-top"), 10);
1125                                 clipSize = clipSize - ( paddingValue || 0 );
1126                                 paddingValue = parseInt($parent.css("padding-bottom"), 10);
1127                                 clipSize = clipSize - ( paddingValue || 0);
1128                                 header = $parent.siblings(".ui-header");
1129                                 footer = $parent.siblings(".ui-footer");
1130
1131                                 if ( header ) {
1132                                         if ( header.outerHeight(true) === null ) {
1133                                                 clipSize = clipSize - ( $(".ui-header").outerHeight() || 0 );
1134                                         } else {
1135                                                 clipSize = clipSize - header.outerHeight(true);
1136                                         }
1137                                 }
1138                                 if ( footer ) {
1139                                         clipSize = clipSize - footer.outerHeight(true);
1140                                 }
1141                         } else {
1142                                 clipSize = view.height();
1143                         }
1144                         return clipSize;
1145                 },
1146
1147                 _calculateColumnSize : function () {
1148                         var self = this,
1149                                 $tempBlock,
1150                                 $cell;
1151
1152                         $tempBlock = self._makeRows( 1 );
1153                         self._$view.append( $tempBlock.children().first() );
1154                         if ( self._direction ) {
1155                                 // x-axis
1156                                 self._viewSize = self._$view.width();
1157                                 $cell = self._$view.children().first().children().first();
1158                                 self._cellSize = $cell.outerWidth(true);
1159                                 self._cellOtherSize = $cell.outerHeight(true);
1160                         } else {
1161                                 // y-axis
1162                                 self._viewSize = self._$view.height();
1163                                 $cell = self._$view.children().first().children().first();
1164                                 self._cellSize = $cell.outerHeight(true);
1165                                 self._cellOtherSize = $cell.outerWidth(true);
1166                         }
1167                         $tempBlock.remove();
1168                         self._$view.children().remove();
1169                 },
1170
1171                 _calculateColumnCount : function ( ) {
1172                         var self = this,
1173                                 $view = $(self.element),
1174                                 viewSize = self._direction ? $view.innerHeight() : $view.innerWidth(),
1175                                 itemCount = 0 ;
1176
1177                         if ( self._direction ) {
1178                                 viewSize = viewSize - ( parseInt( $view.css("padding-top"), 10 ) + parseInt( $view.css("padding-bottom"), 10 ) );
1179                         } else {
1180                                 viewSize = viewSize - ( parseInt( $view.css("padding-left"), 10 ) + parseInt( $view.css("padding-right"), 10 ) );
1181                         }
1182
1183                         itemCount = parseInt( (viewSize / self._cellOtherSize), 10);
1184                         return itemCount > 0 ? itemCount : 1 ;
1185                 },
1186
1187                 // Read the position of clip form property ('webkit-transform').
1188                 // @return : number - position of clip.
1189                 _getClipPosition : function () {
1190                         var self = this,
1191                                 matrix = null,
1192                                 contents = null,
1193                                 result = -self._cellSize,
1194                                 $scrollview = self._$view.closest(".ui-scrollview-view");
1195
1196                         if ( $scrollview ) {
1197                                 matrix = $scrollview.css("-webkit-transform");
1198                                 contents = matrix.substr( 7 );
1199                                 contents = contents.substr( 0, contents.length - 1 );
1200                                 contents = contents.split( ', ' );
1201                                 result =  Math.abs(contents [5]);
1202                         }
1203                         return result;
1204                 },
1205
1206                 //----------------------------------------------------//
1207                 //              DOM Element handle              //
1208                 //----------------------------------------------------//
1209                 _makeRows : function ( count ) {
1210                         var self = this,
1211                                 opts = self.options,
1212                                 index = 0,
1213                                 $row = null,
1214                                 $wrapper = null;
1215
1216                         $wrapper = $(document.createElement("div"));
1217                         $wrapper.addClass("ui-scrollview-view");
1218                         for ( index = 0; index < count ; index += 1 ) {
1219                                 $row = self._makeRow( self._template, index );
1220                                 if ( self._direction ) {
1221                                         $row.css("top", 0).css("left", ( index * self._cellSize ));
1222                                 }
1223                                 $wrapper.append($row);
1224                         }
1225                         return $wrapper;
1226                 },
1227
1228                 // make a single row block
1229                 _makeRow : function ( myTemplate, rowIndex ) {
1230                         var self = this,
1231                                 opts = self.options,
1232                                 index = rowIndex * self._itemCount,
1233                                 htmlData = null,
1234                                 itemData = null,
1235                                 colIndex = 0,
1236                                 attrName = self._direction ? "top" : "left",
1237                                 blockClassName = self._direction ? "ui-virtualgrid-wrapblock-x" : "ui-virtualgrid-wrapblock-y",
1238                                 blockAttrName = self._direction ? "top" : "left",
1239                                 wrapBlock = $( document.createElement( "div" ));
1240
1241                         wrapBlock.addClass( blockClassName ).attr("row-index", rowIndex);
1242                         for ( colIndex = 0; colIndex < self._itemCount; colIndex++ ) {
1243                                 itemData = self._itemData( index );
1244                                 if ( itemData ) {
1245                                         htmlData = self._makeHtmlData( myTemplate, index, colIndex);
1246                                         wrapBlock.append( htmlData );
1247                                         index += 1;
1248                                 }
1249                         }
1250                         return wrapBlock;
1251                 },
1252
1253                 _makeHtmlData : function ( myTemplate, dataIndex, colIndex ) {
1254                         var self = this,
1255                                 htmlData = null,
1256                                 itemData = null,
1257                                 attrName = self._direction ? "top" : "left";
1258
1259                         itemData = self._itemData( dataIndex );
1260                         if ( itemData ) {
1261                                 htmlData = myTemplate.tmpl( itemData );
1262                                 $(htmlData).css(attrName, ( colIndex * self._cellOtherSize )).addClass("virtualgrid-item");
1263                         }
1264                         return htmlData;
1265                 },
1266
1267                 _increaseRow : function ( num ) {
1268                         var self = this,
1269                                 rotation = self.options.rotation,
1270                                 $row = null,
1271                                 headItemIndex = 0,
1272                                 tailItemIndex = 0,
1273                                 itemIndex = 0,
1274                                 size = self._scalableSize,
1275                                 idx = 0;
1276
1277                         headItemIndex = parseInt( $(self._$view.children().first()).attr("row-index"), 10) - 1;
1278                         tailItemIndex = parseInt( $(self._$view.children()[self._rowsPerView]).attr("row-index"), 10) + 1;
1279
1280                         for ( idx = 1 ; idx <= num ; idx++ ) {
1281                                 if ( tailItemIndex + idx  >= self._totalRowCnt ) {
1282                                         $row = self._makeRow( self._template, headItemIndex );
1283                                         self._$view.prepend($row);
1284                                         headItemIndex -= 1;
1285                                 } else {
1286                                         $row = self._makeRow( self._template, tailItemIndex + idx );
1287                                         self._$view.append($row);
1288                                 }
1289                                 if ( self._direction ) {
1290                                         $row.width(self._cellSize);
1291                                 } else {
1292                                         $row.height(self._cellSize);
1293                                 }
1294                         }
1295                 },
1296
1297                 _decreaseRow : function ( num ) {
1298                         var self = this,
1299                                 idx = 0;
1300
1301                         for ( idx = 0 ; idx < num ; idx++ ) {
1302                                 self._$view.children().last().remove();
1303                         }
1304                 },
1305
1306                 _replaceRows : function ( curCnt, prevCnt, maxCnt, clipPosition ) {
1307                         var self = this,
1308                                 $rows = self._$view.children(),
1309                                 prevRowIndex = 0,
1310                                 rowIndex = 0,
1311                                 diffRowCnt = 0,
1312                                 targetCnt = 1,
1313                                 filterCondition = ( self._filterRatio * self._cellSize) + self._cellSize,
1314                                 idx = 0;
1315
1316                         if ( filterCondition < clipPosition ) {
1317                                 targetCnt += 1;
1318                         }
1319
1320                         prevRowIndex = parseInt( $($rows[targetCnt]).attr("row-index"), 10);
1321                         if ( prevRowIndex === 0 ) {
1322                                 // only top.
1323                                 rowIndex = maxCnt - targetCnt;
1324                         } else {
1325                                 rowIndex = Math.round( (prevRowIndex * prevCnt) / curCnt );
1326                                 if ( rowIndex + self._rowsPerView >= maxCnt ) {
1327                                         // only bottom.
1328                                         rowIndex = maxCnt - self._rowsPerView;
1329                                 }
1330                                 diffRowCnt = prevRowIndex - rowIndex;
1331                                 rowIndex -= targetCnt;
1332                         }
1333
1334                         for ( idx = 0 ; idx < $rows.length ; idx += 1 ) {
1335                                 self._replaceRow($rows[idx], circularNum( rowIndex, self._totalRowCnt ));
1336                                 rowIndex++;
1337                         }
1338                         return -diffRowCnt;
1339                 },
1340
1341                 _replaceRow : function ( block, index ) {
1342                         var self = this,
1343                                 opts = self.options,
1344                                 $columns = null,
1345                                 $column = null,
1346                                 data = null,
1347                                 htmlData = null,
1348                                 myTemplate = null,
1349                                 idx = 0,
1350                                 dataIdx = 0,
1351                                 tempBlocks = null;
1352
1353                         $columns = $(block).attr("row-index", index).children();
1354                         if ( $columns.length !== self._itemCount ) {
1355                                 $(block).children().remove();
1356                                 tempBlocks = $(self._makeRow( self._template, index ));
1357                                 $(block).append(tempBlocks.children());
1358                                 tempBlocks.remove();
1359                                 return ;
1360                         }
1361
1362                         dataIdx = index * self._itemCount;
1363                         for ( idx = 0; idx < self._itemCount ; idx += 1 ) {
1364                                 $column = $columns[idx];
1365                                 data = self._itemData(dataIdx);
1366                                 if ( $column && data ) {
1367                                         myTemplate = self._template;
1368                                         htmlData = myTemplate.tmpl( data );
1369                                         self._replace( $column, htmlData, false );
1370                                         htmlData.remove();      // Clear temporary htmlData to free cache
1371                                         dataIdx ++;
1372                                 } else if ($column && !data ) {
1373                                         $($column).remove();
1374                                 }
1375                         }
1376                 },
1377
1378                 /* Text & image src replace function */
1379                 // @param oldItem   : prev HtmlDivElement
1380                 // @param newItem   : new HtmlDivElement for replace
1381                 // @param key       :
1382                 _replace : function ( oldItem, newItem, key ) {
1383                         $( oldItem ).find( ".ui-li-text-main", ".ui-li-text-sub", "ui-btn-text" ).each( function ( index ) {
1384                                 var oldObj = $( this ),
1385                                         newText = $( newItem ).find( ".ui-li-text-main", ".ui-li-text-sub", "ui-btn-text" ).eq( index ).text();
1386
1387                                 $( oldObj ).contents().filter( function () {
1388                                         return ( this.nodeType == 3 );
1389                                 }).get( 0 ).data = newText;
1390                         });
1391
1392                         $( oldItem ).find( "img" ).each( function ( imgIndex ) {
1393                                 var oldObj = $( this ),
1394                                         newImg = $( newItem ).find( "img" ).eq( imgIndex ).attr( "src" );
1395
1396                                 $( oldObj ).attr( "src", newImg );
1397                         });
1398                         $( oldItem).removeData();
1399                         if ( key ) {
1400                                 $( oldItem ).data( key, $( newItem ).data( key ) );
1401                         }
1402                 }
1403         } );
1404
1405         $( document ).bind( "pagecreate create", function ( e ) {
1406                 $(":jqmData(role='virtualgrid')").virtualgrid();
1407         } );
1408 } (jQuery, window, document) );