source: Dev/trunk/src/client/dojox/mobile/scrollable.js @ 532

Last change on this file since 532 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

  • Property svn:executable set to *
File size: 48.8 KB
Line 
1define([
2        "dojo/_base/kernel",
3        "dojo/_base/connect",
4        "dojo/_base/event",
5        "dojo/_base/lang",
6        "dojo/_base/window",
7        "dojo/dom-class",
8        "dojo/dom-construct",
9        "dojo/dom-style",
10        "dojo/dom-geometry",
11        "dojo/touch",
12        "./sniff",
13        "./_css3",
14        "./_maskUtils"
15], function(dojo, connect, event, lang, win, domClass, domConstruct, domStyle,
16                        domGeom, touch, has, css3, maskUtils){
17
18        // module:
19        //              dojox/mobile/scrollable
20
21        // TODO: rename to Scrollable.js (capital S) for 2.0
22
23        // TODO: shouldn't be referencing this dojox/mobile variable, would be better to require the mobile.js module
24        var dm = lang.getObject("dojox.mobile", true);
25
26        // feature detection
27        has.add("translate3d", function(){
28                if(has("css3-animations")){
29                        var elem = win.doc.createElement("div");
30                        elem.style[css3.name("transform")] = "translate3d(0px,1px,0px)";
31                        win.doc.documentElement.appendChild(elem);
32                        var v = win.doc.defaultView.getComputedStyle(elem, '')[css3.name("transform", true)];
33                        var hasTranslate3d = v && v.indexOf("matrix") === 0;
34                        win.doc.documentElement.removeChild(elem);
35                        return hasTranslate3d;
36                }
37        });
38
39        var Scrollable = function(){
40                // summary:
41                //              Mixin for enabling touch scrolling capability.
42                // description:
43                //              Mixin for enabling touch scrolling capability.
44                //              Mobile WebKit browsers do not allow scrolling inner DIVs. (For instance,
45                //              on iOS you need the two-finger operation to scroll them.)
46                //              That means you cannot have fixed-positioned header/footer bars.
47                //              To solve this issue, this module disables the browsers default scrolling
48                //              behavior, and rebuilds its own scrolling machinery by handling touch
49                //              events. In this module, this.domNode has height "100%" and is fixed to
50                //              the window, and this.containerNode scrolls. If you place a bar outside
51                //              of this.containerNode, then it will be fixed-positioned while
52                //              this.containerNode is scrollable.
53                //
54                //              This module has the following features:
55                //
56                //              - Scrolls inner DIVs vertically, horizontally, or both.
57                //              - Vertical and horizontal scroll bars.
58                //              - Flashes the scroll bars when a view is shown.
59                //              - Simulates the flick operation using animation.
60                //              - Respects header/footer bars if any.
61        };
62
63        lang.extend(Scrollable, {
64                // fixedHeaderHeight: Number
65                //              height of a fixed header
66                fixedHeaderHeight: 0,
67
68                // fixedFooterHeight: Number
69                //              height of a fixed footer
70                fixedFooterHeight: 0,
71
72                // isLocalFooter: Boolean
73                //              footer is view-local (as opposed to application-wide)
74                isLocalFooter: false,
75
76                // scrollBar: Boolean
77                //              show scroll bar or not
78                scrollBar: true,
79
80                // scrollDir: String
81                //              v: vertical, h: horizontal, vh: both, f: flip
82                scrollDir: "v",
83
84                // weight: Number
85                //              frictional drag
86                weight: 0.6,
87
88                // fadeScrollBar: Boolean
89                fadeScrollBar: true,
90
91                // disableFlashScrollBar: Boolean
92                disableFlashScrollBar: false,
93
94                // threshold: Number
95                //              drag threshold value in pixels
96                threshold: 4,
97
98                // constraint: Boolean
99                //              bounce back to the content area
100                constraint: true,
101
102                // touchNode: DOMNode
103                //              a node that will have touch event handlers
104                touchNode: null,
105
106                // propagatable: Boolean
107                //              let touchstart event propagate up
108                propagatable: true,
109
110                // dirLock: Boolean
111                //              disable the move handler if scroll starts in the unexpected direction
112                dirLock: false,
113
114                // height: String
115                //              explicitly specified height of this widget (ex. "300px")
116                height: "",
117
118                // scrollType: Number
119                //              - 1: use (-webkit-)transform:translate3d(x,y,z) style, use (-webkit-)animation for slide animation
120                //              - 2: use top/left style,
121                //              - 3: use (-webkit-)transform:translate3d(x,y,z) style, use (-webkit-)transition for slide animation
122                //              - 0: use default value (3 for Android, iOS6+, and BlackBerry; otherwise 1)
123                scrollType: 0,
124               
125                // _parentPadBorderExtentsBottom: [private] Number
126                //              For Tooltip.js.
127                _parentPadBorderExtentsBottom: 0,
128
129                // _moved: [private] Boolean
130                //              Flag that signals if the user have moved in (one of) the scroll
131                //              direction(s) since touch start (a move under the threshold is ignored).
132                _moved: false,
133
134                init: function(/*Object?*/params){
135                        // summary:
136                        //              Initialize according to the given params.
137                        // description:
138                        //              Mixes in the given params into this instance. At least domNode
139                        //              and containerNode have to be given.
140                        //              Starts listening to the touchstart events.
141                        //              Calls resize(), if this widget is a top level widget.
142                        if(params){
143                                for(var p in params){
144                                        if(params.hasOwnProperty(p)){
145                                                this[p] = ((p == "domNode" || p == "containerNode") && typeof params[p] == "string") ?
146                                                        win.doc.getElementById(params[p]) : params[p]; // mix-in params
147                                        }
148                                }
149                        }
150                        // prevent browser scrolling on IE10 (evt.preventDefault() is not enough)
151                        if(typeof this.domNode.style.msTouchAction != "undefined"){
152                                this.domNode.style.msTouchAction = "none";
153                        }
154                        this.touchNode = this.touchNode || this.containerNode;
155                        this._v = (this.scrollDir.indexOf("v") != -1); // vertical scrolling
156                        this._h = (this.scrollDir.indexOf("h") != -1); // horizontal scrolling
157                        this._f = (this.scrollDir == "f"); // flipping views
158
159                        this._ch = []; // connect handlers
160                        this._ch.push(connect.connect(this.touchNode, touch.press, this, "onTouchStart"));
161                        if(has("css3-animations")){
162                                // flag for whether to use -webkit-transform:translate3d(x,y,z) or top/left style.
163                                // top/left style works fine as a workaround for input fields auto-scrolling issue,
164                                // so use top/left in case of Android by default.
165                                this._useTopLeft = this.scrollType ? this.scrollType === 2 : false;
166                                // Flag for using webkit transition on transform, instead of animation + keyframes.
167                                // (keyframes create a slight delay before the slide animation...)
168                                if(!this._useTopLeft){
169                                        this._useTransformTransition =
170                                                this.scrollType ? this.scrollType === 3 : has("ios") >= 6 || has("android") || has("bb");
171                                }
172                                if(!this._useTopLeft){
173                                        if(this._useTransformTransition){
174                                                this._ch.push(connect.connect(this.domNode, css3.name("transitionEnd"), this, "onFlickAnimationEnd"));
175                                                this._ch.push(connect.connect(this.domNode, css3.name("transitionStart"), this, "onFlickAnimationStart"));
176                                        }else{
177                                                this._ch.push(connect.connect(this.domNode, css3.name("animationEnd"), this, "onFlickAnimationEnd"));
178                                                this._ch.push(connect.connect(this.domNode, css3.name("animationStart"), this, "onFlickAnimationStart"));
179       
180                                                // Creation of keyframes takes a little time. If they are created
181                                                // in a lazy manner, a slight delay is noticeable when you start
182                                                // scrolling for the first time. This is to create keyframes up front.
183                                                for(var i = 0; i < 3; i++){
184                                                        this.setKeyframes(null, null, i);
185                                                }
186                                        }
187                                        if(has("translate3d")){ // workaround for flicker issue on iPhone and Android 3.x/4.0
188                                                domStyle.set(this.containerNode, css3.name("transform"), "translate3d(0,0,0)");
189                                        }
190                                }else{
191                                        this._ch.push(connect.connect(this.domNode, css3.name("transitionEnd"), this, "onFlickAnimationEnd"));
192                                        this._ch.push(connect.connect(this.domNode, css3.name("transitionStart"), this, "onFlickAnimationStart"));
193                                }
194                        }
195
196                        this._speed = {x:0, y:0};
197                        this._appFooterHeight = 0;
198                        if(this.isTopLevel() && !this.noResize){
199                                this.resize();
200                        }
201                        var _this = this;
202                        setTimeout(function(){
203                                // Why not using widget.defer() instead of setTimeout()? Because this module
204                                // is not always mixed into a widget (ex. dojox/mobile/_ComboBoxMenu), and adding
205                                // a check to call either defer or setTimeout has been considered overkill.
206                                _this.flashScrollBar();
207                        }, 600);
208                       
209                        // #16363: while navigating among input field using TAB (desktop keyboard) or
210                        // NEXT (mobile soft keyboard), domNode.scrollTop gets modified (this holds even
211                        // if the text widget has selectOnFocus at false, that is even if dijit's _FormWidgetMixin._onFocus
212                        // does not trigger a global scrollIntoView). This messes up ScrollableView's own
213                        // scrolling machinery. To avoid this misbehavior:
214                        if(win.global.addEventListener){ // all supported browsers but IE8
215                                // (for IE8, using attachEvent is not a solution, because it only works in bubbling phase)
216                                this._onScroll = function(e){
217                                        if(!_this.domNode || _this.domNode.style.display === 'none'){ return; }
218                                        var scrollTop = _this.domNode.scrollTop;
219                                        var scrollLeft = _this.domNode.scrollLeft;
220                                        var pos;
221                                        if(scrollTop > 0 || scrollLeft > 0){
222                                                pos = _this.getPos();
223                                                // Reset to zero while compensating using our own scroll:
224                                                _this.domNode.scrollLeft = 0;
225                                                _this.domNode.scrollTop = 0;
226                                                _this.scrollTo({x: pos.x - scrollLeft, y: pos.y - scrollTop}); // no animation
227                                        }
228                                };
229                                win.global.addEventListener("scroll", this._onScroll, true);
230                        }
231                        // #17062: Ensure auto-scroll when navigating focusable fields
232                        if(!this.disableTouchScroll && this.domNode.addEventListener){
233                                this._onFocusScroll = function(e){
234                                        if(!_this.domNode || _this.domNode.style.display === 'none'){ return; }
235                                        var node = win.doc.activeElement;
236                                        var nodeRect, scrollableRect;
237                                        if(node){
238                                                nodeRect = node.getBoundingClientRect();
239                                                scrollableRect = _this.domNode.getBoundingClientRect();
240                                                if(nodeRect.height < _this.getDim().d.h){
241                                                        // do not call scrollIntoView for elements with a height
242                                                        // larger than the height of scrollable's content display
243                                                        // area (it would be ergonomically harmful).
244                                                       
245                                                        if(nodeRect.top < (scrollableRect.top + _this.fixedHeaderHeight)){
246                                                                // scrolling towards top (to bring into the visible area an element
247                                                                // located above it).
248                                                                _this.scrollIntoView(node, true);
249                                                        }else if((nodeRect.top + nodeRect.height) >
250                                                                (scrollableRect.top + scrollableRect.height - _this.fixedFooterHeight)){
251                                                                // scrolling towards bottom (to bring into the visible area an element
252                                                                // located below it).
253                                                                _this.scrollIntoView(node, false);
254                                                        } // else do nothing (the focused node is already visible)
255                                                }
256                                        }
257                                };
258                                this.domNode.addEventListener("focus", this._onFocusScroll, true);
259                        }
260                },
261
262                isTopLevel: function(){
263                        // summary:
264                        //              Returns true if this is a top-level widget.
265                        // description:
266                        //              Subclass may want to override.
267                        return true;
268                },
269
270                cleanup: function(){
271                        // summary:
272                        //              Uninitialize the module.
273                        if(this._ch){
274                                for(var i = 0; i < this._ch.length; i++){
275                                        connect.disconnect(this._ch[i]);
276                                }
277                                this._ch = null;
278                        }
279                        if(this._onScroll && win.global.removeEventListener){ // all supported browsers but IE8
280                                win.global.removeEventListener("scroll", this._onScroll, true);
281                                this._onScroll = null;
282                        }
283                       
284                        if(this._onFocusScroll && this.domNode.removeEventListener){
285                                this.domNode.removeEventListener("focus", this._onFocusScroll, true);
286                                this._onFocusScroll = null;
287                        }
288                },
289
290                findDisp: function(/*DomNode*/node){
291                        // summary:
292                        //              Finds the currently displayed view node from my sibling nodes.
293                        if(!node.parentNode){ return null; }
294
295                        // the given node is the first candidate
296                        if(node.nodeType === 1 && domClass.contains(node, "mblSwapView") && node.style.display !== "none"){
297                                return node;
298                        }
299
300                        var nodes = node.parentNode.childNodes;
301                        for(var i = 0; i < nodes.length; i++){
302                                var n = nodes[i];
303                                if(n.nodeType === 1 && domClass.contains(n, "mblView") && n.style.display !== "none"){
304                                        return n;
305                                }
306                        }
307                        return node;
308                },
309
310                getScreenSize: function(){
311                        // summary:
312                        //              Returns the dimensions of the browser window.
313                        return {
314                                h: win.global.innerHeight||win.doc.documentElement.clientHeight||win.doc.documentElement.offsetHeight,
315                                w: win.global.innerWidth||win.doc.documentElement.clientWidth||win.doc.documentElement.offsetWidth
316                        };
317                },
318
319                resize: function(e){
320                        // summary:
321                        //              Adjusts the height of the widget.
322                        // description:
323                        //              If the height property is 'inherit', the height is inherited
324                        //              from its offset parent. If 'auto', the content height, which
325                        //              could be smaller than the entire screen height, is used. If an
326                        //              explicit height value (ex. "300px"), it is used as the new
327                        //              height. If nothing is specified as the height property, from the
328                        //              current top position of the widget to the bottom of the screen
329                        //              will be the new height.
330
331                        // moved from init() to support dynamically added fixed bars
332                        this._appFooterHeight = (this._fixedAppFooter) ? this._fixedAppFooter.offsetHeight : 0;
333                        if(this.isLocalHeader){
334                                this.containerNode.style.marginTop = this.fixedHeaderHeight + "px";
335                        }
336
337                        // Get the top position. Same as dojo.position(node, true).y
338                        var top = 0;
339                        for(var n = this.domNode; n && n.tagName != "BODY"; n = n.offsetParent){
340                                n = this.findDisp(n); // find the first displayed view node
341                                if(!n){ break; }
342                                top += n.offsetTop + domGeom.getBorderExtents(n).h;
343                        }
344
345                        // adjust the height of this view
346                        var     h,
347                                screenHeight = this.getScreenSize().h,
348                                dh = screenHeight - top - this._appFooterHeight; // default height
349                        if(this.height === "inherit"){
350                                if(this.domNode.offsetParent){
351                                        h = domGeom.getContentBox(this.domNode.offsetParent).h - domGeom.getBorderExtents(this.domNode).h + "px";
352                                }
353                        }else if(this.height === "auto"){
354                                var parent = this.domNode.offsetParent;
355                                if(parent){
356                                        this.domNode.style.height = "0px";
357                                        var     parentRect = parent.getBoundingClientRect(),
358                                                scrollableRect = this.domNode.getBoundingClientRect(),
359                                                contentBottom = parentRect.bottom - this._appFooterHeight - this._parentPadBorderExtentsBottom;
360                                        if(scrollableRect.bottom >= contentBottom){ // use entire screen
361                                                dh = screenHeight - (scrollableRect.top - parentRect.top) - this._appFooterHeight - this._parentPadBorderExtentsBottom;
362                                        }else{ // stretch to fill predefined area
363                                                dh = contentBottom - scrollableRect.bottom;
364                                        }
365                                }
366                                // content could be smaller than entire screen height
367                                var contentHeight = Math.max(this.domNode.scrollHeight, this.containerNode.scrollHeight);
368                                h = (contentHeight ? Math.min(contentHeight, dh) : dh) + "px";
369                        }else if(this.height){
370                                h = this.height;
371                        }
372                        if(!h){
373                                h = dh + "px";
374                        }
375                        if(h.charAt(0) !== "-" && // to ensure that h is not negative (e.g. "-10px")
376                                h !== "default"){
377                                this.domNode.style.height = h;
378                        }
379
380                        if(!this._conn){
381                                // to ensure that the view is within a scrolling area when resized.
382                                this.onTouchEnd();
383                        }
384                },
385
386                onFlickAnimationStart: function(e){
387                        event.stop(e);
388                },
389
390                onFlickAnimationEnd: function(e){
391                        if(has("ios")){
392                                this._keepInputCaretInActiveElement();
393                        }
394                        if(e){
395                                var an = e.animationName;
396                                if(an && an.indexOf("scrollableViewScroll2") === -1){
397                                        if(an.indexOf("scrollableViewScroll0") !== -1){ // scrollBarV
398                                                if(this._scrollBarNodeV){ domClass.remove(this._scrollBarNodeV, "mblScrollableScrollTo0"); }
399                                        }else if(an.indexOf("scrollableViewScroll1") !== -1){ // scrollBarH
400                                                if(this._scrollBarNodeH){ domClass.remove(this._scrollBarNodeH, "mblScrollableScrollTo1"); }
401                                        }else{ // fade or others
402                                                if(this._scrollBarNodeV){ this._scrollBarNodeV.className = ""; }
403                                                if(this._scrollBarNodeH){ this._scrollBarNodeH.className = ""; }
404                                        }
405                                        return;
406                                }
407                                if(this._useTransformTransition || this._useTopLeft){
408                                        var n = e.target;
409                                        if(n === this._scrollBarV || n === this._scrollBarH){
410                                                var cls = "mblScrollableScrollTo" + (n === this._scrollBarV ? "0" : "1");
411                                                if(domClass.contains(n, cls)){
412                                                        domClass.remove(n, cls);
413                                                }else{
414                                                        n.className = "";
415                                                }
416                                                return;
417                                        }
418                                }
419                                if(e.srcElement){
420                                        event.stop(e);
421                                }
422                        }
423                        this.stopAnimation();
424                        if(this._bounce){
425                                var _this = this;
426                                var bounce = _this._bounce;
427                                setTimeout(function(){
428                                        _this.slideTo(bounce, 0.3, "ease-out");
429                                }, 0);
430                                _this._bounce = undefined;
431                        }else{
432                                this.hideScrollBar();
433                                this.removeCover();
434                        }
435                },
436
437                isFormElement: function(/*DOMNode*/node){
438                        // summary:
439                        //              Returns true if the given node is a form control.
440                        if(node && node.nodeType !== 1){ node = node.parentNode; }
441                        if(!node || node.nodeType !== 1){ return false; }
442                        var t = node.tagName;
443                        return (t === "SELECT" || t === "INPUT" || t === "TEXTAREA" || t === "BUTTON");
444                },
445
446                onTouchStart: function(e){
447                        // summary:
448                        //              User-defined function to handle touchStart events.
449                        if(this.disableTouchScroll){ return; }
450                        if(this._conn && (new Date()).getTime() - this.startTime < 500){
451                                return; // ignore successive onTouchStart calls
452                        }
453                        if(!this._conn){
454                                this._conn = [];
455                                this._conn.push(connect.connect(win.doc, touch.move, this, "onTouchMove"));
456                                this._conn.push(connect.connect(win.doc, touch.release, this, "onTouchEnd"));
457                        }
458
459                        this._aborted = false;
460                        if(domClass.contains(this.containerNode, "mblScrollableScrollTo2")){
461                                this.abort();
462                        }else{ // reset scrollbar class especially for reseting fade-out animation
463                                if(this._scrollBarNodeV){ this._scrollBarNodeV.className = ""; }
464                                if(this._scrollBarNodeH){ this._scrollBarNodeH.className = ""; }
465                        }
466                        this.touchStartX = e.touches ? e.touches[0].pageX : e.clientX;
467                        this.touchStartY = e.touches ? e.touches[0].pageY : e.clientY;
468                        this.startTime = (new Date()).getTime();
469                        this.startPos = this.getPos();
470                        this._dim = this.getDim();
471                        this._time = [0];
472                        this._posX = [this.touchStartX];
473                        this._posY = [this.touchStartY];
474                        this._locked = false;
475                        this._moved = false;
476
477                        if(!this.isFormElement(e.target)){
478                                this.propagatable ? e.preventDefault() : event.stop(e);
479                        }
480                },
481
482                onTouchMove: function(e){
483                        // summary:
484                        //              User-defined function to handle touchMove events.
485                        if(this._locked){ return; }
486                        var x = e.touches ? e.touches[0].pageX : e.clientX;
487                        var y = e.touches ? e.touches[0].pageY : e.clientY;
488                        var dx = x - this.touchStartX;
489                        var dy = y - this.touchStartY;
490                        var to = {x:this.startPos.x + dx, y:this.startPos.y + dy};
491                        var dim = this._dim;
492
493                        dx = Math.abs(dx);
494                        dy = Math.abs(dy);
495                        if(this._time.length == 1){ // the first TouchMove after TouchStart
496                                if(this.dirLock){
497                                        if(this._v && !this._h && dx >= this.threshold && dx >= dy ||
498                                                (this._h || this._f) && !this._v && dy >= this.threshold && dy >= dx){
499                                                this._locked = true;
500                                                return;
501                                        }
502                                }
503                                if(this._v && this._h){ // scrollDir="hv"
504                                        if(dy < this.threshold &&
505                                                dx < this.threshold){
506                                                return;
507                                        }
508                                }else{
509                                        if(this._v && dy < this.threshold ||
510                                                (this._h || this._f) && dx < this.threshold){
511                                                return;
512                                        }
513                                }
514                                this._moved = true;
515                                this.addCover();
516                                this.showScrollBar();
517                        }
518
519                        var weight = this.weight;
520                        if(this._v && this.constraint){
521                                if(to.y > 0){ // content is below the screen area
522                                        to.y = Math.round(to.y * weight);
523                                }else if(to.y < -dim.o.h){ // content is above the screen area
524                                        if(dim.c.h < dim.d.h){ // content is shorter than display
525                                                to.y = Math.round(to.y * weight);
526                                        }else{
527                                                to.y = -dim.o.h - Math.round((-dim.o.h - to.y) * weight);
528                                        }
529                                }
530                        }
531                        if((this._h || this._f) && this.constraint){
532                                if(to.x > 0){
533                                        to.x = Math.round(to.x * weight);
534                                }else if(to.x < -dim.o.w){
535                                        if(dim.c.w < dim.d.w){
536                                                to.x = Math.round(to.x * weight);
537                                        }else{
538                                                to.x = -dim.o.w - Math.round((-dim.o.w - to.x) * weight);
539                                        }
540                                }
541                        }
542                        this.scrollTo(to);
543
544                        var max = 10;
545                        var n = this._time.length; // # of samples
546                        if(n >= 2){
547                                this._moved = true;
548                                // Check the direction of the finger move.
549                                // If the direction has been changed, discard the old data.
550                                var d0, d1;
551                                if(this._v && !this._h){
552                                        d0 = this._posY[n - 1] - this._posY[n - 2];
553                                        d1 = y - this._posY[n - 1];
554                                }else if(!this._v && this._h){
555                                        d0 = this._posX[n - 1] - this._posX[n - 2];
556                                        d1 = x - this._posX[n - 1];
557                                }
558                                if(d0 * d1 < 0){ // direction changed
559                                        // leave only the latest data
560                                        this._time = [this._time[n - 1]];
561                                        this._posX = [this._posX[n - 1]];
562                                        this._posY = [this._posY[n - 1]];
563                                        n = 1;
564                                }
565                        }
566                        if(n == max){
567                                this._time.shift();
568                                this._posX.shift();
569                                this._posY.shift();
570                        }
571                        this._time.push((new Date()).getTime() - this.startTime);
572                        this._posX.push(x);
573                        this._posY.push(y);
574                },
575
576                _keepInputCaretInActiveElement: function(){
577                        var activeElement = win.doc.activeElement;
578                        var initialValue;
579                        if(activeElement && (activeElement.tagName == "INPUT" || activeElement.tagName == "TEXTAREA")){
580                                initialValue = activeElement.value;
581                                if(activeElement.type == "number" || activeElement.type == "week"){
582                                        if(initialValue){
583                                                activeElement.value = activeElement.value + 1;
584                                        }else{
585                                                activeElement.value = (activeElement.type == "week") ? "2013-W10" : 1;
586                                        }
587                                        activeElement.value = initialValue;
588                                }else{
589                                        activeElement.value = activeElement.value + " ";
590                                        activeElement.value = initialValue;
591                                }
592                        }
593                },
594
595                onTouchEnd: function(/*Event*/e){
596                        // summary:
597                        //              User-defined function to handle touchEnd events.
598                        if(this._locked){ return; }
599                        var speed = this._speed = {x:0, y:0};
600                        var dim = this._dim;
601                        var pos = this.getPos();
602                        var to = {}; // destination
603                        if(e){
604                                if(!this._conn){ return; } // if we get onTouchEnd without onTouchStart, ignore it.
605                                for(var i = 0; i < this._conn.length; i++){
606                                        connect.disconnect(this._conn[i]);
607                                }
608                                this._conn = null;
609
610                                var clicked = false;
611                                if(!this._aborted && !this._moved){
612                                        clicked = true;
613                                }
614                                if(clicked){ // clicked, not dragged or flicked
615                                        this.hideScrollBar();
616                                        this.removeCover();
617                                        // need to send a synthetic click?
618                                        if(has("touch") && has("clicks-prevented") && !this.isFormElement(e.target)){
619                                                var elem = e.target;
620                                                if(elem.nodeType != 1){
621                                                        elem = elem.parentNode;
622                                                }
623                                                setTimeout(function(){
624                                                        dm._sendClick(elem, e);
625                                                });
626                                        }
627                                        return;
628                                }
629                                speed = this._speed = this.getSpeed();
630                        }else{
631                                if(pos.x == 0 && pos.y == 0){ return; } // initializing
632                                dim = this.getDim();
633                        }
634
635                        if(this._v){
636                                to.y = pos.y + speed.y;
637                        }
638                        if(this._h || this._f){
639                                to.x = pos.x + speed.x;
640                        }
641
642                        if(this.adjustDestination(to, pos, dim) === false){ return; }
643                        if(this.constraint){
644                                if(this.scrollDir == "v" && dim.c.h < dim.d.h){ // content is shorter than display
645                                        this.slideTo({y:0}, 0.3, "ease-out"); // go back to the top
646                                        return;
647                                }else if(this.scrollDir == "h" && dim.c.w < dim.d.w){ // content is narrower than display
648                                        this.slideTo({x:0}, 0.3, "ease-out"); // go back to the left
649                                        return;
650                                }else if(this._v && this._h && dim.c.h < dim.d.h && dim.c.w < dim.d.w){
651                                        this.slideTo({x:0, y:0}, 0.3, "ease-out"); // go back to the top-left
652                                        return;
653                                }
654                        }
655
656                        var duration, easing = "ease-out";
657                        var bounce = {};
658                        if(this._v && this.constraint){
659                                if(to.y > 0){ // going down. bounce back to the top.
660                                        if(pos.y > 0){ // started from below the screen area. return quickly.
661                                                duration = 0.3;
662                                                to.y = 0;
663                                        }else{
664                                                to.y = Math.min(to.y, 20);
665                                                easing = "linear";
666                                                bounce.y = 0;
667                                        }
668                                }else if(-speed.y > dim.o.h - (-pos.y)){ // going up. bounce back to the bottom.
669                                        if(pos.y < -dim.o.h){ // started from above the screen top. return quickly.
670                                                duration = 0.3;
671                                                to.y = dim.c.h <= dim.d.h ? 0 : -dim.o.h; // if shorter, move to 0
672                                        }else{
673                                                to.y = Math.max(to.y, -dim.o.h - 20);
674                                                easing = "linear";
675                                                bounce.y = -dim.o.h;
676                                        }
677                                }
678                        }
679                        if((this._h || this._f) && this.constraint){
680                                if(to.x > 0){ // going right. bounce back to the left.
681                                        if(pos.x > 0){ // started from right of the screen area. return quickly.
682                                                duration = 0.3;
683                                                to.x = 0;
684                                        }else{
685                                                to.x = Math.min(to.x, 20);
686                                                easing = "linear";
687                                                bounce.x = 0;
688                                        }
689                                }else if(-speed.x > dim.o.w - (-pos.x)){ // going left. bounce back to the right.
690                                        if(pos.x < -dim.o.w){ // started from left of the screen top. return quickly.
691                                                duration = 0.3;
692                                                to.x = dim.c.w <= dim.d.w ? 0 : -dim.o.w; // if narrower, move to 0
693                                        }else{
694                                                to.x = Math.max(to.x, -dim.o.w - 20);
695                                                easing = "linear";
696                                                bounce.x = -dim.o.w;
697                                        }
698                                }
699                        }
700                        this._bounce = (bounce.x !== undefined || bounce.y !== undefined) ? bounce : undefined;
701
702                        if(duration === undefined){
703                                var distance, velocity;
704                                if(this._v && this._h){
705                                        velocity = Math.sqrt(speed.x*speed.x + speed.y*speed.y);
706                                        distance = Math.sqrt(Math.pow(to.y - pos.y, 2) + Math.pow(to.x - pos.x, 2));
707                                }else if(this._v){
708                                        velocity = speed.y;
709                                        distance = to.y - pos.y;
710                                }else if(this._h){
711                                        velocity = speed.x;
712                                        distance = to.x - pos.x;
713                                }
714                                if(distance === 0 && !e){ return; } // #13154
715                                duration = velocity !== 0 ? Math.abs(distance / velocity) : 0.01; // time = distance / velocity
716                        }
717                        this.slideTo(to, duration, easing);
718                },
719
720                adjustDestination: function(/*Object*/to, /*Object*/pos, /*Object*/dim){
721                        // summary:
722                        //              A stub function to be overridden by subclasses.
723                        // description:
724                        //              This function is called from onTouchEnd(). The purpose is to give its
725                        //              subclasses a chance to adjust the destination position. If this
726                        //              function returns false, onTouchEnd() returns immediately without
727                        //              performing scroll.
728                        // to:
729                        //              The destination position. An object with x and y.
730                        // pos:
731                        //              The current position. An object with x and y.
732                        // dim:
733                        //              Dimension information returned by getDim().                     
734
735                        // subclass may want to implement
736                        return true; // Boolean
737                },
738
739                abort: function(){
740                        // summary:
741                        //              Aborts scrolling.
742                        // description:
743                        //              This function stops the scrolling animation that is currently
744                        //              running. It is called when the user touches the screen while
745                        //              scrolling.
746                        this._aborted = true;
747                        this.scrollTo(this.getPos());
748                        this.stopAnimation();
749                },
750
751                stopAnimation: function(){
752                        // summary:
753                        //              Stops the currently running animation.
754                        domClass.remove(this.containerNode, "mblScrollableScrollTo2");
755                        if(this._scrollBarV){
756                                this._scrollBarV.className = "";
757                        }
758                        if(this._scrollBarH){
759                                this._scrollBarH.className = "";
760                        }
761                        if(this._useTransformTransition || this._useTopLeft){
762                                this.containerNode.style[css3.name("transition")] = "";
763                                if(this._scrollBarV) { this._scrollBarV.style[css3.name("transition")] = ""; }
764                                if(this._scrollBarH) { this._scrollBarH.style[css3.name("transition")] = ""; }
765                        }
766                },
767
768                scrollIntoView: function(/*DOMNode*/node, /*Boolean?*/alignWithTop, /*Number?*/duration){
769                        // summary:
770                        //              Scrolls the pane until the searching node is in the view.
771                        // node:
772                        //              A DOM node to be searched for view.
773                        // alignWithTop:
774                        //              If true, aligns the node at the top of the pane.
775                        //              If false, aligns the node at the bottom of the pane.
776                        // duration:
777                        //              Duration of scrolling in seconds. (ex. 0.3)
778                        //              If not specified, scrolls without animation.
779                        // description:
780                        //              Just like the scrollIntoView method of DOM elements, this
781                        //              function causes the given node to scroll into view, aligning it
782                        //              either at the top or bottom of the pane.
783
784                        if(!this._v){ return; } // cannot scroll vertically
785
786                        var c = this.containerNode,
787                                h = this.getDim().d.h, // the height of ScrollableView's content display area
788                                top = 0;
789
790                        // Get the top position of node relative to containerNode
791                        for(var n = node; n !== c; n = n.offsetParent){
792                                if(!n || n.tagName === "BODY"){ return; } // exit if node is not a child of scrollableView
793                                top += n.offsetTop;
794                        }
795                        // Calculate scroll destination position
796                        var y = alignWithTop ? Math.max(h - c.offsetHeight, -top) : Math.min(0, h - top - node.offsetHeight);
797
798                        // Scroll to destination position
799                        (duration && typeof duration === "number") ?
800                                this.slideTo({y: y}, duration, "ease-out") : this.scrollTo({y: y});
801                },
802
803                getSpeed: function(){
804                        // summary:
805                        //              Returns an object that indicates the scrolling speed.
806                        // description:
807                        //              From the position and elapsed time information, calculates the
808                        //              scrolling speed, and returns an object with x and y.
809                        var x = 0, y = 0, n = this._time.length;
810                        // if the user holds the mouse or finger more than 0.5 sec, do not move.
811                        if(n >= 2 && (new Date()).getTime() - this.startTime - this._time[n - 1] < 500){
812                                var dy = this._posY[n - (n > 3 ? 2 : 1)] - this._posY[(n - 6) >= 0 ? n - 6 : 0];
813                                var dx = this._posX[n - (n > 3 ? 2 : 1)] - this._posX[(n - 6) >= 0 ? n - 6 : 0];
814                                var dt = this._time[n - (n > 3 ? 2 : 1)] - this._time[(n - 6) >= 0 ? n - 6 : 0];
815                                y = this.calcSpeed(dy, dt);
816                                x = this.calcSpeed(dx, dt);
817                        }
818                        return {x:x, y:y};
819                },
820
821                calcSpeed: function(/*Number*/distance, /*Number*/time){
822                        // summary:
823                        //              Calculate the speed given the distance and time.
824                        return Math.round(distance / time * 100) * 4;
825                },
826
827                scrollTo: function(/*Object*/to, /*Boolean?*/doNotMoveScrollBar, /*DomNode?*/node){
828                        // summary:
829                        //              Scrolls to the given position immediately without animation.
830                        // to:
831                        //              The destination position. An object with x and y.
832                        //              ex. {x:0, y:-5}
833                        // doNotMoveScrollBar:
834                        //              If true, the scroll bar will not be updated. If not specified,
835                        //              it will be updated.
836                        // node:
837                        //              A DOM node to scroll. If not specified, defaults to
838                        //              this.containerNode.
839
840                        // scroll events
841                        var scrollEvent, beforeTopHeight, afterBottomHeight;
842                        var doScroll = true;
843                        if(!this._aborted && this._conn){ // No scroll event if the call to scrollTo comes from abort or onTouchEnd
844                                if(!this._dim){
845                                        this._dim = this.getDim();
846                                }
847                                beforeTopHeight = (to.y > 0)?to.y:0;
848                                afterBottomHeight = (this._dim.o.h + to.y < 0)?-1 * (this._dim.o.h + to.y):0;
849                                scrollEvent = {bubbles: false,
850                                                cancelable: false,
851                                                x: to.x,
852                                                y: to.y,
853                                                beforeTop: beforeTopHeight > 0,
854                                                beforeTopHeight: beforeTopHeight,
855                                                afterBottom: afterBottomHeight > 0,
856                                                afterBottomHeight: afterBottomHeight};
857                                // before scroll event
858                                doScroll = this.onBeforeScroll(scrollEvent);
859                        }
860                       
861                        if(doScroll){
862                                var s = (node || this.containerNode).style;
863                                if(has("css3-animations")){
864                                        if(!this._useTopLeft){
865                                                if(this._useTransformTransition){
866                                                        s[css3.name("transition")] = "";       
867                                                }
868                                                s[css3.name("transform")] = this.makeTranslateStr(to);
869                                        }else{
870                                                s[css3.name("transition")] = "";
871                                                if(this._v){
872                                                        s.top = to.y + "px";
873                                                }
874                                                if(this._h || this._f){
875                                                        s.left = to.x + "px";
876                                                }
877                                        }
878                                }else{
879                                        if(this._v){
880                                                s.top = to.y + "px";
881                                        }
882                                        if(this._h || this._f){
883                                                s.left = to.x + "px";
884                                        }
885                                }
886                                if(has("ios")){
887                                        this._keepInputCaretInActiveElement();
888                                }
889                                if(!doNotMoveScrollBar){
890                                        this.scrollScrollBarTo(this.calcScrollBarPos(to));
891                                }
892                                if(scrollEvent){
893                                        // After scroll event
894                                        this.onAfterScroll(scrollEvent);
895                                }
896                        }
897                },
898
899                onBeforeScroll: function(/*Event*/e){
900                        // e: Event
901                        //              the scroll event, that contains the following attributes:
902                        //              x (x coordinate of the scroll destination),
903                        //              y (y coordinate of the scroll destination),
904                        //              beforeTop (a boolean that is true if the scroll detination is before the top of the scrollable),
905                        //              beforeTopHeight (the number of pixels before the top of the scrollable for the scroll destination),
906                        //              afterBottom (a boolean that is true if the scroll destination is after the bottom of the scrollable),
907                        //              afterBottomHeight (the number of pixels after the bottom of the scrollable for the scroll destination)
908                        // summary:
909                        //              called before a scroll is initiated. If this method returns false,
910                        //              the scroll is canceled.
911                        // tags:
912                        //              callback
913                        return true;
914                },
915
916                onAfterScroll: function(/*Event*/e){
917                        // e: Event
918                        //              the scroll event, that contains the following attributes:
919                        //              x (x coordinate of the scroll destination),
920                        //              y (y coordinate of the scroll destination),
921                        //              beforeTop (a boolean that is true if the scroll detination is before the top of the scrollable),
922                        //              beforeTopHeight (the number of pixels before the top of the scrollable for the scroll destination),
923                        //              afterBottom (a boolean that is true if the scroll destination is after the bottom of the scrollable),
924                        //              afterBottomHeight (the number of pixels after the bottom of the scrollable for the scroll destination)
925                        // summary:
926                        //              called after a scroll has been performed.
927                        // tags:
928                        //              callback
929                },
930               
931                slideTo: function(/*Object*/to, /*Number*/duration, /*String*/easing){
932                        // summary:
933                        //              Scrolls to the given position with the slide animation.
934                        // to:
935                        //              The scroll destination position. An object with x and/or y.
936                        //              ex. {x:0, y:-5}, {y:-29}, etc.
937                        // duration:
938                        //              Duration of scrolling in seconds. (ex. 0.3)
939                        // easing:
940                        //              The name of easing effect which webkit supports.
941                        //              "ease", "linear", "ease-in", "ease-out", etc.
942
943                        this._runSlideAnimation(this.getPos(), to, duration, easing, this.containerNode, 2);
944                        this.slideScrollBarTo(to, duration, easing);
945                },
946
947                makeTranslateStr: function(/*Object*/to){
948                        // summary:
949                        //              Constructs a string value that is passed to the -webkit-transform property.
950                        // to:
951                        //              The destination position. An object with x and/or y.
952                        // description:
953                        //              Return value example: "translate3d(0px,-8px,0px)"
954
955                        var y = this._v && typeof to.y == "number" ? to.y+"px" : "0px";
956                        var x = (this._h||this._f) && typeof to.x == "number" ? to.x+"px" : "0px";
957                        return has("translate3d") ?
958                                        "translate3d("+x+","+y+",0px)" : "translate("+x+","+y+")";
959                },
960
961                getPos: function(){
962                        // summary:
963                        //              Gets the top position in the midst of animation.
964                        if(has("css3-animations")){
965                                var s = win.doc.defaultView.getComputedStyle(this.containerNode, '');
966                                if(!this._useTopLeft){
967                                        var m = s[css3.name("transform")];
968                                        if(m && m.indexOf("matrix") === 0){
969                                                var arr = m.split(/[,\s\)]+/);
970                                                // IE10 returns a matrix3d
971                                                var i = m.indexOf("matrix3d") === 0 ? 12 : 4;
972                                                return {y:arr[i+1] - 0, x:arr[i] - 0};
973                                        }
974                                        return {x:0, y:0};
975                                }else{
976                                        return {x:parseInt(s.left) || 0, y:parseInt(s.top) || 0};
977                                }
978                        }else{
979                                // this.containerNode.offsetTop does not work here,
980                                // because it adds the height of the top margin.
981                                var y = parseInt(this.containerNode.style.top) || 0;
982                                return {y:y, x:this.containerNode.offsetLeft};
983                        }
984                },
985
986                getDim: function(){
987                        // summary:
988                        //              Returns various internal dimensional information needed for calculation.
989
990                        var d = {};
991                        // content width/height
992                        d.c = {h:this.containerNode.offsetHeight, w:this.containerNode.offsetWidth};
993
994                        // view width/height
995                        d.v = {h:this.domNode.offsetHeight + this._appFooterHeight, w:this.domNode.offsetWidth};
996
997                        // display width/height
998                        d.d = {h:d.v.h - this.fixedHeaderHeight - this.fixedFooterHeight - this._appFooterHeight, w:d.v.w};
999
1000                        // overflowed width/height
1001                        d.o = {h:d.c.h - d.v.h + this.fixedHeaderHeight + this.fixedFooterHeight + this._appFooterHeight, w:d.c.w - d.v.w};
1002                        return d;
1003                },
1004
1005                showScrollBar: function(){
1006                        // summary:
1007                        //              Shows the scroll bar.
1008                        // description:
1009                        //              This function creates the scroll bar instance if it does not
1010                        //              exist yet, and calls resetScrollBar() to reset its length and
1011                        //              position.
1012
1013                        if(!this.scrollBar){ return; }
1014
1015                        var dim = this._dim;
1016                        if(this.scrollDir == "v" && dim.c.h <= dim.d.h){ return; }
1017                        if(this.scrollDir == "h" && dim.c.w <= dim.d.w){ return; }
1018                        if(this._v && this._h && dim.c.h <= dim.d.h && dim.c.w <= dim.d.w){ return; }
1019
1020                        var createBar = function(self, dir){
1021                                var bar = self["_scrollBarNode" + dir];
1022                                if(!bar){
1023                                        var wrapper = domConstruct.create("div", null, self.domNode);
1024                                        var props = { position: "absolute", overflow: "hidden" };
1025                                        if(dir == "V"){
1026                                                props.right = "2px";
1027                                                props.width = "5px";
1028                                        }else{
1029                                                props.bottom = (self.isLocalFooter ? self.fixedFooterHeight : 0) + 2 + "px";
1030                                                props.height = "5px";
1031                                        }
1032                                        domStyle.set(wrapper, props);
1033                                        wrapper.className = "mblScrollBarWrapper";
1034                                        self["_scrollBarWrapper"+dir] = wrapper;
1035
1036                                        bar = domConstruct.create("div", null, wrapper);
1037                                        domStyle.set(bar, css3.add({
1038                                                opacity: 0.6,
1039                                                position: "absolute",
1040                                                backgroundColor: "#606060",
1041                                                fontSize: "1px",
1042                                                MozBorderRadius: "2px",
1043                                                zIndex: 2147483647 // max of signed 32-bit integer
1044                                        }, {
1045                                                borderRadius: "2px",
1046                                                transformOrigin: "0 0"
1047                                        }));
1048                                        domStyle.set(bar, dir == "V" ? {width: "5px"} : {height: "5px"});
1049                                        self["_scrollBarNode" + dir] = bar;
1050                                }
1051                                return bar;
1052                        };
1053                        if(this._v && !this._scrollBarV){
1054                                this._scrollBarV = createBar(this, "V");
1055                        }
1056                        if(this._h && !this._scrollBarH){
1057                                this._scrollBarH = createBar(this, "H");
1058                        }
1059                        this.resetScrollBar();
1060                },
1061
1062                hideScrollBar: function(){
1063                        // summary:
1064                        //              Hides the scroll bar.
1065                        // description:
1066                        //              If the fadeScrollBar property is true, hides the scroll bar with
1067                        //              the fade animation.
1068
1069                        if(this.fadeScrollBar && has("css3-animations")){
1070                                if(!dm._fadeRule){
1071                                        var node = domConstruct.create("style", null, win.doc.getElementsByTagName("head")[0]);
1072                                        node.textContent =
1073                                                ".mblScrollableFadeScrollBar{"+
1074                                                "  " + css3.name("animation-duration", true) + ": 1s;"+
1075                                                "  " + css3.name("animation-name", true) + ": scrollableViewFadeScrollBar;}"+
1076                                                "@" + css3.name("keyframes", true) + " scrollableViewFadeScrollBar{"+
1077                                                "  from { opacity: 0.6; }"+
1078                                                "  to { opacity: 0; }}";
1079                                        dm._fadeRule = node.sheet.cssRules[1];
1080                                }
1081                        }
1082                        if(!this.scrollBar){ return; }
1083                        var f = function(bar, self){
1084                                domStyle.set(bar, css3.add({
1085                                        opacity: 0
1086                                }, {
1087                                        animationDuration: ""
1088                                }));
1089                                // do not use fade animation in case of using top/left on Android
1090                                // since it causes screen flicker during adress bar's fading out
1091                                if(!(self._useTopLeft && has('android'))){
1092                                        bar.className = "mblScrollableFadeScrollBar";
1093                                }
1094                        };
1095                        if(this._scrollBarV){
1096                                f(this._scrollBarV, this);
1097                                this._scrollBarV = null;
1098                        }
1099                        if(this._scrollBarH){
1100                                f(this._scrollBarH, this);
1101                                this._scrollBarH = null;
1102                        }
1103                },
1104
1105                calcScrollBarPos: function(/*Object*/to){
1106                        // summary:
1107                        //              Calculates the scroll bar position.
1108                        // description:
1109                        //              Given the scroll destination position, calculates the top and/or
1110                        //              the left of the scroll bar(s). Returns an object with x and y.
1111                        // to:
1112                        //              The scroll destination position. An object with x and y.
1113                        //              ex. {x:0, y:-5}                 
1114
1115                        var pos = {};
1116                        var dim = this._dim;
1117                        var f = function(wrapperH, barH, t, d, c){
1118                                var y = Math.round((d - barH - 8) / (d - c) * t);
1119                                if(y < -barH + 5){
1120                                        y = -barH + 5;
1121                                }
1122                                if(y > wrapperH - 5){
1123                                        y = wrapperH - 5;
1124                                }
1125                                return y;
1126                        };
1127                        if(typeof to.y == "number" && this._scrollBarV){
1128                                pos.y = f(this._scrollBarWrapperV.offsetHeight, this._scrollBarV.offsetHeight, to.y, dim.d.h, dim.c.h);
1129                        }
1130                        if(typeof to.x == "number" && this._scrollBarH){
1131                                pos.x = f(this._scrollBarWrapperH.offsetWidth, this._scrollBarH.offsetWidth, to.x, dim.d.w, dim.c.w);
1132                        }
1133                        return pos;
1134                },
1135
1136                scrollScrollBarTo: function(/*Object*/to){
1137                        // summary:
1138                        //              Moves the scroll bar(s) to the given position without animation.
1139                        // to:
1140                        //              The destination position. An object with x and/or y.
1141                        //              ex. {x:2, y:5}, {y:20}, etc.
1142
1143                        if(!this.scrollBar){ return; }
1144                        if(this._v && this._scrollBarV && typeof to.y == "number"){
1145                                if(has("css3-animations")){
1146                                        if(!this._useTopLeft){
1147                                                if(this._useTransformTransition){
1148                                                        this._scrollBarV.style[css3.name("transition")] = "";
1149                                                }
1150                                                this._scrollBarV.style[css3.name("transform")] = this.makeTranslateStr({y:to.y});
1151                                        }else{
1152                                                domStyle.set(this._scrollBarV, css3.add({
1153                                                        top: to.y + "px"
1154                                                }, {
1155                                                        transition: ""
1156                                                }));
1157                                        }
1158                                }else{
1159                                        this._scrollBarV.style.top = to.y + "px";
1160                                }
1161                        }
1162                        if(this._h && this._scrollBarH && typeof to.x == "number"){
1163                                if(has("css3-animations")){
1164                                        if(!this._useTopLeft){
1165                                                if(this._useTransformTransition){
1166                                                        this._scrollBarH.style[css3.name("transition")] = "";
1167                                                }
1168                                                this._scrollBarH.style[css3.name("transform")] = this.makeTranslateStr({x:to.x});
1169                                        }else{
1170                                                domStyle.set(this._scrollBarH, css3.add({
1171                                                        left: to.x + "px"
1172                                                }, {
1173                                                        transition: ""
1174                                                }));
1175                                        }
1176                                }else{
1177                                        this._scrollBarH.style.left = to.x + "px";
1178                                }
1179                        }
1180                },
1181
1182                slideScrollBarTo: function(/*Object*/to, /*Number*/duration, /*String*/easing){
1183                        // summary:
1184                        //              Moves the scroll bar(s) to the given position with the slide animation.
1185                        // to:
1186                        //              The destination position. An object with x and y.
1187                        //              ex. {x:0, y:-5}
1188                        // duration:
1189                        //              Duration of the animation in seconds. (ex. 0.3)
1190                        // easing:
1191                        //              The name of easing effect which webkit supports.
1192                        //              "ease", "linear", "ease-in", "ease-out", etc.
1193
1194                        if(!this.scrollBar){ return; }
1195                        var fromPos = this.calcScrollBarPos(this.getPos());
1196                        var toPos = this.calcScrollBarPos(to);
1197                        if(this._v && this._scrollBarV){
1198                                this._runSlideAnimation({y:fromPos.y}, {y:toPos.y}, duration, easing, this._scrollBarV, 0);
1199                        }
1200                        if(this._h && this._scrollBarH){
1201                                this._runSlideAnimation({x:fromPos.x}, {x:toPos.x}, duration, easing, this._scrollBarH, 1);
1202                        }
1203                },
1204
1205                _runSlideAnimation: function(/*Object*/from, /*Object*/to, /*Number*/duration, /*String*/easing, /*DomNode*/node, /*Number*/idx){
1206                        // tags:
1207                        //              private
1208                       
1209                        // idx: 0:scrollbarV, 1:scrollbarH, 2:content
1210                        if(has("css3-animations")){
1211                                if(!this._useTopLeft){
1212                                        if(this._useTransformTransition){
1213                                                // for iOS6 (maybe others?): use -webkit-transform + -webkit-transition
1214                                                if(to.x === undefined){ to.x = from.x; }
1215                                                if(to.y === undefined){ to.y = from.y; }
1216                                                 // make sure we actually change the transform, otherwise no webkitTransitionEnd is fired.
1217                                                if(to.x !== from.x || to.y !== from.y){
1218                                                        domStyle.set(node, css3.add({}, {
1219                                                                transitionProperty: css3.name("transform"),
1220                                                                transitionDuration: duration + "s",
1221                                                                transitionTimingFunction: easing
1222                                                        }));
1223                                                        var t = this.makeTranslateStr(to);
1224                                                        setTimeout(function(){ // setTimeout is needed to prevent webkitTransitionEnd not fired
1225                                                                domStyle.set(node, css3.add({}, {
1226                                                                        transform: t
1227                                                                }));
1228                                                        }, 0);
1229                                                        domClass.add(node, "mblScrollableScrollTo"+idx);
1230                                                } else {
1231                                                        // transform not changed, just hide the scrollbar
1232                                                        this.hideScrollBar();
1233                                                        this.removeCover();
1234                                                }
1235                                        }else{
1236                                                // use -webkit-transform + -webkit-animation
1237                                                this.setKeyframes(from, to, idx);
1238                                                domStyle.set(node, css3.add({}, {
1239                                                        animationDuration: duration + "s",
1240                                                        animationTimingFunction: easing
1241                                                }));
1242                                                domClass.add(node, "mblScrollableScrollTo"+idx);
1243                                                if(idx == 2){
1244                                                        this.scrollTo(to, true, node);
1245                                                }else{
1246                                                        this.scrollScrollBarTo(to);
1247                                                }
1248                                        }
1249                                }else{
1250                                        domStyle.set(node, css3.add({}, {
1251                                                transitionProperty: "top, left",
1252                                                transitionDuration: duration + "s",
1253                                                transitionTimingFunction: easing
1254                                        }));
1255                                        setTimeout(function(){ // setTimeout is needed to prevent webkitTransitionEnd not fired
1256                                                domStyle.set(node, {
1257                                                        top: (to.y || 0) + "px",
1258                                                        left: (to.x || 0) + "px"
1259                                                });
1260                                        }, 0);
1261                                        domClass.add(node, "mblScrollableScrollTo"+idx);
1262                                }
1263                        }else if(dojo.fx && dojo.fx.easing && duration){
1264                                // If you want to support non-webkit browsers,
1265                                // your application needs to load necessary modules as follows:
1266                                //
1267                                // | dojo.require("dojo.fx");
1268                                // | dojo.require("dojo.fx.easing");
1269                                //
1270                                // This module itself does not make dependency on them.
1271                                // TODO: for 2.0 the dojo global is going away. Use require("dojo/fx") and require("dojo/fx/easing") instead.
1272                                var s = dojo.fx.slideTo({
1273                                        node: node,
1274                                        duration: duration*1000,
1275                                        left: to.x,
1276                                        top: to.y,
1277                                        easing: (easing == "ease-out") ? dojo.fx.easing.quadOut : dojo.fx.easing.linear
1278                                }).play();
1279                                if(idx == 2){
1280                                        connect.connect(s, "onEnd", this, "onFlickAnimationEnd");
1281                                }
1282                        }else{
1283                                // directly jump to the destination without animation
1284                                if(idx == 2){
1285                                        this.scrollTo(to, false, node);
1286                                        this.onFlickAnimationEnd();
1287                                }else{
1288                                        this.scrollScrollBarTo(to);
1289                                }
1290                        }
1291                },
1292
1293                resetScrollBar: function(){
1294                        // summary:
1295                        //              Resets the scroll bar length, position, etc.
1296                        var f = function(wrapper, bar, d, c, hd, v){
1297                                if(!bar){ return; }
1298                                var props = {};
1299                                props[v ? "top" : "left"] = hd + 4 + "px"; // +4 is for top or left margin
1300                                var t = (d - 8) <= 0 ? 1 : d - 8;
1301                                props[v ? "height" : "width"] = t + "px";
1302                                domStyle.set(wrapper, props);
1303                                var l = Math.round(d * d / c); // scroll bar length
1304                                l = Math.min(Math.max(l - 8, 5), t); // -8 is for margin for both ends
1305                                bar.style[v ? "height" : "width"] = l + "px";
1306                                domStyle.set(bar, {"opacity": 0.6});
1307                        };
1308                        var dim = this.getDim();
1309                        f(this._scrollBarWrapperV, this._scrollBarV, dim.d.h, dim.c.h, this.fixedHeaderHeight, true);
1310                        f(this._scrollBarWrapperH, this._scrollBarH, dim.d.w, dim.c.w, 0);
1311                        this.createMask();
1312                },
1313
1314                createMask: function(){
1315                        // summary:
1316                        //              Creates a mask for a scroll bar edge.
1317                        // description:
1318                        //              This function creates a mask that hides corners of one scroll
1319                        //              bar edge to make it round edge. The other side of the edge is
1320                        //              always visible and round shaped with the border-radius style.
1321                        if(!(has("webkit")||has("svg"))){ return; }
1322                        //var ctx;
1323                        if(this._scrollBarWrapperV){
1324                                var h = this._scrollBarWrapperV.offsetHeight;
1325                                maskUtils.createRoundMask(this._scrollBarWrapperV, 0, 0, 0, 0, 5, h, 2, 2, 0.5);
1326                        }
1327                        if(this._scrollBarWrapperH){
1328                                var w = this._scrollBarWrapperH.offsetWidth;
1329                                maskUtils.createRoundMask(this._scrollBarWrapperH, 0, 0, 0, 0, w, 5, 2, 2, 0.5);
1330                        }
1331                },
1332
1333                flashScrollBar: function(){
1334                        // summary:
1335                        //              Shows the scroll bar instantly.
1336                        // description:
1337                        //              This function shows the scroll bar, and then hides it 300ms
1338                        //              later. This is used to show the scroll bar to the user for a
1339                        //              short period of time when a hidden view is revealed.
1340                        if(this.disableFlashScrollBar || !this.domNode){ return; }
1341                        this._dim = this.getDim();
1342                        if(this._dim.d.h <= 0){ return; } // dom is not ready
1343                        this.showScrollBar();
1344                        var _this = this;
1345                        setTimeout(function(){
1346                                _this.hideScrollBar();
1347                        }, 300);
1348                },
1349
1350                addCover: function(){
1351                        // summary:
1352                        //              Adds the transparent DIV cover.
1353                        // description:
1354                        //              The cover is to prevent DOM events from affecting the child
1355                        //              widgets such as a list widget. Without the cover, for example,
1356                        //              child widgets may receive a click event and respond to it
1357                        //              unexpectedly when the user flicks the screen to scroll.
1358                        //              Note that only the desktop browsers need the cover.
1359
1360                        if(!has('touch') && !this.noCover){
1361                                if(!dm._cover){
1362                                        dm._cover = domConstruct.create("div", null, win.doc.body);
1363                                        dm._cover.className = "mblScrollableCover";
1364                                        domStyle.set(dm._cover, {
1365                                                backgroundColor: "#ffff00",
1366                                                opacity: 0,
1367                                                position: "absolute",
1368                                                top: "0px",
1369                                                left: "0px",
1370                                                width: "100%",
1371                                                height: "100%",
1372                                                zIndex: 2147483647 // max of signed 32-bit integer
1373                                        });
1374                                        this._ch.push(connect.connect(dm._cover, touch.press, this, "onTouchEnd"));
1375                                }else{
1376                                        dm._cover.style.display = "";
1377                                }
1378                                this.setSelectable(dm._cover, false);
1379                                this.setSelectable(this.domNode, false);
1380                        }
1381                },
1382
1383                removeCover: function(){
1384                        // summary:
1385                        //              Removes the transparent DIV cover.
1386
1387                        if(!has('touch') && dm._cover){
1388                                dm._cover.style.display = "none";
1389                                this.setSelectable(dm._cover, true);
1390                                this.setSelectable(this.domNode, true);
1391                        }
1392                },
1393
1394                setKeyframes: function(/*Object*/from, /*Object*/to, /*Number*/idx){
1395                        // summary:
1396                        //              Programmatically sets key frames for the scroll animation.
1397
1398                        if(!dm._rule){
1399                                dm._rule = [];
1400                        }
1401                        // idx: 0:scrollbarV, 1:scrollbarH, 2:content
1402                        if(!dm._rule[idx]){
1403                                var node = domConstruct.create("style", null, win.doc.getElementsByTagName("head")[0]);
1404                                node.textContent =
1405                                        ".mblScrollableScrollTo"+idx+"{" + css3.name("animation-name", true) + ": scrollableViewScroll"+idx+";}"+
1406                                        "@" + css3.name("keyframes", true) + " scrollableViewScroll"+idx+"{}";
1407                                dm._rule[idx] = node.sheet.cssRules[1];
1408                        }
1409                        var rule = dm._rule[idx];
1410                        if(rule){
1411                                if(from){
1412                                        rule.deleteRule(has("webkit")?"from":0);
1413                                        (rule.insertRule||rule.appendRule).call(rule, "from { " + css3.name("transform", true) + ": "+this.makeTranslateStr(from)+"; }");
1414                                }
1415                                if(to){
1416                                        if(to.x === undefined){ to.x = from.x; }
1417                                        if(to.y === undefined){ to.y = from.y; }
1418                                        rule.deleteRule(has("webkit")?"to":1);
1419                                        (rule.insertRule||rule.appendRule).call(rule, "to { " + css3.name("transform", true) + ": "+this.makeTranslateStr(to)+"; }");
1420                                }
1421                        }
1422                },
1423
1424                setSelectable: function(/*DomNode*/node, /*Boolean*/selectable){
1425                        // summary:
1426                        //              Sets the given node as selectable or unselectable.
1427                         
1428                        // dojo.setSelectable has dependency on dojo.query. Redefine our own.
1429                        node.style.KhtmlUserSelect = selectable ? "auto" : "none";
1430                        node.style.MozUserSelect = selectable ? "" : "none";
1431                        node.onselectstart = selectable ? null : function(){return false;};
1432                        if(has("ie")){
1433                                node.unselectable = selectable ? "" : "on";
1434                                var nodes = node.getElementsByTagName("*");
1435                                for(var i = 0; i < nodes.length; i++){
1436                                        nodes[i].unselectable = selectable ? "" : "on";
1437                                }
1438                        }
1439                }
1440        });
1441
1442        lang.setObject("dojox.mobile.scrollable", Scrollable);
1443
1444        return Scrollable;
1445});
Note: See TracBrowser for help on using the repository browser.