1 | //>>includeStart("standaloneScrollable", kwArgs.standaloneScrollable); |
---|
2 | |
---|
3 | // The code block surrounded by the includeStart/End pragma is to simulate |
---|
4 | // several dojo APIs that are used in this module for non-dojo applications. |
---|
5 | // For dojo applications, this code block is removed by the build tool. |
---|
6 | |
---|
7 | if(typeof define === "undefined"){ // assumes dojo.js is not loaded |
---|
8 | dojo = {doc:document, global:window}; |
---|
9 | dojox = {mobile:{}}; |
---|
10 | |
---|
11 | dojo.has = function(name){ |
---|
12 | var ua = navigator.userAgent; |
---|
13 | if(name === "webkit"){ |
---|
14 | return ua.indexOf("WebKit") != -1; |
---|
15 | } |
---|
16 | if(name === "android"){ |
---|
17 | return parseFloat(ua.split("Android ")[1]) || undefined; |
---|
18 | } |
---|
19 | if(name === "iphone"){ |
---|
20 | return ua.match(/(iPhone|iPod|iPad)/); |
---|
21 | } |
---|
22 | if(name === "ie"){ |
---|
23 | return parseFloat(ua.split("MSIE ")[1]) || undefined; |
---|
24 | } |
---|
25 | if(name === "touch"){ |
---|
26 | return (typeof dojo.doc.documentElement.ontouchstart != "undefined" && |
---|
27 | navigator.appVersion.indexOf("Mobile") != -1) || !!dojo.has("android"); |
---|
28 | } |
---|
29 | }; |
---|
30 | |
---|
31 | dojo.stopEvent = function(evt){ |
---|
32 | if(evt.preventDefault){ |
---|
33 | evt.preventDefault(); |
---|
34 | evt.stopPropagation(); |
---|
35 | }else{ |
---|
36 | evt.cancelBubble = true; |
---|
37 | } |
---|
38 | return false; |
---|
39 | }; |
---|
40 | |
---|
41 | dojo.setStyle = function(node, style, value){ |
---|
42 | if(typeof style === "string"){ |
---|
43 | var obj = {}; |
---|
44 | obj[style] = value; |
---|
45 | style = obj; |
---|
46 | } |
---|
47 | for(var s in style){ |
---|
48 | if(style.hasOwnProperty(s)){ |
---|
49 | node.style[s] = style[s]; |
---|
50 | if(s === "opacity" && typeof(node.style.filter) !== "undefined"){ |
---|
51 | node.style.filter = " progid:DXImageTransform.Microsoft.alpha(opacity="+ (style[s]*100) +")"; |
---|
52 | } |
---|
53 | } |
---|
54 | } |
---|
55 | }; |
---|
56 | |
---|
57 | dojo.create = function(tag, attrs, refNode){ |
---|
58 | return refNode.appendChild(dojo.doc.createElement(tag)); |
---|
59 | }; |
---|
60 | |
---|
61 | dojo.hasClass = function(node, s){ |
---|
62 | return (node.className.indexOf(s) != -1); |
---|
63 | }; |
---|
64 | dojo.addClass = function(node, s){ |
---|
65 | if(!dojo.hasClass(node, s)){ |
---|
66 | node.className += " " + s; |
---|
67 | } |
---|
68 | }; |
---|
69 | dojo.removeClass = function(node, s){ |
---|
70 | node.className = node.className.replace(" " + s, ""); |
---|
71 | }; |
---|
72 | |
---|
73 | dojo.connect = function(node, eventName, scope, method){ |
---|
74 | var handler = function(e){ |
---|
75 | e = e || dojo.global.event; |
---|
76 | if(!e.target){ |
---|
77 | e.target = e.srcElement; |
---|
78 | e.pageX = e.offsetX; |
---|
79 | e.pageY = e.offsetY; |
---|
80 | } |
---|
81 | scope[method](e); |
---|
82 | }; |
---|
83 | if(node.addEventListener){ |
---|
84 | node.addEventListener(eventName.replace(/^on/,""), handler, false); |
---|
85 | }else{ |
---|
86 | node.attachEvent(eventName, handler); |
---|
87 | } |
---|
88 | return {node:node, eventName:eventName, handler:handler}; |
---|
89 | }; |
---|
90 | dojo.disconnect = function(handle){ |
---|
91 | if(handle.node.removeEventListener){ |
---|
92 | handle.node.removeEventListener(handle.eventName.replace(/^on/,""), handle.handler, false); |
---|
93 | }else{ |
---|
94 | handle.node.detachEvent(handle.eventName, handle.handler); |
---|
95 | } |
---|
96 | }; |
---|
97 | |
---|
98 | define = function(module, deps, def){ |
---|
99 | var _def = (arguments.length === 2) ? arguments[1] : arguments[2]; |
---|
100 | _def( |
---|
101 | dojo, // dojo |
---|
102 | { // connect |
---|
103 | connect: dojo.connect, |
---|
104 | disconnect: dojo.disconnect |
---|
105 | }, |
---|
106 | { // event |
---|
107 | stop: dojo.stopEvent |
---|
108 | }, |
---|
109 | { // lang |
---|
110 | getObject: function(){} |
---|
111 | }, |
---|
112 | dojo, // win |
---|
113 | { // domClass |
---|
114 | contains: dojo.hasClass, |
---|
115 | add: dojo.addClass, |
---|
116 | remove: dojo.removeClass |
---|
117 | }, |
---|
118 | { // domConstruct |
---|
119 | create: dojo.create |
---|
120 | }, |
---|
121 | { // domStyle |
---|
122 | set: dojo.setStyle |
---|
123 | }, |
---|
124 | dojo.has // has |
---|
125 | ); |
---|
126 | }; |
---|
127 | } |
---|
128 | //>>includeEnd("standaloneScrollable"); |
---|
129 | |
---|
130 | |
---|
131 | define([ |
---|
132 | "dojo/_base/kernel", |
---|
133 | "dojo/_base/connect", |
---|
134 | "dojo/_base/event", |
---|
135 | "dojo/_base/lang", |
---|
136 | "dojo/_base/window", |
---|
137 | "dojo/dom-class", |
---|
138 | "dojo/dom-construct", |
---|
139 | "dojo/dom-style", |
---|
140 | "./sniff" |
---|
141 | ], function(dojo, connect, event, lang, win, domClass, domConstruct, domStyle, has){ |
---|
142 | |
---|
143 | var dm = lang.getObject("dojox.mobile", true); |
---|
144 | |
---|
145 | /*===== |
---|
146 | // summary: |
---|
147 | // Utility for enabling touch scrolling capability. |
---|
148 | // description: |
---|
149 | // Mobile WebKit browsers do not allow scrolling inner DIVs. (You need |
---|
150 | // the two-finger operation to scroll them.) |
---|
151 | // That means you cannot have fixed-positioned header/footer bars. |
---|
152 | // To solve this issue, this module disables the browsers default scrolling |
---|
153 | // behavior, and re-builds its own scrolling machinery by handling touch |
---|
154 | // events. In this module, this.domNode has height "100%" and is fixed to |
---|
155 | // the window, and this.containerNode scrolls. If you place a bar outside |
---|
156 | // of this.containerNode, then it will be fixed-positioned while |
---|
157 | // this.containerNode is scrollable. |
---|
158 | // |
---|
159 | // This module has the following features: |
---|
160 | // - Scrolls inner DIVs vertically, horizontally, or both. |
---|
161 | // - Vertical and horizontal scroll bars. |
---|
162 | // - Flashes the scroll bars when a view is shown. |
---|
163 | // - Simulates the flick operation using animation. |
---|
164 | // - Respects header/footer bars if any. |
---|
165 | // |
---|
166 | // dojox.mobile.scrollable is a simple function object, which holds |
---|
167 | // several properties and functions in it. But if you transform it to a |
---|
168 | // dojo class, it can be used as a mix-in class for any custom dojo |
---|
169 | // widgets. dojox.mobile._ScrollableMixin is such a class. |
---|
170 | // |
---|
171 | // Also, it can be used even for non-dojo applications. In such cases, |
---|
172 | // several dojo APIs used in this module, such as dojo.connect, |
---|
173 | // dojo.create, etc., are re-defined so that the code works without dojo. |
---|
174 | // When in dojo, of course those re-defined functions are not necessary. |
---|
175 | // So, they are surrounded by the includeStart and includeEnd directives |
---|
176 | // so that they can be excluded from the build. |
---|
177 | // |
---|
178 | // If you use this module for non-dojo application, you need to explicitly |
---|
179 | // assign your outer fixed node and inner scrollable node to this.domNode |
---|
180 | // and this.containerNode respectively. |
---|
181 | // |
---|
182 | // Non-dojo application should capture the onorientationchange or |
---|
183 | // the onresize event and call resize() in the event handler. |
---|
184 | // |
---|
185 | // example: |
---|
186 | // Use this module from a non-dojo applicatoin: |
---|
187 | // | function onLoad(){ |
---|
188 | // | var scrollable = new dojox.mobile.scrollable(dojo, dojox); |
---|
189 | // | scrollable.init({ |
---|
190 | // | domNode: "outer", // id or node |
---|
191 | // | containerNode: "inner" // id or node |
---|
192 | // | }); |
---|
193 | // | } |
---|
194 | // | <body onload="onLoad()"> |
---|
195 | // | <h1 id="hd1" style="position:relative;width:100%;z-index:1;"> |
---|
196 | // | Fixed Header |
---|
197 | // | </h1> |
---|
198 | // | <div id="outer" style="position:relative;height:100%;overflow:hidden;"> |
---|
199 | // | <div id="inner" style="position:absolute;width:100%;"> |
---|
200 | // | ... content ... |
---|
201 | // | </div> |
---|
202 | // | </div> |
---|
203 | // | </body> |
---|
204 | =====*/ |
---|
205 | |
---|
206 | var scrollable = function(/*Object?*/dojo, /*Object?*/dojox){ |
---|
207 | this.fixedHeaderHeight = 0; // height of a fixed header |
---|
208 | this.fixedFooterHeight = 0; // height of a fixed footer |
---|
209 | this.isLocalFooter = false; // footer is view-local (as opposed to application-wide) |
---|
210 | this.scrollBar = true; // show scroll bar or not |
---|
211 | this.scrollDir = "v"; // v: vertical, h: horizontal, vh: both, f: flip |
---|
212 | this.weight = 0.6; // frictional drag |
---|
213 | this.fadeScrollBar = true; |
---|
214 | this.disableFlashScrollBar = false; |
---|
215 | this.threshold = 4; // drag threshold value in pixels |
---|
216 | this.constraint = true; // bounce back to the content area |
---|
217 | this.touchNode = null; // a node that will have touch event handlers |
---|
218 | this.isNested = false; // this scrollable's parent is also a scrollable |
---|
219 | this.dirLock = false; // disable the move handler if scroll starts in the unexpected direction |
---|
220 | this.height = ""; // explicitly specified height of this widget (ex. "300px") |
---|
221 | this.androidWorkaroud = true; // workaround input field jumping issue |
---|
222 | |
---|
223 | //>>includeStart("standaloneScrollable", kwArgs.standaloneScrollable); |
---|
224 | if(!dojo){ // namespace objects are not passed |
---|
225 | dojo = window.dojo; |
---|
226 | dojox = window.dojox; |
---|
227 | } |
---|
228 | //>>includeEnd("standaloneScrollable"); |
---|
229 | |
---|
230 | this.init = function(/*Object?*/params){ |
---|
231 | if(params){ |
---|
232 | for(var p in params){ |
---|
233 | if(params.hasOwnProperty(p)){ |
---|
234 | this[p] = ((p == "domNode" || p == "containerNode") && typeof params[p] == "string") ? |
---|
235 | win.doc.getElementById(params[p]) : params[p]; // mix-in params |
---|
236 | } |
---|
237 | } |
---|
238 | } |
---|
239 | this.touchNode = this.touchNode || this.containerNode; |
---|
240 | this._v = (this.scrollDir.indexOf("v") != -1); // vertical scrolling |
---|
241 | this._h = (this.scrollDir.indexOf("h") != -1); // horizontal scrolling |
---|
242 | this._f = (this.scrollDir == "f"); // flipping views |
---|
243 | |
---|
244 | this._ch = []; // connect handlers |
---|
245 | this._ch.push(connect.connect(this.touchNode, |
---|
246 | has('touch') ? "touchstart" : "onmousedown", this, "onTouchStart")); |
---|
247 | if(has("webkit")){ |
---|
248 | this._ch.push(connect.connect(this.domNode, "webkitAnimationEnd", this, "onFlickAnimationEnd")); |
---|
249 | this._ch.push(connect.connect(this.domNode, "webkitAnimationStart", this, "onFlickAnimationStart")); |
---|
250 | |
---|
251 | this._aw = this.androidWorkaroud && |
---|
252 | has("android") >= 2.2 && has("android") < 3; |
---|
253 | if(this._aw){ |
---|
254 | this._ch.push(connect.connect(win.global, "onresize", this, "onScreenSizeChanged")); |
---|
255 | this._ch.push(connect.connect(win.global, "onfocus", this, function(e){ |
---|
256 | if(this.containerNode.style.webkitTransform){ |
---|
257 | this.stopAnimation(); |
---|
258 | this.toTopLeft(); |
---|
259 | } |
---|
260 | })); |
---|
261 | this._sz = this.getScreenSize(); |
---|
262 | } |
---|
263 | |
---|
264 | // Creation of keyframes takes a little time. If they are created |
---|
265 | // in a lazy manner, a slight delay is noticeable when you start |
---|
266 | // scrolling for the first time. This is to create keyframes up front. |
---|
267 | for(var i = 0; i < 3; i++){ |
---|
268 | this.setKeyframes(null, null, i); |
---|
269 | } |
---|
270 | } |
---|
271 | // Workaround for iPhone flicker issue |
---|
272 | if(has('iphone')){ |
---|
273 | domStyle.set(this.containerNode, "webkitTransform", "translate3d(0,0,0)"); |
---|
274 | } |
---|
275 | |
---|
276 | this._speed = {x:0, y:0}; |
---|
277 | this._appFooterHeight = 0; |
---|
278 | if(this.isTopLevel() && !this.noResize){ |
---|
279 | this.resize(); |
---|
280 | } |
---|
281 | var _this = this; |
---|
282 | setTimeout(function(){ |
---|
283 | _this.flashScrollBar(); |
---|
284 | }, 600); |
---|
285 | }; |
---|
286 | |
---|
287 | this.isTopLevel = function(){ |
---|
288 | // subclass may want to override |
---|
289 | return true; |
---|
290 | }; |
---|
291 | |
---|
292 | this.cleanup = function(){ |
---|
293 | if(this._ch){ |
---|
294 | for(var i = 0; i < this._ch.length; i++){ |
---|
295 | connect.disconnect(this._ch[i]); |
---|
296 | } |
---|
297 | this._ch = null; |
---|
298 | } |
---|
299 | }; |
---|
300 | |
---|
301 | this.findDisp = function(/*DomNode*/node){ |
---|
302 | // summary: |
---|
303 | // Finds the currently displayed view node from my sibling nodes. |
---|
304 | if(!node.parentNode){ return null; } |
---|
305 | var nodes = node.parentNode.childNodes; |
---|
306 | for(var i = 0; i < nodes.length; i++){ |
---|
307 | var n = nodes[i]; |
---|
308 | if(n.nodeType === 1 && domClass.contains(n, "mblView") && n.style.display !== "none"){ |
---|
309 | return n; |
---|
310 | } |
---|
311 | } |
---|
312 | return node; |
---|
313 | }; |
---|
314 | |
---|
315 | this.getScreenSize = function(){ |
---|
316 | // summary: |
---|
317 | // Returns the dimensions of the browser window. |
---|
318 | return { |
---|
319 | h: win.global.innerHeight||win.doc.documentElement.clientHeight||win.doc.documentElement.offsetHeight, |
---|
320 | w: win.global.innerWidth||win.doc.documentElement.clientWidth||win.doc.documentElement.offsetWidth |
---|
321 | }; |
---|
322 | }; |
---|
323 | |
---|
324 | this.isKeyboardShown = function(e){ |
---|
325 | // summary: |
---|
326 | // Internal function for android workaround. |
---|
327 | // description: |
---|
328 | // Returns true if a virtual keyboard is shown. |
---|
329 | // Indirectly detects whether a virtual keyboard is shown or not by |
---|
330 | // examining the screen size. |
---|
331 | // TODO: need more reliable detection logic |
---|
332 | if(!this._sz){ return false; } |
---|
333 | var sz = this.getScreenSize(); |
---|
334 | return (sz.w * sz.h) / (this._sz.w * this._sz.h) < 0.8; |
---|
335 | }; |
---|
336 | |
---|
337 | this.disableScroll = function(/*Boolean*/v){ |
---|
338 | // summary: |
---|
339 | // Internal function for android workaround. |
---|
340 | // description: |
---|
341 | // Disables the touch scrolling and enables the browser's default |
---|
342 | // scrolling. |
---|
343 | if(this.disableTouchScroll === v || this.domNode.style.display === "none"){ return; } |
---|
344 | this.disableTouchScroll = v; |
---|
345 | this.scrollBar = !v; |
---|
346 | dm.disableHideAddressBar = dm.disableResizeAll = v; |
---|
347 | var of = v ? "visible" : "hidden"; |
---|
348 | domStyle.set(this.domNode, "overflow", of); |
---|
349 | domStyle.set(win.doc.documentElement, "overflow", of); |
---|
350 | domStyle.set(win.body(), "overflow", of); |
---|
351 | var c = this.containerNode; |
---|
352 | if(v){ |
---|
353 | if(!c.style.webkitTransform){ |
---|
354 | // stop animation when soft keyborad is shown before animation ends. |
---|
355 | // TODO: there might be a better way to wait for animation ending. |
---|
356 | this.stopAnimation(); |
---|
357 | this.toTopLeft(); |
---|
358 | } |
---|
359 | var mt = parseInt(c.style.marginTop) || 0; |
---|
360 | var h = c.offsetHeight + mt + this.fixedFooterHeight - this._appFooterHeight; |
---|
361 | domStyle.set(this.domNode, "height", h + "px"); |
---|
362 | |
---|
363 | this._cPos = { // store containerNode's position |
---|
364 | x: parseInt(c.style.left) || 0, |
---|
365 | y: parseInt(c.style.top) || 0 |
---|
366 | }; |
---|
367 | domStyle.set(c, { |
---|
368 | top: "0px", |
---|
369 | left: "0px" |
---|
370 | }); |
---|
371 | |
---|
372 | var a = win.doc.activeElement; // focused input field |
---|
373 | if(a){ // scrolling to show focused input field |
---|
374 | var at = 0; // top position of focused input field |
---|
375 | for(var n = a; n.tagName != "BODY"; n = n.offsetParent){ |
---|
376 | at += n.offsetTop; |
---|
377 | } |
---|
378 | var st = at + a.clientHeight + 10 - this.getScreenSize().h; // top postion of browser scroll bar |
---|
379 | if(st > 0){ |
---|
380 | win.body().scrollTop = st; |
---|
381 | } |
---|
382 | } |
---|
383 | }else{ |
---|
384 | if(this._cPos){ // restore containerNode's position |
---|
385 | domStyle.set(c, { |
---|
386 | top: this._cPos.y + "px", |
---|
387 | left: this._cPos.x + "px" |
---|
388 | }); |
---|
389 | this._cPos = null; |
---|
390 | } |
---|
391 | var tags = this.domNode.getElementsByTagName("*"); |
---|
392 | for(var i = 0; i < tags.length; i++){ |
---|
393 | tags[i].blur && tags[i].blur(); |
---|
394 | } |
---|
395 | // Call dojox.mobile.resizeAll if exists. |
---|
396 | dm.resizeAll && dm.resizeAll(); |
---|
397 | } |
---|
398 | }; |
---|
399 | |
---|
400 | this.onScreenSizeChanged = function(e){ |
---|
401 | // summary: |
---|
402 | // Internal function for android workaround. |
---|
403 | var sz = this.getScreenSize(); |
---|
404 | if(sz.w * sz.h > this._sz.w * this._sz.h){ |
---|
405 | this._sz = sz; // update the screen size |
---|
406 | } |
---|
407 | this.disableScroll(this.isKeyboardShown()); |
---|
408 | }; |
---|
409 | |
---|
410 | this.toTransform = function(e){ |
---|
411 | // summary: |
---|
412 | // Internal function for android workaround. |
---|
413 | var c = this.containerNode; |
---|
414 | if(c.offsetTop === 0 && c.offsetLeft === 0 || !c._webkitTransform){ return; } |
---|
415 | domStyle.set(c, { |
---|
416 | webkitTransform: c._webkitTransform, |
---|
417 | top: "0px", |
---|
418 | left: "0px" |
---|
419 | }); |
---|
420 | c._webkitTransform = null; |
---|
421 | }; |
---|
422 | |
---|
423 | this.toTopLeft = function(){ |
---|
424 | // summary: |
---|
425 | // Internal function for android workaround. |
---|
426 | var c = this.containerNode; |
---|
427 | if(!c.style.webkitTransform){ return; } // already converted to top/left |
---|
428 | c._webkitTransform = c.style.webkitTransform; |
---|
429 | var pos = this.getPos(); |
---|
430 | domStyle.set(c, { |
---|
431 | webkitTransform: "", |
---|
432 | top: pos.y + "px", |
---|
433 | left: pos.x + "px" |
---|
434 | }); |
---|
435 | }; |
---|
436 | |
---|
437 | this.resize = function(e){ |
---|
438 | // summary: |
---|
439 | // Adjusts the height of the widget. |
---|
440 | // description: |
---|
441 | // If the height property is 'inherit', the height is inherited |
---|
442 | // from its offset parent. If 'auto', the content height, which |
---|
443 | // could be smaller than the entire screen height, is used. If an |
---|
444 | // explicit height value (ex. "300px"), it is used as the new |
---|
445 | // height. If nothing is specified as the height property, from the |
---|
446 | // current top position of the widget to the bottom of the screen |
---|
447 | // will be the new height. |
---|
448 | |
---|
449 | // moved from init() to support dynamically added fixed bars |
---|
450 | this._appFooterHeight = (this.fixedFooterHeight && !this.isLocalFooter) ? |
---|
451 | this.fixedFooterHeight : 0; |
---|
452 | if(this.isLocalHeader){ |
---|
453 | this.containerNode.style.marginTop = this.fixedHeaderHeight + "px"; |
---|
454 | } |
---|
455 | |
---|
456 | // Get the top position. Same as dojo.position(node, true).y |
---|
457 | var top = 0; |
---|
458 | for(var n = this.domNode; n && n.tagName != "BODY"; n = n.offsetParent){ |
---|
459 | n = this.findDisp(n); // find the first displayed view node |
---|
460 | if(!n){ break; } |
---|
461 | top += n.offsetTop; |
---|
462 | } |
---|
463 | |
---|
464 | // adjust the height of this view |
---|
465 | var h, |
---|
466 | screenHeight = this.getScreenSize().h, |
---|
467 | dh = screenHeight - top - this._appFooterHeight; // default height |
---|
468 | if(this.height === "inherit"){ |
---|
469 | if(this.domNode.offsetParent){ |
---|
470 | h = this.domNode.offsetParent.offsetHeight + "px"; |
---|
471 | } |
---|
472 | }else if(this.height === "auto"){ |
---|
473 | var parent = this.domNode.offsetParent; |
---|
474 | if(parent){ |
---|
475 | this.domNode.style.height = "0px"; |
---|
476 | var parentRect = parent.getBoundingClientRect(), |
---|
477 | scrollableRect = this.domNode.getBoundingClientRect(), |
---|
478 | contentBottom = parentRect.bottom - this._appFooterHeight; |
---|
479 | if(scrollableRect.bottom >= contentBottom){ // use entire screen |
---|
480 | dh = screenHeight - (scrollableRect.top - parentRect.top) - this._appFooterHeight; |
---|
481 | }else{ // stretch to fill predefined area |
---|
482 | dh = contentBottom - scrollableRect.bottom; |
---|
483 | } |
---|
484 | } |
---|
485 | // content could be smaller than entire screen height |
---|
486 | var contentHeight = Math.max(this.domNode.scrollHeight, this.containerNode.scrollHeight); |
---|
487 | h = (contentHeight ? Math.min(contentHeight, dh) : dh) + "px"; |
---|
488 | }else if(this.height){ |
---|
489 | h = this.height; |
---|
490 | } |
---|
491 | if(!h){ |
---|
492 | h = dh + "px"; |
---|
493 | } |
---|
494 | if(h.charAt(0) !== "-" && // to ensure that h is not negative (e.g. "-10px") |
---|
495 | h !== "default"){ |
---|
496 | this.domNode.style.height = h; |
---|
497 | } |
---|
498 | |
---|
499 | // to ensure that the view is within a scrolling area when resized. |
---|
500 | this.onTouchEnd(); |
---|
501 | }; |
---|
502 | |
---|
503 | this.onFlickAnimationStart = function(e){ |
---|
504 | event.stop(e); |
---|
505 | }; |
---|
506 | |
---|
507 | this.onFlickAnimationEnd = function(e){ |
---|
508 | var an = e && e.animationName; |
---|
509 | if(an && an.indexOf("scrollableViewScroll2") === -1){ |
---|
510 | if(an.indexOf("scrollableViewScroll0") !== -1){ // scrollBarV |
---|
511 | domClass.remove(this._scrollBarNodeV, "mblScrollableScrollTo0"); |
---|
512 | }else if(an.indexOf("scrollableViewScroll1") !== -1){ // scrollBarH |
---|
513 | domClass.remove(this._scrollBarNodeH, "mblScrollableScrollTo1"); |
---|
514 | }else{ // fade or others |
---|
515 | if(this._scrollBarNodeV){ this._scrollBarNodeV.className = ""; } |
---|
516 | if(this._scrollBarNodeH){ this._scrollBarNodeH.className = ""; } |
---|
517 | } |
---|
518 | return; |
---|
519 | } |
---|
520 | if(e && e.srcElement){ |
---|
521 | event.stop(e); |
---|
522 | } |
---|
523 | this.stopAnimation(); |
---|
524 | if(this._bounce){ |
---|
525 | var _this = this; |
---|
526 | var bounce = _this._bounce; |
---|
527 | setTimeout(function(){ |
---|
528 | _this.slideTo(bounce, 0.3, "ease-out"); |
---|
529 | }, 0); |
---|
530 | _this._bounce = undefined; |
---|
531 | }else{ |
---|
532 | this.hideScrollBar(); |
---|
533 | this.removeCover(); |
---|
534 | if(this._aw){ this.toTopLeft(); } // android workaround |
---|
535 | } |
---|
536 | }; |
---|
537 | |
---|
538 | this.isFormElement = function(node){ |
---|
539 | if(node && node.nodeType !== 1){ node = node.parentNode; } |
---|
540 | if(!node || node.nodeType !== 1){ return false; } |
---|
541 | var t = node.tagName; |
---|
542 | return (t === "SELECT" || t === "INPUT" || t === "TEXTAREA" || t === "BUTTON"); |
---|
543 | }; |
---|
544 | |
---|
545 | this.onTouchStart = function(e){ |
---|
546 | if(this.disableTouchScroll){ return; } |
---|
547 | if(this._conn && (new Date()).getTime() - this.startTime < 500){ |
---|
548 | return; // ignore successive onTouchStart calls |
---|
549 | } |
---|
550 | if(!this._conn){ |
---|
551 | this._conn = []; |
---|
552 | this._conn.push(connect.connect(win.doc, has('touch') ? "touchmove" : "onmousemove", this, "onTouchMove")); |
---|
553 | this._conn.push(connect.connect(win.doc, has('touch') ? "touchend" : "onmouseup", this, "onTouchEnd")); |
---|
554 | } |
---|
555 | |
---|
556 | this._aborted = false; |
---|
557 | if(domClass.contains(this.containerNode, "mblScrollableScrollTo2")){ |
---|
558 | this.abort(); |
---|
559 | }else{ // reset scrollbar class especially for reseting fade-out animation |
---|
560 | if(this._scrollBarNodeV){ this._scrollBarNodeV.className = ""; } |
---|
561 | if(this._scrollBarNodeH){ this._scrollBarNodeH.className = ""; } |
---|
562 | } |
---|
563 | if(this._aw){ this.toTransform(e); } // android workaround |
---|
564 | this.touchStartX = e.touches ? e.touches[0].pageX : e.clientX; |
---|
565 | this.touchStartY = e.touches ? e.touches[0].pageY : e.clientY; |
---|
566 | this.startTime = (new Date()).getTime(); |
---|
567 | this.startPos = this.getPos(); |
---|
568 | this._dim = this.getDim(); |
---|
569 | this._time = [0]; |
---|
570 | this._posX = [this.touchStartX]; |
---|
571 | this._posY = [this.touchStartY]; |
---|
572 | this._locked = false; |
---|
573 | |
---|
574 | if(!this.isFormElement(e.target) && !this.isNested){ |
---|
575 | event.stop(e); |
---|
576 | } |
---|
577 | }; |
---|
578 | |
---|
579 | this.onTouchMove = function(e){ |
---|
580 | if(this._locked){ return; } |
---|
581 | var x = e.touches ? e.touches[0].pageX : e.clientX; |
---|
582 | var y = e.touches ? e.touches[0].pageY : e.clientY; |
---|
583 | var dx = x - this.touchStartX; |
---|
584 | var dy = y - this.touchStartY; |
---|
585 | var to = {x:this.startPos.x + dx, y:this.startPos.y + dy}; |
---|
586 | var dim = this._dim; |
---|
587 | |
---|
588 | dx = Math.abs(dx); |
---|
589 | dy = Math.abs(dy); |
---|
590 | if(this._time.length == 1){ // the first TouchMove after TouchStart |
---|
591 | if(this.dirLock){ |
---|
592 | if(this._v && !this._h && dx >= this.threshold && dx >= dy || |
---|
593 | (this._h || this._f) && !this._v && dy >= this.threshold && dy >= dx){ |
---|
594 | this._locked = true; |
---|
595 | return; |
---|
596 | } |
---|
597 | } |
---|
598 | if(this._v && Math.abs(dy) < this.threshold || |
---|
599 | (this._h || this._f) && Math.abs(dx) < this.threshold){ |
---|
600 | return; |
---|
601 | } |
---|
602 | this.addCover(); |
---|
603 | this.showScrollBar(); |
---|
604 | } |
---|
605 | |
---|
606 | var weight = this.weight; |
---|
607 | if(this._v && this.constraint){ |
---|
608 | if(to.y > 0){ // content is below the screen area |
---|
609 | to.y = Math.round(to.y * weight); |
---|
610 | }else if(to.y < -dim.o.h){ // content is above the screen area |
---|
611 | if(dim.c.h < dim.d.h){ // content is shorter than display |
---|
612 | to.y = Math.round(to.y * weight); |
---|
613 | }else{ |
---|
614 | to.y = -dim.o.h - Math.round((-dim.o.h - to.y) * weight); |
---|
615 | } |
---|
616 | } |
---|
617 | } |
---|
618 | if((this._h || this._f) && this.constraint){ |
---|
619 | if(to.x > 0){ |
---|
620 | to.x = Math.round(to.x * weight); |
---|
621 | }else if(to.x < -dim.o.w){ |
---|
622 | if(dim.c.w < dim.d.w){ |
---|
623 | to.x = Math.round(to.x * weight); |
---|
624 | }else{ |
---|
625 | to.x = -dim.o.w - Math.round((-dim.o.w - to.x) * weight); |
---|
626 | } |
---|
627 | } |
---|
628 | } |
---|
629 | this.scrollTo(to); |
---|
630 | |
---|
631 | var max = 10; |
---|
632 | var n = this._time.length; // # of samples |
---|
633 | if(n >= 2){ |
---|
634 | // Check the direction of the finger move. |
---|
635 | // If the direction has been changed, discard the old data. |
---|
636 | var d0, d1; |
---|
637 | if(this._v && !this._h){ |
---|
638 | d0 = this._posY[n - 1] - this._posY[n - 2]; |
---|
639 | d1 = y - this._posY[n - 1]; |
---|
640 | }else if(!this._v && this._h){ |
---|
641 | d0 = this._posX[n - 1] - this._posX[n - 2]; |
---|
642 | d1 = x - this._posX[n - 1]; |
---|
643 | } |
---|
644 | if(d0 * d1 < 0){ // direction changed |
---|
645 | // leave only the latest data |
---|
646 | this._time = [this._time[n - 1]]; |
---|
647 | this._posX = [this._posX[n - 1]]; |
---|
648 | this._posY = [this._posY[n - 1]]; |
---|
649 | n = 1; |
---|
650 | } |
---|
651 | } |
---|
652 | if(n == max){ |
---|
653 | this._time.shift(); |
---|
654 | this._posX.shift(); |
---|
655 | this._posY.shift(); |
---|
656 | } |
---|
657 | this._time.push((new Date()).getTime() - this.startTime); |
---|
658 | this._posX.push(x); |
---|
659 | this._posY.push(y); |
---|
660 | }; |
---|
661 | |
---|
662 | this.onTouchEnd = function(e){ |
---|
663 | if(this._locked){ return; } |
---|
664 | var speed = this._speed = {x:0, y:0}; |
---|
665 | var dim = this._dim; |
---|
666 | var pos = this.getPos(); |
---|
667 | var to = {}; // destination |
---|
668 | if(e){ |
---|
669 | if(!this._conn){ return; } // if we get onTouchEnd without onTouchStart, ignore it. |
---|
670 | for(var i = 0; i < this._conn.length; i++){ |
---|
671 | connect.disconnect(this._conn[i]); |
---|
672 | } |
---|
673 | this._conn = null; |
---|
674 | |
---|
675 | var n = this._time.length; // # of samples |
---|
676 | var clicked = false; |
---|
677 | if(!this._aborted){ |
---|
678 | if(n <= 1){ |
---|
679 | clicked = true; |
---|
680 | }else if(n == 2 && Math.abs(this._posY[1] - this._posY[0]) < 4 |
---|
681 | && has('touch')){ // for desktop browsers, posY could be the same, since we're using clientY, see onTouchMove() |
---|
682 | clicked = true; |
---|
683 | } |
---|
684 | } |
---|
685 | var isFormElem = this.isFormElement(e.target); |
---|
686 | if(clicked && !isFormElem){ // clicked, not dragged or flicked |
---|
687 | this.hideScrollBar(); |
---|
688 | this.removeCover(); |
---|
689 | if(has('touch')){ |
---|
690 | var elem = e.target; |
---|
691 | if(elem.nodeType != 1){ |
---|
692 | elem = elem.parentNode; |
---|
693 | } |
---|
694 | var ev = win.doc.createEvent("MouseEvents"); |
---|
695 | ev.initMouseEvent("click", true, true, win.global, 1, e.screenX, e.screenY, e.clientX, e.clientY); |
---|
696 | setTimeout(function(){ |
---|
697 | elem.dispatchEvent(ev); |
---|
698 | }, 0); |
---|
699 | } |
---|
700 | return; |
---|
701 | }else if(this._aw && clicked && isFormElem){ // clicked input fields |
---|
702 | this.hideScrollBar(); |
---|
703 | this.toTopLeft(); |
---|
704 | return; |
---|
705 | } |
---|
706 | speed = this._speed = this.getSpeed(); |
---|
707 | }else{ |
---|
708 | if(pos.x == 0 && pos.y == 0){ return; } // initializing |
---|
709 | dim = this.getDim(); |
---|
710 | } |
---|
711 | |
---|
712 | if(this._v){ |
---|
713 | to.y = pos.y + speed.y; |
---|
714 | } |
---|
715 | if(this._h || this._f){ |
---|
716 | to.x = pos.x + speed.x; |
---|
717 | } |
---|
718 | |
---|
719 | this.adjustDestination(to, pos); |
---|
720 | |
---|
721 | if(this.scrollDir == "v" && dim.c.h < dim.d.h){ // content is shorter than display |
---|
722 | this.slideTo({y:0}, 0.3, "ease-out"); // go back to the top |
---|
723 | return; |
---|
724 | }else if(this.scrollDir == "h" && dim.c.w < dim.d.w){ // content is narrower than display |
---|
725 | this.slideTo({x:0}, 0.3, "ease-out"); // go back to the left |
---|
726 | return; |
---|
727 | }else if(this._v && this._h && dim.c.h < dim.d.h && dim.c.w < dim.d.w){ |
---|
728 | this.slideTo({x:0, y:0}, 0.3, "ease-out"); // go back to the top-left |
---|
729 | return; |
---|
730 | } |
---|
731 | |
---|
732 | var duration, easing = "ease-out"; |
---|
733 | var bounce = {}; |
---|
734 | if(this._v && this.constraint){ |
---|
735 | if(to.y > 0){ // going down. bounce back to the top. |
---|
736 | if(pos.y > 0){ // started from below the screen area. return quickly. |
---|
737 | duration = 0.3; |
---|
738 | to.y = 0; |
---|
739 | }else{ |
---|
740 | to.y = Math.min(to.y, 20); |
---|
741 | easing = "linear"; |
---|
742 | bounce.y = 0; |
---|
743 | } |
---|
744 | }else if(-speed.y > dim.o.h - (-pos.y)){ // going up. bounce back to the bottom. |
---|
745 | if(pos.y < -dim.o.h){ // started from above the screen top. return quickly. |
---|
746 | duration = 0.3; |
---|
747 | to.y = dim.c.h <= dim.d.h ? 0 : -dim.o.h; // if shorter, move to 0 |
---|
748 | }else{ |
---|
749 | to.y = Math.max(to.y, -dim.o.h - 20); |
---|
750 | easing = "linear"; |
---|
751 | bounce.y = -dim.o.h; |
---|
752 | } |
---|
753 | } |
---|
754 | } |
---|
755 | if((this._h || this._f) && this.constraint){ |
---|
756 | if(to.x > 0){ // going right. bounce back to the left. |
---|
757 | if(pos.x > 0){ // started from right of the screen area. return quickly. |
---|
758 | duration = 0.3; |
---|
759 | to.x = 0; |
---|
760 | }else{ |
---|
761 | to.x = Math.min(to.x, 20); |
---|
762 | easing = "linear"; |
---|
763 | bounce.x = 0; |
---|
764 | } |
---|
765 | }else if(-speed.x > dim.o.w - (-pos.x)){ // going left. bounce back to the right. |
---|
766 | if(pos.x < -dim.o.w){ // started from left of the screen top. return quickly. |
---|
767 | duration = 0.3; |
---|
768 | to.x = dim.c.w <= dim.d.w ? 0 : -dim.o.w; // if narrower, move to 0 |
---|
769 | }else{ |
---|
770 | to.x = Math.max(to.x, -dim.o.w - 20); |
---|
771 | easing = "linear"; |
---|
772 | bounce.x = -dim.o.w; |
---|
773 | } |
---|
774 | } |
---|
775 | } |
---|
776 | this._bounce = (bounce.x !== undefined || bounce.y !== undefined) ? bounce : undefined; |
---|
777 | |
---|
778 | if(duration === undefined){ |
---|
779 | var distance, velocity; |
---|
780 | if(this._v && this._h){ |
---|
781 | velocity = Math.sqrt(speed.x+speed.x + speed.y*speed.y); |
---|
782 | distance = Math.sqrt(Math.pow(to.y - pos.y, 2) + Math.pow(to.x - pos.x, 2)); |
---|
783 | }else if(this._v){ |
---|
784 | velocity = speed.y; |
---|
785 | distance = to.y - pos.y; |
---|
786 | }else if(this._h){ |
---|
787 | velocity = speed.x; |
---|
788 | distance = to.x - pos.x; |
---|
789 | } |
---|
790 | if(distance === 0 && !e){ return; } // #13154 |
---|
791 | duration = velocity !== 0 ? Math.abs(distance / velocity) : 0.01; // time = distance / velocity |
---|
792 | } |
---|
793 | this.slideTo(to, duration, easing); |
---|
794 | }; |
---|
795 | |
---|
796 | this.adjustDestination = function(to, pos){ |
---|
797 | // subclass may want to implement |
---|
798 | }; |
---|
799 | |
---|
800 | this.abort = function(){ |
---|
801 | this.scrollTo(this.getPos()); |
---|
802 | this.stopAnimation(); |
---|
803 | this._aborted = true; |
---|
804 | }; |
---|
805 | |
---|
806 | this.stopAnimation = function(){ |
---|
807 | // stop the currently running animation |
---|
808 | domClass.remove(this.containerNode, "mblScrollableScrollTo2"); |
---|
809 | if(has("android")){ |
---|
810 | domStyle.set(this.containerNode, "webkitAnimationDuration", "0s"); // workaround for android screen flicker problem |
---|
811 | } |
---|
812 | if(this._scrollBarV){ |
---|
813 | this._scrollBarV.className = ""; |
---|
814 | } |
---|
815 | if(this._scrollBarH){ |
---|
816 | this._scrollBarH.className = ""; |
---|
817 | } |
---|
818 | }; |
---|
819 | |
---|
820 | this.getSpeed = function(){ |
---|
821 | var x = 0, y = 0, n = this._time.length; |
---|
822 | // if the user holds the mouse or finger more than 0.5 sec, do not move. |
---|
823 | if(n >= 2 && (new Date()).getTime() - this.startTime - this._time[n - 1] < 500){ |
---|
824 | var dy = this._posY[n - (n > 3 ? 2 : 1)] - this._posY[(n - 6) >= 0 ? n - 6 : 0]; |
---|
825 | var dx = this._posX[n - (n > 3 ? 2 : 1)] - this._posX[(n - 6) >= 0 ? n - 6 : 0]; |
---|
826 | var dt = this._time[n - (n > 3 ? 2 : 1)] - this._time[(n - 6) >= 0 ? n - 6 : 0]; |
---|
827 | y = this.calcSpeed(dy, dt); |
---|
828 | x = this.calcSpeed(dx, dt); |
---|
829 | } |
---|
830 | return {x:x, y:y}; |
---|
831 | }; |
---|
832 | |
---|
833 | this.calcSpeed = function(/*Number*/d, /*Number*/t){ |
---|
834 | return Math.round(d / t * 100) * 4; |
---|
835 | }; |
---|
836 | |
---|
837 | this.scrollTo = function(/*Object*/to, /*Boolean?*/doNotMoveScrollBar, /*DomNode?*/node){ // to: {x, y} |
---|
838 | // summary: |
---|
839 | // Scrolls to the given position. |
---|
840 | var s = (node || this.containerNode).style; |
---|
841 | if(has("webkit")){ |
---|
842 | s.webkitTransform = this.makeTranslateStr(to); |
---|
843 | }else{ |
---|
844 | if(this._v){ |
---|
845 | s.top = to.y + "px"; |
---|
846 | } |
---|
847 | if(this._h || this._f){ |
---|
848 | s.left = to.x + "px"; |
---|
849 | } |
---|
850 | } |
---|
851 | if(!doNotMoveScrollBar){ |
---|
852 | this.scrollScrollBarTo(this.calcScrollBarPos(to)); |
---|
853 | } |
---|
854 | }; |
---|
855 | |
---|
856 | this.slideTo = function(/*Object*/to, /*Number*/duration, /*String*/easing){ |
---|
857 | // summary: |
---|
858 | // Scrolls to the given position with slide animation. |
---|
859 | this._runSlideAnimation(this.getPos(), to, duration, easing, this.containerNode, 2); |
---|
860 | this.slideScrollBarTo(to, duration, easing); |
---|
861 | }; |
---|
862 | |
---|
863 | this.makeTranslateStr = function(to){ |
---|
864 | var y = this._v && typeof to.y == "number" ? to.y+"px" : "0px"; |
---|
865 | var x = (this._h||this._f) && typeof to.x == "number" ? to.x+"px" : "0px"; |
---|
866 | return dm.hasTranslate3d ? |
---|
867 | "translate3d("+x+","+y+",0px)" : "translate("+x+","+y+")"; |
---|
868 | }; |
---|
869 | |
---|
870 | this.getPos = function(){ |
---|
871 | // summary: |
---|
872 | // Get the top position in the midst of animation |
---|
873 | if(has("webkit")){ |
---|
874 | var m = win.doc.defaultView.getComputedStyle(this.containerNode, '')["-webkit-transform"]; |
---|
875 | if(m && m.indexOf("matrix") === 0){ |
---|
876 | var arr = m.split(/[,\s\)]+/); |
---|
877 | return {y:arr[5] - 0, x:arr[4] - 0}; |
---|
878 | } |
---|
879 | return {x:0, y:0}; |
---|
880 | }else{ |
---|
881 | // this.containerNode.offsetTop does not work here, |
---|
882 | // because it adds the height of the top margin. |
---|
883 | var y = parseInt(this.containerNode.style.top) || 0; |
---|
884 | return {y:y, x:this.containerNode.offsetLeft}; |
---|
885 | } |
---|
886 | }; |
---|
887 | |
---|
888 | this.getDim = function(){ |
---|
889 | var d = {}; |
---|
890 | // content width/height |
---|
891 | d.c = {h:this.containerNode.offsetHeight, w:this.containerNode.offsetWidth}; |
---|
892 | |
---|
893 | // view width/height |
---|
894 | d.v = {h:this.domNode.offsetHeight + this._appFooterHeight, w:this.domNode.offsetWidth}; |
---|
895 | |
---|
896 | // display width/height |
---|
897 | d.d = {h:d.v.h - this.fixedHeaderHeight - this.fixedFooterHeight, w:d.v.w}; |
---|
898 | |
---|
899 | // overflowed width/height |
---|
900 | d.o = {h:d.c.h - d.v.h + this.fixedHeaderHeight + this.fixedFooterHeight, w:d.c.w - d.v.w}; |
---|
901 | return d; |
---|
902 | }; |
---|
903 | |
---|
904 | this.showScrollBar = function(){ |
---|
905 | if(!this.scrollBar){ return; } |
---|
906 | |
---|
907 | var dim = this._dim; |
---|
908 | if(this.scrollDir == "v" && dim.c.h <= dim.d.h){ return; } |
---|
909 | if(this.scrollDir == "h" && dim.c.w <= dim.d.w){ return; } |
---|
910 | if(this._v && this._h && dim.c.h <= dim.d.h && dim.c.w <= dim.d.w){ return; } |
---|
911 | |
---|
912 | var createBar = function(self, dir){ |
---|
913 | var bar = self["_scrollBarNode" + dir]; |
---|
914 | if(!bar){ |
---|
915 | var wrapper = domConstruct.create("div", null, self.domNode); |
---|
916 | var props = { position: "absolute", overflow: "hidden" }; |
---|
917 | if(dir == "V"){ |
---|
918 | props.right = "2px"; |
---|
919 | props.width = "5px"; |
---|
920 | }else{ |
---|
921 | props.bottom = (self.isLocalFooter ? self.fixedFooterHeight : 0) + 2 + "px"; |
---|
922 | props.height = "5px"; |
---|
923 | } |
---|
924 | domStyle.set(wrapper, props); |
---|
925 | wrapper.className = "mblScrollBarWrapper"; |
---|
926 | self["_scrollBarWrapper"+dir] = wrapper; |
---|
927 | |
---|
928 | bar = domConstruct.create("div", null, wrapper); |
---|
929 | domStyle.set(bar, { |
---|
930 | opacity: 0.6, |
---|
931 | position: "absolute", |
---|
932 | backgroundColor: "#606060", |
---|
933 | fontSize: "1px", |
---|
934 | webkitBorderRadius: "2px", |
---|
935 | MozBorderRadius: "2px", |
---|
936 | webkitTransformOrigin: "0 0", |
---|
937 | zIndex: 2147483647 // max of signed 32-bit integer |
---|
938 | }); |
---|
939 | domStyle.set(bar, dir == "V" ? {width: "5px"} : {height: "5px"}); |
---|
940 | self["_scrollBarNode" + dir] = bar; |
---|
941 | } |
---|
942 | return bar; |
---|
943 | }; |
---|
944 | if(this._v && !this._scrollBarV){ |
---|
945 | this._scrollBarV = createBar(this, "V"); |
---|
946 | } |
---|
947 | if(this._h && !this._scrollBarH){ |
---|
948 | this._scrollBarH = createBar(this, "H"); |
---|
949 | } |
---|
950 | this.resetScrollBar(); |
---|
951 | }; |
---|
952 | |
---|
953 | this.hideScrollBar = function(){ |
---|
954 | var fadeRule; |
---|
955 | if(this.fadeScrollBar && has("webkit")){ |
---|
956 | if(!dm._fadeRule){ |
---|
957 | var node = domConstruct.create("style", null, win.doc.getElementsByTagName("head")[0]); |
---|
958 | node.textContent = |
---|
959 | ".mblScrollableFadeScrollBar{"+ |
---|
960 | " -webkit-animation-duration: 1s;"+ |
---|
961 | " -webkit-animation-name: scrollableViewFadeScrollBar;}"+ |
---|
962 | "@-webkit-keyframes scrollableViewFadeScrollBar{"+ |
---|
963 | " from { opacity: 0.6; }"+ |
---|
964 | " to { opacity: 0; }}"; |
---|
965 | dm._fadeRule = node.sheet.cssRules[1]; |
---|
966 | } |
---|
967 | fadeRule = dm._fadeRule; |
---|
968 | } |
---|
969 | if(!this.scrollBar){ return; } |
---|
970 | var f = function(bar, self){ |
---|
971 | domStyle.set(bar, { |
---|
972 | opacity: 0, |
---|
973 | webkitAnimationDuration: "" |
---|
974 | }); |
---|
975 | if(self._aw){ // android workaround |
---|
976 | bar.style.webkitTransform = ""; |
---|
977 | }else{ |
---|
978 | bar.className = "mblScrollableFadeScrollBar"; |
---|
979 | } |
---|
980 | }; |
---|
981 | if(this._scrollBarV){ |
---|
982 | f(this._scrollBarV, this); |
---|
983 | this._scrollBarV = null; |
---|
984 | } |
---|
985 | if(this._scrollBarH){ |
---|
986 | f(this._scrollBarH, this); |
---|
987 | this._scrollBarH = null; |
---|
988 | } |
---|
989 | }; |
---|
990 | |
---|
991 | this.calcScrollBarPos = function(/*Object*/to){ // to: {x, y} |
---|
992 | var pos = {}; |
---|
993 | var dim = this._dim; |
---|
994 | var f = function(wrapperH, barH, t, d, c){ |
---|
995 | var y = Math.round((d - barH - 8) / (d - c) * t); |
---|
996 | if(y < -barH + 5){ |
---|
997 | y = -barH + 5; |
---|
998 | } |
---|
999 | if(y > wrapperH - 5){ |
---|
1000 | y = wrapperH - 5; |
---|
1001 | } |
---|
1002 | return y; |
---|
1003 | }; |
---|
1004 | if(typeof to.y == "number" && this._scrollBarV){ |
---|
1005 | pos.y = f(this._scrollBarWrapperV.offsetHeight, this._scrollBarV.offsetHeight, to.y, dim.d.h, dim.c.h); |
---|
1006 | } |
---|
1007 | if(typeof to.x == "number" && this._scrollBarH){ |
---|
1008 | pos.x = f(this._scrollBarWrapperH.offsetWidth, this._scrollBarH.offsetWidth, to.x, dim.d.w, dim.c.w); |
---|
1009 | } |
---|
1010 | return pos; |
---|
1011 | }; |
---|
1012 | |
---|
1013 | this.scrollScrollBarTo = function(/*Object*/to){ // to: {x, y} |
---|
1014 | if(!this.scrollBar){ return; } |
---|
1015 | if(this._v && this._scrollBarV && typeof to.y == "number"){ |
---|
1016 | if(has("webkit")){ |
---|
1017 | this._scrollBarV.style.webkitTransform = this.makeTranslateStr({y:to.y}); |
---|
1018 | }else{ |
---|
1019 | this._scrollBarV.style.top = to.y + "px"; |
---|
1020 | } |
---|
1021 | } |
---|
1022 | if(this._h && this._scrollBarH && typeof to.x == "number"){ |
---|
1023 | if(has("webkit")){ |
---|
1024 | this._scrollBarH.style.webkitTransform = this.makeTranslateStr({x:to.x}); |
---|
1025 | }else{ |
---|
1026 | this._scrollBarH.style.left = to.x + "px"; |
---|
1027 | } |
---|
1028 | } |
---|
1029 | }; |
---|
1030 | |
---|
1031 | this.slideScrollBarTo = function(/*Object*/to, /*Number*/duration, /*String*/easing){ |
---|
1032 | if(!this.scrollBar){ return; } |
---|
1033 | var fromPos = this.calcScrollBarPos(this.getPos()); |
---|
1034 | var toPos = this.calcScrollBarPos(to); |
---|
1035 | if(this._v && this._scrollBarV){ |
---|
1036 | this._runSlideAnimation({y:fromPos.y}, {y:toPos.y}, duration, easing, this._scrollBarV, 0); |
---|
1037 | } |
---|
1038 | if(this._h && this._scrollBarH){ |
---|
1039 | this._runSlideAnimation({x:fromPos.x}, {x:toPos.x}, duration, easing, this._scrollBarH, 1); |
---|
1040 | } |
---|
1041 | }; |
---|
1042 | |
---|
1043 | this._runSlideAnimation = function(/*Object*/from, /*Object*/to, /*Number*/duration, /*String*/easing, node, idx){ |
---|
1044 | // idx: 0:scrollbarV, 1:scrollbarH, 2:content |
---|
1045 | if(has("webkit")){ |
---|
1046 | this.setKeyframes(from, to, idx); |
---|
1047 | domStyle.set(node, { |
---|
1048 | webkitAnimationDuration: duration + "s", |
---|
1049 | webkitAnimationTimingFunction: easing |
---|
1050 | }); |
---|
1051 | domClass.add(node, "mblScrollableScrollTo"+idx); |
---|
1052 | if(idx == 2){ |
---|
1053 | this.scrollTo(to, true, node); |
---|
1054 | }else{ |
---|
1055 | this.scrollScrollBarTo(to); |
---|
1056 | } |
---|
1057 | //>>excludeStart("webkitMobile", kwArgs.webkitMobile); |
---|
1058 | }else if(dojo.fx && dojo.fx.easing && duration){ |
---|
1059 | // If you want to support non-webkit browsers, |
---|
1060 | // your application needs to load necessary modules as follows: |
---|
1061 | // |
---|
1062 | // | dojo.require("dojo.fx"); |
---|
1063 | // | dojo.require("dojo.fx.easing"); |
---|
1064 | // |
---|
1065 | // This module itself does not make dependency on them. |
---|
1066 | var s = dojo.fx.slideTo({ |
---|
1067 | node: node, |
---|
1068 | duration: duration*1000, |
---|
1069 | left: to.x, |
---|
1070 | top: to.y, |
---|
1071 | easing: (easing == "ease-out") ? dojo.fx.easing.quadOut : dojo.fx.easing.linear |
---|
1072 | }).play(); |
---|
1073 | if(idx == 2){ |
---|
1074 | connect.connect(s, "onEnd", this, "onFlickAnimationEnd"); |
---|
1075 | } |
---|
1076 | }else{ |
---|
1077 | // directly jump to the destination without animation |
---|
1078 | if(idx == 2){ |
---|
1079 | this.scrollTo(to, false, node); |
---|
1080 | this.onFlickAnimationEnd(); |
---|
1081 | }else{ |
---|
1082 | this.scrollScrollBarTo(to); |
---|
1083 | } |
---|
1084 | //>>excludeEnd("webkitMobile"); |
---|
1085 | } |
---|
1086 | }; |
---|
1087 | |
---|
1088 | this.resetScrollBar = function(){ |
---|
1089 | // summary: |
---|
1090 | // Resets the scroll bar length, position, etc. |
---|
1091 | var f = function(wrapper, bar, d, c, hd, v){ |
---|
1092 | if(!bar){ return; } |
---|
1093 | var props = {}; |
---|
1094 | props[v ? "top" : "left"] = hd + 4 + "px"; // +4 is for top or left margin |
---|
1095 | var t = (d - 8) <= 0 ? 1 : d - 8; |
---|
1096 | props[v ? "height" : "width"] = t + "px"; |
---|
1097 | domStyle.set(wrapper, props); |
---|
1098 | var l = Math.round(d * d / c); // scroll bar length |
---|
1099 | l = Math.min(Math.max(l - 8, 5), t); // -8 is for margin for both ends |
---|
1100 | bar.style[v ? "height" : "width"] = l + "px"; |
---|
1101 | domStyle.set(bar, {"opacity": 0.6}); |
---|
1102 | }; |
---|
1103 | var dim = this.getDim(); |
---|
1104 | f(this._scrollBarWrapperV, this._scrollBarV, dim.d.h, dim.c.h, this.fixedHeaderHeight, true); |
---|
1105 | f(this._scrollBarWrapperH, this._scrollBarH, dim.d.w, dim.c.w, 0); |
---|
1106 | this.createMask(); |
---|
1107 | }; |
---|
1108 | |
---|
1109 | this.createMask = function(){ |
---|
1110 | // summary: |
---|
1111 | // Creates a mask for a scroll bar edge. |
---|
1112 | // description: |
---|
1113 | // This function creates a mask that hides corners of one scroll |
---|
1114 | // bar edge to make it round edge. The other side of the edge is |
---|
1115 | // always visible and round shaped with the border-radius style. |
---|
1116 | if(!has("webkit")){ return; } |
---|
1117 | var ctx; |
---|
1118 | if(this._scrollBarWrapperV){ |
---|
1119 | var h = this._scrollBarWrapperV.offsetHeight; |
---|
1120 | ctx = win.doc.getCSSCanvasContext("2d", "scrollBarMaskV", 5, h); |
---|
1121 | ctx.fillStyle = "rgba(0,0,0,0.5)"; |
---|
1122 | ctx.fillRect(1, 0, 3, 2); |
---|
1123 | ctx.fillRect(0, 1, 5, 1); |
---|
1124 | ctx.fillRect(0, h - 2, 5, 1); |
---|
1125 | ctx.fillRect(1, h - 1, 3, 2); |
---|
1126 | ctx.fillStyle = "rgb(0,0,0)"; |
---|
1127 | ctx.fillRect(0, 2, 5, h - 4); |
---|
1128 | this._scrollBarWrapperV.style.webkitMaskImage = "-webkit-canvas(scrollBarMaskV)"; |
---|
1129 | } |
---|
1130 | if(this._scrollBarWrapperH){ |
---|
1131 | var w = this._scrollBarWrapperH.offsetWidth; |
---|
1132 | ctx = win.doc.getCSSCanvasContext("2d", "scrollBarMaskH", w, 5); |
---|
1133 | ctx.fillStyle = "rgba(0,0,0,0.5)"; |
---|
1134 | ctx.fillRect(0, 1, 2, 3); |
---|
1135 | ctx.fillRect(1, 0, 1, 5); |
---|
1136 | ctx.fillRect(w - 2, 0, 1, 5); |
---|
1137 | ctx.fillRect(w - 1, 1, 2, 3); |
---|
1138 | ctx.fillStyle = "rgb(0,0,0)"; |
---|
1139 | ctx.fillRect(2, 0, w - 4, 5); |
---|
1140 | this._scrollBarWrapperH.style.webkitMaskImage = "-webkit-canvas(scrollBarMaskH)"; |
---|
1141 | } |
---|
1142 | }; |
---|
1143 | |
---|
1144 | this.flashScrollBar = function(){ |
---|
1145 | if(this.disableFlashScrollBar || !this.domNode){ return; } |
---|
1146 | this._dim = this.getDim(); |
---|
1147 | if(this._dim.d.h <= 0){ return; } // dom is not ready |
---|
1148 | this.showScrollBar(); |
---|
1149 | var _this = this; |
---|
1150 | setTimeout(function(){ |
---|
1151 | _this.hideScrollBar(); |
---|
1152 | }, 300); |
---|
1153 | }; |
---|
1154 | |
---|
1155 | this.addCover = function(){ |
---|
1156 | //>>excludeStart("webkitMobile", kwArgs.webkitMobile); |
---|
1157 | if(!has('touch') && !this.noCover){ |
---|
1158 | if(!this._cover){ |
---|
1159 | this._cover = domConstruct.create("div", null, win.doc.body); |
---|
1160 | domStyle.set(this._cover, { |
---|
1161 | backgroundColor: "#ffff00", |
---|
1162 | opacity: 0, |
---|
1163 | position: "absolute", |
---|
1164 | top: "0px", |
---|
1165 | left: "0px", |
---|
1166 | width: "100%", |
---|
1167 | height: "100%", |
---|
1168 | zIndex: 2147483647 // max of signed 32-bit integer |
---|
1169 | }); |
---|
1170 | this._ch.push(connect.connect(this._cover, |
---|
1171 | has('touch') ? "touchstart" : "onmousedown", this, "onTouchEnd")); |
---|
1172 | }else{ |
---|
1173 | this._cover.style.display = ""; |
---|
1174 | } |
---|
1175 | this.setSelectable(this._cover, false); |
---|
1176 | this.setSelectable(this.domNode, false); |
---|
1177 | } |
---|
1178 | //>>excludeEnd("webkitMobile"); |
---|
1179 | }; |
---|
1180 | |
---|
1181 | this.removeCover = function(){ |
---|
1182 | //>>excludeStart("webkitMobile", kwArgs.webkitMobile); |
---|
1183 | if(!has('touch') && this._cover){ |
---|
1184 | this._cover.style.display = "none"; |
---|
1185 | this.setSelectable(this._cover, true); |
---|
1186 | this.setSelectable(this.domNode, true); |
---|
1187 | } |
---|
1188 | //>>excludeEnd("webkitMobile"); |
---|
1189 | }; |
---|
1190 | |
---|
1191 | this.setKeyframes = function(/*Object*/from, /*Object*/to, /*Number*/idx){ |
---|
1192 | if(!dm._rule){ |
---|
1193 | dm._rule = []; |
---|
1194 | } |
---|
1195 | // idx: 0:scrollbarV, 1:scrollbarH, 2:content |
---|
1196 | if(!dm._rule[idx]){ |
---|
1197 | var node = domConstruct.create("style", null, win.doc.getElementsByTagName("head")[0]); |
---|
1198 | node.textContent = |
---|
1199 | ".mblScrollableScrollTo"+idx+"{-webkit-animation-name: scrollableViewScroll"+idx+";}"+ |
---|
1200 | "@-webkit-keyframes scrollableViewScroll"+idx+"{}"; |
---|
1201 | dm._rule[idx] = node.sheet.cssRules[1]; |
---|
1202 | } |
---|
1203 | var rule = dm._rule[idx]; |
---|
1204 | if(rule){ |
---|
1205 | if(from){ |
---|
1206 | rule.deleteRule("from"); |
---|
1207 | rule.insertRule("from { -webkit-transform: "+this.makeTranslateStr(from)+"; }"); |
---|
1208 | } |
---|
1209 | if(to){ |
---|
1210 | if(to.x === undefined){ to.x = from.x; } |
---|
1211 | if(to.y === undefined){ to.y = from.y; } |
---|
1212 | rule.deleteRule("to"); |
---|
1213 | rule.insertRule("to { -webkit-transform: "+this.makeTranslateStr(to)+"; }"); |
---|
1214 | } |
---|
1215 | } |
---|
1216 | }; |
---|
1217 | |
---|
1218 | this.setSelectable = function(node, selectable){ |
---|
1219 | // dojo.setSelectable has dependency on dojo.query. Re-define our own. |
---|
1220 | node.style.KhtmlUserSelect = selectable ? "auto" : "none"; |
---|
1221 | node.style.MozUserSelect = selectable ? "" : "none"; |
---|
1222 | node.onselectstart = selectable ? null : function(){return false;}; |
---|
1223 | if(has("ie")){ |
---|
1224 | node.unselectable = selectable ? "" : "on"; |
---|
1225 | var nodes = node.getElementsByTagName("*"); |
---|
1226 | for(var i = 0; i < nodes.length; i++){ |
---|
1227 | nodes[i].unselectable = selectable ? "" : "on"; |
---|
1228 | } |
---|
1229 | } |
---|
1230 | }; |
---|
1231 | |
---|
1232 | // feature detection |
---|
1233 | if(has("webkit")){ |
---|
1234 | var elem = win.doc.createElement("div"); |
---|
1235 | elem.style.webkitTransform = "translate3d(0px,1px,0px)"; |
---|
1236 | win.doc.documentElement.appendChild(elem); |
---|
1237 | var v = win.doc.defaultView.getComputedStyle(elem, '')["-webkit-transform"]; |
---|
1238 | dm.hasTranslate3d = v && v.indexOf("matrix") === 0; |
---|
1239 | win.doc.documentElement.removeChild(elem); |
---|
1240 | } |
---|
1241 | }; |
---|
1242 | |
---|
1243 | //>>includeStart("standaloneScrollable", kwArgs.standaloneScrollable); |
---|
1244 | if(!dm){ dm = dojox.mobile; } |
---|
1245 | //>>includeEnd("standaloneScrollable"); |
---|
1246 | dm.scrollable = scrollable; // for backward compatibility |
---|
1247 | return scrollable; |
---|
1248 | }); |
---|