2 /* ***************************************************************************
3 * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software" ),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 * ***************************************************************************
25 // most of following codes are derived from jquery.mobile.scrollview.js
26 (function ( $, window, document, undefined ) {
28 function circularNum( num, total ) {
36 function setElementTransform( $ele, x, y ) {
37 var v = "translate3d( " + x + "," + y + ", 0px)";
42 "-webkit-transform": v,
47 function MomentumTracker( options ) {
48 this.options = $.extend( {}, options );
49 this.easing = "easeOutQuad";
58 function getCurrentTime() {
62 $.extend( MomentumTracker.prototype, {
63 start: function ( pos, speed, duration ) {
64 this.state = ( speed != 0 ) ? tstates.scrolling : tstates.done;
67 this.duration = duration;
72 this.startTime = getCurrentTime();
76 this.state = tstates.done;
83 var state = this.state,
89 if ( state == tstates.done ) {
93 duration = this.duration;
94 elapsed = getCurrentTime() - this.startTime;
95 elapsed = elapsed > duration ? duration : elapsed;
97 dx = this.speed * ( 1 - $.easing[this.easing](elapsed / duration, elapsed, 0, 1, duration ) );
102 if ( elapsed >= duration ) {
103 this.state = tstates.done;
110 return this.state == tstates.done;
113 getPosition: function () {
118 jQuery.widget( "mobile.circularview", jQuery.mobile.widget, {
122 scrollDuration: 2000,
125 moveIntervalThreshold: 150,
127 startEventName: "scrollstart",
128 updateEventName: "scrollupdate",
129 stopEventName: "scrollstop",
131 eventType: $.support.touch ? "touch" : "mouse",
133 delayedClickSelector: "a, .ui-btn",
134 delayedClickEnabled: false
137 _makePositioned: function ( $ele ) {
138 if ( $ele.css( 'position' ) == 'static' ) {
139 $ele.css( 'position', 'relative' );
143 _create: function () {
146 this._items = $( this.element ).jqmData('list');
147 this._$clip = $( this.element ).addClass( "ui-scrollview-clip" );
148 this._$clip.wrapInner( '<div class="ui-scrollview-view"></div>' );
149 this._$view = $('.ui-scrollview-view', this._$clip );
150 this._$list = $( 'ul', this._$clip );
152 this._$clip.css( "overflow", "hidden" );
153 this._makePositioned( this._$clip );
155 this._$view.css( "overflow", "hidden" );
156 this._tracker = new MomentumTracker( this.options );
158 this._timerInterval = 1000 / this.options.fps;
161 this._timerCB = function () { self._handleMomentumScroll(); };
165 this._addBehaviors();
168 reflow: function () {
169 var xy = this.getScrollPosition();
171 this.scrollTo( xy.x, xy.y );
174 refresh: function () {
177 this._$clip.width( $(window).width() );
178 this._clipWidth = this._$clip.width();
180 this._$list.append(this._items[0]);
181 this._itemWidth = $(this._items[0]).outerWidth();
182 $(this._items[0]).detach();
184 itemsPerView = this._clipWidth / this._itemWidth;
185 itemsPerView = Math.ceil( itemsPerView * 10 ) / 10;
186 this._itemsPerView = parseInt( itemsPerView, 10 );
187 while ( this._itemsPerView + 1 > this._items.length ) {
188 $.merge( this._items, $(this._items).clone() );
190 this._rx = -this._itemWidth;
191 this._sx = -this._itemWidth;
195 _startMScroll: function ( speedX, speedY ) {
198 var keepGoing = false,
199 duration = this.options.scrollDuration,
204 this._$clip.trigger( this.options.startEventName);
206 t.start( this._rx, speedX, duration, (v > c ) ? -(v - c) : 0, 0 );
207 keepGoing = !t.done();
210 this._timerID = setTimeout( this._timerCB, this._timerInterval );
214 //console.log( "startmscroll" + this._rx + "," + this._sx );
217 _stopMScroll: function () {
218 if ( this._timerID ) {
219 this._$clip.trigger( this.options.stopEventName );
220 clearTimeout( this._timerID );
225 if ( this._tracker ) {
226 this._tracker.reset();
228 //console.log( "stopmscroll" + this._rx + "," + this._sx );
231 _handleMomentumScroll: function () {
232 var keepGoing = false,
242 keepGoing = !t.done();
246 this._setScrollPosition( x, y );
249 this._$clip.trigger( this.options.updateEventName, [ { x: x, y: y } ] );
252 this._timerID = setTimeout( this._timerCB, this._timerInterval );
258 _setItems: function () {
262 for ( i = -1; i < this._itemsPerView + 1; i++ ) {
263 $item = this._items[ circularNum( i, this._items.length ) ];
264 this._$list.append( $item );
266 setElementTransform( this._$view, this._sx + "px", 0 );
267 this._$view.width( this._itemWidth * ( this._itemsPerView + 2 ) );
268 this._viewWidth = this._$view.width();
271 _setScrollPosition: function ( x, y ) {
274 di = parseInt( dx / this._itemWidth, 10 ),
280 for ( i = 0; i < di; i++ ) {
281 this._$list.children().last().detach();
282 idx = -parseInt( ( sx / this._itemWidth ) + i + 3, 10 );
283 $item = this._items[ circularNum( idx, this._items.length ) ];
284 this._$list.prepend( $item );
285 //console.log( "di > 0 : " + idx );
287 } else if ( di < 0 ) {
288 for ( i = 0; i > di; i-- ) {
289 this._$list.children().first().detach();
290 idx = this._itemsPerView - parseInt( ( sx / this._itemWidth ) + i, 10 );
291 $item = this._items[ circularNum( idx, this._items.length ) ];
292 this._$list.append( $item );
293 //console.log( "di < 0 : " + idx );
297 this._sx += di * this._itemWidth;
299 setElementTransform( this._$view, ( x - this._sx - this._itemWidth ) + "px", 0 );
301 //console.log( "rx " + this._rx + "sx " + this._sx );
304 _enableTracking: function () {
305 $(document).bind( this._dragMoveEvt, this._dragMoveCB );
306 $(document).bind( this._dragStopEvt, this._dragStopCB );
309 _disableTracking: function () {
310 $(document).unbind( this._dragMoveEvt, this._dragMoveCB );
311 $(document).unbind( this._dragStopEvt, this._dragStopCB );
314 _getScrollHierarchy: function () {
317 this._$clip.parents( '.ui-scrollview-clip' ).each( function () {
318 d = $( this ).jqmData( 'circulaview' );
326 centerTo: function ( selector, duration ) {
330 for ( i = 0; i < this._items.length; i++ ) {
331 if ( $( this._items[i]).is( selector ) ) {
332 newX = -( i * this._itemWidth - this._clipWidth / 2 + this._itemWidth * 1.5 );
333 this.scrollTo( newX + this._itemWidth, 0 );
334 this.scrollTo( newX, 0, duration );
340 scrollTo: function ( x, y, duration ) {
343 this._setScrollPosition( x, y );
349 start = getCurrentTime(),
350 efunc = $.easing.easeOutQuad,
361 tfunc = function () {
362 elapsed = getCurrentTime() - start;
363 if ( elapsed >= duration ) {
365 self._setScrollPosition( x, y );
366 self._$clip.trigger("scrollend");
368 ec = efunc( elapsed / duration, elapsed, 0, 1, duration );
369 self._setScrollPosition( sx + ( dx * ec ), sy + ( dy * ec ) );
370 self._timerID = setTimeout( tfunc, self._timerInterval );
374 this._timerID = setTimeout( tfunc, this._timerInterval );
377 getScrollPosition: function () {
378 return { x: -this._rx, y: 0 };
381 _handleDragStart: function ( e, ex, ey ) {
382 $.each( this._getScrollHierarchy(), function ( i, sv ) {
388 if ( this.options.delayedClickEnabled ) {
389 this._$clickEle = $( e.target ).closest( this.options.delayedClickSelector );
395 this._didDrag = false;
398 this._enableTracking();
403 if ( this.options.eventType == "mouse" || this.options.delayedClickEnabled ) {
406 //console.log( "scrollstart" + this._rx + "," + this._sx );
410 _handleDragMove: function ( e, ex, ey ) {
411 this._lastMove = getCurrentTime();
413 var dx = ex - this._lastX,
414 dy = ey - this._lastY;
419 this._didDrag = true;
424 this._mx = ex - this._ox;
426 this._setScrollPosition( this._nx + this._mx, 0 );
428 //console.log( "scrollmove" + this._rx + "," + this._sx );
432 _handleDragStop: function ( e ) {
433 var l = this._lastMove,
434 t = getCurrentTime(),
435 doScroll = l && ( t - l ) <= this.options.moveIntervalThreshold,
436 sx = ( this._tracker && this._speedX && doScroll ) ? this._speedX : 0,
439 this._rx = this._mx ? this._nx + this._mx : this._rx;
442 this._startMScroll( sx, sy );
445 //console.log( "scrollstop" + this._rx + "," + this._sx );
447 this._disableTracking();
449 if ( !this._didDrag && this.options.delayedClickEnabled && this._$clickEle.length ) {
451 .trigger( "mousedown" )
452 .trigger( "mouseup" )
456 if ( this._didDrag ) {
461 return this._didDrag ? false : undefined;
464 _addBehaviors: function () {
467 if ( this.options.eventType === "mouse" ) {
468 this._dragStartEvt = "mousedown";
469 this._dragStartCB = function ( e ) {
470 return self._handleDragStart( e, e.clientX, e.clientY );
473 this._dragMoveEvt = "mousemove";
474 this._dragMoveCB = function ( e ) {
475 return self._handleDragMove( e, e.clientX, e.clientY );
478 this._dragStopEvt = "mouseup";
479 this._dragStopCB = function ( e ) {
480 return self._handleDragStop( e );
483 this._$view.bind( "vclick", function (e) {
484 return !self._didDrag;
488 this._dragStartEvt = "touchstart";
489 this._dragStartCB = function ( e ) {
490 var t = e.originalEvent.targetTouches[0];
491 return self._handleDragStart(e, t.pageX, t.pageY );
494 this._dragMoveEvt = "touchmove";
495 this._dragMoveCB = function ( e ) {
496 var t = e.originalEvent.targetTouches[0];
497 return self._handleDragMove(e, t.pageX, t.pageY );
500 this._dragStopEvt = "touchend";
501 this._dragStopCB = function ( e ) {
502 return self._handleDragStop( e );
505 this._$view.bind( this._dragStartEvt, this._dragStartCB );
509 $( document ).bind( "pagecreate create", function ( e ) {
510 $( $.mobile.circularview.prototype.options.initSelector, e.target ).circularview();
513 }( jQuery, window, document ) ); // End Component