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 * ***************************************************************************
24 // most of following codes are derived from jquery.mobile.scrollview.js
25 (function ( $, window, document, undefined ) {
27 function circularNum( num, total ) {
35 function setElementTransform( $ele, x, y ) {
36 var v = "translate3d( " + x + "," + y + ", 0px)";
39 "-webkit-transform": v,
44 function MomentumTracker( options ) {
45 this.options = $.extend( {}, options );
46 this.easing = "easeOutQuad";
55 function getCurrentTime() {
59 $.extend( MomentumTracker.prototype, {
60 start: function ( pos, speed, duration ) {
61 this.state = ( speed != 0 ) ? tstates.scrolling : tstates.done;
64 this.duration = duration;
69 this.startTime = getCurrentTime();
73 this.state = tstates.done;
80 var state = this.state,
86 if ( state == tstates.done ) {
90 duration = this.duration;
91 elapsed = getCurrentTime() - this.startTime;
92 elapsed = elapsed > duration ? duration : elapsed;
94 dx = this.speed * ( 1 - $.easing[this.easing](elapsed / duration, elapsed, 0, 1, duration ) );
99 if ( elapsed >= duration ) {
100 this.state = tstates.done;
107 return this.state == tstates.done;
110 getPosition: function () {
115 jQuery.widget( "mobile.circularview", jQuery.mobile.widget, {
119 scrollDuration: 2000,
122 moveIntervalThreshold: 150,
124 startEventName: "scrollstart",
125 updateEventName: "scrollupdate",
126 stopEventName: "scrollstop",
128 eventType: $.support.touch ? "touch" : "mouse",
130 delayedClickSelector: "a, .ui-btn",
131 delayedClickEnabled: false
134 _makePositioned: function ( $ele ) {
135 if ( $ele.css( 'position' ) == 'static' ) {
136 $ele.css( 'position', 'relative' );
140 _create: function () {
143 this._items = $( this.element ).jqmData('list');
144 this._$clip = $( this.element ).addClass( "ui-scrollview-clip" );
145 this._$clip.wrapInner( '<div class="ui-scrollview-view"></div>' );
146 this._$view = $('.ui-scrollview-view', this._$clip );
147 this._$list = $( 'ul', this._$clip );
149 this._$clip.css( "overflow", "hidden" );
150 this._makePositioned( this._$clip );
152 this._$view.css( "overflow", "hidden" );
153 this._tracker = new MomentumTracker( this.options );
155 this._timerInterval = 1000 / this.options.fps;
158 this._timerCB = function () { self._handleMomentumScroll(); };
162 this._addBehaviors();
165 reflow: function () {
166 var xy = this.getScrollPosition();
168 this.scrollTo( xy.x, xy.y );
171 refresh: function () {
174 this._$clip.width( $(window).width() );
175 this._clipWidth = this._$clip.width();
177 this._$list.append(this._items[0]);
178 this._itemWidth = $(this._items[0]).outerWidth();
179 $(this._items[0]).detach();
181 itemsPerView = this._clipWidth / this._itemWidth;
182 itemsPerView = Math.ceil( itemsPerView * 10 ) / 10;
183 this._itemsPerView = parseInt( itemsPerView, 10 );
184 while ( this._itemsPerView + 1 > this._items.length ) {
185 $.merge( this._items, $(this._items).clone() );
187 this._rx = -this._itemWidth;
188 this._sx = -this._itemWidth;
192 _startMScroll: function ( speedX, speedY ) {
195 var keepGoing = false,
196 duration = this.options.scrollDuration,
201 this._$clip.trigger( this.options.startEventName);
203 t.start( this._rx, speedX, duration, (v > c ) ? -(v - c) : 0, 0 );
204 keepGoing = !t.done();
207 this._timerID = setTimeout( this._timerCB, this._timerInterval );
211 //console.log( "startmscroll" + this._rx + "," + this._sx );
214 _stopMScroll: function () {
215 if ( this._timerID ) {
216 this._$clip.trigger( this.options.stopEventName );
217 clearTimeout( this._timerID );
222 if ( this._tracker ) {
223 this._tracker.reset();
225 //console.log( "stopmscroll" + this._rx + "," + this._sx );
228 _handleMomentumScroll: function () {
229 var keepGoing = false,
239 keepGoing = !t.done();
243 this._setScrollPosition( x, y );
246 this._$clip.trigger( this.options.updateEventName, [ { x: x, y: y } ] );
249 this._timerID = setTimeout( this._timerCB, this._timerInterval );
255 _setItems: function () {
259 for ( i = -1; i < this._itemsPerView + 1; i++ ) {
260 $item = this._items[ circularNum( i, this._items.length ) ];
261 this._$list.append( $item );
263 setElementTransform( this._$view, this._sx + "px", 0 );
264 this._$view.width( this._itemWidth * ( this._itemsPerView + 2 ) );
265 this._viewWidth = this._$view.width();
268 _setScrollPosition: function ( x, y ) {
271 di = parseInt( dx / this._itemWidth, 10 ),
277 for ( i = 0; i < di; i++ ) {
278 this._$list.children().last().detach();
279 idx = -parseInt( ( sx / this._itemWidth ) + i + 3, 10 );
280 $item = this._items[ circularNum( idx, this._items.length ) ];
281 this._$list.prepend( $item );
282 //console.log( "di > 0 : " + idx );
284 } else if ( di < 0 ) {
285 for ( i = 0; i > di; i-- ) {
286 this._$list.children().first().detach();
287 idx = this._itemsPerView - parseInt( ( sx / this._itemWidth ) + i, 10 );
288 $item = this._items[ circularNum( idx, this._items.length ) ];
289 this._$list.append( $item );
290 //console.log( "di < 0 : " + idx );
294 this._sx += di * this._itemWidth;
296 setElementTransform( this._$view, ( x - this._sx - this._itemWidth ) + "px", 0 );
298 //console.log( "rx " + this._rx + "sx " + this._sx );
301 _enableTracking: function () {
302 $(document).bind( this._dragMoveEvt, this._dragMoveCB );
303 $(document).bind( this._dragStopEvt, this._dragStopCB );
306 _disableTracking: function () {
307 $(document).unbind( this._dragMoveEvt, this._dragMoveCB );
308 $(document).unbind( this._dragStopEvt, this._dragStopCB );
311 _getScrollHierarchy: function () {
314 this._$clip.parents( '.ui-scrollview-clip' ).each( function () {
315 d = $( this ).jqmData( 'circulaview' );
323 centerTo: function ( selector, duration ) {
327 for ( i = 0; i < this._items.length; i++ ) {
328 if ( $( this._items[i]).is( selector ) ) {
329 newX = -( i * this._itemWidth - this._clipWidth / 2 + this._itemWidth * 1.5 );
330 this.scrollTo( newX + this._clipWidth * 2, 0 );
331 this.scrollTo( newX, 0, duration );
337 scrollTo: function ( x, y, duration ) {
340 this._setScrollPosition( x, y );
346 start = getCurrentTime(),
347 efunc = $.easing.easeOutQuad,
358 tfunc = function () {
359 elapsed = getCurrentTime() - start;
360 if ( elapsed >= duration ) {
362 self._setScrollPosition( x, y );
363 self._$clip.trigger("scrollend");
365 ec = efunc( elapsed / duration, elapsed, 0, 1, duration );
366 self._setScrollPosition( sx + ( dx * ec ), sy + ( dy * ec ) );
367 self._timerID = setTimeout( tfunc, self._timerInterval );
371 this._timerID = setTimeout( tfunc, this._timerInterval );
374 getScrollPosition: function () {
375 return { x: -this._rx, y: 0 };
378 _handleDragStart: function ( e, ex, ey ) {
379 $.each( this._getScrollHierarchy(), function ( i, sv ) {
385 if ( this.options.delayedClickEnabled ) {
386 this._$clickEle = $( e.target ).closest( this.options.delayedClickSelector );
392 this._didDrag = false;
395 this._enableTracking();
400 if ( this.options.eventType == "mouse" || this.options.delayedClickEnabled ) {
403 //console.log( "scrollstart" + this._rx + "," + this._sx );
407 _handleDragMove: function ( e, ex, ey ) {
408 this._lastMove = getCurrentTime();
410 var dx = ex - this._lastX,
411 dy = ey - this._lastY;
416 this._didDrag = true;
421 this._mx = ex - this._ox;
423 this._setScrollPosition( this._nx + this._mx, 0 );
425 //console.log( "scrollmove" + this._rx + "," + this._sx );
429 _handleDragStop: function ( e ) {
430 var l = this._lastMove,
431 t = getCurrentTime(),
432 doScroll = l && ( t - l ) <= this.options.moveIntervalThreshold,
433 sx = ( this._tracker && this._speedX && doScroll ) ? this._speedX : 0,
436 this._rx = this._mx ? this._nx + this._mx : this._rx;
439 this._startMScroll( sx, sy );
442 //console.log( "scrollstop" + this._rx + "," + this._sx );
444 this._disableTracking();
446 if ( !this._didDrag && this.options.delayedClickEnabled && this._$clickEle.length ) {
448 .trigger( "mousedown" )
449 .trigger( "mouseup" )
453 if ( this._didDrag ) {
458 return this._didDrag ? false : undefined;
461 _addBehaviors: function () {
464 if ( this.options.eventType === "mouse" ) {
465 this._dragStartEvt = "mousedown";
466 this._dragStartCB = function ( e ) {
467 return self._handleDragStart( e, e.clientX, e.clientY );
470 this._dragMoveEvt = "mousemove";
471 this._dragMoveCB = function ( e ) {
472 return self._handleDragMove( e, e.clientX, e.clientY );
475 this._dragStopEvt = "mouseup";
476 this._dragStopCB = function ( e ) {
477 return self._handleDragStop( e );
480 this._$view.bind( "vclick", function (e) {
481 return !self._didDrag;
485 this._dragStartEvt = "touchstart";
486 this._dragStartCB = function ( e ) {
487 var t = e.originalEvent.targetTouches[0];
488 return self._handleDragStart(e, t.pageX, t.pageY );
491 this._dragMoveEvt = "touchmove";
492 this._dragMoveCB = function ( e ) {
493 var t = e.originalEvent.targetTouches[0];
494 return self._handleDragMove(e, t.pageX, t.pageY );
497 this._dragStopEvt = "touchend";
498 this._dragStopCB = function ( e ) {
499 return self._handleDragStop( e );
502 this._$view.bind( this._dragStartEvt, this._dragStartCB );
506 $( document ).bind( "pagecreate create", function ( e ) {
507 $( $.mobile.circularview.prototype.options.initSelector, e.target ).circularview();
510 }( jQuery, window, document ) ); // End Component