OSDN Git Service

f7dac754cf335126947b52d455deab0a864f595e
[pettanr/clientJs.git] / 0.6.x / js / 10_ui / 15_ScrollBox.js
1 \r
2 (function(){\r
3 \r
4 var m      = Math,\r
5         ABS    = new Function( 'v', 'return v<0?-v:v' );\r
6 \r
7         function Options(){};\r
8         \r
9         X.Class._override( Options.prototype, {\r
10                 hScroll         : true,\r
11                 vScroll         : true,\r
12                 x               : 0,\r
13                 y               : 0,\r
14                 bounce          : true,\r
15                 bounceLock      : false,\r
16                 momentum        : true,\r
17                 lockDirection   : true,\r
18                 useTransform    : true,\r
19                 useTransition   : true,\r
20                 topOffset       : 0,\r
21                 checkDOMChanges : false,                // Experimental\r
22                 handleClick     : true,\r
23         \r
24                 // Scrollbar\r
25                 hScrollbar      : true,\r
26                 vScrollbar      : true,\r
27                 fixedScrollbar  : X.UA.Android,\r
28                 hideScrollbar   : X.UA.iOS,\r
29                 fadeScrollbar   : X.UA.iOS, //&& has3d,\r
30                 scrollbarClass  : '',\r
31         \r
32                 // Zoom\r
33                 zoom            : false,\r
34                 zoomMin         : 1,\r
35                 zoomMax         : 4,\r
36                 doubleTapZoom   : 2,\r
37                 wheelAction     : 'scroll',\r
38         \r
39                 // Snap\r
40                 snap            : false,\r
41                 snapThreshold   : 1 //,\r
42         });\r
43 \r
44         function Scrollbar( owner, dir ){\r
45                 this.owner   = owner;\r
46                 this.options = owner.options;\r
47                 this.dir     = dir;\r
48                 if( dir === 'h' ){\r
49                         this.XorY          = 'x';\r
50                         this.widthOrHeight = 'width';\r
51                         this.transrateXorY = 'translateX(';\r
52                 };\r
53         };\r
54         X.Class._override( Scrollbar.prototype, {\r
55                 owner          : null,\r
56                 dir            : null,\r
57                 options        : null,\r
58                 XorY           : 'y',\r
59                 widthOrHeight  : 'height',\r
60                 transrateXorY  : 'translateY(',\r
61                 active         : false,\r
62                 xnodeWrapper   : null,\r
63                 xnodeIndicator : null,\r
64                 wrapperSize    : 0,\r
65                 indicatorSize  : 0,\r
66                 maxScroll      : 0,\r
67                 scrollPercent  : 0,\r
68                 \r
69                 update         : function(){\r
70                         // remove scrollbar\r
71                         if( !this.active ){\r
72                                 if( this.xnodeWrapper ){\r
73                                         X.Dom.Style.transform && this.xnodeIndicator.css( 'transform', '' );\r
74                                         this.xnodeWrapper.css( 'display', 'none' );\r
75                                 };\r
76                                 return;\r
77                         };\r
78         \r
79                         // create scrollbar\r
80                         if( !this.xnodeWrapper ){\r
81                                 // Create the scrollbar wrapper\r
82                                 this.xnodeWrapper = this.owner.xnodeTarget.create( 'div' )\r
83                                         .className(\r
84                                                 this.options.scrollbarClass ?\r
85                                                         this.options.scrollbarClass + this.dir.toUpperCase() :\r
86                                                 this.dir === 'h' ?\r
87                                                         ( this.vScrollbar && this.vScrollbar.active ? 'ScrollBox-Scrollbar-Wrapper-V-withH' : 'ScrollBox-Scrollbar-Wrapper-H' ) :\r
88                                                         ( this.hScrollbar && this.hScrollbar.active ? 'ScrollBox-Scrollbar-Wrapper-H-withV' : 'ScrollBox-Scrollbar-Wrapper-V' )                                                 \r
89                                         );\r
90         \r
91                                 this.options.fadeScrollbar &&\r
92                                         this.xnodeWrapper.css(\r
93                                                 {\r
94                                                         opacity            : 0,\r
95                                                         transitionProperty : 'opacity',\r
96                                                         transitionDuration : '350ms'\r
97                                                 }\r
98                                         );\r
99         \r
100                                 // Create the scrollbar indicator\r
101                                 \r
102                                 this.xnodeIndicator = this.xnodeWrapper.create( 'div' );\r
103                                 \r
104                                 !this.options.scrollbarClass &&\r
105                                         this.xnodeIndicator.className(\r
106                                                 this.dir === 'h' ?\r
107                                                         'ScrollBox-Scrollbar-Indicator-H' :\r
108                                                         'ScrollBox-Scrollbar-Indicator-V'\r
109                                         );\r
110                                 //if (this.options.useTransition) bar.style.cssText += ';' + cssVendor + 'transition-timing-function:cubic-bezier(0.33,0.66,0.66,1)';\r
111                         };\r
112         \r
113                         if( this.dir === 'h' ){\r
114                                 this.wrapperSize   = this.hScrollbarWrapper.clientWidth;\r
115                                 this.indicatorSize = m.max( m.round( wrapperSize * wrapperSize / this.owner.scrollerW ), 8 );\r
116                                 //this.hScrollbarIndicator.style.width = this.hScrollbarIndicatorSize + 'px';\r
117                                 this.maxScroll     = wrapperSize - indicatorSize;\r
118                                 this.scrollPercent = maxScroll / this.owner.maxScrollX;\r
119                         } else {\r
120                                 this.wrapperSize   = this.vScrollbarWrapper.clientHeight;\r
121                                 this.indicatorSize = m.max( m.round( wrapperSize * wrapperSize / this.owner.scrollerH ), 8);\r
122                                 // this.vScrollbarIndicator.style.height = indicatorSize + 'px';\r
123                                 this.maxScroll     = this.vScrollbarSize - indicatorSize;\r
124                                 this.scrollPercent = maxScroll / this.owner.maxScrollY;\r
125                         };              \r
126                         this.xnodeIndicator.css( this.widthOrHeight, size + 'px' );\r
127                         // Reset position\r
128                         this.updatePosition( this.owner[ this.XorY ], true );\r
129                 },\r
130                 \r
131                 updatePosition : function( pos, hidden ){\r
132                         var size;\r
133                         pos = this.scrollPercent * pos;\r
134         \r
135                         if( pos < 0 ){\r
136                                 if( !this.options.fixedScrollbar ){\r
137                                         size = this.indicatorSize + m.round( pos * 3 );\r
138                                         if( size < 8 ) size = 8;\r
139                                         //this.xnodeIndicator.style[dir == 'h' ? 'width' : 'height'] = size + 'px';\r
140                                         this.xnodeIndicator.css( this.widthOrHeight, size + 'px' );\r
141                                 };\r
142                                 pos = 0;\r
143                         } else\r
144                         if( this.maxScroll < pos ){\r
145                                 if( !this.options.fixedScrollbar ){\r
146                                         size = this.indicatorSize - m.round( ( pos - this.maxScroll ) * 3 );\r
147                                         if( size < 8 ) size = 8;\r
148                                         //this.xnodeIndicator.style[dir == 'h' ? 'width' : 'height'] = size + 'px';\r
149                                         this.xnodeIndicator.css( this.widthOrHeight, size + 'px' );\r
150                                         pos = this.maxScroll + this.indicatorSize - size;\r
151                                 } else {\r
152                                         pos = this.maxScroll;\r
153                                 };\r
154                         };\r
155         \r
156                         if (this.options.useTransition){\r
157                                 this.xnodeWrapper.css( {\r
158                                         transitionDelay : '0',\r
159                                         opacity         : hidden && this.options.hideScrollbar ? '0' : '1'\r
160                                 });\r
161                                 //this.xnodeIndicator.style[transform] = 'translate(' + (dir == 'h' ? pos + 'px,0)' : '0,' + pos + 'px)') + translateZ;\r
162                                 this.xnodeIndicator.anime( this.dir === 'h' ? { x : pos } : { y : pos } );              \r
163                         };\r
164                 }\r
165         });\r
166 \r
167         // Constructor\r
168         function iScroll( uinodeRoot, uinodeTarget, xnodeTarget, xnodeScroller, options ){\r
169                 var i;\r
170                 \r
171                 this.uinodeRoot     = uinodeRoot;\r
172                 this.uinodeTarget   = uinodeTarget;\r
173                 this.xnodeTarget    = xnodeTarget;\r
174                 this.xnodeScroller  = xnodeScroller;\r
175 \r
176                 // Default options\r
177                 this.options = new Options();\r
178 \r
179                 // User defined options\r
180                 if( options ) for (i in options) this.options[i] = options[i];\r
181                 \r
182                 this.options.hScroll && ( this.hScrollbar = new Scrollbar( 'h', this ) );\r
183                 this.options.vScroll && ( this.vScrollbar = new Scrollbar( 'v', this ) );\r
184                 \r
185                 // Set starting position\r
186                 this.x = this.options.x;\r
187                 this.y = this.options.y;\r
188 \r
189                 // Normalize options\r
190                 this.options.useTransform  = X.Dom.Style.transform && this.options.useTransform;\r
191                 this.options.hScrollbar    = this.options.hScroll && this.options.hScrollbar;\r
192                 this.options.vScrollbar    = this.options.vScroll && this.options.vScrollbar;\r
193                 this.options.zoom          = this.options.useTransform && this.options.zoom;\r
194                 this.options.useTransition = X.Dom.Style.transition && this.options.useTransition;\r
195 \r
196                 // Helpers FIX ANDROID BUG!\r
197                 // translate3d and scale doesn't work together!\r
198                 // Ignoring 3d ONLY WHEN YOU SET this.options.zoom\r
199                 //if ( this.options.zoom && X.UA.isAndroid ){\r
200                 //      translateZ = '';\r
201                 //}\r
202                 \r
203                 // Set some default styles\r
204 \r
205                 if (this.options.useTransform){\r
206                         this.scroller.style[X.Dom.Style.transform]       = 'translate(' + this.x + 'px,' + this.y + 'px)' + translateZ;\r
207                         this.scroller.style[X.Dom.Style.transformOrigin] = '0 0';\r
208                 } else {\r
209                         this.scroller.style.cssText += ';position:absolute;top:' + this.y + 'px;left:' + this.x + 'px';\r
210                 };\r
211 \r
212                 if (this.options.useTransition){\r
213                         this.scroller.style[X.Dom.Style.transition.Property]       = this.options.useTransform ? X.Dom.Style.cssVendor + 'transform' : 'top left';\r
214                         this.scroller.style[X.Dom.Style.transition.Duration]       = '0';                       \r
215                         this.scroller.style[X.Dom.Style.transition.TimingFunction] = 'cubic-bezier(0.33,0.66,0.66,1)';\r
216                         this.options.fixedScrollbar = true;\r
217                 };\r
218 \r
219                 this.refresh();\r
220 \r
221                 //this._bind(RESIZE_EV, window);\r
222                 X.Dom.Event.add( window, RESIZE_EV, this );\r
223                 //this._bind(START_EV);\r
224                 uinodeTarget.listen( X.UI.Event.DRAG_START, this );\r
225         };\r
226 \r
227 // Prototype\r
228 X.Class._override( iScroll.prototype, {\r
229         uinodeRoot     : null,\r
230         uinodeTarget   : null,\r
231         xnodeTarget    : null,\r
232         xnodeScroller  : null,\r
233         options        : null,\r
234         enabled        : true,\r
235         x              : 0,\r
236         y              : 0,\r
237         steps          : [],\r
238         scale          : 1,\r
239         currPageX      : 0,\r
240         currPageY      : 0,\r
241         pagesX         : [],\r
242         pagesY         : [],\r
243         animeTimerID   : 0,\r
244         wheelZoomCount : 0,\r
245         \r
246         wrapperW       : 0,\r
247         wrapperH       : 0,\r
248         minScrollY     : 0,\r
249         scrollerW      : 0,\r
250         scrollerH      : 0,\r
251         maxScrollX     : 0,\r
252         maxScrollY     : 0,\r
253         dirX           : 0,\r
254         dirY           : 0,\r
255         hScrollbar     : false,\r
256         vScrollbar     : false,\r
257         wrapperOffsetLeft : 0,\r
258         wrapperOffsetTop  : 0,\r
259         \r
260         \r
261         currPageX         : 0,\r
262         currPageY         : 0,\r
263         \r
264         moved             : false,\r
265         animating         : false,\r
266         zoomed            : false,\r
267         distX             : false,\r
268         distY             : false,\r
269         absDistX          : false,\r
270         absDistY          : false,\r
271         absStartX         : false,      // Needed by snap threshold\r
272         absStartY         : false,\r
273         startX            : false,\r
274         startY            : false,\r
275         pointX            : false,\r
276         pointY            : false,\r
277         startTime         : false,\r
278         \r
279         handleEvent: function (e) {\r
280                 switch(e.type) {\r
281                         case X.UI.Event.DRAG :\r
282                                 return this._move(e);\r
283                         case X.UI.Event.WHEEL :\r
284                                 return this._wheel(e);\r
285                         case X.UI.Event.DRAG_START :\r
286                                 //if (!hasTouch && e.button !== 0) return;\r
287                                 return this._start(e);\r
288                         case X.UI.Event.DRAG_END :\r
289                                 return this._end(e);\r
290                         case X.UI.Event.ANIME_END :\r
291                                 return this._transitionEnd(e);\r
292                         case RESIZE_EV :\r
293                                 return this._resize();                  \r
294                 }\r
295         },\r
296         \r
297         _trigger : function( type, e ){\r
298                 \r
299                 return this.uinodeTarget.dispatch(  );\r
300         },\r
301         \r
302         _resize: function () {\r
303                 X.Timer.once( X.UA.Android ? 200 : 0, this, this.refresh );\r
304                 // setTimeout( this.refresh(), isAndroid ? 200 : 0);\r
305         },\r
306         \r
307         _updateScrollPosition: function( x, y, time ){\r
308                 if (this.zoomed) return;\r
309 \r
310                 this.xnodeScroller.anime({\r
311                         x : this.x = this.hScrollbar && this.hScrollbar.active ? m.round(x) : 0,\r
312                         y : this.y = this.vScrollbar && this.vScrollbar.active ? m.round(y) : 0\r
313                 }, time );\r
314 \r
315                 this.hScrollbar && this.hScrollbar.active && this.hScrollbar.updatePosition( this.x );\r
316                 this.vScrollbar && this.vScrollbar.active && this.vScrollbar.updatePosition( this.y );\r
317         },\r
318         \r
319         _start: function (e) {\r
320                 var point = hasTouch ? e.touches[0] : e,\r
321                         matrix, x, y,\r
322                         ret;\r
323                         //c1, c2;\r
324 \r
325                 if (!this.enabled) return;\r
326 \r
327                 //if (this.options.onBeforeScrollStart) this.options.onBeforeScrollStart.call(this, e);\r
328                 if( ( ret = this._trigger( X.UI.Event.SCROLL_BEFORE_START ) ) & X.Callback.PREVENT_DEFAULT ){\r
329                         return ret;\r
330                 };\r
331 \r
332                 //if (this.options.useTransition || this.options.zoom) this._transitionTime(0);\r
333 \r
334                 this.moved     = false;\r
335                 this.animating = false;\r
336                 this.zoomed    = false;\r
337                 this.distX     = 0;\r
338                 this.distY     = 0;\r
339                 this.absDistX  = 0;\r
340                 this.absDistY  = 0;\r
341                 this.dirX      = 0;\r
342                 this.dirY      = 0;\r
343 \r
344                 if (this.options.momentum) {\r
345                         if (this.options.useTransform) {\r
346                                 // Very lame general purpose alternative to CSSMatrix\r
347                                 matrix = getComputedStyle(this.scroller, null)[transform].replace(/[^0-9\-.,]/g, '').split(',');\r
348                                 x = +(matrix[12] || matrix[4]);\r
349                                 y = +(matrix[13] || matrix[5]);\r
350                         } else {\r
351                                 x = +getComputedStyle(this.scroller, null).left.replace(/[^0-9-]/g, '');\r
352                                 y = +getComputedStyle(this.scroller, null).top.replace(/[^0-9-]/g, '');\r
353                         };\r
354                         \r
355                         if (x !== this.x || y !== this.y) {\r
356                                 if (this.options.useTransition){\r
357                                         //this._unbind(TRNEND_EV);\r
358                                         X.Dom.Event.remove( this.scroller, TRNEND_EV, this );\r
359                                 } else {\r
360                                         X.Timer.cancelFrame(this.animeTimerID);\r
361                                 };\r
362                                 this.steps = this.steps ? ( this.steps.length = 0 ) : [];\r
363                                 this._updateScrollPosition( x, y, 0 );\r
364                                 //if (this.options.onScrollEnd) this.options.onScrollEnd.call(this);\r
365                                 return this._trigger( X.UI.Event.SCROLL_END, e );\r
366                         };\r
367                 };\r
368 \r
369                 this.absStartX = this.x;        // Needed by snap threshold\r
370                 this.absStartY = this.y;\r
371 \r
372                 this.startX = this.x;\r
373                 this.startY = this.y;\r
374                 this.pointX = point.pageX;\r
375                 this.pointY = point.pageY;\r
376 \r
377                 this.startTime = e.timeStamp || X.getTime();\r
378 \r
379                 this.uinodeRoot.listen( X.UI.Event.DRAG, this );\r
380                 this.uinodeRoot.listen( X.UI.Event.DRAG_END, this );\r
381 \r
382                 return this._trigger( X.UI.Event.SCROLL_START, e );\r
383         },\r
384         \r
385         _move: function (e) {\r
386                 var point  = hasTouch ? e.touches[0] : e,\r
387                         deltaX = point.pageX - this.pointX,\r
388                         deltaY = point.pageY - this.pointY,\r
389                         newX   = this.x + deltaX,\r
390                         newY   = this.y + deltaY,\r
391                         c1, c2, scale,\r
392                         timestamp = e.timeStamp ||X.getTime(), ret;\r
393 \r
394                 //if (this.options.onBeforeScrollMove) this.options.onBeforeScrollMove.call(this, e);\r
395                 if( ( ret = this._trigger( X.UI.Event.SCROLL_BEFORE_MOVE ) ) & X.Callback.PREVENT_DEFAULT ){\r
396                         return ret;\r
397                 };\r
398 \r
399                 this.pointX = point.pageX;\r
400                 this.pointY = point.pageY;\r
401 \r
402                 // Slow down if outside of the boundaries\r
403                 if (newX > 0 || newX < this.maxScrollX) {\r
404                         newX = this.options.bounce ? this.x + (deltaX / 2) : newX >= 0 || this.maxScrollX >= 0 ? 0 : this.maxScrollX;\r
405                 };\r
406                 if (newY > this.minScrollY || newY < this.maxScrollY) {\r
407                         newY = this.options.bounce ? this.y + (deltaY / 2) : newY >= this.minScrollY || this.maxScrollY >= 0 ? this.minScrollY : this.maxScrollY;\r
408                 };\r
409 \r
410                 this.distX += deltaX;\r
411                 this.distY += deltaY;\r
412                 this.absDistX = ABS(this.distX);\r
413                 this.absDistY = ABS(this.distY);\r
414 \r
415                 if (this.absDistX < 6 && this.absDistY < 6) {\r
416                         return;\r
417                 };\r
418 \r
419                 // Lock direction\r
420                 if (this.options.lockDirection) {\r
421                         if (this.absDistX > this.absDistY + 5) {\r
422                                 newY = this.y;\r
423                                 deltaY = 0;\r
424                         } else if (this.absDistY > this.absDistX + 5) {\r
425                                 newX = this.x;\r
426                                 deltaX = 0;\r
427                         };\r
428                 };\r
429 \r
430                 this.moved = true;\r
431                 this._updateScrollPosition(newX, newY, 0);\r
432                 this.dirX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0;\r
433                 this.dirY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0;\r
434 \r
435                 if (timestamp - this.startTime > 300) {\r
436                         this.startTime = timestamp;\r
437                         this.startX = this.x;\r
438                         this.startY = this.y;\r
439                 };\r
440                 \r
441                 //if (this.options.onScrollMove) this.options.onScrollMove.call(this, e);\r
442                 return this._trigger( X.UI.Event.SCROLL_MOVE, e );\r
443         },\r
444         \r
445         _end: function (e) {\r
446                 if (hasTouch && e.touches.length !== 0) return;\r
447 \r
448                 var point     = hasTouch ? e.changedTouches[0] : e,\r
449                         momentumX = { dist:0, time:0 },\r
450                         momentumY = { dist:0, time:0 },\r
451                         duration  = ( e.timeStamp ||X.getTime() ) - this.startTime,\r
452                         newPosX   = this.x,\r
453                         newPosY   = this.y,\r
454                         distX, distY,\r
455                         newDuration,\r
456                         snap,\r
457                         scale;\r
458 \r
459                 this.uinodeRoot.unlisten( X.UI.Event.DRAG, this );\r
460                 this.uinodeRoot.unlisten( X.UI.Event.DRAG_END, this );\r
461 \r
462                 //this._unbind(MOVE_EV, window);\r
463                 //this._unbind(END_EV, window);\r
464                 //this._unbind(CANCEL_EV, window);\r
465 \r
466                 //if (this.options.onBeforeScrollEnd) this.options.onBeforeScrollEnd.call(this, e);\r
467                 \r
468 \r
469                 if (!this.moved) {\r
470 \r
471 \r
472                         this._resetPos(400);\r
473 \r
474                         //if (this.options.onTouchEnd) this.options.onTouchEnd.call(this, e);\r
475                         return;\r
476                 };\r
477 \r
478                 if (duration < 300 && this.options.momentum) {\r
479                         momentumX = newPosX ? this._momentum(newPosX - this.startX, duration, -this.x, this.scrollerW - this.wrapperW + this.x, this.options.bounce ? this.wrapperW : 0) : momentumX;\r
480                         momentumY = newPosY ? this._momentum(newPosY - this.startY, duration, -this.y, (this.maxScrollY < 0 ? this.scrollerH - this.wrapperH + this.y - this.minScrollY : 0), this.options.bounce ? this.wrapperH : 0) : momentumY;\r
481 \r
482                         newPosX = this.x + momentumX.dist;\r
483                         newPosY = this.y + momentumY.dist;\r
484 \r
485                         if ((this.x > 0 && newPosX > 0) || (this.x < this.maxScrollX && newPosX < this.maxScrollX)) momentumX = { dist:0, time:0 };\r
486                         if ((this.y > this.minScrollY && newPosY > this.minScrollY) || (this.y < this.maxScrollY && newPosY < this.maxScrollY)) momentumY = { dist:0, time:0 };\r
487                         \r
488                         if (momentumX.dist || momentumY.dist) {\r
489                                 newDuration = m.max(m.max(momentumX.time, momentumY.time), 10);\r
490         \r
491                                 this.scrollTo(m.round(newPosX), m.round(newPosY), newDuration);\r
492         \r
493                                 //if (this.options.onTouchEnd) this.options.onTouchEnd.call(this, e);\r
494                                 return;\r
495                         };                      \r
496                 };\r
497 \r
498                 this._resetPos(200);\r
499                 //if (this.options.onTouchEnd) this.options.onTouchEnd.call(this, e);\r
500         },\r
501         \r
502         /*\r
503         _onZoomEndEvent : null,\r
504         _onZoomEndTimerComplete : function(){\r
505                 this.options.onZoomEnd.call( this, this._onZoomEndEvent );\r
506         },\r
507         */\r
508         \r
509         /*\r
510         _onDobleTapTimerPoint : null,\r
511         _onDobleTapTimerComplete : function () {\r
512                 var point = this._onDobleTapTimerPoint,\r
513                         target, ev;\r
514                 this.doubleTapTimer = null;\r
515 \r
516                 // Find the last touched element\r
517                 target = point.target;\r
518                 while (target.nodeType !== 1) target = target.parentNode;\r
519 \r
520                 if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA') {\r
521                         ev = document.createEvent('MouseEvents');\r
522                         ev.initMouseEvent('click', true, true, e.view, 1,\r
523                                 point.screenX, point.screenY, point.clientX, point.clientY,\r
524                                 e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,\r
525                                 0, null);\r
526                         ev._fake = true;\r
527                         target.dispatchEvent(ev);\r
528                 }\r
529         },*/\r
530         \r
531         _resetPos: function (time) {\r
532                 var resetX =\r
533                                 0 <= this.x ?\r
534                                         0 :\r
535                                 this.x < this.maxScrollX ?\r
536                                         this.maxScrollX :\r
537                                         this.x,\r
538                         resetY =\r
539                                 this.minScrollY <= this.y || 0 < this.maxScrollY ?\r
540                                         this.minScrollY :\r
541                                 this.y < this.maxScrollY ?\r
542                                         this.maxScrollY :\r
543                                         this.y;\r
544 \r
545                 if( resetX === this.x && resetY === this.y ){\r
546                         if( this.moved ){\r
547                                 this.moved = false;\r
548                                 this._trigger( X.UI.Event.SCROLL_END );\r
549                                 //if (this.options.onScrollEnd) this.options.onScrollEnd.call(this);            // Execute custom code on scroll end\r
550                         };\r
551                         if( this.options.hideScrollbar ){\r
552                                 this.hScrollbar && this.hScrollbar.active &&\r
553                                         \r
554                                         //if (vendor == 'webkit') this.hScrollbarWrapper.style[transitionDelay] = '300ms';\r
555                                         //this.hScrollbarWrapper.style.opacity = '0';\r
556                                         this.hScrollbar.xnodeWraper.anime( {\r
557                                                 opacity : 0\r
558                                         }, 300 );\r
559                                 this.vScrollbar && this.vScrollbar.active &&\r
560                                         //if (vendor == 'webkit') this.vScrollbarWrapper.style[transitionDelay] = '300ms';\r
561                                         //this.vScrollbarWrapper.style.opacity = '0';\r
562                                         this.hScrollbar.xnodeWraper.anime( {\r
563                                                 opacity : 0\r
564                                         }, 300 );                       \r
565                         };\r
566                         return;\r
567                 };\r
568                 this.scrollTo(resetX, resetY, time || 0);\r
569         },\r
570         \r
571         _wheel: function (e) {\r
572                 var wheelDeltaX, wheelDeltaY,\r
573                         deltaX, deltaY,\r
574                         deltaScale;\r
575 /*\r
576                 if ('wheelDeltaX' in e) {\r
577                         wheelDeltaX = e.wheelDeltaX / 12;\r
578                         wheelDeltaY = e.wheelDeltaY / 12;\r
579                 } else if('wheelDelta' in e) {\r
580                         wheelDeltaX = wheelDeltaY = e.wheelDelta / 12;\r
581                 } else if ('detail' in e) {\r
582                         wheelDeltaX = wheelDeltaY = -e.detail * 3;\r
583                 } else {\r
584                         return;\r
585                 } */\r
586                 \r
587                 deltaX = this.x + e.wheelDeltaX;\r
588                 deltaY = this.y + e.wheelDeltaY;\r
589 \r
590                 deltaX =\r
591                         0 < deltaX ?\r
592                                 0 :\r
593                         deltaX < this.maxScrollX ?\r
594                                 this.maxScrollX : deltaX;\r
595 \r
596                 deltaY =\r
597                         this.minScrollY < deltaY ? \r
598                                 this.minScrollY :\r
599                         deltaY < this.maxScrollY ?\r
600                                 this.maxScrollY : deltaY;\r
601     \r
602                 this.maxScrollY < 0 && this.scrollTo(deltaX, deltaY, 0);\r
603         },\r
604         \r
605         /*\r
606         _wheelTimerCompleteEvent : null,\r
607         _wheelTimerComplete : function() {\r
608                 this.wheelZoomCount--;\r
609                 if (!this.wheelZoomCount && this.options.onZoomEnd) this.options.onZoomEnd.call(this, this._wheelTimerCompleteEvent );\r
610         },\r
611         */\r
612         \r
613         _transitionEnd: function( e ){\r
614                 if( e.target !== this.xnodeScroller ) return;\r
615 \r
616                 //this._unbind(TRNEND_EV);\r
617                 //X.Dom.Event.remove( this.scroller, TRNEND_EV, this );\r
618                 this.animating = false;\r
619                 \r
620                 this._startAnime();\r
621                 \r
622                 return X.Callback.UN_LISTEN;\r
623         },\r
624 \r
625 \r
626         /**\r
627         *\r
628         * Utilities\r
629         *\r
630         */\r
631         _startAnime: function () {\r
632                 var startX = this.x,\r
633                         startY = this.y,\r
634                         step, animate;\r
635 \r
636                 if (this.animating) return;\r
637                 \r
638                 if (!this.steps.length) {\r
639                         this._resetPos(400);\r
640                         return;\r
641                 };\r
642                 \r
643                 step = this.steps.shift();\r
644                 \r
645                 if( step.x === startX && step.y === startY ) step.time = 0;\r
646 \r
647                 this.animating = true;\r
648                 this.moved = true;\r
649                 \r
650                 //if (this.options.useTransition) {\r
651                         //this._transitionTime(step.time);\r
652                         this._updateScrollPosition( step.x, step.y, step.time );\r
653                         //this.animating = false;\r
654                         this.xnodeScroller.listenOnce( X.UI.Event.ANIME_END, this );\r
655                         //step.time ? X.Dom.Event.add( this.scroller, TRNEND_EV, this ) /* this._bind(TRNEND_EV) */ : this._resetPos(0);\r
656                         //return;\r
657                 //}\r
658                 //this._doAnimate( X.getTime(), step, startX, startY );\r
659         },\r
660 \r
661 /*\r
662         _doAnimate : function( startTime, step, startX, startY ){\r
663                 var now =X.getTime(),\r
664                         easeOut, newX, newY;\r
665 \r
666                 if (now >= startTime + step.time) {\r
667                         this._updateScrollPosition(step.x, step.y);\r
668                         this.animating = false;\r
669                         //if (this.options.onAnimationEnd) this.options.onAnimationEnd.call( this );                    // Execute custom code on animation end\r
670                         this._startAnime();\r
671                         return;\r
672                 };\r
673 \r
674                 now     = (now - startTime) / step.time - 1;\r
675                 easeOut = m.sqrt(1 - now * now);\r
676                 newX    = (step.x - startX) * easeOut + startX;\r
677                 newY    = (step.y - startY) * easeOut + startY;\r
678                 this._updateScrollPosition(newX, newY);\r
679                 if( this.animating ) this.animeTimerID = X.Timer.requestFrame( this, this._doAnimate, [ startTime, step, startX, startY ] );\r
680         },\r
681 */\r
682 \r
683         _momentum: function (dist, time, maxDistUpper, maxDistLower, size) {\r
684                 var deceleration = 0.0006,\r
685                         speed   = ABS(dist) / time,\r
686                         newDist = (speed * speed) / (2 * deceleration),\r
687                         newTime = 0, outsideDist = 0;\r
688 \r
689                 // Proportinally reduce speed if we are outside of the boundaries\r
690                 if (dist > 0 && newDist > maxDistUpper) {\r
691                         outsideDist = size / (6 / (newDist / speed * deceleration));\r
692                         maxDistUpper = maxDistUpper + outsideDist;\r
693                         speed = speed * maxDistUpper / newDist;\r
694                         newDist = maxDistUpper;\r
695                 } else if (dist < 0 && newDist > maxDistLower) {\r
696                         outsideDist = size / (6 / (newDist / speed * deceleration));\r
697                         maxDistLower = maxDistLower + outsideDist;\r
698                         speed = speed * maxDistLower / newDist;\r
699                         newDist = maxDistLower;\r
700                 }\r
701 \r
702                 newDist = newDist * (dist < 0 ? -1 : 1);\r
703                 newTime = speed / deceleration;\r
704 \r
705                 return { dist: newDist, time: m.round(newTime) };\r
706         },\r
707 \r
708         _offset: function (el) {\r
709                 var left = -el.offsetLeft,\r
710                         top = -el.offsetTop;\r
711                         \r
712                 while (el = el.offsetParent) {\r
713                         left -= el.offsetLeft;\r
714                         top -= el.offsetTop;\r
715                 }\r
716                 \r
717                 if (el != this.wrapper) {\r
718                         left *= this.scale;\r
719                         top *= this.scale;\r
720                 }\r
721 \r
722                 return { left: left, top: top };\r
723         },\r
724 \r
725         /*\r
726         _bind: function (type, el, bubble) {\r
727                 X.Dom.Event.add( el || this.scroller, type, this );\r
728         },\r
729 \r
730         _unbind: function (type, el, bubble) {\r
731                 X.Dom.Event.remove( el || this.scroller, type, this );\r
732         },\r
733         */\r
734 \r
735         /**\r
736         *\r
737         * Public methods\r
738         *\r
739         */\r
740         destroy: function () {\r
741                 this.scroller.style[transform] = '';\r
742 \r
743                 // Remove the scrollbars\r
744                 this.hScrollbar && this.hScrollbar.destroy();\r
745                 this.vScrollbar && this.vScrollbar.destroy();\r
746 \r
747                 // Remove the event listeners\r
748                 X.Dom.Event.add( window, RESIZE_EV, this );\r
749                 this.uinodeTarget.unlisten( X.UI.Event.DRAG_START, this );\r
750                 this.uinodeRoot.unlisten( X.UI.Event.DRAG, this );\r
751                 this.uinodeRoot.unlisten( X.UI.Event.DRAG_END, this );\r
752                 \r
753                 //this._unbind(RESIZE_EV, window);\r
754                 //this._unbind(START_EV);\r
755                 //this._unbind(MOVE_EV, window);\r
756                 //this._unbind(END_EV, window);\r
757                 //this._unbind(CANCEL_EV, window);\r
758                 \r
759                 if (!this.options.hasTouch) {\r
760                         //this._unbind('wheel');\r
761                 }\r
762                 \r
763                 // if (this.options.useTransition) this._unbind(TRNEND_EV);\r
764                 this.options.useTransition && X.Dom.Event.remove( this.scroller, TRNEND_EV, this );\r
765                 \r
766                 //if (this.options.checkDOMChanges) clearInterval(this.checkDOMTime);\r
767                 \r
768                 //if (this.options.onDestroy) this.options.onDestroy.call(this);\r
769         },\r
770 \r
771         refresh: function () {\r
772                 var offset,\r
773                         i, l,\r
774                         els,\r
775                         pos = 0,\r
776                         page = 0;\r
777 \r
778                 if (this.scale < this.options.zoomMin) this.scale = this.options.zoomMin;\r
779                 this.wrapperW = this.wrapper.clientWidth || 1;\r
780                 this.wrapperH = this.wrapper.clientHeight || 1;\r
781 \r
782                 this.minScrollY = -this.options.topOffset || 0;\r
783                 this.scrollerW  = m.round(this.scroller.offsetWidth * this.scale);\r
784                 this.scrollerH  = m.round((this.scroller.offsetHeight + this.minScrollY) * this.scale);\r
785                 this.maxScrollX = this.wrapperW - this.scrollerW;\r
786                 this.maxScrollY = this.wrapperH - this.scrollerH + this.minScrollY;\r
787                 this.dirX       = 0;\r
788                 this.dirY       = 0;\r
789 \r
790                 // if (this.options.onRefresh) this.options.onRefresh.call(this);\r
791                 this._trigger( X.UI.Event.SCROLL_REFRESH, {} );\r
792 \r
793                 this.hScrollbar && ( this.hScrollbar.active = this.maxScrollX < 0 );\r
794                 this.vScrollbar && ( this.vScrollbar.active = !this.options.bounceLock && !this.hScroll || this.scrollerH > this.wrapperH );\r
795                 \r
796                 offset = this._offset(this.wrapper);\r
797                 this.wrapperOffsetLeft = -offset.left;\r
798                 this.wrapperOffsetTop = -offset.top;\r
799 \r
800                 // Prepare snap\r
801                 if (typeof this.options.snap == 'string') {\r
802                         this.pagesX = [];\r
803                         this.pagesY = [];\r
804                         els = this.scroller.querySelectorAll(this.options.snap);\r
805                         for (i=0, l=els.length; i<l; i++) {\r
806                                 pos = this._offset(els[i]);\r
807                                 pos.left += this.wrapperOffsetLeft;\r
808                                 pos.top += this.wrapperOffsetTop;\r
809                                 this.pagesX[i] = pos.left < this.maxScrollX ? this.maxScrollX : pos.left * this.scale;\r
810                                 this.pagesY[i] = pos.top < this.maxScrollY ? this.maxScrollY : pos.top * this.scale;\r
811                         }\r
812                 } else if (this.options.snap) {\r
813                         this.pagesX = [];\r
814                         while (pos >= this.maxScrollX) {\r
815                                 this.pagesX[page] = pos;\r
816                                 pos = pos - this.wrapperW;\r
817                                 page++;\r
818                         }\r
819                         if (this.maxScrollX%this.wrapperW) this.pagesX[this.pagesX.length] = this.maxScrollX - this.pagesX[this.pagesX.length-1] + this.pagesX[this.pagesX.length-1];\r
820 \r
821                         pos = 0;\r
822                         page = 0;\r
823                         this.pagesY = [];\r
824                         while (pos >= this.maxScrollY) {\r
825                                 this.pagesY[page] = pos;\r
826                                 pos = pos - this.wrapperH;\r
827                                 page++;\r
828                         }\r
829                         if (this.maxScrollY%this.wrapperH) this.pagesY[this.pagesY.length] = this.maxScrollY - this.pagesY[this.pagesY.length-1] + this.pagesY[this.pagesY.length-1];\r
830                 }\r
831 \r
832                 // Prepare the scrollbars\r
833                 this._scrollbar('h');\r
834                 this._scrollbar('v');\r
835 \r
836                 if (!this.zoomed) {\r
837                         this.scroller.style[transitionDuration] = '0';\r
838                         this._resetPos(400);\r
839                 }\r
840         },\r
841 \r
842         scrollTo: function (x, y, time, relative) {\r
843                 var step = x,\r
844                         i, l;\r
845 \r
846                 this.stop();\r
847 \r
848                 if( !step.length ) step = [{ x: x, y: y, time: time, relative: relative }];\r
849                 \r
850                 for( i = 0, l = step.length; i < l; ++i ){\r
851                         if( step[ i ].relative ){\r
852                                 step[ i ].x = this.x - step[ i ].x;\r
853                                 step[ i ].y = this.y - step[ i ].y;\r
854                         };\r
855                         this.steps.push( {\r
856                                 x    : step[i].x,\r
857                                 y    : step[i].y,\r
858                                 time : step[i].time || 0\r
859                         });\r
860                 };\r
861 \r
862                 this._startAnime();\r
863         },\r
864 \r
865         disable: function () {\r
866                 this.stop();\r
867                 this._resetPos(0);\r
868                 this.enabled = false;\r
869 \r
870                 // If disabled after touchstart we make sure that there are no left over events\r
871                 //this._unbind(MOVE_EV, window);\r
872                 //this._unbind(END_EV, window);\r
873                 //this._unbind(CANCEL_EV, window);\r
874                 this.uinodeRoot.unlisten( X.UI.Event.DRAG, this );\r
875                 this.uinodeRoot.unlisten( X.UI.Event.DRAG_END, this );\r
876         },\r
877         \r
878         enable: function () {\r
879                 this.enabled = true;\r
880         },\r
881         \r
882         stop: function () {\r
883                 //if (this.options.useTransition) this._unbind(TRNEND_EV);\r
884                 //else X.Timer.cancelFrame( this.animeTimerID );\r
885                 /*\r
886                 if (this.options.useTransition){\r
887                         X.Dom.Event.remove( this.scroller, TRNEND_EV, this );\r
888                 } else {\r
889                         X.Timer.cancelFrame(this.animeTimerID);\r
890                 };\r
891                 */\r
892                 this.xnodeScroller.stop();\r
893                 if( this.steps ) this.steps.length = 0;\r
894                 this.moved = false;\r
895                 this.animating = false;\r
896         },\r
897         \r
898         isReady: function () {\r
899                 return !this.moved && !this.zoomed && !this.animating;\r
900         }\r
901 } );\r
902 \r
903 \r
904 \r
905 X.UI._ScrollBox = X.UI._ChromeBox.inherits(\r
906         '_ScrollBox',\r
907         X.Class.PRIVATE_DATA | X.Class.SUPER_ACCESS,\r
908         {\r
909                 //elmScroll     : null,\r
910                 //elmScroller   : null,\r
911                 //elmScrollbar  : null,\r
912                 \r
913                 scrolling      : false,\r
914                 _scrollX       : 0,\r
915                 _scrollY       : 0,\r
916                 scrollXPercent : 0,\r
917                 scrollYPercent : 0,\r
918                 \r
919                 _containerNode : null,\r
920                 scrollManager  : null,\r
921                 \r
922                 Constructor : function( layout, args ){\r
923                         this.SuperConstructor( layout, args );\r
924                         this._containerNode = _X.Class._getPrivate( this.containerNode );\r
925                 },\r
926                 \r
927                 creationComplete : function(){\r
928                         X.UI._AbstractUINode.prototype.creationComplete.call( this, arguments );\r
929                         this.scrollManager = new iScroll( this.root, this.User, this.rawElement, this._containerNode.rawElement );\r
930                         this._check();\r
931                 },\r
932                 \r
933                 calculate : function(){\r
934                         X.UI._AbstractUINode.prototype.calculate.call( this, arguments );\r
935                         this._check();\r
936                 },\r
937                 \r
938                 _check : function(){\r
939                         if( this.w < this._containerNode.w || this.h < this._containerNode.h ){\r
940                                 // scroll\r
941                                 if( this.scrolling ){\r
942                                         // fix scroll position from scrollXPercent, scrollYPercent\r
943                                         \r
944                                 } else {\r
945                                         // create scroller\r
946                                         this.listen( X.UI.Event.POINTER_START, this );\r
947                                         \r
948                                         \r
949                                         this._move( 0, 0 );\r
950                                         \r
951                                         this.scrolling = true;\r
952                                 };\r
953                         } else\r
954                         // no scroll\r
955                         if( this.scrolling ){\r
956                                 // remove scroller\r
957                                 \r
958                                 ( this._scrollX !== 0 || this._scrollY !== 0 ) && this._move( 0, 0 );\r
959                                 \r
960                                 delete this.scrolling;\r
961                         };\r
962                 },\r
963                 \r
964                 handleEvent : function( e ){\r
965                         switch( e.type ){\r
966                                 case X.UI.Event.POINTER_START :\r
967                                         this.listen( X.UI.Event.POINTER_MOVE, this );\r
968                                         this.listen( X.UI.Event.POINTER_END,  this );\r
969                                         \r
970                                         break;\r
971                                 case X.UI.Event.POINTER_MOVE :\r
972                                         \r
973                                         break;\r
974                                 case X.UI.Event.POINTER_END :\r
975                                         this.unlisten( X.UI.Event.POINTER_MOVE, this );\r
976                                         this.unlisten( X.UI.Event.POINTER_END,  this );\r
977                                         \r
978                                         break;\r
979                         };\r
980                 },\r
981                 \r
982                 _move : function( x, y ){\r
983                         \r
984                 },\r
985                 \r
986                 _remove : function(){\r
987                         X.UI._AbstractUINode.prototype._remove.call( this, arguments );\r
988                         if( this.scrolling ){\r
989                                 // remove scroll\r
990                         };\r
991                 }\r
992                 \r
993         }\r
994 );\r
995 \r
996 })();\r
997 \r
998 X.UI.ScrollBox = X.UI.ChromeBox.inherits(\r
999         'ScrollBox',\r
1000         X.Class.SUPER_ACCESS,\r
1001         X.UI._ScrollBox,\r
1002         {\r
1003                 Constructor : function(){\r
1004                         var args = [],\r
1005                                 i    = arguments.length,\r
1006                                 arg, layout;\r
1007                         \r
1008                         for( ; i; ){\r
1009                                 arg = arguments[ --i ];\r
1010                                 if( arg.instanceOf && arg.instanceOf( X.UI.Layout.Base ) ){\r
1011                                         layout = arg;\r
1012                                 } else {\r
1013                                         args[ args.length ] = arg;\r
1014                                 };\r
1015                         };\r
1016                         /*\r
1017                         this.style = DisplayNodeStyle( this,\r
1018                                 X.Class._newPrivate(\r
1019                                         this,\r
1020                                         X.UI.Layout.Canvas,\r
1021                                         [\r
1022                                                 Box(\r
1023                                                         layout || VerticalLayoutManager,\r
1024                                                         {\r
1025                                                                 name : 'ScrollBox-Scroller',\r
1026                                                                 role : 'container'\r
1027                                                         },\r
1028                                                         args\r
1029                                                 )\r
1030                                         ]\r
1031                                 )\r
1032                         );\r
1033                         this.style.addName( 'ScrollBox' ); */\r
1034                 },\r
1035                 scrollX  : function(){\r
1036                         \r
1037                 },\r
1038                 scrollY  : function(){\r
1039                         \r
1040                 },\r
1041                 scrollWidth : function(){\r
1042                         \r
1043                 },\r
1044                 scrollHeight : function(){\r
1045                         \r
1046                 },\r
1047                 scrollTo : function( nodeOrX, y ){\r
1048                         \r
1049                 }\r
1050         }\r
1051 );