1 | define([ |
---|
2 | "dojo/_base/kernel", // to test dojo.hash |
---|
3 | "dojo/_base/array", |
---|
4 | "dojo/_base/config", |
---|
5 | "dojo/_base/connect", |
---|
6 | "dojo/_base/lang", |
---|
7 | "dojo/_base/window", |
---|
8 | "dojo/dom-class", |
---|
9 | "dojo/dom-construct", |
---|
10 | "dojo/dom-style", |
---|
11 | // "dojo/hash", // optionally prereq'ed |
---|
12 | "dojo/ready", |
---|
13 | "dijit/registry", // registry.toArray |
---|
14 | "./sniff", |
---|
15 | "./uacss" |
---|
16 | ], function(dojo, array, config, connect, lang, win, domClass, domConstruct, domStyle, ready, registry, has, uacss){ |
---|
17 | |
---|
18 | var dm = lang.getObject("dojox.mobile", true); |
---|
19 | /*===== |
---|
20 | var dm = dojox.mobile; |
---|
21 | =====*/ |
---|
22 | |
---|
23 | // module: |
---|
24 | // dojox/mobile/common |
---|
25 | // summary: |
---|
26 | // A common module for dojox.mobile. |
---|
27 | // description: |
---|
28 | // This module includes common utility functions that are used by |
---|
29 | // dojox.mobile widgets. Also, it provides functions that are commonly |
---|
30 | // necessary for mobile web applications, such as the hide address bar |
---|
31 | // function. |
---|
32 | |
---|
33 | dm.getScreenSize = function(){ |
---|
34 | // summary: |
---|
35 | // Returns the dimensions of the browser window. |
---|
36 | return { |
---|
37 | h: win.global.innerHeight || win.doc.documentElement.clientHeight, |
---|
38 | w: win.global.innerWidth || win.doc.documentElement.clientWidth |
---|
39 | }; |
---|
40 | }; |
---|
41 | |
---|
42 | dm.updateOrient = function(){ |
---|
43 | // summary: |
---|
44 | // Updates the orientation specific css classes, 'dj_portrait' and |
---|
45 | // 'dj_landscape'. |
---|
46 | var dim = dm.getScreenSize(); |
---|
47 | domClass.replace(win.doc.documentElement, |
---|
48 | dim.h > dim.w ? "dj_portrait" : "dj_landscape", |
---|
49 | dim.h > dim.w ? "dj_landscape" : "dj_portrait"); |
---|
50 | }; |
---|
51 | dm.updateOrient(); |
---|
52 | |
---|
53 | dm.tabletSize = 500; |
---|
54 | dm.detectScreenSize = function(/*Boolean?*/force){ |
---|
55 | // summary: |
---|
56 | // Detects the screen size and determines if the screen is like |
---|
57 | // phone or like tablet. If the result is changed, |
---|
58 | // it sets either of the following css class to <html> |
---|
59 | // - 'dj_phone' |
---|
60 | // - 'dj_tablet' |
---|
61 | // and it publishes either of the following events. |
---|
62 | // - '/dojox/mobile/screenSize/phone' |
---|
63 | // - '/dojox/mobile/screenSize/tablet' |
---|
64 | var dim = dm.getScreenSize(); |
---|
65 | var sz = Math.min(dim.w, dim.h); |
---|
66 | var from, to; |
---|
67 | if(sz >= dm.tabletSize && (force || (!this._sz || this._sz < dm.tabletSize))){ |
---|
68 | from = "phone"; |
---|
69 | to = "tablet"; |
---|
70 | }else if(sz < dm.tabletSize && (force || (!this._sz || this._sz >= dm.tabletSize))){ |
---|
71 | from = "tablet"; |
---|
72 | to = "phone"; |
---|
73 | } |
---|
74 | if(to){ |
---|
75 | domClass.replace(win.doc.documentElement, "dj_"+to, "dj_"+from); |
---|
76 | connect.publish("/dojox/mobile/screenSize/"+to, [dim]); |
---|
77 | } |
---|
78 | this._sz = sz; |
---|
79 | }; |
---|
80 | dm.detectScreenSize(); |
---|
81 | |
---|
82 | dm.setupIcon = function(/*DomNode*/iconNode, /*String*/iconPos){ |
---|
83 | // summary: |
---|
84 | // Sets up CSS sprite for a foreground image. |
---|
85 | if(iconNode && iconPos){ |
---|
86 | var arr = array.map(iconPos.split(/[ ,]/),function(item){return item-0}); |
---|
87 | var t = arr[0]; // top |
---|
88 | var r = arr[1] + arr[2]; // right |
---|
89 | var b = arr[0] + arr[3]; // bottom |
---|
90 | var l = arr[1]; // left |
---|
91 | domStyle.set(iconNode, { |
---|
92 | clip: "rect("+t+"px "+r+"px "+b+"px "+l+"px)", |
---|
93 | top: (iconNode.parentNode ? domStyle.get(iconNode, "top") : 0) - t + "px", |
---|
94 | left: -l + "px" |
---|
95 | }); |
---|
96 | } |
---|
97 | }; |
---|
98 | |
---|
99 | // dojox.mobile.hideAddressBarWait: Number |
---|
100 | // The time in milliseconds to wait before the fail-safe hiding address |
---|
101 | // bar runs. The value must be larger than 800. |
---|
102 | dm.hideAddressBarWait = typeof(config["mblHideAddressBarWait"]) === "number" ? |
---|
103 | config["mblHideAddressBarWait"] : 1500; |
---|
104 | |
---|
105 | dm.hide_1 = function(force){ |
---|
106 | // summary: |
---|
107 | // Internal function to hide the address bar. |
---|
108 | scrollTo(0, 1); |
---|
109 | var h = dm.getScreenSize().h + "px"; |
---|
110 | if(has('android')){ |
---|
111 | if(force){ |
---|
112 | win.body().style.minHeight = h; |
---|
113 | } |
---|
114 | dm.resizeAll(); |
---|
115 | }else{ |
---|
116 | if(force || dm._h === h && h !== win.body().style.minHeight){ |
---|
117 | win.body().style.minHeight = h; |
---|
118 | dm.resizeAll(); |
---|
119 | } |
---|
120 | } |
---|
121 | dm._h = h; |
---|
122 | }; |
---|
123 | |
---|
124 | dm.hide_fs = function(){ |
---|
125 | // summary: |
---|
126 | // Internal function to hide the address bar for fail-safe. |
---|
127 | // description: |
---|
128 | // Resets the height of the body, performs hiding the address |
---|
129 | // bar, and calls resizeAll(). |
---|
130 | // This is for fail-safe, in case of failure to complete the |
---|
131 | // address bar hiding in time. |
---|
132 | var t = win.body().style.minHeight; |
---|
133 | win.body().style.minHeight = (dm.getScreenSize().h * 2) + "px"; // to ensure enough height for scrollTo to work |
---|
134 | scrollTo(0, 1); |
---|
135 | setTimeout(function(){ |
---|
136 | dm.hide_1(1); |
---|
137 | dm._hiding = false; |
---|
138 | }, 1000); |
---|
139 | }; |
---|
140 | dm.hideAddressBar = function(/*Event?*/evt){ |
---|
141 | // summary: |
---|
142 | // Hides the address bar. |
---|
143 | // description: |
---|
144 | // Tries hiding of the address bar a couple of times to do it as |
---|
145 | // quick as possible while ensuring resize is done after the hiding |
---|
146 | // finishes. |
---|
147 | if(dm.disableHideAddressBar || dm._hiding){ return; } |
---|
148 | dm._hiding = true; |
---|
149 | dm._h = 0; |
---|
150 | win.body().style.minHeight = (dm.getScreenSize().h * 2) + "px"; // to ensure enough height for scrollTo to work |
---|
151 | setTimeout(dm.hide_1, 0); |
---|
152 | setTimeout(dm.hide_1, 200); |
---|
153 | setTimeout(dm.hide_1, 800); |
---|
154 | setTimeout(dm.hide_fs, dm.hideAddressBarWait); |
---|
155 | }; |
---|
156 | |
---|
157 | dm.resizeAll = function(/*Event?*/evt, /*Widget?*/root){ |
---|
158 | // summary: |
---|
159 | // Call the resize() method of all the top level resizable widgets. |
---|
160 | // description: |
---|
161 | // Find all widgets that do not have a parent or the parent does not |
---|
162 | // have the resize() method, and call resize() for them. |
---|
163 | // If a widget has a parent that has resize(), call of the widget's |
---|
164 | // resize() is its parent's responsibility. |
---|
165 | // evt: |
---|
166 | // Native event object |
---|
167 | // root: |
---|
168 | // If specified, search the specified widget recursively for top level |
---|
169 | // resizable widgets. |
---|
170 | // root.resize() is always called regardless of whether root is a |
---|
171 | // top level widget or not. |
---|
172 | // If omitted, search the entire page. |
---|
173 | if(dm.disableResizeAll){ return; } |
---|
174 | connect.publish("/dojox/mobile/resizeAll", [evt, root]); |
---|
175 | dm.updateOrient(); |
---|
176 | dm.detectScreenSize(); |
---|
177 | var isTopLevel = function(w){ |
---|
178 | var parent = w.getParent && w.getParent(); |
---|
179 | return !!((!parent || !parent.resize) && w.resize); |
---|
180 | }; |
---|
181 | var resizeRecursively = function(w){ |
---|
182 | array.forEach(w.getChildren(), function(child){ |
---|
183 | if(isTopLevel(child)){ child.resize(); } |
---|
184 | resizeRecursively(child); |
---|
185 | }); |
---|
186 | }; |
---|
187 | if(root){ |
---|
188 | if(root.resize){ root.resize(); } |
---|
189 | resizeRecursively(root); |
---|
190 | }else{ |
---|
191 | array.forEach(array.filter(registry.toArray(), isTopLevel), |
---|
192 | function(w){ w.resize(); }); |
---|
193 | } |
---|
194 | }; |
---|
195 | |
---|
196 | dm.openWindow = function(url, target){ |
---|
197 | // summary: |
---|
198 | // Opens a new browser window with the given url. |
---|
199 | win.global.open(url, target || "_blank"); |
---|
200 | }; |
---|
201 | |
---|
202 | dm.createDomButton = function(/*DomNode*/refNode, /*Object?*/style, /*DomNode?*/toNode){ |
---|
203 | // summary: |
---|
204 | // Creates a DOM button. |
---|
205 | // description: |
---|
206 | // DOM button is a simple graphical object that consists of one or |
---|
207 | // more nested DIV elements with some CSS styling. It can be used |
---|
208 | // in place of an icon image on ListItem, IconItem, and so on. |
---|
209 | // The kind of DOM button to create is given as a class name of |
---|
210 | // refNode. The number of DIVs to create is searched from the style |
---|
211 | // sheets in the page. However, if the class name has a suffix that |
---|
212 | // starts with an underscore, like mblDomButtonGoldStar_5, then the |
---|
213 | // suffixed number is used instead. A class name for DOM button |
---|
214 | // must starts with 'mblDomButton'. |
---|
215 | // refNode: |
---|
216 | // A node that has a DOM button class name. |
---|
217 | // style: |
---|
218 | // A hash object to set styles to the node. |
---|
219 | // toNode: |
---|
220 | // A root node to create a DOM button. If omitted, refNode is used. |
---|
221 | |
---|
222 | if(!dm._domButtons){ |
---|
223 | if(has("webkit")){ |
---|
224 | var findDomButtons = function(sheet, dic){ |
---|
225 | // summary: |
---|
226 | // Searches the style sheets for DOM buttons. |
---|
227 | // description: |
---|
228 | // Returns a key-value pair object whose keys are DOM |
---|
229 | // button class names and values are the number of DOM |
---|
230 | // elements they need. |
---|
231 | var i, j; |
---|
232 | if(!sheet){ |
---|
233 | var dic = {}; |
---|
234 | var ss = dojo.doc.styleSheets; |
---|
235 | for (i = 0; i < ss.length; i++){ |
---|
236 | ss[i] && findDomButtons(ss[i], dic); |
---|
237 | } |
---|
238 | return dic; |
---|
239 | } |
---|
240 | var rules = sheet.cssRules || []; |
---|
241 | for (i = 0; i < rules.length; i++){ |
---|
242 | var rule = rules[i]; |
---|
243 | if(rule.href && rule.styleSheet){ |
---|
244 | findDomButtons(rule.styleSheet, dic); |
---|
245 | }else if(rule.selectorText){ |
---|
246 | var sels = rule.selectorText.split(/,/); |
---|
247 | for (j = 0; j < sels.length; j++){ |
---|
248 | var sel = sels[j]; |
---|
249 | var n = sel.split(/>/).length - 1; |
---|
250 | if(sel.match(/(mblDomButton\w+)/)){ |
---|
251 | var cls = RegExp.$1; |
---|
252 | if(!dic[cls] || n > dic[cls]){ |
---|
253 | dic[cls] = n; |
---|
254 | } |
---|
255 | } |
---|
256 | } |
---|
257 | } |
---|
258 | } |
---|
259 | } |
---|
260 | dm._domButtons = findDomButtons(); |
---|
261 | }else{ |
---|
262 | dm._domButtons = {}; |
---|
263 | } |
---|
264 | } |
---|
265 | |
---|
266 | var s = refNode.className; |
---|
267 | var node = toNode || refNode; |
---|
268 | if(s.match(/(mblDomButton\w+)/) && s.indexOf("/") === -1){ |
---|
269 | var btnClass = RegExp.$1; |
---|
270 | var nDiv = 4; |
---|
271 | if(s.match(/(mblDomButton\w+_(\d+))/)){ |
---|
272 | nDiv = RegExp.$2 - 0; |
---|
273 | }else if(dm._domButtons[btnClass] !== undefined){ |
---|
274 | nDiv = dm._domButtons[btnClass]; |
---|
275 | } |
---|
276 | var props = null; |
---|
277 | if(has("bb") && config["mblBBBoxShadowWorkaround"] !== false){ |
---|
278 | // Removes box-shadow because BlackBerry incorrectly renders it. |
---|
279 | props = {style:"-webkit-box-shadow:none"}; |
---|
280 | } |
---|
281 | for(var i = 0, p = node; i < nDiv; i++){ |
---|
282 | p = p.firstChild || domConstruct.create("DIV", props, p); |
---|
283 | } |
---|
284 | if(toNode){ |
---|
285 | setTimeout(function(){ |
---|
286 | domClass.remove(refNode, btnClass); |
---|
287 | }, 0); |
---|
288 | domClass.add(toNode, btnClass); |
---|
289 | } |
---|
290 | }else if(s.indexOf(".") !== -1){ // file name |
---|
291 | domConstruct.create("IMG", {src:s}, node); |
---|
292 | }else{ |
---|
293 | return null; |
---|
294 | } |
---|
295 | domClass.add(node, "mblDomButton"); |
---|
296 | if(config["mblAndroidWorkaround"] !== false && has('android') >= 2.2){ |
---|
297 | // Android workaround for the issue that domButtons' -webkit-transform styles sometimes invalidated |
---|
298 | // by applying -webkit-transform:translated3d(x,y,z) style programmatically to non-ancestor elements, |
---|
299 | // which results in breaking domButtons. |
---|
300 | domStyle.set(node, "webkitTransform", "translate3d(0,0,0)"); |
---|
301 | } |
---|
302 | !!style && domStyle.set(node, style); |
---|
303 | return node; |
---|
304 | }; |
---|
305 | |
---|
306 | dm.createIcon = function(/*String*/icon, /*String*/iconPos, /*DomNode*/node, /*String?*/title, /*DomNode?*/parent){ |
---|
307 | // summary: |
---|
308 | // Creates or updates an icon node |
---|
309 | // description: |
---|
310 | // If node exists, updates the existing node. Otherwise, creates a new one. |
---|
311 | // icon: |
---|
312 | // Path for an image, or DOM button class name. |
---|
313 | if(icon && icon.indexOf("mblDomButton") === 0){ |
---|
314 | // DOM button |
---|
315 | if(node && node.className.match(/(mblDomButton\w+)/)){ |
---|
316 | domClass.remove(node, RegExp.$1); |
---|
317 | }else{ |
---|
318 | node = domConstruct.create("DIV"); |
---|
319 | } |
---|
320 | node.title = title; |
---|
321 | domClass.add(node, icon); |
---|
322 | dm.createDomButton(node); |
---|
323 | }else if(icon && icon !== "none"){ |
---|
324 | // Image |
---|
325 | if(!node || node.nodeName !== "IMG"){ |
---|
326 | node = domConstruct.create("IMG", { |
---|
327 | alt: title |
---|
328 | }); |
---|
329 | } |
---|
330 | node.src = (icon || "").replace("${theme}", dm.currentTheme); |
---|
331 | dm.setupIcon(node, iconPos); |
---|
332 | if(parent && iconPos){ |
---|
333 | var arr = iconPos.split(/[ ,]/); |
---|
334 | domStyle.set(parent, { |
---|
335 | width: arr[2] + "px", |
---|
336 | height: arr[3] + "px" |
---|
337 | }); |
---|
338 | } |
---|
339 | } |
---|
340 | if(parent){ |
---|
341 | parent.appendChild(node); |
---|
342 | } |
---|
343 | return node; |
---|
344 | }; |
---|
345 | |
---|
346 | // flag for iphone flicker workaround |
---|
347 | dm._iw = config["mblIosWorkaround"] !== false && has('iphone'); |
---|
348 | if(dm._iw){ |
---|
349 | dm._iwBgCover = domConstruct.create("div"); // Cover to hide flicker in the background |
---|
350 | } |
---|
351 | |
---|
352 | if(config.parseOnLoad){ |
---|
353 | ready(90, function(){ |
---|
354 | // avoid use of query |
---|
355 | /* |
---|
356 | var list = query('[lazy=true] [dojoType]', null); |
---|
357 | list.forEach(function(node, index, nodeList){ |
---|
358 | node.setAttribute("__dojoType", node.getAttribute("dojoType")); |
---|
359 | node.removeAttribute("dojoType"); |
---|
360 | }); |
---|
361 | */ |
---|
362 | |
---|
363 | var nodes = win.body().getElementsByTagName("*"); |
---|
364 | var i, len, s; |
---|
365 | len = nodes.length; |
---|
366 | for(i = 0; i < len; i++){ |
---|
367 | s = nodes[i].getAttribute("dojoType"); |
---|
368 | if(s){ |
---|
369 | if(nodes[i].parentNode.getAttribute("lazy") == "true"){ |
---|
370 | nodes[i].setAttribute("__dojoType", s); |
---|
371 | nodes[i].removeAttribute("dojoType"); |
---|
372 | } |
---|
373 | } |
---|
374 | } |
---|
375 | }); |
---|
376 | } |
---|
377 | |
---|
378 | ready(function(){ |
---|
379 | dm.detectScreenSize(true); |
---|
380 | if(config["mblApplyPageStyles"] !== false){ |
---|
381 | domClass.add(win.doc.documentElement, "mobile"); |
---|
382 | } |
---|
383 | if(has('chrome')){ |
---|
384 | // dojox.mobile does not load uacss (only _compat does), but we need dj_chrome. |
---|
385 | domClass.add(win.doc.documentElement, "dj_chrome"); |
---|
386 | } |
---|
387 | |
---|
388 | if(config["mblAndroidWorkaround"] !== false && has('android') >= 2.2){ // workaround for android screen flicker problem |
---|
389 | if(config["mblAndroidWorkaroundButtonStyle"] !== false){ |
---|
390 | // workaround to avoid buttons disappear due to the side-effect of the webkitTransform workaroud below |
---|
391 | domConstruct.create("style", {innerHTML:"BUTTON,INPUT[type='button'],INPUT[type='submit'],INPUT[type='reset'],INPUT[type='file']::-webkit-file-upload-button{-webkit-appearance:none;}"}, win.doc.head, "first"); |
---|
392 | } |
---|
393 | if(has('android') < 3){ // for Android 2.2.x and 2.3.x |
---|
394 | domStyle.set(win.doc.documentElement, "webkitTransform", "translate3d(0,0,0)"); |
---|
395 | // workaround for auto-scroll issue when focusing input fields |
---|
396 | connect.connect(null, "onfocus", null, function(e){ |
---|
397 | domStyle.set(win.doc.documentElement, "webkitTransform", ""); |
---|
398 | }); |
---|
399 | connect.connect(null, "onblur", null, function(e){ |
---|
400 | domStyle.set(win.doc.documentElement, "webkitTransform", "translate3d(0,0,0)"); |
---|
401 | }); |
---|
402 | }else{ // for Android 3.x |
---|
403 | if(config["mblAndroid3Workaround"] !== false){ |
---|
404 | domStyle.set(win.doc.documentElement, { |
---|
405 | webkitBackfaceVisibility: "hidden", |
---|
406 | webkitPerspective: 8000 |
---|
407 | }); |
---|
408 | } |
---|
409 | } |
---|
410 | } |
---|
411 | |
---|
412 | // You can disable hiding the address bar with the following djConfig. |
---|
413 | // var djConfig = { mblHideAddressBar: false }; |
---|
414 | var f = dm.resizeAll; |
---|
415 | if(config["mblHideAddressBar"] !== false && |
---|
416 | navigator.appVersion.indexOf("Mobile") != -1 || |
---|
417 | config["mblForceHideAddressBar"] === true){ |
---|
418 | dm.hideAddressBar(); |
---|
419 | if(config["mblAlwaysHideAddressBar"] === true){ |
---|
420 | f = dm.hideAddressBar; |
---|
421 | } |
---|
422 | } |
---|
423 | connect.connect(null, (win.global.onorientationchange !== undefined && !has('android')) |
---|
424 | ? "onorientationchange" : "onresize", null, f); |
---|
425 | |
---|
426 | // avoid use of query |
---|
427 | /* |
---|
428 | var list = query('[__dojoType]', null); |
---|
429 | list.forEach(function(node, index, nodeList){ |
---|
430 | node.setAttribute("dojoType", node.getAttribute("__dojoType")); |
---|
431 | node.removeAttribute("__dojoType"); |
---|
432 | }); |
---|
433 | */ |
---|
434 | |
---|
435 | var nodes = win.body().getElementsByTagName("*"); |
---|
436 | var i, len = nodes.length, s; |
---|
437 | for(i = 0; i < len; i++){ |
---|
438 | s = nodes[i].getAttribute("__dojoType"); |
---|
439 | if(s){ |
---|
440 | nodes[i].setAttribute("dojoType", s); |
---|
441 | nodes[i].removeAttribute("__dojoType"); |
---|
442 | } |
---|
443 | } |
---|
444 | |
---|
445 | if(dojo.hash){ |
---|
446 | // find widgets under root recursively |
---|
447 | var findWidgets = function(root){ |
---|
448 | if(!root){ return []; } |
---|
449 | var arr = registry.findWidgets(root); |
---|
450 | var widgets = arr; |
---|
451 | for(var i = 0; i < widgets.length; i++){ |
---|
452 | arr = arr.concat(findWidgets(widgets[i].containerNode)); |
---|
453 | } |
---|
454 | return arr; |
---|
455 | }; |
---|
456 | connect.subscribe("/dojo/hashchange", null, function(value){ |
---|
457 | var view = dm.currentView; |
---|
458 | if(!view){ return; } |
---|
459 | var params = dm._params; |
---|
460 | if(!params){ // browser back/forward button was pressed |
---|
461 | var moveTo = value ? value : dm._defaultView.id; |
---|
462 | var widgets = findWidgets(view.domNode); |
---|
463 | var dir = 1, transition = "slide"; |
---|
464 | for(i = 0; i < widgets.length; i++){ |
---|
465 | var w = widgets[i]; |
---|
466 | if("#"+moveTo == w.moveTo){ |
---|
467 | // found a widget that has the given moveTo |
---|
468 | transition = w.transition; |
---|
469 | dir = (w instanceof dm.Heading) ? -1 : 1; |
---|
470 | break; |
---|
471 | } |
---|
472 | } |
---|
473 | params = [ moveTo, dir, transition ]; |
---|
474 | } |
---|
475 | view.performTransition.apply(view, params); |
---|
476 | dm._params = null; |
---|
477 | }); |
---|
478 | } |
---|
479 | |
---|
480 | win.body().style.visibility = "visible"; |
---|
481 | }); |
---|
482 | |
---|
483 | // To search _parentNode first. TODO:1.8 reconsider this redefinition. |
---|
484 | registry.getEnclosingWidget = function(node){ |
---|
485 | while(node){ |
---|
486 | var id = node.getAttribute && node.getAttribute("widgetId"); |
---|
487 | if(id){ |
---|
488 | return registry.byId(id); |
---|
489 | } |
---|
490 | node = node._parentNode || node.parentNode; |
---|
491 | } |
---|
492 | return null; |
---|
493 | }; |
---|
494 | |
---|
495 | return dm; |
---|
496 | }); |
---|