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 | }); |
---|