UNPKG

dojox

Version:

Dojo eXtensions, a rollup of many useful sub-projects and varying states of maturity – from very stable and robust, to alpha and experimental. See individual projects contain README files for details.

440 lines (405 loc) 15.2 kB
define([ "dojo/_base/array", "dojo/_base/config", "dojo/_base/connect", "dojo/_base/lang", "dojo/_base/window", "dojo/_base/kernel", "dojo/dom", "dojo/dom-class", "dojo/dom-construct", "dojo/domReady", "dojo/ready", "dojo/touch", "dijit/registry", "./sniff", "./uacss" // (no direct references) ], function(array, config, connect, lang, win, kernel, dom, domClass, domConstruct, domReady, ready, touch, registry, has){ // module: // dojox/mobile/common var dm = lang.getObject("dojox.mobile", true); // tell dojo/touch to generate synthetic clicks immediately // and regardless of preventDefault() calls on touch events win.doc.dojoClick = true; /// ... but let user disable this by removing dojoClick from the document if(has("touch")){ // Do we need to send synthetic clicks when preventDefault() is called on touch events? // This is normally true on anything except Android 4.1+ and IE10+, but users reported // exceptions like Galaxy Note 2. So let's use a has("clicks-prevented") flag, and let // applications override it through data-dojo-config="has:{'clicks-prevented':true}" if needed. has.add("clicks-prevented", !(has("android") >= 4.1 || (has("ie") === 10) || (!has("ie") && has("trident") > 6))); if(has("clicks-prevented")){ dm._sendClick = function(target, e){ // dojo/touch will send a click if dojoClick is set, so don't do it again. for(var node = target; node; node = node.parentNode){ if(node.dojoClick){ return; } } var ev = win.doc.createEvent("MouseEvents"); ev.initMouseEvent("click", true, true, win.global, 1, e.screenX, e.screenY, e.clientX, e.clientY); target.dispatchEvent(ev); }; } } dm.getScreenSize = function(){ // summary: // Returns the dimensions of the browser window. return { h: win.global.innerHeight || win.doc.documentElement.clientHeight, w: win.global.innerWidth || win.doc.documentElement.clientWidth }; }; dm.updateOrient = function(){ // summary: // Updates the orientation specific CSS classes, 'dj_portrait' and // 'dj_landscape'. var dim = dm.getScreenSize(); domClass.replace(win.doc.documentElement, dim.h > dim.w ? "dj_portrait" : "dj_landscape", dim.h > dim.w ? "dj_landscape" : "dj_portrait"); }; dm.updateOrient(); dm.tabletSize = 500; dm.detectScreenSize = function(/*Boolean?*/force){ // summary: // Detects the screen size and determines if the screen is like // phone or like tablet. If the result is changed, // it sets either of the following css class to `<html>`: // // - 'dj_phone' // - 'dj_tablet' // // and it publishes either of the following events: // // - '/dojox/mobile/screenSize/phone' // - '/dojox/mobile/screenSize/tablet' var dim = dm.getScreenSize(); var sz = Math.min(dim.w, dim.h); var from, to; if(sz >= dm.tabletSize && (force || (!this._sz || this._sz < dm.tabletSize))){ from = "phone"; to = "tablet"; }else if(sz < dm.tabletSize && (force || (!this._sz || this._sz >= dm.tabletSize))){ from = "tablet"; to = "phone"; } if(to){ domClass.replace(win.doc.documentElement, "dj_"+to, "dj_"+from); connect.publish("/dojox/mobile/screenSize/"+to, [dim]); } this._sz = sz; }; dm.detectScreenSize(); // dojox/mobile.hideAddressBarWait: Number // The time in milliseconds to wait before the fail-safe hiding address // bar runs. The value must be larger than 800. dm.hideAddressBarWait = typeof(config.mblHideAddressBarWait) === "number" ? config.mblHideAddressBarWait : 1500; dm.hide_1 = function(){ // summary: // Internal function to hide the address bar. // tags: // private scrollTo(0, 1); dm._hidingTimer = (dm._hidingTimer == 0) ? 200 : dm._hidingTimer * 2; setTimeout(function(){ // wait for a while for "scrollTo" to finish if(dm.isAddressBarHidden() || dm._hidingTimer > dm.hideAddressBarWait){ // Succeeded to hide address bar, or failed but timed out dm.resizeAll(); dm._hiding = false; }else{ // Failed to hide address bar, so retry after a while setTimeout(dm.hide_1, dm._hidingTimer); } }, 50); //50ms is an experiential value }; dm.hideAddressBar = function(/*Event?*/evt){ // summary: // Hides the address bar. // description: // Tries to hide the address bar a couple of times. The purpose is to do // it as quick as possible while ensuring the resize is done after the hiding // finishes. if(dm.disableHideAddressBar || dm._hiding){ return; } dm._hiding = true; dm._hidingTimer = has("ios") ? 200 : 0; // Need to wait longer in case of iPhone var minH = screen.availHeight; if(has('android')){ minH = outerHeight / devicePixelRatio; // On some Android devices such as Galaxy SII, minH might be 0 at this time. // In that case, retry again after a while. (200ms is an experiential value) if(minH == 0){ dm._hiding = false; setTimeout(function(){ dm.hideAddressBar(); }, 200); } // On some Android devices such as HTC EVO, "outerHeight/devicePixelRatio" // is too short to hide address bar, so make it high enough if(minH <= innerHeight){ minH = outerHeight; } // On Android 2.2/2.3, hiding address bar fails when "overflow:hidden" style is // applied to html/body element, so force "overflow:visible" style if(has('android') < 3){ win.doc.documentElement.style.overflow = win.body().style.overflow = "visible"; } } if(win.body().offsetHeight < minH){ // to ensure enough height for scrollTo to work win.body().style.minHeight = minH + "px"; dm._resetMinHeight = true; } setTimeout(dm.hide_1, dm._hidingTimer); }; dm.isAddressBarHidden = function(){ return pageYOffset === 1; }; dm.resizeAll = function(/*Event?*/evt, /*Widget?*/root){ // summary: // Calls the resize() method of all the top level resizable widgets. // description: // Finds all widgets that do not have a parent or the parent does not // have the resize() method, and calls resize() for them. // If a widget has a parent that has resize(), calling widget's // resize() is its parent's responsibility. // evt: // Native event object // root: // If specified, searches the specified widget recursively for top-level // resizable widgets. // root.resize() is always called regardless of whether root is a // top level widget or not. // If omitted, searches the entire page. if(dm.disableResizeAll){ return; } connect.publish("/dojox/mobile/resizeAll", [evt, root]); // back compat connect.publish("/dojox/mobile/beforeResizeAll", [evt, root]); if(dm._resetMinHeight){ win.body().style.minHeight = dm.getScreenSize().h + "px"; } dm.updateOrient(); dm.detectScreenSize(); var isTopLevel = function(w){ var parent = w.getParent && w.getParent(); return !!((!parent || !parent.resize) && w.resize); }; var resizeRecursively = function(w){ array.forEach(w.getChildren(), function(child){ if(isTopLevel(child)){ child.resize(); } resizeRecursively(child); }); }; if(root){ if(root.resize){ root.resize(); } resizeRecursively(root); }else{ array.forEach(array.filter(registry.toArray(), isTopLevel), function(w){ w.resize(); }); } connect.publish("/dojox/mobile/afterResizeAll", [evt, root]); }; dm.openWindow = function(url, target){ // summary: // Opens a new browser window with the given URL. win.global.open(url, target || "_blank"); }; dm._detectWindowsTheme = function(){ // summary: // Detects if the "windows" theme is used, // if it is used, set has("windows-theme") and // add the .windows_theme class on the document. // Avoid unwanted (un)zoom on some WP8 devices (at least Nokia Lumia 920) if(navigator.userAgent.match(/IEMobile\/10\.0/)){ domConstruct.create("style", {innerHTML: "@-ms-viewport {width: auto !important}"}, win.doc.head); } var setWindowsTheme = function(){ domClass.add(win.doc.documentElement, "windows_theme"); kernel.experimental("Dojo Mobile Windows theme", "Behavior and appearance of the Windows theme are experimental."); }; // First see if the "windows-theme" feature has already been set explicitly // in that case skip aut-detect var windows = has("windows-theme"); if(windows !== undefined){ if(windows){ setWindowsTheme(); } return; } // check css var i, j; var check = function(href){ // TODO: find a better regexp to match? if(href && href.indexOf("/windows/") !== -1){ has.add("windows-theme", true); setWindowsTheme(); return true; } return false; }; // collect @import var s = win.doc.styleSheets; for(i = 0; i < s.length; i++){ if(s[i].href){ continue; } var r = s[i].cssRules || s[i].imports; if(!r){ continue; } for(j = 0; j < r.length; j++){ if(check(r[j].href)){ return; } } } // collect <link> var elems = win.doc.getElementsByTagName("link"); for(i = 0; i < elems.length; i++){ if(check(elems[i].href)){ return; } } }; if(config.mblApplyPageStyles !== false){ domClass.add(win.doc.documentElement, "mobile"); } if(has('chrome')){ // dojox/mobile does not load uacss (only _compat does), but we need dj_chrome. domClass.add(win.doc.documentElement, "dj_chrome"); } if(win.global._no_dojo_dm){ // deviceTheme seems to be loaded from a script tag (= non-dojo usage) var _dm = win.global._no_dojo_dm; for(var i in _dm){ dm[i] = _dm[i]; } dm.deviceTheme.setDm(dm); } // flag for Android transition animation flicker workaround has.add('mblAndroidWorkaround', config.mblAndroidWorkaround !== false && has('android') < 3, undefined, true); has.add('mblAndroid3Workaround', config.mblAndroid3Workaround !== false && has('android') >= 3, undefined, true); dm._detectWindowsTheme(); dm.setSelectable = function(/*Node*/node, /*Boolean*/selectable){ var nodes, i; node = dom.byId(node); if (has("ie") <= 9){ // (IE < 10) Fall back to setting/removing the // unselectable attribute on the element and all its children // except the input element (see https://bugs.dojotoolkit.org/ticket/13846) nodes = node.getElementsByTagName("*"); i = nodes.length; if(selectable){ node.removeAttribute("unselectable"); while(i--){ nodes[i].removeAttribute("unselectable"); } }else{ node.setAttribute("unselectable", "on"); while(i--){ if (nodes[i].tagName !== "INPUT"){ nodes[i].setAttribute("unselectable", "on"); } } } }else{ domClass.toggle(node, "unselectable", !selectable); } }; var touchActionProp = has("pointer-events") ? "touchAction" : has("MSPointer") ? "msTouchAction" : null; dm._setTouchAction = touchActionProp ? function(/*Node*/node, /*Boolean*/value){ node.style[touchActionProp] = value; } : function(){}; // Set the background style using dojo/domReady, not dojo/ready, to ensure it is already // set at widget initialization time. (#17418) domReady(function(){ if(config.mblApplyPageStyles !== false){ domClass.add(win.body(), "mblBackground"); } }); ready(function(){ dm.detectScreenSize(true); if(config.mblAndroidWorkaroundButtonStyle !== false && has('android')){ // workaround for the form button disappearing issue on Android 2.2-4.0 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"); } if(has('mblAndroidWorkaround')){ // add a css class to show view offscreen for android flicker workaround domConstruct.create("style", {innerHTML:".mblView.mblAndroidWorkaround{position:absolute;top:-9999px !important;left:-9999px !important;}"}, win.doc.head, "last"); } var f = dm.resizeAll; // Address bar hiding var isHidingPossible = navigator.appVersion.indexOf("Mobile") != -1 && // only mobile browsers // #17455: hiding Safari's address bar works in iOS < 7 but this is // no longer possible since iOS 7. Hence, exclude iOS 7 and later: !(has("ios") >= 7); // You can disable the hiding of the address bar with the following dojoConfig: // var dojoConfig = { mblHideAddressBar: false }; // If unspecified, the flag defaults to true. if((config.mblHideAddressBar !== false && isHidingPossible) || config.mblForceHideAddressBar === true){ dm.hideAddressBar(); if(config.mblAlwaysHideAddressBar === true){ f = dm.hideAddressBar; } } var ios6 = has("ios") >= 6; // Full-screen support for iOS6 or later if((has('android') || ios6) && win.global.onorientationchange !== undefined){ var _f = f; var curSize, curClientWidth, curClientHeight; if(ios6){ curClientWidth = win.doc.documentElement.clientWidth; curClientHeight = win.doc.documentElement.clientHeight; }else{ // Android // Call resize for the first resize event after orientationchange // because the size information may not yet be up to date when the // event orientationchange occurs. f = function(evt){ var _conn = connect.connect(null, "onresize", null, function(e){ connect.disconnect(_conn); _f(e); }); }; curSize = dm.getScreenSize(); }; // Android: Watch for resize events when the virtual keyboard is shown/hidden. // The heuristic to detect this is that the screen width does not change // and the height changes by more than 100 pixels. // // iOS >= 6: Watch for resize events when entering or existing the new iOS6 // full-screen mode. The heuristic to detect this is that clientWidth does not // change while the clientHeight does change. connect.connect(null, "onresize", null, function(e){ if(ios6){ var newClientWidth = win.doc.documentElement.clientWidth, newClientHeight = win.doc.documentElement.clientHeight; if(newClientWidth == curClientWidth && newClientHeight != curClientHeight){ // full-screen mode has been entered/exited (iOS6) _f(e); } curClientWidth = newClientWidth; curClientHeight = newClientHeight; }else{ // Android var newSize = dm.getScreenSize(); if(newSize.w == curSize.w && Math.abs(newSize.h - curSize.h) >= 100){ // keyboard has been shown/hidden (Android) _f(e); } curSize = newSize; } }); } connect.connect(null, win.global.onorientationchange !== undefined ? "onorientationchange" : "onresize", null, f); win.body().style.visibility = "visible"; }); // TODO: return functions declared above in this hash, rather than // dojox.mobile. /*===== return { // summary: // A common module for dojox/mobile. // description: // This module includes common utility functions that are used by // dojox/mobile widgets. Also, it provides functions that are commonly // necessary for mobile web applications, such as the hide address bar // function. }; =====*/ return dm; });