1 | define([ |
---|
2 | "dojo/_base/array", |
---|
3 | "dojo/_base/config", |
---|
4 | "dojo/_base/connect", |
---|
5 | "dojo/_base/lang", |
---|
6 | "dojo/_base/window", |
---|
7 | "dojo/_base/kernel", |
---|
8 | "dojo/dom-class", |
---|
9 | "dojo/dom-construct", |
---|
10 | "dojo/domReady", |
---|
11 | "dojo/ready", |
---|
12 | "dojo/touch", |
---|
13 | "dijit/registry", |
---|
14 | "./sniff", |
---|
15 | "./uacss" // (no direct references) |
---|
16 | ], function(array, config, connect, lang, win, kernel, domClass, domConstruct, domReady, ready, touch, registry, has){ |
---|
17 | |
---|
18 | // module: |
---|
19 | // dojox/mobile/common |
---|
20 | |
---|
21 | var dm = lang.getObject("dojox.mobile", true); |
---|
22 | |
---|
23 | // tell dojo/touch to generate synthetic clicks immediately |
---|
24 | // and regardless of preventDefault() calls on touch events |
---|
25 | win.doc.dojoClick = true; |
---|
26 | /// ... but let user disable this by removing dojoClick from the document |
---|
27 | if(has("touch")){ |
---|
28 | // Do we need to send synthetic clicks when preventDefault() is called on touch events? |
---|
29 | // This is normally true on anything except Android 4.1+ and IE10+, but users reported |
---|
30 | // exceptions like Galaxy Note 2. So let's use a has("clicks-prevented") flag, and let |
---|
31 | // applications override it through data-dojo-config="has:{'clicks-prevented':true}" if needed. |
---|
32 | has.add("clicks-prevented", !(has("android") >= 4.1 || (has("ie") === 10) || (!has("ie") && has("trident") > 6))); |
---|
33 | if(has("clicks-prevented")){ |
---|
34 | dm._sendClick = function(target, e){ |
---|
35 | // dojo/touch will send a click if dojoClick is set, so don't do it again. |
---|
36 | for(var node = target; node; node = node.parentNode){ |
---|
37 | if(node.dojoClick){ |
---|
38 | return; |
---|
39 | } |
---|
40 | } |
---|
41 | var ev = win.doc.createEvent("MouseEvents"); |
---|
42 | ev.initMouseEvent("click", true, true, win.global, 1, e.screenX, e.screenY, e.clientX, e.clientY); |
---|
43 | target.dispatchEvent(ev); |
---|
44 | }; |
---|
45 | } |
---|
46 | } |
---|
47 | |
---|
48 | dm.getScreenSize = function(){ |
---|
49 | // summary: |
---|
50 | // Returns the dimensions of the browser window. |
---|
51 | return { |
---|
52 | h: win.global.innerHeight || win.doc.documentElement.clientHeight, |
---|
53 | w: win.global.innerWidth || win.doc.documentElement.clientWidth |
---|
54 | }; |
---|
55 | }; |
---|
56 | |
---|
57 | dm.updateOrient = function(){ |
---|
58 | // summary: |
---|
59 | // Updates the orientation specific CSS classes, 'dj_portrait' and |
---|
60 | // 'dj_landscape'. |
---|
61 | var dim = dm.getScreenSize(); |
---|
62 | domClass.replace(win.doc.documentElement, |
---|
63 | dim.h > dim.w ? "dj_portrait" : "dj_landscape", |
---|
64 | dim.h > dim.w ? "dj_landscape" : "dj_portrait"); |
---|
65 | }; |
---|
66 | dm.updateOrient(); |
---|
67 | |
---|
68 | dm.tabletSize = 500; |
---|
69 | dm.detectScreenSize = function(/*Boolean?*/force){ |
---|
70 | // summary: |
---|
71 | // Detects the screen size and determines if the screen is like |
---|
72 | // phone or like tablet. If the result is changed, |
---|
73 | // it sets either of the following css class to `<html>`: |
---|
74 | // |
---|
75 | // - 'dj_phone' |
---|
76 | // - 'dj_tablet' |
---|
77 | // |
---|
78 | // and it publishes either of the following events: |
---|
79 | // |
---|
80 | // - '/dojox/mobile/screenSize/phone' |
---|
81 | // - '/dojox/mobile/screenSize/tablet' |
---|
82 | |
---|
83 | var dim = dm.getScreenSize(); |
---|
84 | var sz = Math.min(dim.w, dim.h); |
---|
85 | var from, to; |
---|
86 | if(sz >= dm.tabletSize && (force || (!this._sz || this._sz < dm.tabletSize))){ |
---|
87 | from = "phone"; |
---|
88 | to = "tablet"; |
---|
89 | }else if(sz < dm.tabletSize && (force || (!this._sz || this._sz >= dm.tabletSize))){ |
---|
90 | from = "tablet"; |
---|
91 | to = "phone"; |
---|
92 | } |
---|
93 | if(to){ |
---|
94 | domClass.replace(win.doc.documentElement, "dj_"+to, "dj_"+from); |
---|
95 | connect.publish("/dojox/mobile/screenSize/"+to, [dim]); |
---|
96 | } |
---|
97 | this._sz = sz; |
---|
98 | }; |
---|
99 | dm.detectScreenSize(); |
---|
100 | |
---|
101 | // dojox/mobile.hideAddressBarWait: Number |
---|
102 | // The time in milliseconds to wait before the fail-safe hiding address |
---|
103 | // bar runs. The value must be larger than 800. |
---|
104 | dm.hideAddressBarWait = typeof(config["mblHideAddressBarWait"]) === "number" ? |
---|
105 | config["mblHideAddressBarWait"] : 1500; |
---|
106 | |
---|
107 | dm.hide_1 = function(){ |
---|
108 | // summary: |
---|
109 | // Internal function to hide the address bar. |
---|
110 | // tags: |
---|
111 | // private |
---|
112 | scrollTo(0, 1); |
---|
113 | dm._hidingTimer = (dm._hidingTimer == 0) ? 200 : dm._hidingTimer * 2; |
---|
114 | setTimeout(function(){ // wait for a while for "scrollTo" to finish |
---|
115 | if(dm.isAddressBarHidden() || dm._hidingTimer > dm.hideAddressBarWait){ |
---|
116 | // Succeeded to hide address bar, or failed but timed out |
---|
117 | dm.resizeAll(); |
---|
118 | dm._hiding = false; |
---|
119 | }else{ |
---|
120 | // Failed to hide address bar, so retry after a while |
---|
121 | setTimeout(dm.hide_1, dm._hidingTimer); |
---|
122 | } |
---|
123 | }, 50); //50ms is an experiential value |
---|
124 | }; |
---|
125 | |
---|
126 | dm.hideAddressBar = function(/*Event?*/evt){ |
---|
127 | // summary: |
---|
128 | // Hides the address bar. |
---|
129 | // description: |
---|
130 | // Tries to hide the address bar a couple of times. The purpose is to do |
---|
131 | // it as quick as possible while ensuring the resize is done after the hiding |
---|
132 | // finishes. |
---|
133 | if(dm.disableHideAddressBar || dm._hiding){ return; } |
---|
134 | dm._hiding = true; |
---|
135 | dm._hidingTimer = has("ios") ? 200 : 0; // Need to wait longer in case of iPhone |
---|
136 | var minH = screen.availHeight; |
---|
137 | if(has('android')){ |
---|
138 | minH = outerHeight / devicePixelRatio; |
---|
139 | // On some Android devices such as Galaxy SII, minH might be 0 at this time. |
---|
140 | // In that case, retry again after a while. (200ms is an experiential value) |
---|
141 | if(minH == 0){ |
---|
142 | dm._hiding = false; |
---|
143 | setTimeout(function(){ dm.hideAddressBar(); }, 200); |
---|
144 | } |
---|
145 | // On some Android devices such as HTC EVO, "outerHeight/devicePixelRatio" |
---|
146 | // is too short to hide address bar, so make it high enough |
---|
147 | if(minH <= innerHeight){ minH = outerHeight; } |
---|
148 | // On Android 2.2/2.3, hiding address bar fails when "overflow:hidden" style is |
---|
149 | // applied to html/body element, so force "overflow:visible" style |
---|
150 | if(has('android') < 3){ |
---|
151 | win.doc.documentElement.style.overflow = win.body().style.overflow = "visible"; |
---|
152 | } |
---|
153 | } |
---|
154 | if(win.body().offsetHeight < minH){ // to ensure enough height for scrollTo to work |
---|
155 | win.body().style.minHeight = minH + "px"; |
---|
156 | dm._resetMinHeight = true; |
---|
157 | } |
---|
158 | setTimeout(dm.hide_1, dm._hidingTimer); |
---|
159 | }; |
---|
160 | |
---|
161 | dm.isAddressBarHidden = function(){ |
---|
162 | return pageYOffset === 1; |
---|
163 | }; |
---|
164 | |
---|
165 | dm.resizeAll = function(/*Event?*/evt, /*Widget?*/root){ |
---|
166 | // summary: |
---|
167 | // Calls the resize() method of all the top level resizable widgets. |
---|
168 | // description: |
---|
169 | // Finds all widgets that do not have a parent or the parent does not |
---|
170 | // have the resize() method, and calls resize() for them. |
---|
171 | // If a widget has a parent that has resize(), calling widget's |
---|
172 | // resize() is its parent's responsibility. |
---|
173 | // evt: |
---|
174 | // Native event object |
---|
175 | // root: |
---|
176 | // If specified, searches the specified widget recursively for top-level |
---|
177 | // resizable widgets. |
---|
178 | // root.resize() is always called regardless of whether root is a |
---|
179 | // top level widget or not. |
---|
180 | // If omitted, searches the entire page. |
---|
181 | if(dm.disableResizeAll){ return; } |
---|
182 | connect.publish("/dojox/mobile/resizeAll", [evt, root]); // back compat |
---|
183 | connect.publish("/dojox/mobile/beforeResizeAll", [evt, root]); |
---|
184 | if(dm._resetMinHeight){ |
---|
185 | win.body().style.minHeight = dm.getScreenSize().h + "px"; |
---|
186 | } |
---|
187 | dm.updateOrient(); |
---|
188 | dm.detectScreenSize(); |
---|
189 | var isTopLevel = function(w){ |
---|
190 | var parent = w.getParent && w.getParent(); |
---|
191 | return !!((!parent || !parent.resize) && w.resize); |
---|
192 | }; |
---|
193 | var resizeRecursively = function(w){ |
---|
194 | array.forEach(w.getChildren(), function(child){ |
---|
195 | if(isTopLevel(child)){ child.resize(); } |
---|
196 | resizeRecursively(child); |
---|
197 | }); |
---|
198 | }; |
---|
199 | if(root){ |
---|
200 | if(root.resize){ root.resize(); } |
---|
201 | resizeRecursively(root); |
---|
202 | }else{ |
---|
203 | array.forEach(array.filter(registry.toArray(), isTopLevel), |
---|
204 | function(w){ w.resize(); }); |
---|
205 | } |
---|
206 | connect.publish("/dojox/mobile/afterResizeAll", [evt, root]); |
---|
207 | }; |
---|
208 | |
---|
209 | dm.openWindow = function(url, target){ |
---|
210 | // summary: |
---|
211 | // Opens a new browser window with the given URL. |
---|
212 | win.global.open(url, target || "_blank"); |
---|
213 | }; |
---|
214 | |
---|
215 | dm._detectWindowsTheme = function(){ |
---|
216 | // summary: |
---|
217 | // Detects if the "windows" theme is used, |
---|
218 | // if it is used, set has("windows-theme") and |
---|
219 | // add the .windows_theme class on the document. |
---|
220 | |
---|
221 | // Avoid unwanted (un)zoom on some WP8 devices (at least Nokia Lumia 920) |
---|
222 | if(navigator.userAgent.match(/IEMobile\/10\.0/)){ |
---|
223 | domConstruct.create("style", |
---|
224 | {innerHTML: "@-ms-viewport {width: auto !important}"}, win.doc.head); |
---|
225 | } |
---|
226 | |
---|
227 | var setWindowsTheme = function(){ |
---|
228 | domClass.add(win.doc.documentElement, "windows_theme"); |
---|
229 | kernel.experimental("Dojo Mobile Windows theme", "Behavior and appearance of the Windows theme are experimental."); |
---|
230 | }; |
---|
231 | |
---|
232 | // First see if the "windows-theme" feature has already been set explicitly |
---|
233 | // in that case skip aut-detect |
---|
234 | var windows = has("windows-theme"); |
---|
235 | if(windows !== undefined){ |
---|
236 | if(windows){ |
---|
237 | setWindowsTheme(); |
---|
238 | } |
---|
239 | return; |
---|
240 | } |
---|
241 | |
---|
242 | // check css |
---|
243 | var i, j; |
---|
244 | |
---|
245 | var check = function(href){ |
---|
246 | // TODO: find a better regexp to match? |
---|
247 | if(href && href.indexOf("/windows/") !== -1){ |
---|
248 | has.add("windows-theme", true); |
---|
249 | setWindowsTheme(); |
---|
250 | return true; |
---|
251 | } |
---|
252 | return false; |
---|
253 | }; |
---|
254 | |
---|
255 | // collect @import |
---|
256 | var s = win.doc.styleSheets; |
---|
257 | for(i = 0; i < s.length; i++){ |
---|
258 | if(s[i].href){ continue; } |
---|
259 | var r = s[i].cssRules || s[i].imports; |
---|
260 | if(!r){ continue; } |
---|
261 | for(j = 0; j < r.length; j++){ |
---|
262 | if(check(r[j].href)){ |
---|
263 | return; |
---|
264 | } |
---|
265 | } |
---|
266 | } |
---|
267 | |
---|
268 | // collect <link> |
---|
269 | var elems = win.doc.getElementsByTagName("link"); |
---|
270 | for(i = 0; i < elems.length; i++){ |
---|
271 | if(check(elems[i].href)){ |
---|
272 | return; |
---|
273 | } |
---|
274 | } |
---|
275 | }; |
---|
276 | |
---|
277 | if(config["mblApplyPageStyles"] !== false){ |
---|
278 | domClass.add(win.doc.documentElement, "mobile"); |
---|
279 | } |
---|
280 | if(has('chrome')){ |
---|
281 | // dojox/mobile does not load uacss (only _compat does), but we need dj_chrome. |
---|
282 | domClass.add(win.doc.documentElement, "dj_chrome"); |
---|
283 | } |
---|
284 | |
---|
285 | if(win.global._no_dojo_dm){ |
---|
286 | // deviceTheme seems to be loaded from a script tag (= non-dojo usage) |
---|
287 | var _dm = win.global._no_dojo_dm; |
---|
288 | for(var i in _dm){ |
---|
289 | dm[i] = _dm[i]; |
---|
290 | } |
---|
291 | dm.deviceTheme.setDm(dm); |
---|
292 | } |
---|
293 | |
---|
294 | // flag for Android transition animation flicker workaround |
---|
295 | has.add('mblAndroidWorkaround', |
---|
296 | config["mblAndroidWorkaround"] !== false && has('android') < 3, undefined, true); |
---|
297 | has.add('mblAndroid3Workaround', |
---|
298 | config["mblAndroid3Workaround"] !== false && has('android') >= 3, undefined, true); |
---|
299 | |
---|
300 | dm._detectWindowsTheme(); |
---|
301 | |
---|
302 | // Set the background style using dojo/domReady, not dojo/ready, to ensure it is already |
---|
303 | // set at widget initialization time. (#17418) |
---|
304 | domReady(function(){ |
---|
305 | domClass.add(win.body(), "mblBackground"); |
---|
306 | }); |
---|
307 | |
---|
308 | ready(function(){ |
---|
309 | dm.detectScreenSize(true); |
---|
310 | if(config["mblAndroidWorkaroundButtonStyle"] !== false && has('android')){ |
---|
311 | // workaround for the form button disappearing issue on Android 2.2-4.0 |
---|
312 | domConstruct.create("style", {innerHTML:"BUTTON,INPUT[type='button'],INPUT[type='submit'],INPUT[type='reset'],INPUT[type='file']::-webkit-file-upload-button{-webkit-appearance:none;} audio::-webkit-media-controls-play-button,video::-webkit-media-controls-play-button{-webkit-appearance:media-play-button;} video::-webkit-media-controls-fullscreen-button{-webkit-appearance:media-fullscreen-button;}"}, win.doc.head, "first"); |
---|
313 | } |
---|
314 | if(has('mblAndroidWorkaround')){ |
---|
315 | // add a css class to show view offscreen for android flicker workaround |
---|
316 | domConstruct.create("style", {innerHTML:".mblView.mblAndroidWorkaround{position:absolute;top:-9999px !important;left:-9999px !important;}"}, win.doc.head, "last"); |
---|
317 | } |
---|
318 | |
---|
319 | var f = dm.resizeAll; |
---|
320 | // Address bar hiding |
---|
321 | var isHidingPossible = |
---|
322 | navigator.appVersion.indexOf("Mobile") != -1 && // only mobile browsers |
---|
323 | // #17455: hiding Safari's address bar works in iOS < 7 but this is |
---|
324 | // no longer possible since iOS 7. Hence, exclude iOS 7 and later: |
---|
325 | !(has("ios") >= 7); |
---|
326 | // You can disable the hiding of the address bar with the following dojoConfig: |
---|
327 | // var dojoConfig = { mblHideAddressBar: false }; |
---|
328 | // If unspecified, the flag defaults to true. |
---|
329 | if((config["mblHideAddressBar"] !== false && isHidingPossible) || |
---|
330 | config["mblForceHideAddressBar"] === true){ |
---|
331 | dm.hideAddressBar(); |
---|
332 | if(config["mblAlwaysHideAddressBar"] === true){ |
---|
333 | f = dm.hideAddressBar; |
---|
334 | } |
---|
335 | } |
---|
336 | |
---|
337 | var ios6 = has("ios") >= 6; // Full-screen support for iOS6 or later |
---|
338 | if((has('android') || ios6) && win.global.onorientationchange !== undefined){ |
---|
339 | var _f = f; |
---|
340 | var curSize, curClientWidth, curClientHeight; |
---|
341 | if(ios6){ |
---|
342 | curClientWidth = win.doc.documentElement.clientWidth; |
---|
343 | curClientHeight = win.doc.documentElement.clientHeight; |
---|
344 | }else{ // Android |
---|
345 | // Call resize for the first resize event after orientationchange |
---|
346 | // because the size information may not yet be up to date when the |
---|
347 | // event orientationchange occurs. |
---|
348 | f = function(evt){ |
---|
349 | var _conn = connect.connect(null, "onresize", null, function(e){ |
---|
350 | connect.disconnect(_conn); |
---|
351 | _f(e); |
---|
352 | }); |
---|
353 | } |
---|
354 | curSize = dm.getScreenSize(); |
---|
355 | }; |
---|
356 | // Android: Watch for resize events when the virtual keyboard is shown/hidden. |
---|
357 | // The heuristic to detect this is that the screen width does not change |
---|
358 | // and the height changes by more than 100 pixels. |
---|
359 | // |
---|
360 | // iOS >= 6: Watch for resize events when entering or existing the new iOS6 |
---|
361 | // full-screen mode. The heuristic to detect this is that clientWidth does not |
---|
362 | // change while the clientHeight does change. |
---|
363 | connect.connect(null, "onresize", null, function(e){ |
---|
364 | if(ios6){ |
---|
365 | var newClientWidth = win.doc.documentElement.clientWidth, |
---|
366 | newClientHeight = win.doc.documentElement.clientHeight; |
---|
367 | if(newClientWidth == curClientWidth && newClientHeight != curClientHeight){ |
---|
368 | // full-screen mode has been entered/exited (iOS6) |
---|
369 | _f(e); |
---|
370 | } |
---|
371 | curClientWidth = newClientWidth; |
---|
372 | curClientHeight = newClientHeight; |
---|
373 | }else{ // Android |
---|
374 | var newSize = dm.getScreenSize(); |
---|
375 | if(newSize.w == curSize.w && Math.abs(newSize.h - curSize.h) >= 100){ |
---|
376 | // keyboard has been shown/hidden (Android) |
---|
377 | _f(e); |
---|
378 | } |
---|
379 | curSize = newSize; |
---|
380 | } |
---|
381 | }); |
---|
382 | } |
---|
383 | |
---|
384 | connect.connect(null, win.global.onorientationchange !== undefined |
---|
385 | ? "onorientationchange" : "onresize", null, f); |
---|
386 | win.body().style.visibility = "visible"; |
---|
387 | }); |
---|
388 | |
---|
389 | // TODO: return functions declared above in this hash, rather than |
---|
390 | // dojox.mobile. |
---|
391 | |
---|
392 | /*===== |
---|
393 | return { |
---|
394 | // summary: |
---|
395 | // A common module for dojox/mobile. |
---|
396 | // description: |
---|
397 | // This module includes common utility functions that are used by |
---|
398 | // dojox/mobile widgets. Also, it provides functions that are commonly |
---|
399 | // necessary for mobile web applications, such as the hide address bar |
---|
400 | // function. |
---|
401 | }; |
---|
402 | =====*/ |
---|
403 | return dm; |
---|
404 | }); |
---|