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)";
41 "-webkit-transform": v,
46 function MomentumTracker( options ) {
47 this.options = $.extend( {}, options );
48 this.easing = "easeOutQuad";
57 function getCurrentTime() {
61 $.extend( MomentumTracker.prototype, {
62 start: function ( pos, speed, duration ) {
63 this.state = ( speed != 0 ) ? tstates.scrolling : tstates.done;
66 this.duration = duration;
71 this.startTime = getCurrentTime();
75 this.state = tstates.done;
82 var state = this.state,
88 if ( state == tstates.done ) {
92 duration = this.duration;
93 elapsed = getCurrentTime() - this.startTime;
94 elapsed = elapsed > duration ? duration : elapsed;
96 dx = this.speed * ( 1 - $.easing[this.easing](elapsed / duration, elapsed, 0, 1, duration ) );
101 if ( elapsed >= duration ) {
102 this.state = tstates.done;
109 return this.state == tstates.done;
112 getPosition: function () {
117 jQuery.widget( "mobile.circularview", jQuery.mobile.widget, {
121 scrollDuration: 2000,
124 moveIntervalThreshold: 150,
126 startEventName: "scrollstart",
127 updateEventName: "scrollupdate",
128 stopEventName: "scrollstop",
130 eventType: $.support.touch ? "touch" : "mouse",
132 delayedClickSelector: "a, .ui-btn",
133 delayedClickEnabled: false
136 _makePositioned: function ( $ele ) {
137 if ( $ele.css( 'position' ) == 'static' ) {
138 $ele.css( 'position', 'relative' );
142 _create: function () {
145 this._items = $( this.element ).jqmData('list');
146 this._$clip = $( this.element ).addClass( "ui-scrollview-clip" );
147 this._$clip.wrapInner( '<div class="ui-scrollview-view"></div>' );
148 this._$view = $('.ui-scrollview-view', this._$clip );
149 this._$list = $( 'ul', this._$clip );
151 this._$clip.css( "overflow", "hidden" );
152 this._makePositioned( this._$clip );
154 this._$view.css( "overflow", "hidden" );
155 this._tracker = new MomentumTracker( this.options );
157 this._timerInterval = 1000 / this.options.fps;
160 this._timerCB = function () { self._handleMomentumScroll(); };
164 this._addBehaviors();
167 reflow: function () {
168 var xy = this.getScrollPosition();
170 this.scrollTo( xy.x, xy.y );
173 refresh: function () {
176 this._$clip.width( $(window).width() );
177 this._clipWidth = this._$clip.width();
179 this._$list.append(this._items[0]);
180 this._itemWidth = $(this._items[0]).outerWidth();
181 $(this._items[0]).detach();
183 itemsPerView = this._clipWidth / this._itemWidth;
184 itemsPerView = Math.ceil( itemsPerView * 10 ) / 10;
185 this._itemsPerView = parseInt( itemsPerView, 10 );
186 while ( this._itemsPerView + 1 > this._items.length ) {
187 $.merge( this._items, $(this._items).clone() );
189 this._rx = -this._itemWidth;
190 this._sx = -this._itemWidth;
194 _startMScroll: function ( speedX, speedY ) {
197 var keepGoing = false,
198 duration = this.options.scrollDuration,
203 this._$clip.trigger( this.options.startEventName);
205 t.start( this._rx, speedX, duration, (v > c ) ? -(v - c) : 0, 0 );
206 keepGoing = !t.done();
209 this._timerID = setTimeout( this._timerCB, this._timerInterval );
213 //console.log( "startmscroll" + this._rx + "," + this._sx );
216 _stopMScroll: function () {
217 if ( this._timerID ) {
218 this._$clip.trigger( this.options.stopEventName );
219 clearTimeout( this._timerID );
224 if ( this._tracker ) {
225 this._tracker.reset();
227 //console.log( "stopmscroll" + this._rx + "," + this._sx );
230 _handleMomentumScroll: function () {
231 var keepGoing = false,
241 keepGoing = !t.done();
245 this._setScrollPosition( x, y );
248 this._$clip.trigger( this.options.updateEventName, [ { x: x, y: y } ] );
251 this._timerID = setTimeout( this._timerCB, this._timerInterval );
257 _setItems: function () {
261 for ( i = -1; i < this._itemsPerView + 1; i++ ) {
262 $item = this._items[ circularNum( i, this._items.length ) ];
263 this._$list.append( $item );
265 setElementTransform( this._$view, this._sx + "px", 0 );
266 this._$view.width( this._itemWidth * ( this._itemsPerView + 2 ) );
267 this._viewWidth = this._$view.width();
270 _setScrollPosition: function ( x, y ) {
273 di = parseInt( dx / this._itemWidth, 10 ),
279 for ( i = 0; i < di; i++ ) {
280 this._$list.children().last().detach();
281 idx = -parseInt( ( sx / this._itemWidth ) + i + 3, 10 );
282 $item = this._items[ circularNum( idx, this._items.length ) ];
283 this._$list.prepend( $item );
284 //console.log( "di > 0 : " + idx );
286 } else if ( di < 0 ) {
287 for ( i = 0; i > di; i-- ) {
288 this._$list.children().first().detach();
289 idx = this._itemsPerView - parseInt( ( sx / this._itemWidth ) + i, 10 );
290 $item = this._items[ circularNum( idx, this._items.length ) ];
291 this._$list.append( $item );
292 //console.log( "di < 0 : " + idx );
296 this._sx += di * this._itemWidth;
298 setElementTransform( this._$view, ( x - this._sx - this._itemWidth ) + "px", 0 );
300 //console.log( "rx " + this._rx + "sx " + this._sx );
303 _enableTracking: function () {
304 $(document).bind( this._dragMoveEvt, this._dragMoveCB );
305 $(document).bind( this._dragStopEvt, this._dragStopCB );
308 _disableTracking: function () {
309 $(document).unbind( this._dragMoveEvt, this._dragMoveCB );
310 $(document).unbind( this._dragStopEvt, this._dragStopCB );
313 _getScrollHierarchy: function () {
316 this._$clip.parents( '.ui-scrollview-clip' ).each( function () {
317 d = $( this ).jqmData( 'circulaview' );
325 centerTo: function ( selector, duration ) {
329 for ( i = 0; i < this._items.length; i++ ) {
330 if ( $( this._items[i]).is( selector ) ) {
331 newX = -( i * this._itemWidth - this._clipWidth / 2 + this._itemWidth * 1.5 );
332 this.scrollTo( newX + this._itemWidth, 0 );
333 this.scrollTo( newX, 0, duration );
339 scrollTo: function ( x, y, duration ) {
342 this._setScrollPosition( x, y );
348 start = getCurrentTime(),
349 efunc = $.easing.easeOutQuad,
360 tfunc = function () {
361 elapsed = getCurrentTime() - start;
362 if ( elapsed >= duration ) {
364 self._setScrollPosition( x, y );
365 self._$clip.trigger("scrollend");
367 ec = efunc( elapsed / duration, elapsed, 0, 1, duration );
368 self._setScrollPosition( sx + ( dx * ec ), sy + ( dy * ec ) );
369 self._timerID = setTimeout( tfunc, self._timerInterval );
373 this._timerID = setTimeout( tfunc, this._timerInterval );
376 getScrollPosition: function () {
377 return { x: -this._rx, y: 0 };
380 _handleDragStart: function ( e, ex, ey ) {
381 $.each( this._getScrollHierarchy(), function ( i, sv ) {
387 if ( this.options.delayedClickEnabled ) {
388 this._$clickEle = $( e.target ).closest( this.options.delayedClickSelector );
394 this._didDrag = false;
397 this._enableTracking();
402 if ( this.options.eventType == "mouse" || this.options.delayedClickEnabled ) {
405 //console.log( "scrollstart" + this._rx + "," + this._sx );
409 _handleDragMove: function ( e, ex, ey ) {
410 this._lastMove = getCurrentTime();
412 var dx = ex - this._lastX,
413 dy = ey - this._lastY;
418 this._didDrag = true;
423 this._mx = ex - this._ox;
425 this._setScrollPosition( this._nx + this._mx, 0 );
427 //console.log( "scrollmove" + this._rx + "," + this._sx );
431 _handleDragStop: function ( e ) {
432 var l = this._lastMove,
433 t = getCurrentTime(),
434 doScroll = l && ( t - l ) <= this.options.moveIntervalThreshold,
435 sx = ( this._tracker && this._speedX && doScroll ) ? this._speedX : 0,
438 this._rx = this._mx ? this._nx + this._mx : this._rx;
441 this._startMScroll( sx, sy );
444 //console.log( "scrollstop" + this._rx + "," + this._sx );
446 this._disableTracking();
448 if ( !this._didDrag && this.options.delayedClickEnabled && this._$clickEle.length ) {
450 .trigger( "mousedown" )
451 .trigger( "mouseup" )
455 if ( this._didDrag ) {
460 return this._didDrag ? false : undefined;
463 _addBehaviors: function () {
466 if ( this.options.eventType === "mouse" ) {
467 this._dragStartEvt = "mousedown";
468 this._dragStartCB = function ( e ) {
469 return self._handleDragStart( e, e.clientX, e.clientY );
472 this._dragMoveEvt = "mousemove";
473 this._dragMoveCB = function ( e ) {
474 return self._handleDragMove( e, e.clientX, e.clientY );
477 this._dragStopEvt = "mouseup";
478 this._dragStopCB = function ( e ) {
479 return self._handleDragStop( e );
482 this._$view.bind( "vclick", function (e) {
483 return !self._didDrag;
487 this._dragStartEvt = "touchstart";
488 this._dragStartCB = function ( e ) {
489 var t = e.originalEvent.targetTouches[0];
490 return self._handleDragStart(e, t.pageX, t.pageY );
493 this._dragMoveEvt = "touchmove";
494 this._dragMoveCB = function ( e ) {
495 var t = e.originalEvent.targetTouches[0];
496 return self._handleDragMove(e, t.pageX, t.pageY );
499 this._dragStopEvt = "touchend";
500 this._dragStopCB = function ( e ) {
501 return self._handleDragStop( e );
504 this._$view.bind( this._dragStartEvt, this._dragStartCB );
508 $( document ).bind( "pagecreate create", function ( e ) {
509 $( $.mobile.circularview.prototype.options.initSelector, e.target ).circularview();
512 }( jQuery, window, document ) ); // End Component