Revert "Export"
[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 ( function ($, window, document, undefined) {
103
104         function circularNum (num, total) {
105                 var n = num % total;
106                 if (n < 0) {
107                         n = total + n;
108                 }
109                 return n;
110         }
111
112         function MomentumTracker (options) {
113                 this.options = $.extend({}, options);
114                 this.easing = "easeOutQuad";
115                 this.reset();
116         }
117
118         var tstates = {
119                 scrolling : 0,
120                 done : 1
121         };
122
123         function getCurrentTime () {
124                 return Date.now();
125         }
126
127         $.extend (MomentumTracker.prototype, {
128                 start : function (pos, speed, duration) {
129                         this.state = (speed !== 0 ) ? tstates.scrolling : tstates.done;
130                         this.pos = pos;
131                         this.speed = speed;
132                         this.duration = duration;
133
134                         this.fromPos = 0;
135                         this.toPos = 0;
136
137                         this.startTime = getCurrentTime();
138                 },
139
140                 reset : function () {
141                         this.state = tstates.done;
142                         this.pos = 0;
143                         this.speed = 0;
144                         this.duration = 0;
145                 },
146
147                 update : function () {
148                         var state = this.state, duration, elapsed, dx, x;
149
150                         if (state == tstates.done) {
151                                 return this.pos;
152                         }
153                         duration = this.duration;
154                         elapsed = getCurrentTime () - this.startTime;
155                         elapsed = elapsed > duration ? duration : elapsed;
156                         dx = this.speed * (1 - $.easing[this.easing] (elapsed / duration, elapsed, 0, 1, duration) );
157                         x = this.pos + dx;
158                         this.pos = x;
159
160                         if (elapsed >= duration) {
161                                 this.state = tstates.done;
162                         }
163                         return this.pos;
164                 },
165
166                 done : function () {
167                         return this.state == tstates.done;
168                 },
169
170                 getPosition : function () {
171                         return this.pos;
172                 }
173         });
174
175         jQuery.widget ("mobile.virtualgrid", jQuery.mobile.widget, {
176                 options : {
177                         // virtualgrid option
178                         template : "",
179                         direction : "y",
180                         rotation : false,
181                         itemcount : 1
182                 },
183
184                 create : function () {
185                         this._create.apply( this, arguments );
186                 },
187
188                 _create : function ( args ) {
189                         $.extend(this, {
190                                 // view
191                                 _$view : null,
192                                 _$clip : null,
193                                 _$rows : null,
194                                 _tracker : null,
195                                 _viewSize : 0,
196                                 _clipSize : 0,
197                                 _cellSize : undefined,
198                                 _currentItemCount : 0,
199                                 _itemCount : 1,
200                                 _isAuto : false,
201
202                                 // timer
203                                 _timerInterval : 0,
204                                 _timerID : 0,
205                                 _timerCB : null,
206                                 _lastMove : null,
207
208                                 // Data
209                                 _itemData : function ( idx ) { return null; },
210                                 _numItemData : 0,
211                                 _cacheItemData : function ( minIdx, maxIdx ) { },
212                                 _totalRowCnt : 0,
213                                 _template : null,
214                                 _maxViewSize : 0,
215                                 _modifyViewPos : 0,
216                                 _maxSize : 0,
217
218                                 // axis - ( true : x , false : y )
219                                 _direction : false,
220                                 _didDrag : true,
221                                 _reservedPos : 0,
222                                 _scalableSize : 0,
223                                 _eventPos : 0,
224                                 _nextPos : 0,
225                                 _movePos : 0,
226                                 _lastY : 0,
227                                 _speedY : 0,
228                                 _lastX : 0,
229                                 _speedX : 0,
230                                 _rowsPerView : 0,
231
232                                 _filterRatio : 0.9
233                         });
234
235                         var self = this,
236                                 $dom = $(self.element),
237                                 opts = self.options,
238                                 $item = null;
239
240                         // itemData
241                         // If mandatory options are not given, Do nothing.
242                         if ( !args ) {
243                                 return ;
244                         }
245
246                         if ( !self._loadData(args) ) {
247                                 return;
248                         }
249
250                         // set a scroll direction.
251                         self._direction = opts.direction === 'x' ? true : false;
252
253                         // itemcount is assigned 'auto'
254                         if ( typeof opts.itemcount === "string" && opts.itemcount.toUpperCase() == "AUTO" ) {
255                                 self._isAuto = true;
256                         }
257
258                         // make view layer
259                         self._$clip = $(self.element).addClass("ui-scrollview-clip").addClass("ui-virtualgrid-view");
260                         $item = $(document.createElement("div")).addClass("ui-scrollview-view");
261                         self._clipSize =  self._calculateClipSize();
262                         self._$clip.append($item);
263                         self._$view = $item;
264                         self._$clip.css("overflow", "hidden");
265                         self._$view.css("overflow", "hidden");
266
267                         // inherit from scrollview widget.
268                         self._scrollView = $.tizen.scrollview.prototype;
269                         self._initScrollView();
270
271                         // create tracker.
272                         self._createTracker();
273                         self._makePositioned(self._$clip);
274                         self._timerInterval = 1000 / self.options.fps;
275
276                         self._timerID = 0;
277                         self._timerCB = function () {
278                                 self._handleMomentumScroll();
279                         };
280                         $dom.closest(".ui-content").addClass("ui-virtualgrid-content").css("overflow", "hidden");
281
282                         // add event handler.
283                         self._addBehaviors();
284
285                         self._currentItemCount = 0;
286                         self._createScrollBar();
287                         self.refresh();
288                 },
289
290                 // The argument is checked for compliance with the specified format.
291                 // @param args   : Object
292                 // @return boolean
293                 _loadData : function ( args ) {
294                         var self = this;
295
296                         if ( args.itemData && typeof args.itemData == 'function'  ) {
297                                 self._itemData = args.itemData;
298                         } else {
299                                 return false;
300                         }
301                         if ( args.numItemData ) {
302                                 if ( typeof args.numItemData == 'function' ) {
303                                         self._numItemData = args.numItemData( );
304                                 } else if ( typeof args.numItemData == 'number' ) {
305                                         self._numItemData = args.numItemData;
306                                 } else {
307                                         return false;
308                                 }
309                         } else {
310                                 return false;
311                         }
312                         return true;
313                 },
314
315                 // Make up the first screen.
316                 _initLayout: function () {
317                         var self = this,
318                                 opts = self.options,
319                                 i,
320                                 $row;
321
322                         for ( i = -1; i < self._rowsPerView + 1; i += 1 ) {
323                                 $row = self._$rows[ circularNum( i, self._$rows.length ) ];
324                                 self._$view.append( $row );
325                         }
326                         self._setElementTransform( -self._cellSize );
327
328                         self._replaceRow(self._$view.children().first(), self._totalRowCnt - 1);
329                         if ( opts.rotation && self._rowsPerView >= self._totalRowCnt ) {
330                                 self._replaceRow(self._$view.children().last(), 0);
331                         }
332                         self._setViewSize();
333                 },
334
335                 _setViewSize : function () {
336                         var self = this,
337                                 height = 0,
338                                 width = 0;
339
340                         if ( self._direction ) {
341                                 width = self._cellSize * ( self._rowsPerView + 2 );
342                                 width = parseInt(width, 10) + 1;
343                                 self._$view.width( width );
344                                 self._viewSize = self._$view.width();
345                         } else {
346                                 self._$view.height( self._cellSize * ( self._rowsPerView + 2 ) );
347                                 self._$clip.height( self._clipSize );
348                                 self._viewSize = self._$view.height();
349                         }
350                 },
351
352                 refresh : function () {
353                         var self = this,
354                                 opts = self.options,
355                                 width = 0,
356                                 height = 0;
357
358                         self._template = $( "#" + opts.template );
359                         if ( !self._template ) {
360                                 return ;
361                         }
362
363                         width = self._calculateClipWidth();
364                         height = self._calculateClipHeight();
365                         self._$view.width(width).height(height);
366                         self._$clip.width(width).height(height);
367
368                         self._clipSize = self._calculateClipSize();
369                         self._calculateColumnSize();
370                         self._initPageProperty();
371                         self._setScrollBarSize( );
372                 },
373
374                 _initPageProperty : function () {
375                         var self = this,
376                                 rowsPerView = 0,
377                                 $child,
378                                 columnCount = 0,
379                                 totalRowCnt = 0,
380                                 attributeName = self._direction ? "width" : "height";
381
382                         if ( self._isAuto ) {
383                                 columnCount = self._calculateColumnCount();
384                         } else {
385                                 columnCount = self.options.itemcount;
386                         }
387                         totalRowCnt = parseInt(self._numItemData / columnCount , 10 );
388                         self._totalRowCnt = self._numItemData % columnCount === 0 ? totalRowCnt : totalRowCnt + 1;
389                         self._itemCount = columnCount;
390
391                         if ( self._cellSize <= 0) {
392                                 return ;
393                         }
394
395                         rowsPerView = self._clipSize / self._cellSize;
396                         rowsPerView = Math.ceil( rowsPerView );
397                         self._rowsPerView = parseInt( rowsPerView, 10);
398
399                         $child = self._makeRows( rowsPerView + 2 );
400                         $(self._$view).append($child.children());
401                         self._$view.children().css(attributeName, self._cellSize + "px");
402                         self._$rows = self._$view.children().detach();
403
404                         self._reservedPos = -self._cellSize;
405                         self._scalableSize = -self._cellSize;
406
407                         self._initLayout();
408
409                         self._blockScroll = self._rowsPerView > self._totalRowCnt;
410                         self._maxSize = ( self._totalRowCnt - self._rowsPerView ) * self._cellSize;
411                         self._maxViewSize = ( self._rowsPerView ) * self._cellSize;
412                         self._modifyViewPos = -self._cellSize;
413                         if ( self._clipSize < self._maxViewSize ) {
414                                 self._modifyViewPos = (-self._cellSize) + ( self._clipSize - self._maxViewSize );
415                         }
416                 },
417
418                 resize : function ( ) {
419                         var self = this,
420                                 rowsPerView = 0,
421                                 itemCount = 0,
422                                 totalRowCnt = 0,
423                                 diffRowCnt = 0,
424                                 clipSize = 0,
425                                 prevcnt = 0,
426                                 clipPosition = 0;
427
428                         itemCount = self._calculateColumnCount();
429                         if ( self._isAuto && itemCount != self._itemCount ) {
430                                 totalRowCnt = parseInt(self._numItemData / itemCount , 10 );
431                                 self._totalRowCnt = self._numItemData % itemCount === 0 ? totalRowCnt : totalRowCnt + 1;
432                                 prevcnt = self._itemCount;
433                                 self._itemCount = itemCount;
434                                 clipPosition = self._getClipPosition();
435                                 self._$view.hide();
436
437                                 diffRowCnt = self._replaceRows(itemCount, prevcnt, self._totalRowCnt, clipPosition);
438                                 self._maxSize = ( self._totalRowCnt - self._rowsPerView ) * self._cellSize;
439                                 self._scalableSize += (-diffRowCnt) * self._cellSize;
440                                 self._reservedPos  += (-diffRowCnt) * self._cellSize;
441                                 self._setScrollBarSize();
442                                 self._setScrollBarPosition(diffRowCnt);
443
444                                 self._$view.show();
445                         }
446
447                         clipSize = self._calculateClipSize();
448                         if ( clipSize !== self._clipSize ) {
449                                 rowsPerView = clipSize / self._cellSize;
450                                 rowsPerView = parseInt( Math.ceil( rowsPerView ), 10 );
451
452                                 if ( rowsPerView > self._rowsPerView ) {
453                                         // increase row.
454                                         self._increaseRow( rowsPerView - self._rowsPerView );
455                                 } else if ( rowsPerView < self._rowsPerView ) {
456                                         // decrease row.
457                                         self._decreaseRow( self._rowsPerView - rowsPerView );
458                                 }
459                                 self._rowsPerView = rowsPerView;
460                                 self._clipSize = clipSize;
461                                 self._blockScroll = self._rowsPerView > self._totalRowCnt;
462                                 self._maxSize = ( self._totalRowCnt - self._rowsPerView ) * self._cellSize;
463                                 self._maxViewSize = ( self._rowsPerView ) * self._cellSize;
464                                 if ( self._clipSize < self._maxViewSize ) {
465                                         self._modifyViewPos = (-self._cellSize) + ( self._clipSize - self._maxViewSize );
466                                 }
467                                 if ( self._direction ) {
468                                         self._$clip.width(self._clipSize);
469                                 } else {
470                                         self._$clip.height(self._clipSize);
471                                 }
472                                 self._setScrollBarSize();
473                                 self._setScrollBarPosition(0);
474                                 self._setViewSize();
475                         }
476                 },
477
478                 _initScrollView : function () {
479                         var self = this;
480                         $.extend(self.options, self._scrollView.options);
481                         self.options.moveThreshold = 10;
482                         self.options.showScrollBars = false;
483                         self._getScrollHierarchy = self._scrollView._getScrollHierarchy;
484                         self._makePositioned =  self._scrollView._makePositioned;
485                         self._set_scrollbar_size = self._scrollView._set_scrollbar_size;
486                         self._setStyleTransform = self._scrollView._setElementTransform;
487                 },
488
489                 _createTracker : function () {
490                         var self = this;
491
492                         self._tracker = new MomentumTracker(self.options);
493                         if ( self._direction ) {
494                                 self._hTracker = self._tracker;
495                                 self._$clip.width(self._clipSize);
496                         } else {
497                                 self._vTracker = self._tracker;
498                                 self._$clip.height(self._clipSize);
499                         }
500                 },
501
502                 //----------------------------------------------------//
503                 //              Scrollbar               //
504                 //----------------------------------------------------//
505                 _createScrollBar : function () {
506                         var self = this,
507                                 prefix = "<div class=\"ui-scrollbar ui-scrollbar-",
508                                 suffix = "\"><div class=\"ui-scrollbar-track\"><div class=\"ui-scrollbar-thumb\"></div></div></div>";
509
510                         if ( self.options.rotation ) {
511                                 return ;
512                         }
513
514                         if ( self._direction ) {
515                                 self._$clip.append( prefix + "x" + suffix );
516                                 self._hScrollBar = $(self._$clip.children(".ui-scrollbar-x"));
517                                 self._hScrollBar.find(".ui-scrollbar-thumb").addClass("ui-scrollbar-thumb-x");
518                         } else {
519                                 self._$clip.append( prefix + "y" + suffix );
520                                 self._vScrollBar = $(self._$clip.children(".ui-scrollbar-y"));
521                                 self._vScrollBar.find(".ui-scrollbar-thumb").addClass("ui-scrollbar-thumb-y");
522                         }
523                 },
524
525                 _setScrollBarSize: function () {
526                         var self = this,
527                                 scrollBarSize = 0,
528                                 currentSize = 0,
529                                 $scrollBar,
530                                 attrName,
531                                 className;
532
533                         if ( self.options.rotation ) {
534                                 return ;
535                         }
536
537                         scrollBarSize = parseInt( self._maxViewSize / self._clipSize , 10);
538                         if ( self._direction ) {
539                                 $scrollBar = self._hScrollBar.find(".ui-scrollbar-thumb");
540                                 attrName = "width";
541                                 currentSize = $scrollBar.width();
542                                 className = "ui-scrollbar-thumb-x";
543                                 self._hScrollBar.css("width", self._clipSize);
544                         } else {
545                                 $scrollBar = self._vScrollBar.find(".ui-scrollbar-thumb");
546                                 attrName = "height";
547                                 className = "ui-scrollbar-thumb-y";
548                                 currentSize = $scrollBar.height();
549                                 self._vScrollBar.css("height", self._clipSize);
550                         }
551
552                         if ( scrollBarSize > currentSize ) {
553                                 $scrollBar.removeClass(className);
554                                 $scrollBar.css(attrName, scrollBarSize);
555                         } else {
556                                 scrollBarSize = currentSize;
557                         }
558
559                         self._itemScrollSize = parseFloat( ( self._clipSize - scrollBarSize ) / ( self._totalRowCnt - self._rowsPerView ) );
560                         self._itemScrollSize = Math.round(self._itemScrollSize * 100) / 100;
561                 },
562
563                 _setScrollBarPosition : function ( di, duration ) {
564                         var self = this,
565                                 $sbt = null,
566                                 x = "0px",
567                                 y = "0px";
568
569                         if ( self.options.rotation ) {
570                                 return ;
571                         }
572
573                         self._currentItemCount = self._currentItemCount + di;
574                         if ( self._vScrollBar ) {
575                                 $sbt = self._vScrollBar .find(".ui-scrollbar-thumb");
576                                 y = ( self._currentItemCount * self._itemScrollSize ) + "px";
577                         } else {
578                                 $sbt = self._hScrollBar .find(".ui-scrollbar-thumb");
579                                 x = ( self._currentItemCount * self._itemScrollSize ) + "px";
580                         }
581                         self._setStyleTransform( $sbt, x, y, duration );
582                 },
583
584                 _hideScrollBars : function () {
585                         var self = this,
586                                 vclass = "ui-scrollbar-visible";
587
588                         if ( self.options.rotation ) {
589                                 return ;
590                         }
591
592                         if ( self._vScrollBar ) {
593                                 self._vScrollBar.removeClass( vclass );
594                         } else {
595                                 self._hScrollBar.removeClass( vclass );
596                         }
597                 },
598
599                 _showScrollBars : function () {
600                         var self = this,
601                                 vclass = "ui-scrollbar-visible";
602
603                         if ( self.options.rotation ) {
604                                 return ;
605                         }
606
607                         if ( self._vScrollBar ) {
608                                 self._vScrollBar.addClass( vclass );
609                         } else {
610                                 self._hScrollBar.addClass( vclass );
611                         }
612                 },
613
614                 //----------------------------------------------------//
615                 //              scroll process          //
616                 //----------------------------------------------------//
617                 centerTo: function ( selector ) {
618                         var self = this,
619                                 i,
620                                 newX = 0,
621                                 newY = 0;
622
623                         if ( !self.options.rotation ) {
624                                 return;
625                         }
626
627                         for ( i = 0; i < self._$rows.length; i++ ) {
628                                 if ( $( self._$rows[i]).hasClass( selector ) ) {
629                                         if ( self._direction ) {
630                                                 newX = -( i * self._cellSize - self._clipSize / 2 + self._cellSize * 2 );
631                                         } else {
632                                                 newY = -( i * self._cellSize - self._clipSize / 2 + self._cellSize * 2 );
633                                         }
634                                         self.scrollTo( newX, newY );
635                                         return;
636                                 }
637                         }
638                 },
639
640                 scrollTo: function ( x, y, duration ) {
641                         var self = this;
642                         if ( self._direction ) {
643                                 self._sx = self._reservedPos;
644                                 self._reservedPos = x;
645                         } else {
646                                 self._sy = self._reservedPos;
647                                 self._reservedPos = y;
648                         }
649                         self._scrollView.scrollTo.apply( this, [ x, y, duration ] );
650                 },
651
652                 getScrollPosition: function () {
653                         if ( this.direction ) {
654                                 return { x: -this._ry, y: 0 };
655                         }
656                         return { x: 0, y: -this._ry };
657                 },
658
659                 _setScrollPosition: function ( x, y ) {
660                         var self = this,
661                                 sy = self._scalableSize,
662                                 distance = self._direction ? x : y,
663                                 dy = distance - sy,
664                                 di = parseInt( dy / self._cellSize, 10 ),
665                                 i = 0,
666                                 idx = 0,
667                                 $row = null;
668
669                         if ( self._blockScroll ) {
670                                 return ;
671                         }
672
673                         if ( ! self.options.rotation ) {
674                                 if ( dy > 0 && distance >= -self._cellSize && self._scalableSize >= -self._cellSize ) {
675                                         // top
676                                         self._stopMScroll();
677                                         self._scalableSize = -self._cellSize;
678                                         self._setElementTransform( -self._cellSize );
679                                         return;
680                                 }
681                                 if ( (dy < 0 && self._scalableSize <= -(self._maxSize + self._cellSize) )) {
682                                         // bottom
683                                         self._stopMScroll();
684                                         self._scalableSize = -(self._maxSize + self._cellSize);
685                                         self._setElementTransform( self._modifyViewPos );
686                                         return;
687                                 }
688                         }
689
690                         if ( di > 0 ) { // scroll up
691                                 for ( i = 0; i < di; i++ ) {
692                                         idx = -parseInt( ( sy / self._cellSize ) + i + 3, 10 );
693                                         $row = self._$view.children( ).last( ).detach( );
694                                         self._replaceRow( $row, circularNum( idx, self._totalRowCnt ) );
695                                         self._$view.prepend( $row );
696                                         self._setScrollBarPosition(-1);
697                                 }
698                         } else if ( di < 0 ) { // scroll down
699                                 for ( i = 0; i > di; i-- ) {
700                                         idx = self._rowsPerView - parseInt( ( sy / self._cellSize ) + i, 10 );
701                                         $row = self._$view.children().first().detach();
702                                         self._replaceRow($row, circularNum( idx, self._totalRowCnt ) );
703                                         self._$view.append( $row );
704                                         self._setScrollBarPosition(1);
705                                 }
706                         }
707                         self._scalableSize += di * self._cellSize;
708                         self._setElementTransform( distance - self._scalableSize - self._cellSize );
709                 },
710
711                 _setElementTransform : function ( value ) {
712                         var self = this,
713                                 x = 0,
714                                 y = 0;
715
716                         if ( self._direction ) {
717                                 x = value + "px";
718                         } else {
719                                 y = value + "px";
720                         }
721                         self._setStyleTransform(self._$view, x, y );
722                 },
723
724                 //----------------------------------------------------//
725                 //              Event handler           //
726                 //----------------------------------------------------//
727                 _handleMomentumScroll: function () {
728                         var self = this,
729                                 opts = self.options,
730                                 keepGoing = false,
731                                 v = this._$view,
732                                 x = 0,
733                                 y = 0,
734                                 t = self._tracker;
735
736                         if ( t ) {
737                                 t.update();
738                                 if ( self._direction ) {
739                                         x = t.getPosition();
740                                 } else {
741                                         y = t.getPosition();
742                                 }
743                                 keepGoing = !t.done();
744                         }
745
746                         self._setScrollPosition( x, y );
747                         if ( !opts.rotation ) {
748                                 keepGoing = !t.done();
749                                 self._reservedPos = self._direction ? x : y;
750                                 // bottom
751                                 self._reservedPos = self._reservedPos <= (-(self._maxSize - self._modifyViewPos)) ? ( - ( self._maxSize + self._cellSize) ) : self._reservedPos;
752                                 // top
753                                 self._reservedPos = self._reservedPos > -self._cellSize ? -self._cellSize : self._reservedPos;
754                         } else {
755                                 self._reservedPos = self._direction ? x : y;
756                         }
757                         self._$clip.trigger( self.options.updateEventName, [ { x: x, y: y } ] );
758
759                         if ( keepGoing ) {
760                                 self._timerID = setTimeout( self._timerCB, self._timerInterval );
761                         } else {
762                                 self._stopMScroll();
763                         }
764                 },
765
766                 _startMScroll: function ( speedX, speedY ) {
767                         var self = this;
768                         if ( self._direction  ) {
769                                 self._sx = self._reservedPos;
770                         } else {
771                                 self._sy = self._reservedPos;
772                         }
773                         self._scrollView._startMScroll.apply(self, [speedX, speedY]);
774                 },
775
776                 _stopMScroll: function () {
777                         this._scrollView._stopMScroll.apply(this);
778                 },
779
780                 _enableTracking: function () {
781                         $(document).bind( this._dragMoveEvt, this._dragMoveCB );
782                         $(document).bind( this._dragStopEvt, this._dragStopCB );
783                 },
784
785                 _disableTracking: function () {
786                         $(document).unbind( this._dragMoveEvt, this._dragMoveCB );
787                         $(document).unbind( this._dragStopEvt, this._dragStopCB );
788                 },
789
790                 _handleDragStart: function ( e, ex, ey ) {
791                         var self = this;
792                         self._scrollView._handleDragStart.apply( this, [ e, ex, ey ] );
793                         self._eventPos = self._direction ? ex : ey;
794                         self._nextPos = self._reservedPos;
795                 },
796
797                 _handleDragMove: function ( e, ex, ey ) {
798                         var self = this,
799                                 dx = ex - self._lastX,
800                                 dy = ey - self._lastY,
801                                 x = 0,
802                                 y = 0;
803
804                         self._lastMove = getCurrentTime();
805                         self._speedX = dx;
806                         self._speedY = dy;
807
808                         self._didDrag = true;
809
810                         self._lastX = ex;
811                         self._lastY = ey;
812
813                         if ( self._direction ) {
814                                 self._movePos = ex - self._eventPos;
815                                 x = self._nextPos + self._movePos;
816                         } else {
817                                 self._movePos = ey - self._eventPos;
818                                 y = self._nextPos + self._movePos;
819                         }
820                         self._showScrollBars();
821                         self._setScrollPosition( x, y );
822                         return false;
823                 },
824
825                 _handleDragStop: function ( e ) {
826                         var self = this;
827
828                         self._reservedPos = self._movePos ? self._nextPos + self._movePos : self._reservedPos;
829                         self._scrollView._handleDragStop.apply( this, [ e ] );
830                         return self._didDrag ? false : undefined;
831                 },
832
833                 _addBehaviors: function () {
834                         var self = this;
835
836                         // scroll event handler.
837                         if ( self.options.eventType === "mouse" ) {
838                                 self._dragStartEvt = "mousedown";
839                                 self._dragStartCB = function ( e ) {
840                                         return self._handleDragStart( e, e.clientX, e.clientY );
841                                 };
842
843                                 self._dragMoveEvt = "mousemove";
844                                 self._dragMoveCB = function ( e ) {
845                                         return self._handleDragMove( e, e.clientX, e.clientY );
846                                 };
847
848                                 self._dragStopEvt = "mouseup";
849                                 self._dragStopCB = function ( e ) {
850                                         return self._handleDragStop( e, e.clientX, e.clientY );
851                                 };
852
853                                 self._$view.bind( "vclick", function (e) {
854                                         return !self._didDrag;
855                                 } );
856                         } else { //touch
857                                 self._dragStartEvt = "touchstart";
858                                 self._dragStartCB = function ( e ) {
859                                         var t = e.originalEvent.targetTouches[0];
860                                         return self._handleDragStart(e, t.pageX, t.pageY );
861                                 };
862
863                                 self._dragMoveEvt = "touchmove";
864                                 self._dragMoveCB = function ( e ) {
865                                         var t = e.originalEvent.targetTouches[0];
866                                         return self._handleDragMove(e, t.pageX, t.pageY );
867                                 };
868
869                                 self._dragStopEvt = "touchend";
870                                 self._dragStopCB = function ( e ) {
871                                         return self._handleDragStop( e );
872                                 };
873                         }
874                         self._$view.bind( self._dragStartEvt, self._dragStartCB );
875
876                         // other events.
877                         self._$view.delegate(".virtualgrid-item", "click", function (event) {
878                                 var $selectedItem = $(this);
879                                 $selectedItem.trigger("select", this);
880                         });
881
882                         $( window ).bind("resize", function ( e ) {
883                                 var height = 0,
884                                         $virtualgrid = $(".ui-virtualgrid-view");
885                                 if ( $virtualgrid.length !== 0 ) {
886                                         if ( self._direction ) {
887                                                 height = self._calculateClipHeight();
888                                                 self._$view.height(height);
889                                                 self._$clip.height(height);
890                                         } else {
891                                                 height = self._calculateClipWidth();
892                                                 self._$view.width(height);
893                                                 self._$clip.width(height);
894                                         }
895                                         self.resize( );
896                                 }
897                         });
898
899                         $(document).one("pageshow", function (event) {
900                                 var $page = $(self.element).parents(".ui-page"),
901                                         $header = $page.find( ":jqmData(role='header')" ),
902                                         $footer = $page.find( ":jqmData(role='footer')" ),
903                                         $content = $page.find( ":jqmData(role='content')" ),
904                                         footerHeight = $footer ? $footer.height() : 0,
905                                         headerHeight = $header ? $header.height() : 0;
906
907                                 if ( $page && $content ) {
908                                         $content.height(window.innerHeight - headerHeight - footerHeight).css("overflow", "hidden");
909                                         $content.addClass("ui-virtualgrid-content");
910                                 }
911                         });
912                 },
913
914                 //----------------------------------------------------//
915                 //              Calculate size about dom element.               //
916                 //----------------------------------------------------//
917                 _calculateClipSize : function () {
918                         var self = this,
919                                 clipSize = 0;
920
921                         if ( self._direction ) {
922                                 clipSize = self._calculateClipWidth();
923                         } else {
924                                 clipSize = self._calculateClipHeight();
925                         }
926                         return clipSize;
927                 },
928
929                 _calculateClipWidth : function () {
930                         var self = this,
931                                 view = $(self.element),
932                                 $parent = $(self.element).parent(),
933                                 paddingValue = 0,
934                                 clipSize = $(window).width();
935                         if ( $parent.hasClass("ui-content") ) {
936                                 paddingValue = parseInt($parent.css("padding-left"), 10);
937                                 clipSize = clipSize - ( paddingValue || 0 );
938                                 paddingValue = parseInt($parent.css("padding-right"), 10);
939                                 clipSize = clipSize - ( paddingValue || 0);
940                         } else {
941                                 clipSize = view.width();
942                         }
943                         return clipSize;
944                 },
945
946                 _calculateClipHeight : function () {
947                         var self = this,
948                                 view = $(self.element),
949                                 $parent = $(self.element).parent(),
950                                 header = null,
951                                 footer = null,
952                                 paddingValue = 0,
953                                 clipSize = $(window).height();
954                         if ( $parent.hasClass("ui-content") ) {
955                                 paddingValue = parseInt($parent.css("padding-top"), 10);
956                                 clipSize = clipSize - ( paddingValue || 0 );
957                                 paddingValue = parseInt($parent.css("padding-bottom"), 10);
958                                 clipSize = clipSize - ( paddingValue || 0);
959                                 header = $parent.siblings(".ui-header");
960                                 footer = $parent.siblings(".ui-footer");
961
962                                 if ( header ) {
963                                         if ( header.outerHeight(true) === null ) {
964                                                 clipSize = clipSize - ( $(".ui-header").outerHeight() || 0 );
965                                         } else {
966                                                 clipSize = clipSize - header.outerHeight(true);
967                                         }
968                                 }
969                                 if ( footer ) {
970                                         clipSize = clipSize - footer.outerHeight(true);
971                                 }
972                         } else {
973                                 clipSize = view.height();
974                         }
975                         return clipSize;
976                 },
977
978                 _calculateColumnSize : function () {
979                         var self = this,
980                                 $tempBlock,
981                                 $cell;
982
983                         $tempBlock = self._makeRows( 1 );
984                         self._$view.append( $tempBlock.children().first() );
985                         if ( self._direction ) {
986                                 // x-axis
987                                 self._viewSize = self._$view.width();
988                                 $cell = self._$view.children().first().children().first();
989                                 self._cellSize = $cell.outerWidth(true);
990                                 self._cellOtherSize = $cell.outerHeight(true);
991                         } else {
992                                 // y-axis
993                                 self._viewSize = self._$view.height();
994                                 $cell = self._$view.children().first().children().first();
995                                 self._cellSize = $cell.outerHeight(true);
996                                 self._cellOtherSize = $cell.outerWidth(true);
997                         }
998                         $tempBlock.remove();
999                         self._$view.children().remove();
1000                 },
1001
1002                 _calculateColumnCount : function ( ) {
1003                         var self = this,
1004                                 $view = $(self.element),
1005                                 viewSize = self._direction ? $view.innerHeight() : $view.innerWidth(),
1006                                 itemCount = 0 ;
1007
1008                         if ( self._direction ) {
1009                                 viewSize = viewSize - ( parseInt( $view.css("padding-top"), 10 ) + parseInt( $view.css("padding-bottom"), 10 ) );
1010                         } else {
1011                                 viewSize = viewSize - ( parseInt( $view.css("padding-left"), 10 ) + parseInt( $view.css("padding-right"), 10 ) );
1012                         }
1013
1014                         itemCount = parseInt( (viewSize / self._cellOtherSize), 10);
1015                         return itemCount > 0 ? itemCount : 1 ;
1016                 },
1017
1018                 // Read the position of clip form property ('webkit-transform').
1019                 // @return : number - position of clip.
1020                 _getClipPosition : function () {
1021                         var self = this,
1022                                 matrix = null,
1023                                 contents = null,
1024                                 result = -self._cellSize,
1025                                 $scrollview = self._$view.closest(".ui-scrollview-view");
1026
1027                         if ( $scrollview ) {
1028                                 matrix = $scrollview.css("-webkit-transform");
1029                                 contents = matrix.substr( 7 );
1030                                 contents = contents.substr( 0, contents.length - 1 );
1031                                 contents = contents.split( ', ' );
1032                                 result =  Math.abs(contents [5]);
1033                         }
1034                         return result;
1035                 },
1036
1037                 //----------------------------------------------------//
1038                 //              DOM Element handle              //
1039                 //----------------------------------------------------//
1040                 _makeRows : function ( count ) {
1041                         var self = this,
1042                                 opts = self.options,
1043                                 index = 0,
1044                                 $row = null,
1045                                 $wrapper = null;
1046
1047                         $wrapper = $(document.createElement("div"));
1048                         $wrapper.addClass("ui-scrollview-view");
1049                         for ( index = 0; index < count ; index += 1 ) {
1050                                 $row = self._makeRow( self._template, index );
1051                                 if ( self._direction ) {
1052                                         $row.css("top", 0).css("left", ( index * self._cellSize ));
1053                                 }
1054                                 $wrapper.append($row);
1055                         }
1056                         return $wrapper;
1057                 },
1058
1059                 // make a single row block
1060                 _makeRow : function ( myTemplate, rowIndex ) {
1061                         var self = this,
1062                                 opts = self.options,
1063                                 index = rowIndex * self._itemCount,
1064                                 htmlData = null,
1065                                 itemData = null,
1066                                 colIndex = 0,
1067                                 attrName = self._direction ? "top" : "left",
1068                                 blockClassName = self._direction ? "ui-virtualgrid-wrapblock-x" : "ui-virtualgrid-wrapblock-y",
1069                                 blockAttrName = self._direction ? "top" : "left",
1070                                 wrapBlock = $( document.createElement( "div" ));
1071
1072                         wrapBlock.addClass( blockClassName ).attr("row-index", rowIndex);
1073                         for ( colIndex = 0; colIndex < self._itemCount; colIndex++ ) {
1074                                 itemData = self._itemData( index );
1075                                 if ( itemData ) {
1076                                         htmlData = self._makeHtmlData( myTemplate, index, colIndex);
1077                                         wrapBlock.append( htmlData );
1078                                         index += 1;
1079                                 }
1080                         }
1081                         return wrapBlock;
1082                 },
1083
1084                 _makeHtmlData : function ( myTemplate, dataIndex, colIndex ) {
1085                         var self = this,
1086                                 htmlData = null,
1087                                 itemData = null,
1088                                 attrName = self._direction ? "top" : "left";
1089
1090                         itemData = self._itemData( dataIndex );
1091                         if ( itemData ) {
1092                                 htmlData = myTemplate.tmpl( itemData );
1093                                 $(htmlData).css(attrName, ( colIndex * self._cellOtherSize )).addClass("virtualgrid-item");
1094                         }
1095                         return htmlData;
1096                 },
1097
1098                 _increaseRow : function ( num ) {
1099                         var self = this,
1100                                 rotation = self.options.rotation,
1101                                 $row = null,
1102                                 headItemIndex = 0,
1103                                 tailItemIndex = 0,
1104                                 itemIndex = 0,
1105                                 size = self._scalableSize,
1106                                 idx = 0;
1107
1108                         headItemIndex = parseInt( $(self._$view.children().first()).attr("row-index"), 10) - 1;
1109                         tailItemIndex = parseInt( $(self._$view.children()[self._rowsPerView]).attr("row-index"), 10) + 1;
1110
1111                         for ( idx = 1 ; idx <= num ; idx++ ) {
1112                                 if ( tailItemIndex + idx  >= self._totalRowCnt ) {
1113                                         $row = self._makeRow( self._template, headItemIndex );
1114                                         self._$view.prepend($row);
1115                                         headItemIndex -= 1;
1116                                 } else {
1117                                         $row = self._makeRow( self._template, tailItemIndex + idx );
1118                                         self._$view.append($row);
1119                                 }
1120                                 if ( self._direction ) {
1121                                         $row.width(self._cellSize);
1122                                 } else {
1123                                         $row.height(self._cellSize);
1124                                 }
1125                         }
1126                 },
1127
1128                 _decreaseRow : function ( num ) {
1129                         var self = this,
1130                                 idx = 0;
1131
1132                         for ( idx = 0 ; idx < num ; idx++ ) {
1133                                 self._$view.children().last().remove();
1134                         }
1135                 },
1136
1137                 _replaceRows : function ( curCnt, prevCnt, maxCnt, clipPosition ) {
1138                         var self = this,
1139                                 $rows = self._$view.children(),
1140                                 prevRowIndex = 0,
1141                                 rowIndex = 0,
1142                                 diffRowCnt = 0,
1143                                 targetCnt = 1,
1144                                 filterCondition = ( self._filterRatio * self._cellSize) + self._cellSize,
1145                                 idx = 0;
1146
1147                         if ( filterCondition < clipPosition ) {
1148                                 targetCnt += 1;
1149                         }
1150
1151                         prevRowIndex = parseInt( $($rows[targetCnt]).attr("row-index"), 10);
1152                         if ( prevRowIndex === 0 ) {
1153                                 // only top.
1154                                 rowIndex = maxCnt - targetCnt;
1155                         } else {
1156                                 rowIndex = Math.round( (prevRowIndex * prevCnt) / curCnt );
1157                                 if ( rowIndex + self._rowsPerView >= maxCnt ) {
1158                                         // only bottom.
1159                                         rowIndex = maxCnt - self._rowsPerView;
1160                                 }
1161                                 diffRowCnt = prevRowIndex - rowIndex;
1162                                 rowIndex -= targetCnt;
1163                         }
1164
1165                         for ( idx = 0 ; idx < $rows.length ; idx += 1 ) {
1166                                 self._replaceRow($rows[idx], circularNum( rowIndex, self._totalRowCnt ));
1167                                 rowIndex++;
1168                         }
1169                         return -diffRowCnt;
1170                 },
1171
1172                 _replaceRow : function ( block, index ) {
1173                         var self = this,
1174                                 opts = self.options,
1175                                 $columns = null,
1176                                 $column = null,
1177                                 data = null,
1178                                 htmlData = null,
1179                                 myTemplate = null,
1180                                 idx = 0,
1181                                 dataIdx = 0,
1182                                 tempBlocks = null;
1183
1184                         $columns = $(block).attr("row-index", index).children();
1185                         if ( $columns.length !== self._itemCount ) {
1186                                 $(block).children().remove();
1187                                 tempBlocks = $(self._makeRow( self._template, index ));
1188                                 $(block).append(tempBlocks.children());
1189                                 tempBlocks.remove();
1190                                 return ;
1191                         }
1192
1193                         dataIdx = index * self._itemCount;
1194                         for ( idx = 0; idx < self._itemCount ; idx += 1 ) {
1195                                 $column = $columns[idx];
1196                                 data = self._itemData(dataIdx);
1197                                 if ( $column && data ) {
1198                                         myTemplate = self._template;
1199                                         htmlData = myTemplate.tmpl( data );
1200                                         self._replace( $column, htmlData, false );
1201                                         htmlData.remove();      // Clear temporary htmlData to free cache
1202                                         dataIdx ++;
1203                                 } else if ($column && !data ) {
1204                                         $($column).remove();
1205                                 }
1206                         }
1207                 },
1208
1209                 /* Text & image src replace function */
1210                 // @param oldItem   : prev HtmlDivElement
1211                 // @param newItem   : new HtmlDivElement for replace
1212                 // @param key       :
1213                 _replace : function ( oldItem, newItem, key ) {
1214                         $( oldItem ).find( ".ui-li-text-main", ".ui-li-text-sub", "ui-btn-text" ).each( function ( index ) {
1215                                 var oldObj = $( this ),
1216                                         newText = $( newItem ).find( ".ui-li-text-main", ".ui-li-text-sub", "ui-btn-text" ).eq( index ).text();
1217
1218                                 $( oldObj ).contents().filter( function () {
1219                                         return ( this.nodeType == 3 );
1220                                 }).get( 0 ).data = newText;
1221                         });
1222
1223                         $( oldItem ).find( "img" ).each( function ( imgIndex ) {
1224                                 var oldObj = $( this ),
1225                                         newImg = $( newItem ).find( "img" ).eq( imgIndex ).attr( "src" );
1226
1227                                 $( oldObj ).attr( "src", newImg );
1228                         });
1229                         $( oldItem).removeData();
1230                         if ( key ) {
1231                                 $( oldItem ).data( key, $( newItem ).data( key ) );
1232                         }
1233                 }
1234         } );
1235
1236         $( document ).bind( "pagecreate create", function ( e ) {
1237                 $(":jqmData(role='virtualgrid')").virtualgrid();
1238         } );
1239 } (jQuery, window, document) );