You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
439 lines
15 KiB
439 lines
15 KiB
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; |
|
});
|
|
|