1 /* ***************************************************************************
2 * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
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:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
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 * ***************************************************************************
23 * Author: Kangsik Kim <kangsik81.kim@samsung.com>
24 * Youmin Ha <youmin.ha@samsung.com>
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.
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-direction : This option define the direction of the scroll.
44 * You must choose one of the 'x' and 'y' (Default : y)
45 * data-rotation : This option defines whether or not the circulation of the data.
46 * If option is 'true' and scroll is reached the last data,
47 * Widget will present the first data on the screen.
48 * If option is ‘false’, Widget will operate like a scrollview.
50 * ID : <DIV> element that has "data-role=virtualgrid" must have ID attribute.
55 * itemData: function ( idx ) { return json_obj; },
56 * numItemData: number or function () { return number; },
57 * cacheItemData: function ( minIdx, maxIdx ) {}
59 * : Create VirtualGrid widget. At this moment, _create method is called.
60 * args : A collection of options
61 * itemData: A function that returns JSON object for given index. Mandatory.
62 * numItemData: Total number of itemData. Mandatory.
63 * cacheItemData: Virtuallist will ask itemData between minIdx and maxIdx.
64 * Developers can implement this function for preparing data.
68 * : Find a DOM Element with the given class name.
69 * This element will be centered on the screen.
70 * Serveral elements were found, the first element is displayed.
73 * scrollstart : : This event triggers when a user begin to move the scroll on VirtualGrid.
74 * scrollupdate : : This event triggers while a user moves the scroll on VirtualGrid.
75 * scrollstop : This event triggers when a user stop the scroll on VirtualGrid.
76 * select : This event triggers when a cell is selected.
80 * <script id="tizen-demo-namecard" type="text/x-jquery-tmpl">
81 * <div class="ui-demo-namecard">
82 * <div class="ui-demo-namecard-pic">
83 * <img class="ui-demo-namecard-pic-img" src="${TEAM_LOGO}" />
85 * <div class="ui-demo-namecard-contents">
86 * <span class="name ui-li-text-main">${NAME}</span>
87 * <span class="active ui-li-text-sub">${ACTIVE}</span>
88 * <span class="from ui-li-text-sub">${FROM}</span>
92 * <div id="virtualgrid-demo" data-role="virtualgrid" data-template="tizen-demo-namecard" >
97 // most of following codes are derived from jquery.mobile.scrollview.js
101 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.
103 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.
105 To add a virtual grid widget to the application, use the following code:
107 <script id="tizen-demo-namecard" type="text/x-jquery-tmpl">
108 <div class="ui-demo-namecard">
109 <div class="ui-demo-namecard-pic">
110 <img class="ui-demo-namecard-pic-img" src="${TEAM_LOGO}" />
112 <div class="ui-demo-namecard-contents">
113 <span class="name ui-li-text-main">${NAME}</span>
117 <div id="virtualgrid-demo" data-role="virtualgrid" data-template="tizen-demo-namecard">
121 @property {String} data-template
122 Specifies the jQuery.template element ID.
123 The jQuery.template must be defined. The template style can use rem units to support scalability.
126 @property {String} data-direction
127 Defines the scroll direction. The direction options are x (horizontal) and y (vertical).
128 The default value is y.
131 @property {Boolean} data-rotation
132 Defines whether the data elements are displayed from the beginning of the list again once the end of file is reached.
133 The default value is false.
137 The scrollstart event is fired when the user starts scrolling through the grid:
139 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
141 $(".selector").virtualgrid
143 scrollstart: function(event, ui)
145 // Handle the scrollstart event
149 $(".selector").bind("scrollstart", function(event, ui)
151 // Handle the scrollstart event
156 The scrollupdate event is fired when the user moves the scroll bar in the grid:
158 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
160 $(".selector").virtualgrid
162 scrollupdate: function(event, ui)
164 // Handle the scrollupdate event
168 $(".selector").bind("scrollupdate", function(event, ui)
170 // Handle the scrollupdate event
175 The scrollstop event is fired when the user stops scrolling:
177 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
179 $(".selector").virtualgrid
181 scrollstop: function(event, ui)
183 // Handle the scrollstop event
187 $(".selector").bind("scrollstop", function(event, ui)
189 // Handle the scrollstop event
194 The select event is fired when a virtual grid cell is selected:
196 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
198 $(".selector").virtualgrid
200 select: function(event, ui)
202 // Handle the select event
206 $(".selector").bind("select", function(event, ui)
208 // Handle the select event
213 @param {function} itemData(index)
214 @param {Number} numItemData
215 @param {function} cacheItemData(minIndex, maxIndex)
216 The create method is used to call the jQuery _create method. In the method parameters:
218 function itemData(index) returns the JSON object matched with the given index. The index value is between 0 and numItemData-1.<br/>
219 number numItemData or function numItemData() defines or returns a static number of items.<br/>
220 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/>
222 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
223 function itemData(idx)
227 function cacheItemData(minIdx, maxIdx)
229 // Prepare JSON data between minIdx and maxIdx
231 var numItemData = DATA.length;
232 $(".selector").virtualgrid("create",
234 itemData, numItemData, cacheItemData
239 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.
241 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
242 $(".selector").virtualgrid("centerTo", "selector");
246 The resize method is used to rearrange the DOM elements to fit a new screen size when the screen is resized:
248 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
249 ".selector").virtualgrid("resize");
254 ( function ($, window, document, undefined) {
256 function circularNum (num, total) {
264 function MomentumTracker (options) {
265 this.options = $.extend({}, options);
266 this.easing = "easeOutQuad";
275 function getCurrentTime () {
279 $.extend (MomentumTracker.prototype, {
280 start : function (pos, speed, duration) {
281 this.state = (speed !== 0 ) ? tstates.scrolling : tstates.done;
284 this.duration = duration;
289 this.startTime = getCurrentTime();
292 reset : function () {
293 this.state = tstates.done;
299 update : function () {
300 var state = this.state, duration, elapsed, dx, x;
302 if (state == tstates.done) {
305 duration = this.duration;
306 elapsed = getCurrentTime () - this.startTime;
307 elapsed = elapsed > duration ? duration : elapsed;
308 dx = this.speed * (1 - $.easing[this.easing] (elapsed / duration, elapsed, 0, 1, duration) );
312 if (elapsed >= duration) {
313 this.state = tstates.done;
319 return this.state == tstates.done;
322 getPosition : function () {
327 jQuery.widget ("mobile.virtualgrid", jQuery.mobile.widget, {
329 // virtualgrid option
335 create : function () {
336 this._create.apply( this, arguments );
339 _create : function ( args ) {
348 _cellSize : undefined,
349 _currentItemCount : 0,
351 _inheritedSize : null,
360 _itemData : function ( idx ) { return null; },
362 _cacheItemData : function ( minIdx, maxIdx ) { },
369 // axis - ( true : x , false : y )
388 $dom = $(self.element),
393 // If mandatory options are not given, Do nothing.
398 if ( !self._loadData(args) ) {
403 self._fragment = document.createDocumentFragment();
405 // read defined properties(width and height) from dom element.
406 self._inheritedSize = self._getinheritedSize(self.element);
408 // set a scroll direction.
409 self._direction = opts.direction === 'x' ? true : false;
412 self._$clip = $(self.element).addClass("ui-scrollview-clip").addClass("ui-virtualgrid-view");
413 $item = $(document.createElement("div")).addClass("ui-scrollview-view");
414 self._clipSize = self._calculateClipSize();
415 self._$clip.append($item);
417 self._$clip.css("overflow", "hidden");
418 self._$view.css("overflow", "hidden");
420 // inherit from scrollview widget.
421 self._scrollView = $.tizen.scrollview.prototype;
422 self._initScrollView();
425 self._createTracker();
426 self._makePositioned(self._$clip);
427 self._timerInterval = 1000 / self.options.fps;
430 self._timerCB = function () {
431 self._handleMomentumScroll();
433 $dom.closest(".ui-content").addClass("ui-virtualgrid-content").css("overflow", "hidden");
435 // add event handler.
436 self._addBehaviors();
438 self._currentItemCount = 0;
439 self._createScrollBar();
443 // The argument is checked for compliance with the specified format.
444 // @param args : Object
446 _loadData : function ( args ) {
449 if ( args.itemData && typeof args.itemData == 'function' ) {
450 self._itemData = args.itemData;
454 if ( args.numItemData ) {
455 if ( typeof args.numItemData == 'function' ) {
456 self._numItemData = args.numItemData( );
457 } else if ( typeof args.numItemData == 'number' ) {
458 self._numItemData = args.numItemData;
468 // Make up the first screen.
469 _initLayout: function () {
475 for ( i = -1; i < self._rowsPerView + 1; i += 1 ) {
476 $row = self._$rows[ circularNum( i, self._$rows.length ) ];
477 self._$view.append( $row );
479 self._setElementTransform( -self._cellSize );
481 self._replaceRow(self._$view.children().first(), self._totalRowCnt - 1);
482 if ( opts.rotation && self._rowsPerView >= self._totalRowCnt ) {
483 self._replaceRow(self._$view.children().last(), 0);
488 _setViewSize : function () {
493 if ( self._direction ) {
494 width = self._cellSize * ( self._rowsPerView + 2 );
495 width = parseInt(width, 10) + 1;
496 self._$view.width( width );
497 self._viewSize = self._$view.width();
499 self._$view.height( self._cellSize * ( self._rowsPerView + 2 ) );
500 self._$clip.height( self._clipSize );
501 self._viewSize = self._$view.height();
505 _getViewHeight : function () {
507 return self._$view.height();
510 refresh : function () {
516 self._template = $( "#" + opts.template );
517 if ( !self._template ) {
521 width = self._calculateClipWidth();
522 height = self._calculateClipHeight();
523 self._$view.width(width).height(height);
524 self._$clip.width(width).height(height);
526 self._clipSize = self._calculateClipSize();
527 self._calculateColumnSize();
528 self._initPageProperty();
529 self._setScrollBarSize();
532 _initPageProperty : function () {
538 attributeName = self._direction ? "width" : "height";
540 columnCount = self._calculateColumnCount();
542 totalRowCnt = parseInt(self._numItemData / columnCount , 10 );
543 self._totalRowCnt = self._numItemData % columnCount === 0 ? totalRowCnt : totalRowCnt + 1;
544 self._itemCount = columnCount;
546 if ( self._cellSize <= 0) {
550 rowsPerView = self._clipSize / self._cellSize;
551 rowsPerView = Math.ceil( rowsPerView );
552 self._rowsPerView = parseInt( rowsPerView, 10);
554 $child = self._makeRows( rowsPerView + 2 );
555 $(self._$view).append($child.children());
556 self._$view.children().css(attributeName, self._cellSize + "px");
557 self._$rows = self._$view.children().detach();
559 self._reservedPos = -self._cellSize;
560 self._scalableSize = -self._cellSize;
564 self._blockScroll = self._rowsPerView > self._totalRowCnt;
565 self._maxSize = ( self._totalRowCnt - self._rowsPerView ) * self._cellSize;
566 self._maxViewSize = ( self._rowsPerView ) * self._cellSize;
567 self._modifyViewPos = -self._cellSize;
568 if ( self._clipSize < self._maxViewSize ) {
569 self._modifyViewPos = (-self._cellSize) + ( self._clipSize - self._maxViewSize );
573 _getinheritedSize : function ( elem ) {
574 var $target = $(elem),
577 NODETYPE = { ELEMENT_NODE : 1, TEXT_NODE : 3 },
579 isDefinedWidth : false,
580 isDefinedHeight : false,
585 while ( $target[0].nodeType === NODETYPE.ELEMENT_NODE && (ret.isDefinedWidth === false || ret.isHeightDefined === false )) {
586 height = $target[0].style.height;
587 width = $target[0].style.width;
589 if (ret.isDefinedHeight === false && height !== "" ) {
591 ret.isDefinedHeight = true;
592 ret.height = parseInt(height, 10);
595 if ( ret.isDefinedWidth === false && width !== "" ) {
597 ret.isDefinedWidth = true;
598 ret.width = parseInt(width, 10);
600 $target = $target.parent();
605 resize : function ( ) {
616 itemCount = self._calculateColumnCount();
617 if ( itemCount != self._itemCount ) {
618 totalRowCnt = parseInt(self._numItemData / itemCount , 10 );
619 self._totalRowCnt = self._numItemData % itemCount === 0 ? totalRowCnt : totalRowCnt + 1;
620 prevcnt = self._itemCount;
621 self._itemCount = itemCount;
622 clipPosition = self._getClipPosition();
625 diffRowCnt = self._replaceRows(itemCount, prevcnt, self._totalRowCnt, clipPosition);
626 self._maxSize = ( self._totalRowCnt - self._rowsPerView ) * self._cellSize;
627 self._scalableSize += (-diffRowCnt) * self._cellSize;
628 self._reservedPos += (-diffRowCnt) * self._cellSize;
629 self._setScrollBarSize();
630 self._setScrollBarPosition(diffRowCnt);
635 clipSize = self._calculateClipSize();
636 if ( clipSize !== self._clipSize ) {
637 rowsPerView = clipSize / self._cellSize;
638 rowsPerView = parseInt( Math.ceil( rowsPerView ), 10 );
640 if ( rowsPerView > self._rowsPerView ) {
642 self._increaseRow( rowsPerView - self._rowsPerView );
643 } else if ( rowsPerView < self._rowsPerView ) {
645 self._decreaseRow( self._rowsPerView - rowsPerView );
647 self._rowsPerView = rowsPerView;
648 self._clipSize = clipSize;
649 self._blockScroll = self._rowsPerView > self._totalRowCnt;
650 self._maxSize = ( self._totalRowCnt - self._rowsPerView ) * self._cellSize;
651 self._maxViewSize = ( self._rowsPerView ) * self._cellSize;
652 if ( self._clipSize < self._maxViewSize ) {
653 self._modifyViewPos = (-self._cellSize) + ( self._clipSize - self._maxViewSize );
655 if ( self._direction ) {
656 self._$clip.width(self._clipSize);
658 self._$clip.height(self._clipSize);
660 self._setScrollBarSize();
661 self._setScrollBarPosition(0);
666 _initScrollView : function () {
668 $.extend(self.options, self._scrollView.options);
669 self.options.moveThreshold = 10;
670 self.options.showScrollBars = false;
671 self._getScrollHierarchy = self._scrollView._getScrollHierarchy;
672 self._makePositioned = self._scrollView._makePositioned;
673 self._set_scrollbar_size = self._scrollView._set_scrollbar_size;
674 self._setStyleTransform = self._scrollView._setElementTransform;
675 self._hideOverflowIndicator = self._scrollView._hideOverflowIndicator;
676 self._showOverflowIndicator = self._scrollView._showOverflowIndicator;
677 self._setGestureScroll = self._scrollView._setGestureScroll;
680 _createTracker : function () {
683 self._tracker = new MomentumTracker(self.options);
684 if ( self._direction ) {
685 self._hTracker = self._tracker;
686 self._$clip.width(self._clipSize);
688 self._vTracker = self._tracker;
689 self._$clip.height(self._clipSize);
693 //----------------------------------------------------//
695 //----------------------------------------------------//
696 _createScrollBar : function () {
698 prefix = "<div class=\"ui-scrollbar ui-scrollbar-",
699 suffix = "\"><div class=\"ui-scrollbar-track\"><div class=\"ui-scrollbar-thumb\"></div></div></div>";
701 if ( self.options.rotation ) {
705 if ( self._direction ) {
706 self._$clip.append( prefix + "x" + suffix );
707 self._hScrollBar = $(self._$clip.children(".ui-scrollbar-x"));
708 self._hScrollBar.find(".ui-scrollbar-thumb").addClass("ui-scrollbar-thumb-x");
710 self._$clip.append( prefix + "y" + suffix );
711 self._vScrollBar = $(self._$clip.children(".ui-scrollbar-y"));
712 self._vScrollBar.find(".ui-scrollbar-thumb").addClass("ui-scrollbar-thumb-y");
716 _setScrollBarSize: function () {
724 if ( self.options.rotation ) {
728 scrollBarSize = parseInt( self._maxViewSize / self._clipSize , 10);
729 if ( self._direction ) {
730 $scrollBar = self._hScrollBar.find(".ui-scrollbar-thumb");
732 currentSize = $scrollBar.width();
733 className = "ui-scrollbar-thumb-x";
734 self._hScrollBar.css("width", self._clipSize);
736 $scrollBar = self._vScrollBar.find(".ui-scrollbar-thumb");
738 className = "ui-scrollbar-thumb-y";
739 currentSize = $scrollBar.height();
740 self._vScrollBar.css("height", self._clipSize);
743 if ( scrollBarSize > currentSize ) {
744 $scrollBar.removeClass(className);
745 $scrollBar.css(attrName, scrollBarSize);
747 scrollBarSize = currentSize;
750 self._itemScrollSize = parseFloat( ( self._clipSize - scrollBarSize ) / ( self._totalRowCnt - self._rowsPerView ) );
751 self._itemScrollSize = Math.round(self._itemScrollSize * 100) / 100;
754 _setScrollBarPosition : function ( di, duration ) {
760 if ( self.options.rotation ) {
764 self._currentItemCount = self._currentItemCount + di;
765 if ( self._vScrollBar ) {
766 $sbt = self._vScrollBar .find(".ui-scrollbar-thumb");
767 y = ( self._currentItemCount * self._itemScrollSize ) + "px";
769 $sbt = self._hScrollBar .find(".ui-scrollbar-thumb");
770 x = ( self._currentItemCount * self._itemScrollSize ) + "px";
772 self._setStyleTransform( $sbt, x, y, duration );
775 _hideScrollBars : function () {
777 vclass = "ui-scrollbar-visible";
779 if ( self.options.rotation ) {
783 if ( self._vScrollBar ) {
784 self._vScrollBar.removeClass( vclass );
786 self._hScrollBar.removeClass( vclass );
790 _showScrollBars : function () {
792 vclass = "ui-scrollbar-visible";
794 if ( self.options.rotation ) {
798 if ( self._vScrollBar ) {
799 self._vScrollBar.addClass( vclass );
801 self._hScrollBar.addClass( vclass );
805 //----------------------------------------------------//
807 //----------------------------------------------------//
808 centerTo: function ( selector ) {
814 if ( !self.options.rotation ) {
818 for ( i = 0; i < self._$rows.length; i++ ) {
819 if ( $( self._$rows[i]).hasClass( selector ) ) {
820 if ( self._direction ) {
821 newX = -( i * self._cellSize - self._clipSize / 2 + self._cellSize * 2 );
823 newY = -( i * self._cellSize - self._clipSize / 2 + self._cellSize * 2 );
825 self.scrollTo( newX, newY );
831 scrollTo: function ( x, y, duration ) {
833 if ( self._direction ) {
834 self._sx = self._reservedPos;
835 self._reservedPos = x;
837 self._sy = self._reservedPos;
838 self._reservedPos = y;
840 self._scrollView.scrollTo.apply( this, [ x, y, duration ] );
843 getScrollPosition: function () {
844 if ( this.direction ) {
845 return { x: -this._ry, y: 0 };
847 return { x: 0, y: -this._ry };
850 _setScrollPosition: function ( x, y ) {
852 sy = self._scalableSize,
853 distance = self._direction ? x : y,
855 di = parseInt( dy / self._cellSize, 10 ),
860 if ( self._blockScroll ) {
864 if ( ! self.options.rotation ) {
865 if ( dy > 0 && distance >= -self._cellSize && self._scalableSize >= -self._cellSize ) {
868 self._scalableSize = -self._cellSize;
869 self._setElementTransform( -self._cellSize );
872 if ( (dy < 0 && self._scalableSize <= -(self._maxSize + self._cellSize) )) {
875 self._scalableSize = -(self._maxSize + self._cellSize);
876 self._setElementTransform( self._modifyViewPos );
881 if ( di > 0 ) { // scroll up
882 for ( i = 0; i < di; i++ ) {
883 idx = -parseInt( ( sy / self._cellSize ) + i + 3, 10 );
884 $row = self._$view.children( ).last( ).detach( );
885 self._replaceRow( $row, circularNum( idx, self._totalRowCnt ) );
886 self._$view.prepend( $row );
887 self._setScrollBarPosition(-1);
889 } else if ( di < 0 ) { // scroll down
890 for ( i = 0; i > di; i-- ) {
891 idx = self._rowsPerView - parseInt( ( sy / self._cellSize ) + i, 10 );
892 $row = self._$view.children().first().detach();
893 self._replaceRow($row, circularNum( idx, self._totalRowCnt ) );
894 self._$view.append( $row );
895 self._setScrollBarPosition(1);
898 self._scalableSize += di * self._cellSize;
899 self._setElementTransform( distance - self._scalableSize - self._cellSize );
902 _setElementTransform : function ( value ) {
907 if ( self._direction ) {
912 self._setStyleTransform(self._$view, x, y );
915 //----------------------------------------------------//
917 //----------------------------------------------------//
918 _handleMomentumScroll: function () {
929 if ( self._direction ) {
934 keepGoing = !t.done();
937 self._setScrollPosition( x, y );
938 if ( !opts.rotation ) {
939 keepGoing = !t.done();
940 self._reservedPos = self._direction ? x : y;
942 self._reservedPos = self._reservedPos <= (-(self._maxSize - self._modifyViewPos)) ? ( - ( self._maxSize + self._cellSize) ) : self._reservedPos;
944 self._reservedPos = self._reservedPos > -self._cellSize ? -self._cellSize : self._reservedPos;
946 self._reservedPos = self._direction ? x : y;
948 self._$clip.trigger( self.options.updateEventName, [ { x: x, y: y } ] );
951 self._timerID = setTimeout( self._timerCB, self._timerInterval );
957 _startMScroll: function ( speedX, speedY ) {
959 if ( self._direction ) {
960 self._sx = self._reservedPos;
962 self._sy = self._reservedPos;
964 self._scrollView._startMScroll.apply(self, [speedX, speedY]);
967 _stopMScroll: function () {
968 this._scrollView._stopMScroll.apply(this);
971 _enableTracking: function () {
973 self._$view.bind( self._dragMoveEvt, self._dragMoveCB );
974 self._$view.bind( self._dragStopEvt, self._dragStopCB );
975 self._scrollView._enableTracking.apply( self );
978 _disableTracking: function () {
980 self._$view.unbind( self._dragMoveEvt, self._dragMoveCB );
981 self._$view.unbind( self._dragStopEvt, self._dragStopCB );
982 self._scrollView._disableTracking.apply( self );
985 _handleDragStart: function ( e, ex, ey ) {
987 self._scrollView._handleDragStart.apply( this, [ e, ex, ey ] );
988 self._eventPos = self._direction ? ex : ey;
989 self._nextPos = self._reservedPos;
992 _handleDragMove: function ( e, ex, ey ) {
994 dx = ex - self._lastX,
995 dy = ey - self._lastY,
999 self._lastMove = getCurrentTime();
1003 self._didDrag = true;
1008 if ( self._direction ) {
1009 self._movePos = ex - self._eventPos;
1010 x = self._nextPos + self._movePos;
1012 self._movePos = ey - self._eventPos;
1013 y = self._nextPos + self._movePos;
1015 self._showScrollBars();
1016 self._setScrollPosition( x, y );
1020 _handleDragStop: function ( e ) {
1023 self._reservedPos = self._movePos ? self._nextPos + self._movePos : self._reservedPos;
1024 self._scrollView._handleDragStop.apply( this, [ e ] );
1025 return self._didDrag ? false : undefined;
1028 _addBehaviors: function () {
1031 // scroll event handler.
1032 if ( self.options.eventType === "mouse" ) {
1033 self._dragStartEvt = "mousedown";
1034 self._dragStartCB = function ( e ) {
1035 return self._handleDragStart( e, e.clientX, e.clientY );
1038 self._dragMoveEvt = "mousemove";
1039 self._dragMoveCB = function ( e ) {
1040 return self._handleDragMove( e, e.clientX, e.clientY );
1043 self._dragStopEvt = "mouseup";
1044 self._dragStopCB = function ( e ) {
1045 return self._handleDragStop( e, e.clientX, e.clientY );
1048 self._$view.bind( "vclick", function (e) {
1049 return !self._didDrag;
1052 self._dragStartEvt = "touchstart";
1053 self._dragStartCB = function ( e ) {
1054 var t = e.originalEvent.targetTouches[0];
1055 return self._handleDragStart(e, t.pageX, t.pageY );
1058 self._dragMoveEvt = "touchmove";
1059 self._dragMoveCB = function ( e ) {
1060 var t = e.originalEvent.targetTouches[0];
1061 return self._handleDragMove(e, t.pageX, t.pageY );
1064 self._dragStopEvt = "touchend";
1065 self._dragStopCB = function ( e ) {
1066 return self._handleDragStop( e );
1069 self._$view.bind( self._dragStartEvt, self._dragStartCB );
1072 self._$view.delegate(".virtualgrid-item", "click", function (event) {
1073 var $selectedItem = $(this);
1074 $selectedItem.trigger("select", this);
1077 $( window ).bind("resize", function ( e ) {
1079 $virtualgrid = $(".ui-virtualgrid-view");
1080 if ( $virtualgrid.length !== 0 ) {
1081 if ( self._direction ) {
1082 height = self._calculateClipHeight();
1083 self._$view.height(height);
1084 self._$clip.height(height);
1086 height = self._calculateClipWidth();
1087 self._$view.width(height);
1088 self._$clip.width(height);
1094 $(document).one("pageshow", function (event) {
1095 var $page = $(self.element).parents(".ui-page"),
1096 $header = $page.find( ":jqmData(role='header')" ),
1097 $footer = $page.find( ":jqmData(role='footer')" ),
1098 $content = $page.find( ":jqmData(role='content')" ),
1099 footerHeight = $footer ? $footer.height() : 0,
1100 headerHeight = $header ? $header.height() : 0;
1102 if ( $page && $content ) {
1103 $content.height(window.innerHeight - headerHeight - footerHeight).css("overflow", "hidden");
1104 $content.addClass("ui-virtualgrid-content");
1109 //----------------------------------------------------//
1110 // Calculate size about dom element. //
1111 //----------------------------------------------------//
1112 _calculateClipSize : function () {
1116 if ( self._direction ) {
1117 clipSize = self._calculateClipWidth();
1119 clipSize = self._calculateClipHeight();
1124 _calculateClipWidth : function () {
1126 view = $(self.element),
1127 $parent = $(self.element).parent(),
1129 clipSize = $(window).width();
1131 if ( self._inheritedSize.isDefinedWidth ) {
1132 return self._inheritedSize.width;
1135 if ( $parent.hasClass("ui-content") ) {
1136 paddingValue = parseInt($parent.css("padding-left"), 10);
1137 clipSize = clipSize - ( paddingValue || 0 );
1138 paddingValue = parseInt($parent.css("padding-right"), 10);
1139 clipSize = clipSize - ( paddingValue || 0);
1141 clipSize = view.width();
1146 _calculateClipHeight : function () {
1148 view = $(self.element),
1149 $parent = $(self.element).parent(),
1153 clipSize = $(window).height();
1155 if ( self._inheritedSize.isDefinedHeight ) {
1156 return self._inheritedSize.height;
1159 if ( $parent.hasClass("ui-content") ) {
1160 paddingValue = parseInt($parent.css("padding-top"), 10);
1161 clipSize = clipSize - ( paddingValue || 0 );
1162 paddingValue = parseInt($parent.css("padding-bottom"), 10);
1163 clipSize = clipSize - ( paddingValue || 0);
1164 header = $parent.siblings(".ui-header");
1165 footer = $parent.siblings(".ui-footer");
1168 if ( header.outerHeight(true) === null ) {
1169 clipSize = clipSize - ( $(".ui-header").outerHeight() || 0 );
1171 clipSize = clipSize - header.outerHeight(true);
1175 clipSize = clipSize - footer.outerHeight(true);
1178 clipSize = view.height();
1183 _calculateColumnSize : function () {
1188 $tempBlock = self._makeRows( 1 );
1189 self._$view.append( $tempBlock.children().first() );
1190 if ( self._direction ) {
1192 self._viewSize = self._$view.width();
1193 $cell = self._$view.children().first().children().first();
1194 self._cellSize = $cell.outerWidth(true);
1195 self._cellOtherSize = $cell.outerHeight(true);
1198 self._viewSize = self._$view.height();
1199 $cell = self._$view.children().first().children().first();
1200 self._cellSize = $cell.outerHeight(true);
1201 self._cellOtherSize = $cell.outerWidth(true);
1203 $tempBlock.remove();
1204 self._$view.children().remove();
1207 _calculateColumnCount : function ( ) {
1209 $view = $(self.element),
1210 viewSize = self._direction ? $view.innerHeight() : $view.innerWidth(),
1213 if ( self._direction ) {
1214 viewSize = viewSize - ( parseInt( $view.css("padding-top"), 10 ) + parseInt( $view.css("padding-bottom"), 10 ) );
1216 viewSize = viewSize - ( parseInt( $view.css("padding-left"), 10 ) + parseInt( $view.css("padding-right"), 10 ) );
1219 itemCount = parseInt( (viewSize / self._cellOtherSize), 10);
1220 return itemCount > 0 ? itemCount : 1 ;
1223 // Read the position of clip form property ('webkit-transform').
1224 // @return : number - position of clip.
1225 _getClipPosition : function () {
1229 result = -self._cellSize,
1230 $scrollview = self._$view.closest(".ui-scrollview-view");
1232 if ( $scrollview ) {
1233 matrix = $scrollview.css("-webkit-transform");
1234 contents = matrix.substr( 7 );
1235 contents = contents.substr( 0, contents.length - 1 );
1236 contents = contents.split( ', ' );
1237 result = Math.abs(contents [5]);
1242 //----------------------------------------------------//
1243 // DOM Element handle //
1244 //----------------------------------------------------//
1245 _makeRows : function ( count ) {
1247 opts = self.options,
1252 $wrapper = $( self._createElement( "div" ) );
1253 $wrapper.addClass("ui-scrollview-view");
1254 for ( index = 0; index < count ; index += 1 ) {
1255 $row = self._makeRow( self._template, index );
1256 if ( self._direction ) {
1257 $row.css("top", 0).css("left", ( index * self._cellSize ));
1259 $wrapper.append($row);
1264 // make a single row block
1265 _makeRow : function ( myTemplate, rowIndex ) {
1267 opts = self.options,
1268 index = rowIndex * self._itemCount,
1272 attrName = self._direction ? "top" : "left",
1273 blockClassName = self._direction ? "ui-virtualgrid-wrapblock-x" : "ui-virtualgrid-wrapblock-y",
1274 blockAttrName = self._direction ? "top" : "left",
1275 wrapBlock = $( self._createElement( "div" ) );
1277 wrapBlock.addClass( blockClassName ).attr("row-index", rowIndex);
1278 for ( colIndex = 0; colIndex < self._itemCount; colIndex++ ) {
1279 htmlData = self._makeHtmlData( myTemplate, index, colIndex);
1281 wrapBlock.append( htmlData );
1288 _makeHtmlData : function ( myTemplate, dataIndex, colIndex ) {
1292 attrName = self._direction ? "top" : "left";
1294 itemData = self._itemData( dataIndex );
1296 htmlData = myTemplate.tmpl( itemData );
1297 $(htmlData).css(attrName, ( colIndex * self._cellOtherSize )).addClass("virtualgrid-item");
1302 _increaseRow : function ( num ) {
1304 rotation = self.options.rotation,
1309 size = self._scalableSize,
1312 headItemIndex = parseInt( $(self._$view.children().first()).attr("row-index"), 10) - 1;
1313 tailItemIndex = parseInt( $(self._$view.children()[self._rowsPerView]).attr("row-index"), 10) + 1;
1315 for ( idx = 1 ; idx <= num ; idx++ ) {
1316 if ( tailItemIndex + idx >= self._totalRowCnt ) {
1317 $row = self._makeRow( self._template, headItemIndex );
1318 self._$view.prepend($row);
1321 $row = self._makeRow( self._template, tailItemIndex + idx );
1322 self._$view.append($row);
1324 if ( self._direction ) {
1325 $row.width(self._cellSize);
1327 $row.height(self._cellSize);
1332 _decreaseRow : function ( num ) {
1336 for ( idx = 0 ; idx < num ; idx++ ) {
1337 self._$view.children().last().remove();
1341 _replaceRows : function ( curCnt, prevCnt, maxCnt, clipPosition ) {
1343 $rows = self._$view.children(),
1348 filterCondition = ( self._filterRatio * self._cellSize) + self._cellSize,
1351 if ( filterCondition < clipPosition ) {
1355 prevRowIndex = parseInt( $($rows[targetCnt]).attr("row-index"), 10);
1356 if ( prevRowIndex === 0 ) {
1358 rowIndex = maxCnt - targetCnt;
1360 rowIndex = Math.round( (prevRowIndex * prevCnt) / curCnt );
1361 if ( rowIndex + self._rowsPerView >= maxCnt ) {
1363 rowIndex = maxCnt - self._rowsPerView;
1365 diffRowCnt = prevRowIndex - rowIndex;
1366 rowIndex -= targetCnt;
1369 for ( idx = 0 ; idx < $rows.length ; idx += 1 ) {
1370 self._replaceRow($rows[idx], circularNum( rowIndex, self._totalRowCnt ));
1376 _replaceRow : function ( block, index ) {
1378 opts = self.options,
1381 $block = block.attr ? block : $( block ),
1389 $columns = $block.attr("row-index", index).children();
1390 if ( $columns.length !== self._itemCount ) {
1391 $block.children().remove();
1392 tempBlocks = $(self._makeRow( self._template, index ));
1393 $block.append(tempBlocks.children());
1394 tempBlocks.remove();
1398 dataIdx = index * self._itemCount;
1399 for ( idx = 0; idx < self._itemCount ; idx += 1 ) {
1400 $column = $columns.eq(idx);
1401 data = self._itemData(dataIdx);
1402 if ( $column && data ) {
1403 myTemplate = self._template;
1404 htmlData = myTemplate.tmpl( data );
1405 self._replace( $column, htmlData, false );
1406 htmlData.remove(); // Clear temporary htmlData to free cache
1408 } else if ($column && !data ) {
1414 _createElement : function ( tag ) {
1415 var element = document.createElement( tag );
1417 this._fragment.appendChild( element );
1421 /* Text & image src replace function */
1422 // @param oldItem : prev HtmlDivElement
1423 // @param newItem : new HtmlDivElement for replace
1425 _replace : function ( oldItem, newItem, key ) {
1426 var NODETYPE = { ELEMENT_NODE : 1, TEXT_NODE : 3 };
1428 $( oldItem ).find( ".ui-li-text-main", ".ui-li-text-sub", "ui-btn-text" ).each( function ( index ) {
1429 var oldObj = $( this ),
1430 newText = $( newItem ).find( ".ui-li-text-main", ".ui-li-text-sub", "ui-btn-text" ).eq( index ).text();
1432 $( oldObj ).contents().filter( function () {
1433 return ( this.nodeType == NODETYPE.TEXT_NODE );
1434 }).get( 0 ).data = newText;
1437 $( oldItem ).find( "img" ).each( function ( imgIndex ) {
1438 var oldObj = $( this ),
1439 newImg = $( newItem ).find( "img" ).eq( imgIndex ).attr( "src" );
1441 $( oldObj ).attr( "src", newImg );
1443 $( oldItem).removeData();
1445 $( oldItem ).data( key, $( newItem ).data( key ) );
1450 $( document ).bind( "pagecreate create", function ( e ) {
1451 $(":jqmData(role='virtualgrid')").virtualgrid();
1453 } (jQuery, window, document) );