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