1 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
2 //>>description: Shows grid swapping its contents automatically
3 //>>label: Virtual grid
4 //>>group: Tizen:Widgets
6 define( [ '../jquery.mobile.tizen.core', '../jquery.mobile.tizen.scrollview' ], function ( ) {
7 //>>excludeEnd("jqmBuildExclude");
9 /* ***************************************************************************
10 * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
12 * Permission is hereby granted, free of charge, to any person obtaining a
13 * copy of this software and associated documentation files (the "Software"),
14 * to deal in the Software without restriction, including without limitation
15 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16 * and/or sell copies of the Software, and to permit persons to whom the
17 * Software is furnished to do so, subject to the following conditions:
19 * The above copyright notice and this permission notice shall be included in
20 * all copies or substantial portions of the Software.
22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28 * DEALINGS IN THE SOFTWARE.
29 * ***************************************************************************
31 * Author: Kangsik Kim <kangsik81.kim@samsung.com>
32 * Youmin Ha <youmin.ha@samsung.com>
36 * In the web environment, it is challenging to display a large amount of data in a grid.
37 * When an application needs to show, for example, image gallery with over 1,000 images,
38 * the same enormous data must be inserted into a HTML document.
39 * It takes a long time to display the data and manipulating DOM is complex.
40 * The virtual grid widget supports storing unlimited data without performance issues
41 * by reusing a limited number of grid elements.
42 * The virtual grid widget is based on the jQuery.template plug-in
43 * For more information, see jQuery.template.
47 * data-role: virtualgrid
48 * data-template : Has the ID of the jQuery.template element.
49 * jQuery.template for a virtual grid must be defined.
50 * Style for template would use rem unit to support scalability.
51 * data-direction : This option define the direction of the scroll.
52 * You must choose one of the 'x' and 'y' (Default : y)
53 * data-rotation : This option defines whether or not the circulation of the data.
54 * If option is 'true' and scroll is reached the last data,
55 * Widget will present the first data on the screen.
56 * If option is ‘false’, Widget will operate like a scrollview.
58 * ID : <DIV> element that has "data-role=virtualgrid" must have ID attribute.
63 * itemData: function ( idx ) { return json_obj; },
64 * numItemData: number or function () { return number; },
65 * cacheItemData: function ( minIdx, maxIdx ) {}
67 * : Create VirtualGrid widget. At this moment, _create method is called.
68 * args : A collection of options
69 * itemData: A function that returns JSON object for given index. Mandatory.
70 * numItemData: Total number of itemData. Mandatory.
71 * cacheItemData: Virtuallist will ask itemData between minIdx and maxIdx.
72 * Developers can implement this function for preparing data.
76 * : Find a DOM Element with the given class name.
77 * This element will be centered on the screen.
78 * Serveral elements were found, the first element is displayed.
81 * scrollstart : : This event triggers when a user begin to move the scroll on VirtualGrid.
82 * scrollupdate : : This event triggers while a user moves the scroll on VirtualGrid.
83 * scrollstop : This event triggers when a user stop the scroll on VirtualGrid.
84 * select : This event triggers when a cell is selected.
88 * <script id="tizen-demo-namecard" type="text/x-jquery-tmpl">
89 * <div class="ui-demo-namecard">
90 * <div class="ui-demo-namecard-pic">
91 * <img class="ui-demo-namecard-pic-img" src="${TEAM_LOGO}" />
93 * <div class="ui-demo-namecard-contents">
94 * <span class="name ui-li-text-main">${NAME}</span>
95 * <span class="active ui-li-text-sub">${ACTIVE}</span>
96 * <span class="from ui-li-text-sub">${FROM}</span>
100 * <div id="virtualgrid-demo" data-role="virtualgrid" data-template="tizen-demo-namecard" >
105 // most of following codes are derived from jquery.mobile.scrollview.js
109 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.
111 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.
113 To add a virtual grid widget to the application, use the following code:
115 <script id="tizen-demo-namecard" type="text/x-jquery-tmpl">
116 <div class="ui-demo-namecard">
117 <div class="ui-demo-namecard-pic">
118 <img class="ui-demo-namecard-pic-img" src="${TEAM_LOGO}" />
120 <div class="ui-demo-namecard-contents">
121 <span class="name ui-li-text-main">${NAME}</span>
125 <div id="virtualgrid-demo" data-role="virtualgrid" data-template="tizen-demo-namecard">
129 @property {String} data-template
130 Specifies the jQuery.template element ID.
131 The jQuery.template must be defined. The template style can use rem units to support scalability.
134 @property {String} data-direction
135 Defines the scroll direction. The direction options are x (horizontal) and y (vertical).
136 The default value is y.
139 @property {Boolean} data-rotation
140 Defines whether the data elements are displayed from the beginning of the list again once the end of file is reached.
141 The default value is false.
145 The scrollstart event is fired when the user starts scrolling through the grid:
147 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
149 $(".selector").virtualgrid
151 scrollstart: function(event, ui)
153 // Handle the scrollstart event
157 $(".selector").bind("scrollstart", function(event, ui)
159 // Handle the scrollstart event
164 The scrollupdate event is fired when the user moves the scroll bar in the grid:
166 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
168 $(".selector").virtualgrid
170 scrollupdate: function(event, ui)
172 // Handle the scrollupdate event
176 $(".selector").bind("scrollupdate", function(event, ui)
178 // Handle the scrollupdate event
183 The scrollstop event is fired when the user stops scrolling:
185 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
187 $(".selector").virtualgrid
189 scrollstop: function(event, ui)
191 // Handle the scrollstop event
195 $(".selector").bind("scrollstop", function(event, ui)
197 // Handle the scrollstop event
202 The select event is fired when a virtual grid cell is selected:
204 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
206 $(".selector").virtualgrid
208 select: function(event, ui)
210 // Handle the select event
214 $(".selector").bind("select", function(event, ui)
216 // Handle the select event
221 @param {function} itemData(index)
222 @param {Number} numItemData
223 @param {function} cacheItemData(minIndex, maxIndex)
224 The create method is used to call the jQuery _create method. In the method parameters:
226 function itemData(index) returns the JSON object matched with the given index. The index value is between 0 and numItemData-1.<br/>
227 number numItemData or function numItemData() defines or returns a static number of items.<br/>
228 function cacheItemData(minIndex, maxIndex) prepares the JSON data. This method is called before calling the itemData() method with index values between minIndex and maxIndex.<br/>
230 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
231 function itemData(idx)
235 function cacheItemData(minIdx, maxIdx)
237 // Prepare JSON data between minIdx and maxIdx
239 var numItemData = DATA.length;
240 $(".selector").virtualgrid("create",
242 itemData, numItemData, cacheItemData
247 The centerTo method is used to search for the DOM element with a specified class name. The retrieved element is placed at the center of the virtual grid. If multiple elements are retrieved, the first element from the result list is placed at the center of the virtual grid.
249 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
250 $(".selector").virtualgrid("centerTo", "selector");
254 The resize method is used to rearrange the DOM elements to fit a new screen size when the screen is resized:
256 <div data-role="virtualgrid" data-scroll="y" data-template="tizen-demo-namecard"></div>
257 ".selector").virtualgrid("resize");
262 ( function ( $, window, document, undefined ) {
264 function circularNum ( num, total ) {
272 function MomentumTracker ( options ) {
273 this.options = $.extend( {}, options );
274 this.easing = "easeOutQuad";
282 _OVERFLOW_DIR_NONE = 0, /* ENUM */
283 _OVERFLOW_DIR_UP = 1, /* ENUM */
284 _OVERFLOW_DIR_DOWN = -1; /* ENUM */
286 function getCurrentTime () {
290 $.extend( MomentumTracker.prototype, {
291 start : function ( pos, speed, duration ) {
292 this.state = ( speed !== 0 ) ? tstates.scrolling : tstates.done;
295 this.duration = duration;
300 this.startTime = getCurrentTime();
303 reset : function () {
304 this.state = tstates.done;
310 update : function () {
311 var state = this.state, duration, elapsed, dx, x;
313 if ( state == tstates.done ) {
316 duration = this.duration;
317 elapsed = getCurrentTime () - this.startTime;
318 elapsed = elapsed > duration ? duration : elapsed;
319 dx = this.speed * ( 1 - $.easing[this.easing]( elapsed / duration, elapsed, 0, 1, duration ) );
323 if ( elapsed >= duration ) {
324 this.state = tstates.done;
330 return this.state == tstates.done;
333 getPosition : function () {
338 jQuery.widget ( "mobile.virtualgrid", jQuery.mobile.widget, {
340 // virtualgrid option
346 create : function () {
347 this._create.apply( this, arguments );
350 _create : function ( args ) {
359 _cellSize : undefined,
360 _currentItemCount : 0,
362 _inheritedSize : null,
371 _itemData : function ( idx ) { return null; },
373 _cacheItemData : function ( minIdx, maxIdx ) { },
378 _maxSizeExceptClip : 0,
381 // axis - ( true : x , false : y )
398 _overflowStartPos : 0,
400 _overflowMaxDragDist : 100
404 $dom = $( self.element ),
409 // If mandatory options are not given, Do nothing.
414 if ( !self._loadData( args ) ) {
419 self._fragment = document.createDocumentFragment();
421 // read defined properties(width and height) from dom element.
422 self._inheritedSize = self._getinheritedSize( self.element );
424 // set a scroll direction.
425 self._direction = opts.direction === 'x' ? true : false;
428 self._$clip = $( self.element ).addClass( "ui-scrollview-clip" ).addClass( "ui-virtualgrid-view" );
429 $item = $( document.createElement( "div" ) ).addClass( "ui-scrollview-view" );
430 self._clipSize = self._calculateClipSize();
431 self._$clip.append( $item );
433 self._$clip.css( "overflow", "hidden" );
434 self._$view.css( "overflow", "hidden" );
436 // inherit from scrollview widget.
437 self._scrollView = $.tizen.scrollview.prototype;
438 self._initScrollView();
441 self._createTracker();
442 self._makePositioned( self._$clip );
443 self._timerInterval = 1000 / self.options.fps;
446 self._timerCB = function () {
447 self._handleMomentumScroll();
449 $dom.closest( ".ui-content" ).addClass( "ui-virtualgrid-content" ).css( "overflow", "hidden" );
451 // add event handler.
452 self._addBehaviors();
454 self._currentItemCount = 0;
455 self._createOverflowArea();
456 self._createScrollBar();
460 // The argument is checked for compliance with the specified format.
461 // @param args : Object
463 _loadData : function ( args ) {
466 if ( args.itemData && typeof args.itemData == 'function' ) {
467 self._itemData = args.itemData;
471 if ( args.numItemData ) {
472 if ( typeof args.numItemData == 'function' ) {
473 self._numItemData = args.numItemData( );
474 } else if ( typeof args.numItemData == 'number' ) {
475 self._numItemData = args.numItemData;
482 self._getObjectNames( self._itemData( 0 ) );
486 // Make up the first screen.
487 _initLayout: function () {
493 for ( i = -1; i < self._rowsPerView + 1; i += 1 ) {
494 $row = self._$rows[ circularNum( i, self._$rows.length ) ];
495 self._$view.append( $row );
497 self._setElementTransform( -self._cellSize );
499 self._replaceRow( self._$view.children().first(), self._totalRowCnt - 1 );
500 if ( opts.rotation && self._rowsPerView >= self._totalRowCnt ) {
501 self._replaceRow( self._$view.children().last(), 0 );
506 _setViewSize : function () {
511 if ( self._direction ) {
512 width = self._cellSize * ( self._rowsPerView + 2 );
513 width = parseInt( width, 10 ) + 1;
514 self._$view.width( width );
515 self._viewSize = self._$view.width();
517 self._$view.height( self._cellSize * ( self._rowsPerView + 2 ) );
518 self._$clip.height( self._clipSize );
519 self._viewSize = self._$view.height();
523 _getViewWidth : function () {
525 return self._maxSize;
528 _getViewHeight : function () {
530 return self._maxSize;
533 refresh : function () {
539 self._template = $( "#" + opts.template );
540 if ( !self._template ) {
544 width = self._calculateClipWidth();
545 height = self._calculateClipHeight();
546 self._$view.width( width ).height( height );
547 self._$clip.width( width ).height( height );
549 self._clipSize = self._calculateClipSize();
550 self._calculateColumnSize();
551 self._initPageProperty();
552 self._setScrollBarSize();
555 _initPageProperty : function () {
561 attributeName = self._direction ? "width" : "height";
563 columnCount = self._calculateColumnCount();
565 totalRowCnt = parseInt( self._numItemData / columnCount, 10 );
566 self._totalRowCnt = self._numItemData % columnCount === 0 ? totalRowCnt : totalRowCnt + 1;
567 self._itemCount = columnCount;
569 if ( self._cellSize <= 0 ) {
573 rowsPerView = self._clipSize / self._cellSize;
574 rowsPerView = Math.ceil( rowsPerView );
575 self._rowsPerView = parseInt( rowsPerView, 10 );
577 $child = self._makeRows( rowsPerView + 2 );
578 $( self._$view ).append( $child.children() );
579 self._$view.children().css( attributeName, self._cellSize + "px" );
580 self._$rows = self._$view.children().detach();
582 self._reservedPos = -self._cellSize;
583 self._scalableSize = -self._cellSize;
587 self._blockScroll = self._rowsPerView > self._totalRowCnt;
588 self._maxSizeExceptClip = ( self._totalRowCnt - self._rowsPerView ) * self._cellSize;
589 self._maxSize = self._totalRowCnt * self._cellSize;
590 self._maxViewSize = ( self._rowsPerView ) * self._cellSize;
591 self._modifyViewPos = -self._cellSize;
592 if ( self._clipSize < self._maxViewSize ) {
593 self._modifyViewPos = ( -self._cellSize ) + ( self._clipSize - self._maxViewSize );
597 _getinheritedSize : function ( elem ) {
598 var $target = $( elem ),
601 NODETYPE = { ELEMENT_NODE : 1, TEXT_NODE : 3 },
603 isDefinedWidth : false,
604 isDefinedHeight : false,
609 while ( $target[0].nodeType === NODETYPE.ELEMENT_NODE && ( ret.isDefinedWidth === false || ret.isHeightDefined === false ) ) {
610 height = $target[0].style.height;
611 width = $target[0].style.width;
613 if ( ret.isDefinedHeight === false && height !== "" ) {
615 ret.isDefinedHeight = true;
616 ret.height = parseInt( height, 10 );
619 if ( ret.isDefinedWidth === false && width !== "" ) {
621 ret.isDefinedWidth = true;
622 ret.width = parseInt( width, 10 );
624 $target = $target.parent();
625 if ( $target.hasClass( "ui-content" ) ) {
632 resize : function ( ) {
643 itemCount = self._calculateColumnCount();
644 if ( itemCount != self._itemCount ) {
645 totalRowCnt = parseInt( self._numItemData / itemCount, 10 );
646 self._totalRowCnt = self._numItemData % itemCount === 0 ? totalRowCnt : totalRowCnt + 1;
647 prevcnt = self._itemCount;
648 self._itemCount = itemCount;
649 clipPosition = self._getClipPosition();
652 diffRowCnt = self._replaceRows(itemCount, prevcnt, self._totalRowCnt, clipPosition);
653 self._maxSizeExceptClip = ( self._totalRowCnt - self._rowsPerView ) * self._cellSize;
654 self._maxSize = self._totalRowCnt * self._cellSize;
655 self._scalableSize += (-diffRowCnt) * self._cellSize;
656 self._reservedPos += (-diffRowCnt) * self._cellSize;
657 self._setScrollBarSize();
658 self._setScrollBarPosition( diffRowCnt );
663 clipSize = self._calculateClipSize();
664 if ( clipSize !== self._clipSize ) {
665 rowsPerView = clipSize / self._cellSize;
666 rowsPerView = parseInt( Math.ceil( rowsPerView ), 10 );
668 if ( rowsPerView > self._rowsPerView ) {
670 self._increaseRow( rowsPerView - self._rowsPerView );
671 } else if ( rowsPerView < self._rowsPerView ) {
673 self._decreaseRow( self._rowsPerView - rowsPerView );
675 self._rowsPerView = rowsPerView;
676 self._clipSize = clipSize;
677 self._blockScroll = self._rowsPerView > self._totalRowCnt;
678 self._maxSizeExceptClip = ( self._totalRowCnt - self._rowsPerView ) * self._cellSize;
679 self._maxSize = self._totalRowCnt * self._cellSize;
680 self._maxViewSize = ( self._rowsPerView ) * self._cellSize;
681 if ( self._clipSize < self._maxViewSize ) {
682 self._modifyViewPos = ( -self._cellSize ) + ( self._clipSize - self._maxViewSize );
684 if ( self._direction ) {
685 self._$clip.width( self._clipSize );
687 self._$clip.height( self._clipSize );
689 self._setScrollBarSize();
690 self._setScrollBarPosition( 0 );
695 _initScrollView : function () {
697 $.extend( self.options, self._scrollView.options );
698 self.options.moveThreshold = 10;
699 self.options.showScrollBars = false;
700 self._getScrollHierarchy = self._scrollView._getScrollHierarchy;
701 self._makePositioned = self._scrollView._makePositioned;
702 self._set_scrollbar_size = self._scrollView._set_scrollbar_size;
703 self._setStyleTransform = self._scrollView._setElementTransform;
704 self._hideOverflowIndicator = self._scrollView._hideOverflowIndicator;
705 self._showOverflowIndicator = self._scrollView._showOverflowIndicator;
706 self._setGestureScroll = self._scrollView._setGestureScroll;
709 _createTracker : function () {
712 self._tracker = new MomentumTracker( self.options );
713 if ( self._direction ) {
714 self._hTracker = self._tracker;
715 self._$clip.width( self._clipSize );
717 self._vTracker = self._tracker;
718 self._$clip.height( self._clipSize );
722 //----------------------------------------------------//
724 //----------------------------------------------------//
725 _createOverflowArea : function () {
727 prefix = "<div class=\"ui-virtualgrid-overflow-indicator-",
728 suffixTop = "-top\"></div>",
729 suffixBottom = "-bottom\"></div>";
731 if ( self.options.rotation ) {
735 if ( self._direction ) {
736 self._overflowTop = $( prefix + "x" + suffixTop );
737 self._overflowBottom = $( prefix + "x" + suffixBottom );
739 self._overflowTop = $( prefix + "y" + suffixTop );
740 self._overflowBottom = $( prefix + "y" + suffixBottom );
743 self._$clip.append( self._overflowTop );
744 self._$clip.append( self._overflowBottom );
745 self._overflowDisplayed = false;
748 _hideVGOverflowIndicator : function () {
749 if ( this._overflowDisplayed === false ) {
753 this._overflowTop.animate( { opacity: 0 }, 300 );
754 this._overflowBottom.animate( { opacity: 0 }, 300 );
755 this._overflowDisplayed = false;
758 //----------------------------------------------------//
760 //----------------------------------------------------//
761 _createScrollBar : function () {
763 prefix = "<div class=\"ui-scrollbar ui-scrollbar-",
764 suffix = "\"><div class=\"ui-scrollbar-track\"><div class=\"ui-scrollbar-thumb\"></div></div></div>";
766 if ( self.options.rotation ) {
770 if ( self._direction ) {
771 self._$clip.append( prefix + "x" + suffix );
772 self._hScrollBar = $( self._$clip.children( ".ui-scrollbar-x" ) );
773 self._hScrollBar.find( ".ui-scrollbar-thumb" ).addClass( "ui-scrollbar-thumb-x" );
775 self._$clip.append( prefix + "y" + suffix );
776 self._vScrollBar = $( self._$clip.children( ".ui-scrollbar-y" ) );
777 self._vScrollBar.find( ".ui-scrollbar-thumb" ).addClass( "ui-scrollbar-thumb-y" );
781 _setScrollBarSize: function () {
789 if ( self.options.rotation ) {
793 scrollBarSize = parseInt( self._maxViewSize / self._clipSize, 10 );
794 if ( self._direction ) {
795 $scrollBar = self._hScrollBar.find( ".ui-scrollbar-thumb" );
797 currentSize = $scrollBar.width();
798 className = "ui-scrollbar-thumb-x";
799 self._hScrollBar.css( "width", self._clipSize );
801 $scrollBar = self._vScrollBar.find( ".ui-scrollbar-thumb" );
803 className = "ui-scrollbar-thumb-y";
804 currentSize = $scrollBar.height();
805 self._vScrollBar.css( "height", self._clipSize );
808 if ( scrollBarSize > currentSize ) {
809 $scrollBar.removeClass( className );
810 $scrollBar.css( attrName, scrollBarSize );
812 scrollBarSize = currentSize;
815 self._itemScrollSize = parseFloat( ( self._clipSize - scrollBarSize ) / ( self._totalRowCnt - self._rowsPerView ) );
816 self._itemScrollSize = Math.round( self._itemScrollSize * 100 ) / 100;
819 _setScrollBarPosition : function ( di, duration ) {
825 if ( self.options.rotation ) {
829 self._currentItemCount = self._currentItemCount + di;
830 if ( self._vScrollBar ) {
831 $sbt = self._vScrollBar .find( ".ui-scrollbar-thumb" );
832 y = ( self._currentItemCount * self._itemScrollSize ) + "px";
834 $sbt = self._hScrollBar .find( ".ui-scrollbar-thumb" );
835 x = ( self._currentItemCount * self._itemScrollSize ) + "px";
837 self._setStyleTransform( $sbt, x, y, duration );
840 _hideScrollBars : function () {
842 vclass = "ui-scrollbar-visible";
844 if ( self.options.rotation ) {
848 if ( self._vScrollBar ) {
849 self._vScrollBar.removeClass( vclass );
851 self._hScrollBar.removeClass( vclass );
855 _showScrollBars : function () {
857 vclass = "ui-scrollbar-visible";
859 if ( self.options.rotation ) {
863 if ( self._vScrollBar ) {
864 self._vScrollBar.addClass( vclass );
866 self._hScrollBar.addClass( vclass );
870 //----------------------------------------------------//
872 //----------------------------------------------------//
873 centerTo: function ( selector ) {
879 if ( !self.options.rotation ) {
883 for ( i = 0; i < self._$rows.length; i++ ) {
884 if ( $( self._$rows[i] ).hasClass( selector ) ) {
885 if ( self._direction ) {
886 newX = -( i * self._cellSize - self._clipSize / 2 + self._cellSize * 2 );
888 newY = -( i * self._cellSize - self._clipSize / 2 + self._cellSize * 2 );
890 self.scrollTo( newX, newY );
896 scrollTo: function ( x, y, duration ) {
898 if ( self._direction ) {
900 self._sx = self._reservedPos;
901 self._reservedPos = x;
904 self._sy = self._reservedPos;
905 self._reservedPos = y;
907 self._scrollView.scrollTo.apply( this, [ x, y, duration ] );
910 getScrollPosition: function () {
911 if ( this.direction ) {
912 return { x: -this._ry, y: 0 };
914 return { x: 0, y: -this._ry };
917 _setScrollPosition: function ( x, y ) {
919 sy = self._scalableSize,
920 distance = self._direction ? x : y,
922 di = parseInt( dy / self._cellSize, 10 ),
926 realRowCount = self._rowsPerView + 2,
929 if ( self._blockScroll ) {
930 if ( dy > 0 && distance >= -self._cellSize && self._scalableSize >= -self._cellSize ) {
931 self._overflowDir = _OVERFLOW_DIR_UP;
933 if ( (dy < 0 && self._scalableSize <= -(self._maxSize + self._cellSize) )) {
934 self._overflowDir = _OVERFLOW_DIR_DOWN;
939 if ( ! self.options.rotation ) {
940 if ( dy > 0 && distance >= -self._cellSize && self._scalableSize >= -self._cellSize ) {
943 self._scalableSize = -self._cellSize;
944 self._setElementTransform( -self._cellSize );
945 if ( self._overflowDir === _OVERFLOW_DIR_NONE ) {
946 self._overflowDir = _OVERFLOW_DIR_UP;
950 if ( ( dy < 0 && self._scalableSize <= -( self._maxSize + self._cellSize ) ) ) {
953 self._scalableSize = -(self._maxSizeExceptClip + self._cellSize);
954 self._setElementTransform( self._modifyViewPos );
955 if ( self._overflowDir === _OVERFLOW_DIR_NONE ) {
956 self._overflowDir = _OVERFLOW_DIR_DOWN;
962 replaceStartIdx = ( Math.abs( di ) < realRowCount ) ? 0 : ( di > 0 ) ? di - realRowCount : di + realRowCount;
963 if ( di > 0 ) { // scroll up
964 for ( i = replaceStartIdx; i < di; i++ ) {
965 idx = -parseInt( ( sy / self._cellSize ) + i + 3, 10 );
966 $row = self._$view.children().last().detach();
967 self._replaceRow( $row, circularNum( idx, self._totalRowCnt ) );
968 self._$view.prepend( $row );
970 } else if ( di < 0 ) { // scroll down
971 for ( i = replaceStartIdx; i > di; i-- ) {
972 idx = self._rowsPerView - parseInt( ( sy / self._cellSize ) + i, 10 );
973 $row = self._$view.children().first().detach();
974 self._replaceRow( $row, circularNum( idx, self._totalRowCnt ) );
975 self._$view.append( $row );
978 self._setScrollBarPosition( -di );
979 self._scalableSize += di * self._cellSize;
980 self._setElementTransform( distance - self._scalableSize - self._cellSize );
983 _setElementTransform : function ( value ) {
988 if ( self._direction ) {
993 self._setStyleTransform( self._$view, x, y );
996 //----------------------------------------------------//
998 //----------------------------------------------------//
999 _handleMomentumScroll: function () {
1001 opts = self.options,
1010 if ( self._direction ) {
1011 x = t.getPosition();
1013 y = t.getPosition();
1015 keepGoing = !t.done();
1018 self._setScrollPosition( x, y );
1019 if ( !opts.rotation ) {
1020 keepGoing = !t.done();
1021 self._reservedPos = self._direction ? x : y;
1023 self._reservedPos = self._reservedPos <= (-(self._maxSizeExceptClip - self._modifyViewPos)) ? ( - ( self._maxSizeExceptClip + self._cellSize) ) : self._reservedPos;
1025 self._reservedPos = self._reservedPos > -self._cellSize ? -self._cellSize : self._reservedPos;
1027 self._reservedPos = self._direction ? x : y;
1029 self._$clip.trigger( self.options.updateEventName, [ { x: x, y: y } ] );
1032 self._timerID = setTimeout( self._timerCB, self._timerInterval );
1034 self._stopMScroll();
1038 _startMScroll: function ( speedX, speedY ) {
1040 if ( self._direction ) {
1041 self._sx = self._reservedPos;
1043 self._sy = self._reservedPos;
1045 self._scrollView._startMScroll.apply( self, [ speedX, speedY ] );
1048 _stopMScroll: function () {
1049 this._scrollView._stopMScroll.apply( this );
1052 _enableTracking: function () {
1054 self._$view.bind( self._dragMoveEvt, self._dragMoveCB );
1055 self._$view.bind( self._dragStopEvt, self._dragStopCB );
1056 self._scrollView._enableTracking.apply( self );
1059 _disableTracking: function () {
1061 self._$view.unbind( self._dragMoveEvt, self._dragMoveCB );
1062 self._$view.unbind( self._dragStopEvt, self._dragStopCB );
1063 self._scrollView._disableTracking.apply( self );
1066 _handleDragStart: function ( e, ex, ey ) {
1068 self._scrollView._handleDragStart.apply( this, [ e, ex, ey ] );
1069 self._eventPos = self._direction ? ex : ey;
1070 self._nextPos = self._reservedPos;
1073 _handleDragMove: function ( e, ex, ey ) {
1075 dx = ex - self._lastX,
1076 dy = ey - self._lastY,
1079 diffFromStartPos = 0,
1080 diffFromLastPos = 0,
1083 overFlowTarget = null;
1085 self._lastMove = getCurrentTime();
1089 self._didDrag = true;
1094 if ( self._direction ) {
1095 self._movePos = ex - self._eventPos;
1096 x = self._nextPos + self._movePos;
1099 self._movePos = ey - self._eventPos;
1100 y = self._nextPos + self._movePos;
1103 self._showScrollBars();
1104 self._setScrollPosition( x, y );
1105 if ( self._overflowDir !== _OVERFLOW_DIR_NONE ) {
1106 overFlowTarget = ( self._overflowDir === _OVERFLOW_DIR_UP ) ? self._overflowTop : self._overflowBottom;
1107 if ( !self._overflowDisplayed ) {
1108 self._overflowDisplayed = true;
1109 self._overflowStartPos = overflowPos;
1111 diffFromStartPos = ( overflowPos - self._overflowStartPos ) * self._overflowDir;
1112 opacity = ( diffFromStartPos < 0 ) ?
1113 0 : ( diffFromStartPos > self._overflowMaxDragDist ) ?
1114 1 : ( diffFromStartPos / self._overflowMaxDragDist );
1115 overFlowTarget.css( "opacity", opacity );
1121 _handleDragStop: function ( e ) {
1124 self._reservedPos = self._movePos ? self._nextPos + self._movePos : self._reservedPos;
1125 self._scrollView._handleDragStop.apply( this, [ e ] );
1126 if ( self._overflowDir !== _OVERFLOW_DIR_NONE ) {
1127 self._overflowDir = _OVERFLOW_DIR_NONE;
1128 self._hideVGOverflowIndicator();
1130 return self._didDrag ? false : undefined;
1133 _addBehaviors: function () {
1136 // scroll event handler.
1137 if ( self.options.eventType === "mouse" ) {
1138 self._dragStartEvt = "mousedown";
1139 self._dragStartCB = function ( e ) {
1140 return self._handleDragStart( e, e.clientX, e.clientY );
1143 self._dragMoveEvt = "mousemove";
1144 self._dragMoveCB = function ( e ) {
1145 return self._handleDragMove( e, e.clientX, e.clientY );
1148 self._dragStopEvt = "mouseup";
1149 self._dragStopCB = function ( e ) {
1150 return self._handleDragStop( e, e.clientX, e.clientY );
1153 self._$view.bind( "vclick", function ( e ) {
1154 return !self._didDrag;
1157 self._dragStartEvt = "touchstart";
1158 self._dragStartCB = function ( e ) {
1159 var t = e.originalEvent.targetTouches[0];
1160 return self._handleDragStart( e, t.pageX, t.pageY );
1163 self._dragMoveEvt = "touchmove";
1164 self._dragMoveCB = function ( e ) {
1165 var t = e.originalEvent.targetTouches[0];
1166 return self._handleDragMove( e, t.pageX, t.pageY );
1169 self._dragStopEvt = "touchend";
1170 self._dragStopCB = function ( e ) {
1171 return self._handleDragStop( e );
1174 self._$view.bind( self._dragStartEvt, self._dragStartCB );
1177 self._$view.delegate( ".virtualgrid-item", "click", function ( event ) {
1178 var $selectedItem = $( this );
1179 $selectedItem.trigger( "select", this );
1182 $( window ).bind( "resize", function ( e ) {
1184 $virtualgrid = $( ".ui-virtualgrid-view" );
1185 if ( $virtualgrid.length !== 0 ) {
1186 if ( self._direction ) {
1187 height = self._calculateClipHeight();
1188 self._$view.height( height );
1189 self._$clip.height( height );
1191 height = self._calculateClipWidth();
1192 self._$view.width( height );
1193 self._$clip.width( height );
1199 $( document ).one( "pageshow", function ( event ) {
1200 var $page = $( self.element ).parents( ".ui-page" ),
1201 $header = $page.find( ":jqmData(role='header')" ),
1202 $footer = $page.find( ":jqmData(role='footer')" ),
1203 $content = $page.find( ":jqmData(role='content')" ),
1204 footerHeight = $footer ? $footer.height() : 0,
1205 headerHeight = $header ? $header.height() : 0;
1207 if ( $page && $content ) {
1208 $content.height( window.innerHeight - headerHeight - footerHeight ).css( "overflow", "hidden" );
1209 $content.addClass( "ui-virtualgrid-content" );
1214 //----------------------------------------------------//
1215 // Calculate size about dom element. //
1216 //----------------------------------------------------//
1217 _calculateClipSize : function () {
1221 if ( self._direction ) {
1222 clipSize = self._calculateClipWidth();
1224 clipSize = self._calculateClipHeight();
1229 _calculateClipWidth : function () {
1231 view = $( self.element ),
1232 $parent = $( self.element ).parent(),
1234 clipSize = $( window ).width();
1236 if ( self._inheritedSize.isDefinedWidth ) {
1237 return self._inheritedSize.width;
1240 if ( $parent.hasClass( "ui-content" ) ) {
1241 paddingValue = parseInt( $parent.css( "padding-left" ), 10 );
1242 clipSize = clipSize - ( paddingValue || 0 );
1243 paddingValue = parseInt( $parent.css( "padding-right" ), 10 );
1244 clipSize = clipSize - ( paddingValue || 0 );
1246 clipSize = view.width();
1251 _calculateClipHeight : function () {
1253 view = $( self.element ),
1254 $parent = view.parent(),
1258 clipSize = $( window ).height();
1260 if ( self._inheritedSize.isDefinedHeight ) {
1261 return self._inheritedSize.height;
1264 if ( $parent.hasClass( "ui-content" ) ) {
1265 paddingValue = parseInt( $parent.css( "padding-top" ), 10 );
1266 clipSize = clipSize - ( paddingValue || 0 );
1267 paddingValue = parseInt( $parent.css( "padding-bottom" ), 10 );
1268 clipSize = clipSize - ( paddingValue || 0 );
1269 header = $parent.siblings( ".ui-header" );
1270 footer = $parent.siblings( ".ui-footer" );
1273 if ( header.outerHeight( true ) === null ) {
1274 clipSize = clipSize - ( $( ".ui-header" ).outerHeight() || 0 );
1276 clipSize = clipSize - header.outerHeight( true );
1280 clipSize = clipSize - footer.outerHeight( true );
1283 clipSize = view.height();
1288 _calculateColumnSize : function () {
1293 $tempBlock = self._makeRows( 1 );
1294 self._$view.append( $tempBlock.children().first() );
1295 if ( self._direction ) {
1297 self._viewSize = self._$view.width();
1298 $cell = self._$view.children().first().children().first();
1299 self._cellSize = $cell.outerWidth( true );
1300 self._cellOtherSize = $cell.outerHeight( true );
1303 self._viewSize = self._$view.height();
1304 $cell = self._$view.children().first().children().first();
1305 self._cellSize = $cell.outerHeight( true );
1306 self._cellOtherSize = $cell.outerWidth( true );
1308 $tempBlock.remove();
1309 self._$view.children().remove();
1312 _calculateColumnCount : function ( ) {
1314 $view = $( self.element ),
1315 viewSize = self._direction ? $view.innerHeight() : $view.innerWidth(),
1318 if ( self._direction ) {
1319 viewSize = viewSize - ( parseInt( $view.css( "padding-top" ), 10 ) + parseInt( $view.css( "padding-bottom" ), 10 ) );
1321 viewSize = viewSize - ( parseInt( $view.css( "padding-left" ), 10 ) + parseInt( $view.css( "padding-right" ), 10 ) );
1324 itemCount = parseInt( ( viewSize / self._cellOtherSize ), 10 );
1325 return itemCount > 0 ? itemCount : 1 ;
1328 // Read the position of clip form property ('webkit-transform').
1329 // @return : number - position of clip.
1330 _getClipPosition : function () {
1334 result = -self._cellSize,
1335 $scrollview = self._$view.closest( ".ui-scrollview-view" );
1337 if ( $scrollview ) {
1338 matrix = $scrollview.css( "-webkit-transform" );
1339 contents = matrix.substr( 7 );
1340 contents = contents.substr( 0, contents.length - 1 );
1341 contents = contents.split( ', ' );
1342 result = Math.abs( contents [5] );
1347 //----------------------------------------------------//
1348 // DOM Element handle //
1349 //----------------------------------------------------//
1350 _makeRows : function ( count ) {
1352 opts = self.options,
1357 $wrapper = $( self._createElement( "div" ) );
1358 $wrapper.addClass( "ui-scrollview-view" );
1359 for ( index = 0; index < count ; index += 1 ) {
1360 $row = self._makeRow( self._template, index );
1361 if ( self._direction ) {
1362 $row.css( "top", 0 ).css( "left", ( index * self._cellSize ) );
1364 $wrapper.append( $row );
1369 // make a single row block
1370 _makeRow : function ( myTemplate, rowIndex ) {
1372 opts = self.options,
1373 index = rowIndex * self._itemCount,
1377 attrName = self._direction ? "top" : "left",
1378 blockClassName = self._direction ? "ui-virtualgrid-wrapblock-x" : "ui-virtualgrid-wrapblock-y",
1379 blockAttrName = self._direction ? "top" : "left",
1380 wrapBlock = $( self._createElement( "div" ) );
1382 wrapBlock.addClass( blockClassName ).attr( "row-index", rowIndex );
1383 for ( colIndex = 0; colIndex < self._itemCount; colIndex++ ) {
1384 htmlData = self._makeHtmlData( myTemplate, index, colIndex );
1386 wrapBlock.append( htmlData );
1393 _makeHtmlData : function ( myTemplate, dataIndex, colIndex ) {
1397 attrName = self._direction ? "top" : "left";
1399 itemData = self._itemData( dataIndex );
1401 htmlData = self._tmpl( itemData );
1402 htmlData.css( attrName, ( colIndex * self._cellOtherSize ) ).addClass( "virtualgrid-item" );
1407 _increaseRow : function ( num ) {
1409 rotation = self.options.rotation,
1414 size = self._scalableSize,
1417 headItemIndex = parseInt( $( self._$view.children().first() ).attr( "row-index" ), 10 ) - 1;
1418 tailItemIndex = parseInt( $( self._$view.children()[self._rowsPerView] ).attr( "row-index" ), 10 ) + 1;
1420 for ( idx = 1 ; idx <= num ; idx++ ) {
1421 if ( tailItemIndex + idx >= self._totalRowCnt ) {
1422 $row = self._makeRow( self._template, headItemIndex );
1423 self._$view.prepend( $row );
1426 $row = self._makeRow( self._template, tailItemIndex + idx );
1427 self._$view.append( $row );
1429 if ( self._direction ) {
1430 $row.width( self._cellSize );
1432 $row.height( self._cellSize );
1437 _decreaseRow : function ( num ) {
1441 for ( idx = 0 ; idx < num ; idx++ ) {
1442 self._$view.children().last().remove();
1446 _replaceRows : function ( curCnt, prevCnt, maxCnt, clipPosition ) {
1448 $rows = self._$view.children(),
1453 filterCondition = ( self._filterRatio * self._cellSize ) + self._cellSize,
1456 if ( filterCondition < clipPosition ) {
1460 prevRowIndex = parseInt( $( $rows[targetCnt] ).attr( "row-index" ), 10 );
1461 if ( prevRowIndex === 0 ) {
1463 rowIndex = maxCnt - targetCnt;
1465 rowIndex = Math.round( ( prevRowIndex * prevCnt ) / curCnt );
1466 if ( rowIndex + self._rowsPerView >= maxCnt ) {
1468 rowIndex = maxCnt - self._rowsPerView;
1470 diffRowCnt = prevRowIndex - rowIndex;
1471 rowIndex -= targetCnt;
1474 for ( idx = 0 ; idx < $rows.length ; idx += 1 ) {
1475 self._replaceRow( $rows[idx], circularNum( rowIndex, self._totalRowCnt ) );
1481 _replaceRow : function ( block, index ) {
1483 opts = self.options,
1486 $block = block.attr ? block : $( block ),
1494 $columns = $block.attr( "row-index", index ).children();
1495 if ( $columns.length !== self._itemCount ) {
1496 $block.children().remove();
1497 tempBlocks = $( self._makeRow( self._template, index ) );
1498 $block.append( tempBlocks.children() );
1499 tempBlocks.remove();
1503 dataIdx = index * self._itemCount;
1504 for ( idx = 0; idx < self._itemCount ; idx += 1 ) {
1505 $column = $columns.eq( idx );
1506 data = self._itemData( dataIdx );
1507 if ( $column && data ) {
1508 htmlData = self._tmpl( data );
1509 self._replace( $column, htmlData, false );
1510 htmlData.remove(); // Clear temporary htmlData to free cache
1512 } else if ( $column && !data ) {
1518 _createElement : function ( tag ) {
1519 var element = document.createElement( tag );
1521 this._fragment.appendChild( element );
1524 _getObjectNames : function ( obj ) {
1525 var properties = [],
1528 for ( name in obj ) {
1529 properties.push( name );
1531 this._properties = properties;
1534 _tmpl : function ( data ) {
1543 plainMsg = self._template.text();
1544 for ( idx = 0 ; idx < self._properties.length ; idx++ ) {
1545 plainMsg = self._strReplace( plainMsg, "${" + self._properties[ idx ] + "}" , data[ self._properties[ idx ] ] );
1547 ret = $( plainMsg );
1551 _strReplace : function ( plainMsg, stringToFind, stringToReplace ) {
1552 var temp = plainMsg,
1553 index = plainMsg.indexOf( stringToFind );
1554 while ( index !== -1 ) {
1555 temp = temp.replace( stringToFind, stringToReplace );
1556 index = temp.indexOf( stringToFind );
1561 /* Text & image src replace function */
1562 // @param oldItem : prev HtmlDivElement
1563 // @param newItem : new HtmlDivElement for replace
1565 _replace : function ( oldItem, newItem, key ) {
1566 var NODETYPE = { ELEMENT_NODE : 1, TEXT_NODE : 3 };
1568 $( oldItem ).find( ".ui-li-text-main", ".ui-li-text-sub", "ui-btn-text" ).each( function ( index ) {
1569 var oldObj = $( this ),
1570 newText = $( newItem ).find( ".ui-li-text-main", ".ui-li-text-sub", "ui-btn-text" ).eq( index ).text();
1572 $( oldObj ).contents().filter( function () {
1573 return ( this.nodeType == NODETYPE.TEXT_NODE );
1574 }).get( 0 ).data = newText;
1577 $( oldItem ).find( "img" ).each( function ( imgIndex ) {
1578 var oldObj = $( this ),
1579 newImg = $( newItem ).find( "img" ).eq( imgIndex ).attr( "src" );
1581 $( oldObj ).attr( "src", newImg );
1583 $( oldItem ).removeData();
1585 $( oldItem ).data( key, $( newItem ).data( key ) );
1590 $( document ).bind( "pagecreate create", function ( e ) {
1591 $( ":jqmData(role='virtualgrid')" ).virtualgrid();
1593 } ( jQuery, window, document ) );