Export 0.1.45
[platform/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                                 } else {
364                                         ec = efunc( elapsed / duration, elapsed, 0, 1, duration );
365                                         self._setScrollPosition( sx + ( dx * ec ), sy + ( dy * ec ) );
366                                         self._timerID = setTimeout( tfunc, self._timerInterval );
367                                 }
368                         };
369
370                         this._timerID = setTimeout( tfunc, this._timerInterval );
371                 },
372
373                 getScrollPosition: function () {
374                         return { x: -this._rx, y: 0 };
375                 },
376
377                 _handleDragStart: function ( e, ex, ey ) {
378                         $.each( this._getScrollHierarchy(), function ( i, sv ) {
379                                 sv._stopMScroll();
380                         } );
381
382                         this._stopMScroll();
383
384                         if ( this.options.delayedClickEnabled ) {
385                                 this._$clickEle = $( e.target ).closest( this.options.delayedClickSelector );
386                         }
387                         this._lastX = ex;
388                         this._lastY = ey;
389                         this._speedX = 0;
390                         this._speedY = 0;
391                         this._didDrag = false;
392
393                         this._lastMove = 0;
394                         this._enableTracking();
395
396                         this._ox = ex;
397                         this._nx = this._rx;
398
399                         if ( this.options.eventType == "mouse" || this.options.delayedClickEnabled ) {
400                                 e.preventDefault();
401                         }
402                         //console.log( "scrollstart" + this._rx + "," + this._sx );
403                         e.stopPropagation();
404                 },
405
406                 _handleDragMove: function ( e, ex, ey ) {
407                         this._lastMove = getCurrentTime();
408
409                         var dx = ex - this._lastX,
410                                 dy = ey - this._lastY;
411
412                         this._speedX = dx;
413                         this._speedY = 0;
414
415                         this._didDrag = true;
416
417                         this._lastX = ex;
418                         this._lastY = ey;
419
420                         this._mx = ex - this._ox;
421
422                         this._setScrollPosition( this._nx + this._mx, 0 );
423
424                         //console.log( "scrollmove" + this._rx + "," + this._sx );
425                         return false;
426                 },
427
428                 _handleDragStop: function ( e ) {
429                         var l = this._lastMove,
430                                 t = getCurrentTime(),
431                                 doScroll = l && ( t - l ) <= this.options.moveIntervalThreshold,
432                                 sx = ( this._tracker && this._speedX && doScroll ) ? this._speedX : 0,
433                                 sy = 0;
434
435                         this._rx = this._mx ? this._nx + this._mx : this._rx;
436
437                         if ( sx ) {
438                                 this._startMScroll( sx, sy );
439                         }
440
441                         //console.log( "scrollstop" + this._rx + "," + this._sx );
442
443                         this._disableTracking();
444
445                         if ( !this._didDrag && this.options.delayedClickEnabled && this._$clickEle.length ) {
446                                 this._$clickEle
447                                         .trigger( "mousedown" )
448                                         .trigger( "mouseup" )
449                                         .trigger( "click" );
450                         }
451
452                         if ( this._didDrag ) {
453                                 e.preventDefault();
454                                 e.stopPropagation();
455                         }
456
457                         return this._didDrag ? false : undefined;
458                 },
459
460                 _addBehaviors: function () {
461                         var self = this;
462
463                         if ( this.options.eventType === "mouse" ) {
464                                 this._dragStartEvt = "mousedown";
465                                 this._dragStartCB = function ( e ) {
466                                         return self._handleDragStart( e, e.clientX, e.clientY );
467                                 };
468
469                                 this._dragMoveEvt = "mousemove";
470                                 this._dragMoveCB = function ( e ) {
471                                         return self._handleDragMove( e, e.clientX, e.clientY );
472                                 };
473
474                                 this._dragStopEvt = "mouseup";
475                                 this._dragStopCB = function ( e ) {
476                                         return self._handleDragStop( e );
477                                 };
478
479                                 this._$view.bind( "vclick", function (e) {
480                                         return !self._didDrag;
481                                 } );
482
483                         } else { //touch
484                                 this._dragStartEvt = "touchstart";
485                                 this._dragStartCB = function ( e ) {
486                                         var t = e.originalEvent.targetTouches[0];
487                                         return self._handleDragStart(e, t.pageX, t.pageY );
488                                 };
489
490                                 this._dragMoveEvt = "touchmove";
491                                 this._dragMoveCB = function ( e ) {
492                                         var t = e.originalEvent.targetTouches[0];
493                                         return self._handleDragMove(e, t.pageX, t.pageY );
494                                 };
495
496                                 this._dragStopEvt = "touchend";
497                                 this._dragStopCB = function ( e ) {
498                                         return self._handleDragStop( e );
499                                 };
500                         }
501                         this._$view.bind( this._dragStartEvt, this._dragStartCB );
502                 }
503         } );
504
505         $( document ).bind( "pagecreate create", function ( e ) {
506                 $( $.mobile.circularview.prototype.options.initSelector, e.target ).circularview();
507         } );
508
509 }( jQuery, window, document ) ); // End Component