f26895cd2a2120d25414efd5a0756a1e6ed66de8
[framework/web/web-ui-fw.git] / src / widgets / circularview / js / jquery.mobile.tizen.circularview.js
1 /* ***************************************************************************
2  * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
3  *
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:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
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  * ***************************************************************************
22  */
23
24 // most of following codes are derived from jquery.mobile.scrollview.js
25 (function ( $, window, document, undefined ) {
26
27         function circularNum( num, total ) {
28                 var n = num % total;
29                 if ( n < 0 ) {
30                         n = total + n;
31                 }
32                 return n;
33         }
34
35         function setElementTransform( $ele, x, y ) {
36                 var v = "translate3d( " + x + "," + y + ", 0px)";
37                 $ele.css({
38                         "-moz-transform": v,
39                         "-webkit-transform": v,
40                         "transform": v
41                 } );
42         }
43
44         function MomentumTracker( options ) {
45                 this.options = $.extend( {}, options );
46                 this.easing = "easeOutQuad";
47                 this.reset();
48         }
49
50         var tstates = {
51                 scrolling : 0,
52                 done : 1
53         };
54
55         function getCurrentTime() {
56                 return ( new Date()).getTime();
57         }
58
59         $.extend( MomentumTracker.prototype, {
60                 start: function ( pos, speed, duration ) {
61                         this.state = ( speed != 0 ) ? tstates.scrolling : tstates.done;
62                         this.pos = pos;
63                         this.speed = speed;
64                         this.duration = duration;
65
66                         this.fromPos = 0;
67                         this.toPos = 0;
68
69                         this.startTime = getCurrentTime();
70                 },
71
72                 reset: function () {
73                         this.state = tstates.done;
74                         this.pos = 0;
75                         this.speed = 0;
76                         this.duration = 0;
77                 },
78
79                 update: function () {
80                         var state = this.state,
81                                 duration,
82                                 elapsed,
83                                 dx,
84                                 x;
85
86                         if ( state == tstates.done ) {
87                                 return this.pos;
88                         }
89
90                         duration = this.duration;
91                         elapsed = getCurrentTime() - this.startTime;
92                         elapsed = elapsed > duration ? duration : elapsed;
93
94                         dx = this.speed * ( 1 - $.easing[this.easing](elapsed / duration, elapsed, 0, 1, duration ) );
95
96                         x = this.pos + dx;
97                         this.pos = x;
98
99                         if ( elapsed >= duration ) {
100                                 this.state = tstates.done;
101                         }
102
103                         return this.pos;
104                 },
105
106                 done: function () {
107                         return this.state == tstates.done;
108                 },
109
110                 getPosition: function () {
111                         return this.pos;
112                 }
113         } );
114
115         jQuery.widget( "mobile.circularview", jQuery.mobile.widget, {
116                 options: {
117                         fps:                            60,
118
119                         scrollDuration:         2000,
120
121                         moveThreshold:          10,
122                         moveIntervalThreshold:  150,
123
124                         startEventName:         "scrollstart",
125                         updateEventName:        "scrollupdate",
126                         stopEventName:          "scrollstop",
127
128                         eventType:                      $.support.touch ? "touch" : "mouse",
129
130                         delayedClickSelector: "a, .ui-btn",
131                         delayedClickEnabled: false
132                 },
133
134                 _makePositioned: function ( $ele ) {
135                         if ( $ele.css( 'position' ) == 'static' ) {
136                                 $ele.css( 'position', 'relative' );
137                         }
138                 },
139
140                 _create: function () {
141                         this._$clip = $( this.element).addClass( "ui-scrollview-clip" );
142                         var $child = this._$clip.children(),
143                                 self;
144                         //if ( $child.length > 1 ) {
145                         $child = this._$clip.wrapInner( "<div></div>" ).children();
146                         //}
147                         this._$view = $child.addClass( "ui-scrollview-view" );
148                         this._$list = $child.children();
149
150                         this._$clip.css( "overflow", "hidden" );
151                         this._makePositioned( this._$clip );
152
153                         this._$view.css( "overflow", "hidden" );
154                         this._tracker = new MomentumTracker( this.options );
155
156                         this._timerInterval = 1000 / this.options.fps;
157                         this._timerID = 0;
158
159                         self = this;
160                         this._timerCB = function () { self._handleMomentumScroll(); };
161
162                         this.refresh();
163
164                         this._addBehaviors();
165                 },
166
167                 refresh: function () {
168                         var itemsPerView;
169
170                         this._viewWidth = this._$view.width();
171                         this._clipWidth = $( window ).width();
172                         this._itemWidth = this._$list.children().first().outerWidth();
173                         this._$items = this._$list.children().detach();
174                         itemsPerView = this._clipWidth / this._itemWidth;
175                         itemsPerView = Math.ceil( itemsPerView * 10 ) / 10;
176                         this._itemsPerView = parseInt( itemsPerView, 10 );
177
178                         this._rx = -this._itemWidth;
179                         this._sx = -this._itemWidth;
180                         this._setItems();
181                 },
182
183                 _startMScroll: function ( speedX, speedY ) {
184                         this._stopMScroll();
185
186                         var keepGoing = false,
187                                 duration = this.options.scrollDuration,
188                                 t = this._tracker,
189                                 c = this._clipWidth,
190                                 v = this._viewWidth;
191
192                         this._$clip.trigger( this.options.startEventName);
193
194                         t.start( this._rx, speedX, duration, (v > c ) ? -(v - c) : 0, 0 );
195                         keepGoing = !t.done();
196
197                         if ( keepGoing ) {
198                                 this._timerID = setTimeout( this._timerCB, this._timerInterval );
199                         } else {
200                                 this._stopMScroll();
201                         }
202                         //console.log( "startmscroll" + this._rx + "," + this._sx );
203                 },
204
205                 _stopMScroll: function () {
206                         if ( this._timerID ) {
207                                 this._$clip.trigger( this.options.stopEventName );
208                                 clearTimeout( this._timerID );
209                         }
210
211                         this._timerID = 0;
212
213                         if ( this._tracker ) {
214                                 this._tracker.reset();
215                         }
216                         //console.log( "stopmscroll" + this._rx + "," + this._sx );
217                 },
218
219                 _handleMomentumScroll: function () {
220                         var keepGoing = false,
221                                 v = this._$view,
222                                 x = 0,
223                                 y = 0,
224                                 t = this._tracker;
225
226                         if ( t ) {
227                                 t.update();
228                                 x = t.getPosition();
229
230                                 keepGoing = !t.done();
231
232                         }
233
234                         this._setScrollPosition( x, y );
235                         this._rx = x;
236
237                         this._$clip.trigger( this.options.updateEventName, [ { x: x, y: y } ] );
238
239                         if ( keepGoing ) {
240                                 this._timerID = setTimeout( this._timerCB, this._timerInterval );
241                         } else {
242                                 this._stopMScroll();
243                         }
244                 },
245
246                 _setItems: function () {
247                         var i,
248                                 $item;
249
250                         for ( i = -1; i < this._itemsPerView + 1; i++ ) {
251                                 $item = this._$items[ circularNum( i, this._$items.length ) ];
252                                 this._$list.append( $item );
253                         }
254                         setElementTransform( this._$view, this._sx + "px", 0 );
255                         this._$view.width( this._itemWidth * ( this._itemsPerView + 2 ) );
256                         this._viewWidth = this._$view.width();
257                 },
258
259                 _setScrollPosition: function ( x, y ) {
260                         var sx = this._sx,
261                                 dx = x - sx,
262                                 di = parseInt( dx / this._itemWidth, 10 ),
263                                 i,
264                                 idx,
265                                 $item;
266
267                         if ( di > 0 ) {
268                                 for ( i = 0; i < di; i++ ) {
269                                         this._$list.children().last().detach();
270                                         idx = -parseInt( ( sx / this._itemWidth ) + i + 3, 10 );
271                                         $item = this._$items[ circularNum( idx, this._$items.length ) ];
272                                         this._$list.prepend( $item );
273                                         //console.log( "di > 0 : " + idx );
274                                 }
275                         } else if ( di < 0 ) {
276                                 for ( i = 0; i > di; i-- ) {
277                                         this._$list.children().first().detach();
278                                         idx = this._itemsPerView - parseInt( ( sx / this._itemWidth ) + i, 10 );
279                                         $item = this._$items[ circularNum( idx, this._$items.length ) ];
280                                         this._$list.append( $item );
281                                         //console.log( "di < 0 : " + idx );
282                                 }
283                         }
284
285                         this._sx += di * this._itemWidth;
286
287                         setElementTransform( this._$view, ( x - this._sx - this._itemWidth ) + "px", 0 );
288
289                         //console.log( "rx " + this._rx + "sx " + this._sx );
290                 },
291
292                 _enableTracking: function () {
293                         $(document).bind( this._dragMoveEvt, this._dragMoveCB );
294                         $(document).bind( this._dragStopEvt, this._dragStopCB );
295                 },
296
297                 _disableTracking: function () {
298                         $(document).unbind( this._dragMoveEvt, this._dragMoveCB );
299                         $(document).unbind( this._dragStopEvt, this._dragStopCB );
300                 },
301
302                 _getScrollHierarchy: function () {
303                         var svh = [],
304                                 d;
305                         this._$clip.parents( '.ui-scrollview-clip' ).each( function () {
306                                 d = $( this ).jqmData( 'circulaview' );
307                                 if ( d ) {
308                                         svh.unshift( d );
309                                 }
310                         } );
311                         return svh;
312                 },
313
314                 centerTo: function ( selector ) {
315                         var i,
316                                 newX;
317
318                         for ( i = 0; i < this._$items.length; i++ ) {
319                                 if ( $( this._$items[i]).is( selector ) ) {
320                                         newX = -( i * this._itemWidth - this._clipWidth / 2 + this._itemWidth * 2 );
321                                         this.scrollTo( newX, 0 );
322                                         console.log( i + "," + newX );
323                                         return;
324                                 }
325                         }
326                 },
327
328                 scrollTo: function ( x, y, duration ) {
329                         this._stopMScroll();
330                         if ( !duration ) {
331                                 this._setScrollPosition( x, y );
332                                 this._rx = x;
333                                 return;
334                         }
335
336                         x = -x;
337                         y = -y;
338
339                         var self = this,
340                                 start = getCurrentTime(),
341                                 efunc = $.easing.easeOutQuad,
342                                 sx = this._rx,
343                                 sy = 0,
344                                 dx = x - sx,
345                                 dy = 0,
346                                 tfunc,
347                                 elapsed,
348                                 ec;
349
350                         tfunc = function () {
351                                 elapsed = getCurrentTime() - start;
352                                 if ( elapsed >= duration ) {
353                                         self._timerID = 0;
354                                         self._setScrollPosition( x, y );
355                                 } else {
356                                         ec = efunc( elapsed / duration, elapsed, 0, 1, duration );
357                                         self._setScrollPosition( sx + ( dx * ec ), sy + ( dy * ec ) );
358                                         self._timerID = setTimeout( tfunc, self._timerInterval );
359                                 }
360                         };
361
362                         this._timerID = setTimeout( tfunc, this._timerInterval );
363                 },
364
365                 getScrollPosition: function () {
366                         return { x: -this._rx, y: 0 };
367                 },
368
369                 _handleDragStart: function ( e, ex, ey ) {
370                         $.each( this._getScrollHierarchy(), function ( i, sv ) {
371                                 sv._stopMScroll();
372                         } );
373
374                         this._stopMScroll();
375
376                         if ( this.options.delayedClickEnabled ) {
377                                 this._$clickEle = $( e.target ).closest( this.options.delayedClickSelector );
378                         }
379                         this._lastX = ex;
380                         this._lastY = ey;
381                         this._speedX = 0;
382                         this._speedY = 0;
383                         this._didDrag = false;
384
385                         this._lastMove = 0;
386                         this._enableTracking();
387
388                         this._ox = ex;
389                         this._nx = this._rx;
390
391                         if ( this.options.eventType == "mouse" || this.options.delayedClickEnabled ) {
392                                 e.preventDefault();
393                         }
394                         //console.log( "scrollstart" + this._rx + "," + this._sx );
395                         e.stopPropagation();
396                 },
397
398                 _handleDragMove: function ( e, ex, ey ) {
399                         this._lastMove = getCurrentTime();
400
401                         var dx = ex - this._lastX,
402                                 dy = ey - this._lastY;
403
404                         this._speedX = dx;
405                         this._speedY = 0;
406
407                         this._didDrag = true;
408
409                         this._lastX = ex;
410                         this._lastY = ey;
411
412                         this._mx = ex - this._ox;
413
414                         this._setScrollPosition( this._nx + this._mx, 0 );
415
416                         //console.log( "scrollmove" + this._rx + "," + this._sx );
417                         return false;
418                 },
419
420                 _handleDragStop: function ( e ) {
421                         var l = this._lastMove,
422                                 t = getCurrentTime(),
423                                 doScroll = l && ( t - l ) <= this.options.moveIntervalThreshold,
424                                 sx = ( this._tracker && this._speedX && doScroll ) ? this._speedX : 0,
425                                 sy = 0;
426
427                         this._rx = this._mx ? this._nx + this._mx : this._rx;
428
429                         if ( sx ) {
430                                 this._startMScroll( sx, sy );
431                         }
432
433                         //console.log( "scrollstop" + this._rx + "," + this._sx );
434
435                         this._disableTracking();
436
437                         if ( !this._didDrag && this.options.delayedClickEnabled && this._$clickEle.length ) {
438                                 this._$clickEle
439                                         .trigger( "mousedown" )
440                                         .trigger( "mouseup" )
441                                         .trigger( "click" );
442                         }
443
444                         if ( this._didDrag ) {
445                                 e.preventDefault();
446                                 e.stopPropagation();
447                         }
448
449                         return this._didDrag ? false : undefined;
450                 },
451
452                 _addBehaviors: function () {
453                         var self = this;
454
455                         if ( this.options.eventType === "mouse" ) {
456                                 this._dragStartEvt = "mousedown";
457                                 this._dragStartCB = function ( e ) {
458                                         return self._handleDragStart( e, e.clientX, e.clientY );
459                                 };
460
461                                 this._dragMoveEvt = "mousemove";
462                                 this._dragMoveCB = function ( e ) {
463                                         return self._handleDragMove( e, e.clientX, e.clientY );
464                                 };
465
466                                 this._dragStopEvt = "mouseup";
467                                 this._dragStopCB = function ( e ) {
468                                         return self._handleDragStop( e );
469                                 };
470
471                                 this._$view.bind( "vclick", function (e) {
472                                         return !self._didDrag;
473                                 } );
474
475                         } else { //touch
476                                 this._dragStartEvt = "touchstart";
477                                 this._dragStartCB = function ( e ) {
478                                         var t = e.originalEvent.targetTouches[0];
479                                         return self._handleDragStart(e, t.pageX, t.pageY );
480                                 };
481
482                                 this._dragMoveEvt = "touchmove";
483                                 this._dragMoveCB = function ( e ) {
484                                         var t = e.originalEvent.targetTouches[0];
485                                         return self._handleDragMove(e, t.pageX, t.pageY );
486                                 };
487
488                                 this._dragStopEvt = "touchend";
489                                 this._dragStopCB = function ( e ) {
490                                         return self._handleDragStop( e );
491                                 };
492                         }
493                         this._$view.bind( this._dragStartEvt, this._dragStartCB );
494                 }
495         } );
496
497         $( document ).bind( "pagecreate create", function ( e ) {
498                 $( $.mobile.circularview.prototype.options.initSelector, e.target ).circularview();
499         } );
500
501 }( jQuery, window, document ) ); // End Component