[483] | 1 | define([ |
---|
| 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 | }); |
---|