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 )
387 $dom = $(self.element),
392 // If mandatory options are not given, Do nothing.
397 if ( !self._loadData(args) ) {
401 // read defined properties(width and height) from dom element.
402 self._inheritedSize = self._getinheritedSize(self.element);
404 // set a scroll direction.
405 self._direction = opts.direction === 'x' ? true : false;
408 self._$clip = $(self.element).addClass("ui-scrollview-clip").addClass("ui-virtualgrid-view");
409 $item = $(document.createElement("div")).addClass("ui-scrollview-view");
410 self._clipSize = self._calculateClipSize();
411 self._$clip.append($item);
413 self._$clip.css("overflow", "hidden");
414 self._$view.css("overflow", "hidden");
416 // inherit from scrollview widget.
417 self._scrollView = $.tizen.scrollview.prototype;
418 self._initScrollView();
421 self._createTracker();
422 self._makePositioned(self._$clip);
423 self._timerInterval = 1000 / self.options.fps;
426 self._timerCB = function () {
427 self._handleMomentumScroll();
429 $dom.closest(".ui-content").addClass("ui-virtualgrid-content").css("overflow", "hidden");
431 // add event handler.
432 self._addBehaviors();
434 self._currentItemCount = 0;
435 self._createScrollBar();
439 // The argument is checked for compliance with the specified format.
440 // @param args : Object
442 _loadData : function ( args ) {
445 if ( args.itemData && typeof args.itemData == 'function' ) {
446 self._itemData = args.itemData;
450 if ( args.numItemData ) {
451 if ( typeof args.numItemData == 'function' ) {
452 self._numItemData = args.numItemData( );
453 } else if ( typeof args.numItemData == 'number' ) {
454 self._numItemData = args.numItemData;
464 // Make up the first screen.
465 _initLayout: function () {
471 for ( i = -1; i < self._rowsPerView + 1; i += 1 ) {
472 $row = self._$rows[ circularNum( i, self._$rows.length ) ];
473 self._$view.append( $row );
475 self._setElementTransform( -self._cellSize );
477 self._replaceRow(self._$view.children().first(), self._totalRowCnt - 1);
478 if ( opts.rotation && self._rowsPerView >= self._totalRowCnt ) {
479 self._replaceRow(self._$view.children().last(), 0);
484 _setViewSize : function () {
489 if ( self._direction ) {
490 width = self._cellSize * ( self._rowsPerView + 2 );
491 width = parseInt(width, 10) + 1;
492 self._$view.width( width );
493 self._viewSize = self._$view.width();
495 self._$view.height( self._cellSize * ( self._rowsPerView + 2 ) );
496 self._$clip.height( self._clipSize );
497 self._viewSize = self._$view.height();
501 _getViewHeight : function () {
503 return self._$view.height();
506 refresh : function () {
512 self._template = $( "#" + opts.template );
513 if ( !self._template ) {
517 width = self._calculateClipWidth();
518 height = self._calculateClipHeight();
519 self._$view.width(width).height(height);
520 self._$clip.width(width).height(height);
522 self._clipSize = self._calculateClipSize();
523 self._calculateColumnSize();
524 self._initPageProperty();
525 self._setScrollBarSize();
528 _initPageProperty : function () {
534 attributeName = self._direction ? "width" : "height";
536 columnCount = self._calculateColumnCount();
538 totalRowCnt = parseInt(self._numItemData / columnCount , 10 );
539 self._totalRowCnt = self._numItemData % columnCount === 0 ? totalRowCnt : totalRowCnt + 1;
540 self._itemCount = columnCount;
542 if ( self._cellSize <= 0) {
546 rowsPerView = self._clipSize / self._cellSize;
547 rowsPerView = Math.ceil( rowsPerView );
548 self._rowsPerView = parseInt( rowsPerView, 10);
550 $child = self._makeRows( rowsPerView + 2 );
551 $(self._$view).append($child.children());
552 self._$view.children().css(attributeName, self._cellSize + "px");
553 self._$rows = self._$view.children().detach();
555 self._reservedPos = -self._cellSize;
556 self._scalableSize = -self._cellSize;
560 self._blockScroll = self._rowsPerView > self._totalRowCnt;
561 self._maxSize = ( self._totalRowCnt - self._rowsPerView ) * self._cellSize;
562 self._maxViewSize = ( self._rowsPerView ) * self._cellSize;
563 self._modifyViewPos = -self._cellSize;
564 if ( self._clipSize < self._maxViewSize ) {
565 self._modifyViewPos = (-self._cellSize) + ( self._clipSize - self._maxViewSize );
569 _getinheritedSize : function ( elem ) {
570 var $target = $(elem),
574 isDefinedWidth : false,
575 isDefinedHeight : false,
580 while ( $target[0].nodeType === Node.ELEMENT_NODE && (ret.isDefinedWidth === false || ret.isHeightDefined === false )) {
581 height = $target[0].style.height;
582 width = $target[0].style.width;
584 if (ret.isDefinedHeight === false && height !== "" ) {
586 ret.isDefinedHeight = true;
587 ret.height = parseInt(height, 10);
590 if ( ret.isDefinedWidth === false && width !== "" ) {
592 ret.isDefinedWidth = true;
593 ret.width = parseInt(width, 10);
595 $target = $target.parent();
600 resize : function ( ) {
611 itemCount = self._calculateColumnCount();
612 if ( itemCount != self._itemCount ) {
613 totalRowCnt = parseInt(self._numItemData / itemCount , 10 );
614 self._totalRowCnt = self._numItemData % itemCount === 0 ? totalRowCnt : totalRowCnt + 1;
615 prevcnt = self._itemCount;
616 self._itemCount = itemCount;
617 clipPosition = self._getClipPosition();
620 diffRowCnt = self._replaceRows(itemCount, prevcnt, self._totalRowCnt, clipPosition);
621 self._maxSize = ( self._totalRowCnt - self._rowsPerView ) * self._cellSize;
622 self._scalableSize += (-diffRowCnt) * self._cellSize;
623 self._reservedPos += (-diffRowCnt) * self._cellSize;
624 self._setScrollBarSize();
625 self._setScrollBarPosition(diffRowCnt);
630 clipSize = self._calculateClipSize();
631 if ( clipSize !== self._clipSize ) {
632 rowsPerView = clipSize / self._cellSize;
633 rowsPerView = parseInt( Math.ceil( rowsPerView ), 10 );
635 if ( rowsPerView > self._rowsPerView ) {
637 self._increaseRow( rowsPerView - self._rowsPerView );
638 } else if ( rowsPerView < self._rowsPerView ) {
640 self._decreaseRow( self._rowsPerView - rowsPerView );
642 self._rowsPerView = rowsPerView;
643 self._clipSize = clipSize;
644 self._blockScroll = self._rowsPerView > self._totalRowCnt;
645 self._maxSize = ( self._totalRowCnt - self._rowsPerView ) * self._cellSize;
646 self._maxViewSize = ( self._rowsPerView ) * self._cellSize;
647 if ( self._clipSize < self._maxViewSize ) {
648 self._modifyViewPos = (-self._cellSize) + ( self._clipSize - self._maxViewSize );
650 if ( self._direction ) {
651 self._$clip.width(self._clipSize);
653 self._$clip.height(self._clipSize);
655 self._setScrollBarSize();
656 self._setScrollBarPosition(0);
661 _initScrollView : function () {
663 $.extend(self.options, self._scrollView.options);
664 self.options.moveThreshold = 10;
665 self.options.showScrollBars = false;
666 self._getScrollHierarchy = self._scrollView._getScrollHierarchy;
667 self._makePositioned = self._scrollView._makePositioned;
668 self._set_scrollbar_size = self._scrollView._set_scrollbar_size;
669 self._setStyleTransform = self._scrollView._setElementTransform;
670 self._hideOverflowIndicator = self._scrollView._hideOverflowIndicator;
671 self._showOverflowIndicator = self._scrollView._showOverflowIndicator;
672 self._setGestureScroll = self._scrollView._setGestureScroll;
675 _createTracker : function () {
678 self._tracker = new MomentumTracker(self.options);
679 if ( self._direction ) {
680 self._hTracker = self._tracker;
681 self._$clip.width(self._clipSize);
683 self._vTracker = self._tracker;
684 self._$clip.height(self._clipSize);
688 //----------------------------------------------------//
690 //----------------------------------------------------//
691 _createScrollBar : function () {
693 prefix = "<div class=\"ui-scrollbar ui-scrollbar-",
694 suffix = "\"><div class=\"ui-scrollbar-track\"><div class=\"ui-scrollbar-thumb\"></div></div></div>";
696 if ( self.options.rotation ) {
700 if ( self._direction ) {
701 self._$clip.append( prefix + "x" + suffix );
702 self._hScrollBar = $(self._$clip.children(".ui-scrollbar-x"));
703 self._hScrollBar.find(".ui-scrollbar-thumb").addClass("ui-scrollbar-thumb-x");
705 self._$clip.append( prefix + "y" + suffix );
706 self._vScrollBar = $(self._$clip.children(".ui-scrollbar-y"));
707 self._vScrollBar.find(".ui-scrollbar-thumb").addClass("ui-scrollbar-thumb-y");
711 _setScrollBarSize: function () {
719 if ( self.options.rotation ) {
723 scrollBarSize = parseInt( self._maxViewSize / self._clipSize , 10);
724 if ( self._direction ) {
725 $scrollBar = self._hScrollBar.find(".ui-scrollbar-thumb");
727 currentSize = $scrollBar.width();
728 className = "ui-scrollbar-thumb-x";
729 self._hScrollBar.css("width", self._clipSize);
731 $scrollBar = self._vScrollBar.find(".ui-scrollbar-thumb");
733 className = "ui-scrollbar-thumb-y";
734 currentSize = $scrollBar.height();
735 self._vScrollBar.css("height", self._clipSize);
738 if ( scrollBarSize > currentSize ) {
739 $scrollBar.removeClass(className);
740 $scrollBar.css(attrName, scrollBarSize);
742 scrollBarSize = currentSize;
745 self._itemScrollSize = parseFloat( ( self._clipSize - scrollBarSize ) / ( self._totalRowCnt - self._rowsPerView ) );
746 self._itemScrollSize = Math.round(self._itemScrollSize * 100) / 100;
749 _setScrollBarPosition : function ( di, duration ) {
755 if ( self.options.rotation ) {
759 self._currentItemCount = self._currentItemCount + di;
760 if ( self._vScrollBar ) {
761 $sbt = self._vScrollBar .find(".ui-scrollbar-thumb");
762 y = ( self._currentItemCount * self._itemScrollSize ) + "px";
764 $sbt = self._hScrollBar .find(".ui-scrollbar-thumb");
765 x = ( self._currentItemCount * self._itemScrollSize ) + "px";
767 self._setStyleTransform( $sbt, x, y, duration );
770 _hideScrollBars : function () {
772 vclass = "ui-scrollbar-visible";
774 if ( self.options.rotation ) {
778 if ( self._vScrollBar ) {
779 self._vScrollBar.removeClass( vclass );
781 self._hScrollBar.removeClass( vclass );
785 _showScrollBars : function () {
787 vclass = "ui-scrollbar-visible";
789 if ( self.options.rotation ) {
793 if ( self._vScrollBar ) {
794 self._vScrollBar.addClass( vclass );
796 self._hScrollBar.addClass( vclass );
800 //----------------------------------------------------//
802 //----------------------------------------------------//
803 centerTo: function ( selector ) {
809 if ( !self.options.rotation ) {
813 for ( i = 0; i < self._$rows.length; i++ ) {
814 if ( $( self._$rows[i]).hasClass( selector ) ) {
815 if ( self._direction ) {
816 newX = -( i * self._cellSize - self._clipSize / 2 + self._cellSize * 2 );
818 newY = -( i * self._cellSize - self._clipSize / 2 + self._cellSize * 2 );
820 self.scrollTo( newX, newY );
826 scrollTo: function ( x, y, duration ) {
828 if ( self._direction ) {
829 self._sx = self._reservedPos;
830 self._reservedPos = x;
832 self._sy = self._reservedPos;
833 self._reservedPos = y;
835 self._scrollView.scrollTo.apply( this, [ x, y, duration ] );
838 getScrollPosition: function () {
839 if ( this.direction ) {
840 return { x: -this._ry, y: 0 };
842 return { x: 0, y: -this._ry };
845 _setScrollPosition: function ( x, y ) {
847 sy = self._scalableSize,
848 distance = self._direction ? x : y,
850 di = parseInt( dy / self._cellSize, 10 ),
855 if ( self._blockScroll ) {
859 if ( ! self.options.rotation ) {
860 if ( dy > 0 && distance >= -self._cellSize && self._scalableSize >= -self._cellSize ) {
863 self._scalableSize = -self._cellSize;
864 self._setElementTransform( -self._cellSize );
867 if ( (dy < 0 && self._scalableSize <= -(self._maxSize + self._cellSize) )) {
870 self._scalableSize = -(self._maxSize + self._cellSize);
871 self._setElementTransform( self._modifyViewPos );
876 if ( di > 0 ) { // scroll up
877 for ( i = 0; i < di; i++ ) {
878 idx = -parseInt( ( sy / self._cellSize ) + i + 3, 10 );
879 $row = self._$view.children( ).last( ).detach( );
880 self._replaceRow( $row, circularNum( idx, self._totalRowCnt ) );
881 self._$view.prepend( $row );
882 self._setScrollBarPosition(-1);
884 } else if ( di < 0 ) { // scroll down
885 for ( i = 0; i > di; i-- ) {
886 idx = self._rowsPerView - parseInt( ( sy / self._cellSize ) + i, 10 );
887 $row = self._$view.children().first().detach();
888 self._replaceRow($row, circularNum( idx, self._totalRowCnt ) );
889 self._$view.append( $row );
890 self._setScrollBarPosition(1);
893 self._scalableSize += di * self._cellSize;
894 self._setElementTransform( distance - self._scalableSize - self._cellSize );
897 _setElementTransform : function ( value ) {
902 if ( self._direction ) {
907 self._setStyleTransform(self._$view, x, y );
910 //----------------------------------------------------//
912 //----------------------------------------------------//
913 _handleMomentumScroll: function () {
924 if ( self._direction ) {
929 keepGoing = !t.done();
932 self._setScrollPosition( x, y );
933 if ( !opts.rotation ) {
934 keepGoing = !t.done();
935 self._reservedPos = self._direction ? x : y;
937 self._reservedPos = self._reservedPos <= (-(self._maxSize - self._modifyViewPos)) ? ( - ( self._maxSize + self._cellSize) ) : self._reservedPos;
939 self._reservedPos = self._reservedPos > -self._cellSize ? -self._cellSize : self._reservedPos;
941 self._reservedPos = self._direction ? x : y;
943 self._$clip.trigger( self.options.updateEventName, [ { x: x, y: y } ] );
946 self._timerID = setTimeout( self._timerCB, self._timerInterval );
952 _startMScroll: function ( speedX, speedY ) {
954 if ( self._direction ) {
955 self._sx = self._reservedPos;
957 self._sy = self._reservedPos;
959 self._scrollView._startMScroll.apply(self, [speedX, speedY]);
962 _stopMScroll: function () {
963 this._scrollView._stopMScroll.apply(this);
966 _enableTracking: function () {
967 $(document).bind( this._dragMoveEvt, this._dragMoveCB );
968 $(document).bind( this._dragStopEvt, this._dragStopCB );
971 _disableTracking: function () {
972 $(document).unbind( this._dragMoveEvt, this._dragMoveCB );
973 $(document).unbind( this._dragStopEvt, this._dragStopCB );
976 _handleDragStart: function ( e, ex, ey ) {
978 self._scrollView._handleDragStart.apply( this, [ e, ex, ey ] );
979 self._eventPos = self._direction ? ex : ey;
980 self._nextPos = self._reservedPos;
983 _handleDragMove: function ( e, ex, ey ) {
985 dx = ex - self._lastX,
986 dy = ey - self._lastY,
990 self._lastMove = getCurrentTime();
994 self._didDrag = true;
999 if ( self._direction ) {
1000 self._movePos = ex - self._eventPos;
1001 x = self._nextPos + self._movePos;
1003 self._movePos = ey - self._eventPos;
1004 y = self._nextPos + self._movePos;
1006 self._showScrollBars();
1007 self._setScrollPosition( x, y );
1011 _handleDragStop: function ( e ) {
1014 self._reservedPos = self._movePos ? self._nextPos + self._movePos : self._reservedPos;
1015 self._scrollView._handleDragStop.apply( this, [ e ] );
1016 return self._didDrag ? false : undefined;
1019 _addBehaviors: function () {
1022 // scroll event handler.
1023 if ( self.options.eventType === "mouse" ) {
1024 self._dragStartEvt = "mousedown";
1025 self._dragStartCB = function ( e ) {
1026 return self._handleDragStart( e, e.clientX, e.clientY );
1029 self._dragMoveEvt = "mousemove";
1030 self._dragMoveCB = function ( e ) {
1031 return self._handleDragMove( e, e.clientX, e.clientY );
1034 self._dragStopEvt = "mouseup";
1035 self._dragStopCB = function ( e ) {
1036 return self._handleDragStop( e, e.clientX, e.clientY );
1039 self._$view.bind( "vclick", function (e) {
1040 return !self._didDrag;
1043 self._dragStartEvt = "touchstart";
1044 self._dragStartCB = function ( e ) {
1045 var t = e.originalEvent.targetTouches[0];
1046 return self._handleDragStart(e, t.pageX, t.pageY );
1049 self._dragMoveEvt = "touchmove";
1050 self._dragMoveCB = function ( e ) {
1051 var t = e.originalEvent.targetTouches[0];
1052 return self._handleDragMove(e, t.pageX, t.pageY );
1055 self._dragStopEvt = "touchend";
1056 self._dragStopCB = function ( e ) {
1057 return self._handleDragStop( e );
1060 self._$view.bind( self._dragStartEvt, self._dragStartCB );
1063 self._$view.delegate(".virtualgrid-item", "click", function (event) {
1064 var $selectedItem = $(this);
1065 $selectedItem.trigger("select", this);
1068 $( window ).bind("resize", function ( e ) {
1070 $virtualgrid = $(".ui-virtualgrid-view");
1071 if ( $virtualgrid.length !== 0 ) {
1072 if ( self._direction ) {
1073 height = self._calculateClipHeight();
1074 self._$view.height(height);
1075 self._$clip.height(height);
1077 height = self._calculateClipWidth();
1078 self._$view.width(height);
1079 self._$clip.width(height);
1085 $(document).one("pageshow", function (event) {
1086 var $page = $(self.element).parents(".ui-page"),
1087 $header = $page.find( ":jqmData(role='header')" ),
1088 $footer = $page.find( ":jqmData(role='footer')" ),
1089 $content = $page.find( ":jqmData(role='content')" ),
1090 footerHeight = $footer ? $footer.height() : 0,
1091 headerHeight = $header ? $header.height() : 0;
1093 if ( $page && $content ) {
1094 $content.height(window.innerHeight - headerHeight - footerHeight).css("overflow", "hidden");
1095 $content.addClass("ui-virtualgrid-content");
1100 //----------------------------------------------------//
1101 // Calculate size about dom element. //
1102 //----------------------------------------------------//
1103 _calculateClipSize : function () {
1107 if ( self._direction ) {
1108 clipSize = self._calculateClipWidth();
1110 clipSize = self._calculateClipHeight();
1115 _calculateClipWidth : function () {
1117 view = $(self.element),
1118 $parent = $(self.element).parent(),
1120 clipSize = $(window).width();
1122 if ( self._inheritedSize.isDefinedWidth ) {
1123 return self._inheritedSize.width;
1126 if ( $parent.hasClass("ui-content") ) {
1127 paddingValue = parseInt($parent.css("padding-left"), 10);
1128 clipSize = clipSize - ( paddingValue || 0 );
1129 paddingValue = parseInt($parent.css("padding-right"), 10);
1130 clipSize = clipSize - ( paddingValue || 0);
1132 clipSize = view.width();
1137 _calculateClipHeight : function () {
1139 view = $(self.element),
1140 $parent = $(self.element).parent(),
1144 clipSize = $(window).height();
1146 if ( self._inheritedSize.isDefinedHeight ) {
1147 return self._inheritedSize.height;
1150 if ( $parent.hasClass("ui-content") ) {
1151 paddingValue = parseInt($parent.css("padding-top"), 10);
1152 clipSize = clipSize - ( paddingValue || 0 );
1153 paddingValue = parseInt($parent.css("padding-bottom"), 10);
1154 clipSize = clipSize - ( paddingValue || 0);
1155 header = $parent.siblings(".ui-header");
1156 footer = $parent.siblings(".ui-footer");
1159 if ( header.outerHeight(true) === null ) {
1160 clipSize = clipSize - ( $(".ui-header").outerHeight() || 0 );
1162 clipSize = clipSize - header.outerHeight(true);
1166 clipSize = clipSize - footer.outerHeight(true);
1169 clipSize = view.height();
1174 _calculateColumnSize : function () {
1179 $tempBlock = self._makeRows( 1 );
1180 self._$view.append( $tempBlock.children().first() );
1181 if ( self._direction ) {
1183 self._viewSize = self._$view.width();
1184 $cell = self._$view.children().first().children().first();
1185 self._cellSize = $cell.outerWidth(true);
1186 self._cellOtherSize = $cell.outerHeight(true);
1189 self._viewSize = self._$view.height();
1190 $cell = self._$view.children().first().children().first();
1191 self._cellSize = $cell.outerHeight(true);
1192 self._cellOtherSize = $cell.outerWidth(true);
1194 $tempBlock.remove();
1195 self._$view.children().remove();
1198 _calculateColumnCount : function ( ) {
1200 $view = $(self.element),
1201 viewSize = self._direction ? $view.innerHeight() : $view.innerWidth(),
1204 if ( self._direction ) {
1205 viewSize = viewSize - ( parseInt( $view.css("padding-top"), 10 ) + parseInt( $view.css("padding-bottom"), 10 ) );
1207 viewSize = viewSize - ( parseInt( $view.css("padding-left"), 10 ) + parseInt( $view.css("padding-right"), 10 ) );
1210 itemCount = parseInt( (viewSize / self._cellOtherSize), 10);
1211 return itemCount > 0 ? itemCount : 1 ;
1214 // Read the position of clip form property ('webkit-transform').
1215 // @return : number - position of clip.
1216 _getClipPosition : function () {
1220 result = -self._cellSize,
1221 $scrollview = self._$view.closest(".ui-scrollview-view");
1223 if ( $scrollview ) {
1224 matrix = $scrollview.css("-webkit-transform");
1225 contents = matrix.substr( 7 );
1226 contents = contents.substr( 0, contents.length - 1 );
1227 contents = contents.split( ', ' );
1228 result = Math.abs(contents [5]);
1233 //----------------------------------------------------//
1234 // DOM Element handle //
1235 //----------------------------------------------------//
1236 _makeRows : function ( count ) {
1238 opts = self.options,
1243 $wrapper = $(document.createElement("div"));
1244 $wrapper.addClass("ui-scrollview-view");
1245 for ( index = 0; index < count ; index += 1 ) {
1246 $row = self._makeRow( self._template, index );
1247 if ( self._direction ) {
1248 $row.css("top", 0).css("left", ( index * self._cellSize ));
1250 $wrapper.append($row);
1255 // make a single row block
1256 _makeRow : function ( myTemplate, rowIndex ) {
1258 opts = self.options,
1259 index = rowIndex * self._itemCount,
1263 attrName = self._direction ? "top" : "left",
1264 blockClassName = self._direction ? "ui-virtualgrid-wrapblock-x" : "ui-virtualgrid-wrapblock-y",
1265 blockAttrName = self._direction ? "top" : "left",
1266 wrapBlock = $( document.createElement( "div" ));
1268 wrapBlock.addClass( blockClassName ).attr("row-index", rowIndex);
1269 for ( colIndex = 0; colIndex < self._itemCount; colIndex++ ) {
1270 itemData = self._itemData( index );
1272 htmlData = self._makeHtmlData( myTemplate, index, colIndex);
1273 wrapBlock.append( htmlData );
1280 _makeHtmlData : function ( myTemplate, dataIndex, colIndex ) {
1284 attrName = self._direction ? "top" : "left";
1286 itemData = self._itemData( dataIndex );
1288 htmlData = myTemplate.tmpl( itemData );
1289 $(htmlData).css(attrName, ( colIndex * self._cellOtherSize )).addClass("virtualgrid-item");
1294 _increaseRow : function ( num ) {
1296 rotation = self.options.rotation,
1301 size = self._scalableSize,
1304 headItemIndex = parseInt( $(self._$view.children().first()).attr("row-index"), 10) - 1;
1305 tailItemIndex = parseInt( $(self._$view.children()[self._rowsPerView]).attr("row-index"), 10) + 1;
1307 for ( idx = 1 ; idx <= num ; idx++ ) {
1308 if ( tailItemIndex + idx >= self._totalRowCnt ) {
1309 $row = self._makeRow( self._template, headItemIndex );
1310 self._$view.prepend($row);
1313 $row = self._makeRow( self._template, tailItemIndex + idx );
1314 self._$view.append($row);
1316 if ( self._direction ) {
1317 $row.width(self._cellSize);
1319 $row.height(self._cellSize);
1324 _decreaseRow : function ( num ) {
1328 for ( idx = 0 ; idx < num ; idx++ ) {
1329 self._$view.children().last().remove();
1333 _replaceRows : function ( curCnt, prevCnt, maxCnt, clipPosition ) {
1335 $rows = self._$view.children(),
1340 filterCondition = ( self._filterRatio * self._cellSize) + self._cellSize,
1343 if ( filterCondition < clipPosition ) {
1347 prevRowIndex = parseInt( $($rows[targetCnt]).attr("row-index"), 10);
1348 if ( prevRowIndex === 0 ) {
1350 rowIndex = maxCnt - targetCnt;
1352 rowIndex = Math.round( (prevRowIndex * prevCnt) / curCnt );
1353 if ( rowIndex + self._rowsPerView >= maxCnt ) {
1355 rowIndex = maxCnt - self._rowsPerView;
1357 diffRowCnt = prevRowIndex - rowIndex;
1358 rowIndex -= targetCnt;
1361 for ( idx = 0 ; idx < $rows.length ; idx += 1 ) {
1362 self._replaceRow($rows[idx], circularNum( rowIndex, self._totalRowCnt ));
1368 _replaceRow : function ( block, index ) {
1370 opts = self.options,
1380 $columns = $(block).attr("row-index", index).children();
1381 if ( $columns.length !== self._itemCount ) {
1382 $(block).children().remove();
1383 tempBlocks = $(self._makeRow( self._template, index ));
1384 $(block).append(tempBlocks.children());
1385 tempBlocks.remove();
1389 dataIdx = index * self._itemCount;
1390 for ( idx = 0; idx < self._itemCount ; idx += 1 ) {
1391 $column = $columns[idx];
1392 data = self._itemData(dataIdx);
1393 if ( $column && data ) {
1394 myTemplate = self._template;
1395 htmlData = myTemplate.tmpl( data );
1396 self._replace( $column, htmlData, false );
1397 htmlData.remove(); // Clear temporary htmlData to free cache
1399 } else if ($column && !data ) {
1400 $($column).remove();
1405 /* Text & image src replace function */
1406 // @param oldItem : prev HtmlDivElement
1407 // @param newItem : new HtmlDivElement for replace
1409 _replace : function ( oldItem, newItem, key ) {
1410 $( oldItem ).find( ".ui-li-text-main", ".ui-li-text-sub", "ui-btn-text" ).each( function ( index ) {
1411 var oldObj = $( this ),
1412 newText = $( newItem ).find( ".ui-li-text-main", ".ui-li-text-sub", "ui-btn-text" ).eq( index ).text();
1414 $( oldObj ).contents().filter( function () {
1415 return ( this.nodeType == 3 );
1416 }).get( 0 ).data = newText;
1419 $( oldItem ).find( "img" ).each( function ( imgIndex ) {
1420 var oldObj = $( this ),
1421 newImg = $( newItem ).find( "img" ).eq( imgIndex ).attr( "src" );
1423 $( oldObj ).attr( "src", newImg );
1425 $( oldItem).removeData();
1427 $( oldItem ).data( key, $( newItem ).data( key ) );
1432 $( document ).bind( "pagecreate create", function ( e ) {
1433 $(":jqmData(role='virtualgrid')").virtualgrid();
1435 } (jQuery, window, document) );