Export 0.1.47
[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 Date.now();
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                         var self = this;
142
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 );
148
149                         this._$clip.css( "overflow", "hidden" );
150                         this._makePositioned( this._$clip );
151
152                         this._$view.css( "overflow", "hidden" );
153                         this._tracker = new MomentumTracker( this.options );
154
155                         this._timerInterval = 1000 / this.options.fps;
156                         this._timerID = 0;
157
158                         this._timerCB = function () { self._handleMomentumScroll(); };
159
160                         this.refresh();
161
162                         this._addBehaviors();
163                 },
164
165                 reflow: function () {
166                         var xy = this.getScrollPosition();
167                         this.refresh();
168                         this.scrollTo( xy.x, xy.y );
169                 },
170
171                 refresh: function () {
172                         var itemsPerView;
173
174                         this._$clip.width( $(window).width() );
175                         this._clipWidth = this._$clip.width();
176                         this._$list.empty();
177                         this._$list.append(this._items[0]);
178                         this._itemWidth = $(this._items[0]).outerWidth();
179                         $(this._items[0]).detach();
180
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() );
186                         }
187                         this._rx = -this._itemWidth;
188                         this._sx = -this._itemWidth;
189                         this._setItems();
190                 },
191
192                 _startMScroll: function ( speedX, speedY ) {
193                         this._stopMScroll();
194
195                         var keepGoing = false,
196                                 duration = this.options.scrollDuration,
197                                 t = this._tracker,
198                                 c = this._clipWidth,
199                                 v = this._viewWidth;
200
201                         this._$clip.trigger( this.options.startEventName);
202
203                         t.start( this._rx, speedX, duration, (v > c ) ? -(v - c) : 0, 0 );
204                         keepGoing = !t.done();
205
206                         if ( keepGoing ) {
207                                 this._timerID = setTimeout( this._timerCB, this._timerInterval );
208                         } else {
209                                 this._stopMScroll();
210                         }
211                         //console.log( "startmscroll" + this._rx + "," + this._sx );
212                 },
213
214                 _stopMScroll: function () {
215                         if ( this._timerID ) {
216                                 this._$clip.trigger( this.options.stopEventName );
217                                 clearTimeout( this._timerID );
218                         }
219
220                         this._timerID = 0;
221
222                         if ( this._tracker ) {
223                                 this._tracker.reset();
224                         }
225                         //console.log( "stopmscroll" + this._rx + "," + this._sx );
226                 },
227
228                 _handleMomentumScroll: function () {
229                         var keepGoing = false,
230                                 v = this._$view,
231                                 x = 0,
232                                 y = 0,
233                                 t = this._tracker;
234
235                         if ( t ) {
236                                 t.update();
237                                 x = t.getPosition();
238
239                                 keepGoing = !t.done();
240
241                         }
242
243                         this._setScrollPosition( x, y );
244                         this._rx = x;
245
246                         this._$clip.trigger( this.options.updateEventName, [ { x: x, y: y } ] );
247
248                         if ( keepGoing ) {
249                                 this._timerID = setTimeout( this._timerCB, this._timerInterval );
250                         } else {
251                                 this._stopMScroll();
252                         }
253                 },
254
255                 _setItems: function () {
256                         var i,
257                                 $item;
258
259                         for ( i = -1; i < this._itemsPerView + 1; i++ ) {
260                                 $item = this._items[ circularNum( i, this._items.length ) ];
261                                 this._$list.append( $item );
262                         }
263                         setElementTransform( this._$view, this._sx + "px", 0 );
264                         this._$view.width( this._itemWidth * ( this._itemsPerView + 2 ) );
265                         this._viewWidth = this._$view.width();
266                 },
267
268                 _setScrollPosition: function ( x, y ) {
269                         var sx = this._sx,
270                                 dx = x - sx,
271                                 di = parseInt( dx / this._itemWidth, 10 ),
272                                 i,
273                                 idx,
274                                 $item;
275
276                         if ( di > 0 ) {
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 );
283                                 }
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 );
291                                 }
292                         }
293
294                         this._sx += di * this._itemWidth;
295
296                         setElementTransform( this._$view, ( x - this._sx - this._itemWidth ) + "px", 0 );
297
298                         //console.log( "rx " + this._rx + "sx " + this._sx );
299                 },
300
301                 _enableTracking: function () {
302                         $(document).bind( this._dragMoveEvt, this._dragMoveCB );
303                         $(document).bind( this._dragStopEvt, this._dragStopCB );
304                 },
305
306                 _disableTracking: function () {
307                         $(document).unbind( this._dragMoveEvt, this._dragMoveCB );
308                         $(document).unbind( this._dragStopEvt, this._dragStopCB );
309                 },
310
311                 _getScrollHierarchy: function () {
312                         var svh = [],
313                                 d;
314                         this._$clip.parents( '.ui-scrollview-clip' ).each( function () {
315                                 d = $( this ).jqmData( 'circulaview' );
316                                 if ( d ) {
317                                         svh.unshift( d );
318                                 }
319                         } );
320                         return svh;
321                 },
322
323                 centerTo: function ( selector, duration ) {
324                         var i,
325                                 newX;
326
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 );
332                                         return;
333                                 }
334                         }
335                 },
336
337                 scrollTo: function ( x, y, duration ) {
338                         this._stopMScroll();
339                         if ( !duration ) {
340                                 this._setScrollPosition( x, y );
341                                 this._rx = x;
342                                 return;
343                         }
344
345                         var self = this,
346                                 start = getCurrentTime(),
347                                 efunc = $.easing.easeOutQuad,
348                                 sx = this._rx,
349                                 sy = 0,
350                                 dx = x - sx,
351                                 dy = 0,
352                                 tfunc,
353                                 elapsed,
354                                 ec;
355
356                         this._rx = x;
357
358                         tfunc = function () {
359                                 elapsed = getCurrentTime() - start;
360                                 if ( elapsed >= duration ) {
361                                         self._timerID = 0;
362                                         self._setScrollPosition( x, y );
363                                         self._$clip.trigger("scrollend");
364                                 } else {
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 );
368                                 }
369                         };
370
371                         this._timerID = setTimeout( tfunc, this._timerInterval );
372                 },
373
374                 getScrollPosition: function () {
375                         return { x: -this._rx, y: 0 };
376                 },
377
378                 _handleDragStart: function ( e, ex, ey ) {
379                         $.each( this._getScrollHierarchy(), function ( i, sv ) {
380                                 sv._stopMScroll();
381                         } );
382
383                         this._stopMScroll();
384
385                         if ( this.options.delayedClickEnabled ) {
386                                 this._$clickEle = $( e.target ).closest( this.options.delayedClickSelector );
387                         }
388                         this._lastX = ex;
389                         this._lastY = ey;
390                         this._speedX = 0;
391                         this._speedY = 0;
392                         this._didDrag = false;
393
394                         this._lastMove = 0;
395                         this._enableTracking();
396
397                         this._ox = ex;
398                         this._nx = this._rx;
399
400                         if ( this.options.eventType == "mouse" || this.options.delayedClickEnabled ) {
401                                 e.preventDefault();
402                         }
403                         //console.log( "scrollstart" + this._rx + "," + this._sx );
404                         e.stopPropagation();
405                 },
406
407                 _handleDragMove: function ( e, ex, ey ) {
408                         this._lastMove = getCurrentTime();
409
410                         var dx = ex - this._lastX,
411                                 dy = ey - this._lastY;
412
413                         this._speedX = dx;
414                         this._speedY = 0;
415
416                         this._didDrag = true;
417
418                         this._lastX = ex;
419                         this._lastY = ey;
420
421                         this._mx = ex - this._ox;
422
423                         this._setScrollPosition( this._nx + this._mx, 0 );
424
425                         //console.log( "scrollmove" + this._rx + "," + this._sx );
426                         return false;
427                 },
428
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,
434                                 sy = 0;
435
436                         this._rx = this._mx ? this._nx + this._mx : this._rx;
437
438                         if ( sx ) {
439                                 this._startMScroll( sx, sy );
440                         }
441
442                         //console.log( "scrollstop" + this._rx + "," + this._sx );
443
444                         this._disableTracking();
445
446                         if ( !this._didDrag && this.options.delayedClickEnabled && this._$clickEle.length ) {
447                                 this._$clickEle
448                                         .trigger( "mousedown" )
449                                         .trigger( "mouseup" )
450                                         .trigger( "click" );
451                         }
452
453                         if ( this._didDrag ) {
454                                 e.preventDefault();
455                                 e.stopPropagation();
456                         }
457
458                         return this._didDrag ? false : undefined;
459                 },
460
461                 _addBehaviors: function () {
462                         var self = this;
463
464                         if ( this.options.eventType === "mouse" ) {
465                                 this._dragStartEvt = "mousedown";
466                                 this._dragStartCB = function ( e ) {
467                                         return self._handleDragStart( e, e.clientX, e.clientY );
468                                 };
469
470                                 this._dragMoveEvt = "mousemove";
471                                 this._dragMoveCB = function ( e ) {
472                                         return self._handleDragMove( e, e.clientX, e.clientY );
473                                 };
474
475                                 this._dragStopEvt = "mouseup";
476                                 this._dragStopCB = function ( e ) {
477                                         return self._handleDragStop( e );
478                                 };
479
480                                 this._$view.bind( "vclick", function (e) {
481                                         return !self._didDrag;
482                                 } );
483
484                         } else { //touch
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 );
489                                 };
490
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 );
495                                 };
496
497                                 this._dragStopEvt = "touchend";
498                                 this._dragStopCB = function ( e ) {
499                                         return self._handleDragStop( e );
500                                 };
501                         }
502                         this._$view.bind( this._dragStartEvt, this._dragStartCB );
503                 }
504         } );
505
506         $( document ).bind( "pagecreate create", function ( e ) {
507                 $( $.mobile.circularview.prototype.options.initSelector, e.target ).circularview();
508         } );
509
510 }( jQuery, window, document ) ); // End Component