//>>built define("dojox/mobile/common", [ "dojo/_base/kernel", // to test dojo.hash "dojo/_base/array", "dojo/_base/config", "dojo/_base/connect", "dojo/_base/lang", "dojo/_base/window", "dojo/dom-class", "dojo/dom-construct", "dojo/dom-style", // "dojo/hash", // optionally prereq'ed "dojo/ready", "dijit/registry", // registry.toArray "./sniff", "./uacss" ], function(dojo, array, config, connect, lang, win, domClass, domConstruct, domStyle, ready, registry, has, uacss){ var dm = lang.getObject("dojox.mobile", true); /*===== var dm = dojox.mobile; =====*/ // module: // dojox/mobile/common // 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. 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 // - '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(); dm.setupIcon = function(/*DomNode*/iconNode, /*String*/iconPos){ // summary: // Sets up CSS sprite for a foreground image. if(iconNode && iconPos){ var arr = array.map(iconPos.split(/[ ,]/),function(item){return item-0}); var t = arr[0]; // top var r = arr[1] + arr[2]; // right var b = arr[0] + arr[3]; // bottom var l = arr[1]; // left domStyle.set(iconNode, { clip: "rect("+t+"px "+r+"px "+b+"px "+l+"px)", top: (iconNode.parentNode ? domStyle.get(iconNode, "top") : 0) - t + "px", left: -l + "px" }); } }; // 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(force){ // summary: // Internal function to hide the address bar. scrollTo(0, 1); var h = dm.getScreenSize().h + "px"; if(has("android")){ if(force){ win.body().style.minHeight = h; } dm.resizeAll(); }else{ if(force || dm._h === h && h !== win.body().style.minHeight){ win.body().style.minHeight = h; dm.resizeAll(); } } dm._h = h; }; dm.hide_fs = function(){ // summary: // Internal function to hide the address bar for fail-safe. // description: // Resets the height of the body, performs hiding the address // bar, and calls resizeAll(). // This is for fail-safe, in case of failure to complete the // address bar hiding in time. var t = win.body().style.minHeight; win.body().style.minHeight = (dm.getScreenSize().h * 2) + "px"; // to ensure enough height for scrollTo to work scrollTo(0, 1); setTimeout(function(){ dm.hide_1(1); dm._hiding = false; }, 1000); }; dm.hideAddressBar = function(/*Event?*/evt){ // summary: // Hides the address bar. // description: // Tries hiding of the address bar a couple of times to do it as // quick as possible while ensuring resize is done after the hiding // finishes. if(dm.disableHideAddressBar || dm._hiding){ return; } dm._hiding = true; dm._h = 0; win.body().style.minHeight = (dm.getScreenSize().h * 2) + "px"; // to ensure enough height for scrollTo to work setTimeout(dm.hide_1, 0); setTimeout(dm.hide_1, 200); setTimeout(dm.hide_1, 800); setTimeout(dm.hide_fs, dm.hideAddressBarWait); }; dm.resizeAll = function(/*Event?*/evt, /*Widget?*/root){ // summary: // Call the resize() method of all the top level resizable widgets. // description: // Find all widgets that do not have a parent or the parent does not // have the resize() method, and call resize() for them. // If a widget has a parent that has resize(), call of the widget's // resize() is its parent's responsibility. // evt: // Native event object // root: // If specified, search 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, search the entire page. if(dm.disableResizeAll){ return; } connect.publish("/dojox/mobile/resizeAll", [evt, root]); 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(); }); } }; dm.openWindow = function(url, target){ // summary: // Opens a new browser window with the given url. win.global.open(url, target || "_blank"); }; dm.createDomButton = function(/*DomNode*/refNode, /*Object?*/style, /*DomNode?*/toNode){ // summary: // Creates a DOM button. // description: // DOM button is a simple graphical object that consists of one or // more nested DIV elements with some CSS styling. It can be used // in place of an icon image on ListItem, IconItem, and so on. // The kind of DOM button to create is given as a class name of // refNode. The number of DIVs to create is searched from the style // sheets in the page. However, if the class name has a suffix that // starts with an underscore, like mblDomButtonGoldStar_5, then the // suffixed number is used instead. A class name for DOM button // must starts with 'mblDomButton'. // refNode: // A node that has a DOM button class name. // style: // A hash object to set styles to the node. // toNode: // A root node to create a DOM button. If omitted, refNode is used. if(!dm._domButtons){ if(has("webkit")){ var findDomButtons = function(sheet, dic){ // summary: // Searches the style sheets for DOM buttons. // description: // Returns a key-value pair object whose keys are DOM // button class names and values are the number of DOM // elements they need. var i, j; if(!sheet){ var dic = {}; var ss = dojo.doc.styleSheets; for (i = 0; i < ss.length; i++){ ss[i] && findDomButtons(ss[i], dic); } return dic; } var rules = sheet.cssRules || []; for (i = 0; i < rules.length; i++){ var rule = rules[i]; if(rule.href && rule.styleSheet){ findDomButtons(rule.styleSheet, dic); }else if(rule.selectorText){ var sels = rule.selectorText.split(/,/); for (j = 0; j < sels.length; j++){ var sel = sels[j]; var n = sel.split(/>/).length - 1; if(sel.match(/(mblDomButton\w+)/)){ var cls = RegExp.$1; if(!dic[cls] || n > dic[cls]){ dic[cls] = n; } } } } } } dm._domButtons = findDomButtons(); }else{ dm._domButtons = {}; } } var s = refNode.className; var node = toNode || refNode; if(s.match(/(mblDomButton\w+)/) && s.indexOf("/") === -1){ var btnClass = RegExp.$1; var nDiv = 4; if(s.match(/(mblDomButton\w+_(\d+))/)){ nDiv = RegExp.$2 - 0; }else if(dm._domButtons[btnClass] !== undefined){ nDiv = dm._domButtons[btnClass]; } var props = null; if(has("bb") && config["mblBBBoxShadowWorkaround"] !== false){ // Removes box-shadow because BlackBerry incorrectly renders it. props = {style:"-webkit-box-shadow:none"}; } for(var i = 0, p = node; i < nDiv; i++){ p = p.firstChild || domConstruct.create("DIV", props, p); } if(toNode){ setTimeout(function(){ domClass.remove(refNode, btnClass); }, 0); domClass.add(toNode, btnClass); } }else if(s.indexOf(".") !== -1){ // file name domConstruct.create("IMG", {src:s}, node); }else{ return null; } domClass.add(node, "mblDomButton"); if(config["mblAndroidWorkaround"] !== false && has("android") >= 2.2){ // Android workaround for the issue that domButtons' -webkit-transform styles sometimes invalidated // by applying -webkit-transform:translated3d(x,y,z) style programmatically to non-ancestor elements, // which results in breaking domButtons. domStyle.set(node, "webkitTransform", "translate3d(0,0,0)"); } !!style && domStyle.set(node, style); return node; }; dm.createIcon = function(/*String*/icon, /*String*/iconPos, /*DomNode*/node, /*String?*/title, /*DomNode?*/parent){ // summary: // Creates or updates an icon node // description: // If node exists, updates the existing node. Otherwise, creates a new one. // icon: // Path for an image, or DOM button class name. if(icon && icon.indexOf("mblDomButton") === 0){ // DOM button if(node && node.className.match(/(mblDomButton\w+)/)){ domClass.remove(node, RegExp.$1); }else{ node = domConstruct.create("DIV"); } node.title = title; domClass.add(node, icon); dm.createDomButton(node); }else if(icon && icon !== "none"){ // Image if(!node || node.nodeName !== "IMG"){ node = domConstruct.create("IMG", { alt: title }); } node.src = (icon || "").replace("${theme}", dm.currentTheme); dm.setupIcon(node, iconPos); if(parent && iconPos){ var arr = iconPos.split(/[ ,]/); domStyle.set(parent, { width: arr[2] + "px", height: arr[3] + "px" }); } } if(parent){ parent.appendChild(node); } return node; }; // flag for iphone flicker workaround dm._iw = config["mblIosWorkaround"] !== false && has("iphone"); if(dm._iw){ dm._iwBgCover = domConstruct.create("div"); // Cover to hide flicker in the background } if(config.parseOnLoad){ ready(90, function(){ // avoid use of query /* var list = query('[lazy=true] [dojoType]', null); list.forEach(function(node, index, nodeList){ node.setAttribute("__dojoType", node.getAttribute("dojoType")); node.removeAttribute("dojoType"); }); */ var nodes = win.body().getElementsByTagName("*"); var i, len, s; len = nodes.length; for(i = 0; i < len; i++){ s = nodes[i].getAttribute("dojoType"); if(s){ if(nodes[i].parentNode.getAttribute("lazy") == "true"){ nodes[i].setAttribute("__dojoType", s); nodes[i].removeAttribute("dojoType"); } } } }); } ready(function(){ dm.detectScreenSize(true); 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(config["mblAndroidWorkaround"] !== false && has("android") >= 2.2){ // workaround for android screen flicker problem if(config["mblAndroidWorkaroundButtonStyle"] !== false){ // workaround to avoid buttons disappear due to the side-effect of the webkitTransform workaroud below 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"); } if(has("android") < 3){ // for Android 2.2.x and 2.3.x domStyle.set(win.doc.documentElement, "webkitTransform", "translate3d(0,0,0)"); // workaround for auto-scroll issue when focusing input fields connect.connect(null, "onfocus", null, function(e){ domStyle.set(win.doc.documentElement, "webkitTransform", ""); }); connect.connect(null, "onblur", null, function(e){ domStyle.set(win.doc.documentElement, "webkitTransform", "translate3d(0,0,0)"); }); }else{ // for Android 3.x if(config["mblAndroid3Workaround"] !== false){ domStyle.set(win.doc.documentElement, { webkitBackfaceVisibility: "hidden", webkitPerspective: 8000 }); } } } // You can disable hiding the address bar with the following djConfig. // var djConfig = { mblHideAddressBar: false }; var f = dm.resizeAll; if(config["mblHideAddressBar"] !== false && navigator.appVersion.indexOf("Mobile") != -1 || config["mblForceHideAddressBar"] === true){ dm.hideAddressBar(); if(config["mblAlwaysHideAddressBar"] === true){ f = dm.hideAddressBar; } } connect.connect(null, (win.global.onorientationchange !== undefined && !has("android")) ? "onorientationchange" : "onresize", null, f); // avoid use of query /* var list = query('[__dojoType]', null); list.forEach(function(node, index, nodeList){ node.setAttribute("dojoType", node.getAttribute("__dojoType")); node.removeAttribute("__dojoType"); }); */ var nodes = win.body().getElementsByTagName("*"); var i, len = nodes.length, s; for(i = 0; i < len; i++){ s = nodes[i].getAttribute("__dojoType"); if(s){ nodes[i].setAttribute("dojoType", s); nodes[i].removeAttribute("__dojoType"); } } if(dojo.hash){ // find widgets under root recursively var findWidgets = function(root){ if(!root){ return []; } var arr = registry.findWidgets(root); var widgets = arr; for(var i = 0; i < widgets.length; i++){ arr = arr.concat(findWidgets(widgets[i].containerNode)); } return arr; }; connect.subscribe("/dojo/hashchange", null, function(value){ var view = dm.currentView; if(!view){ return; } var params = dm._params; if(!params){ // browser back/forward button was pressed var moveTo = value ? value : dm._defaultView.id; var widgets = findWidgets(view.domNode); var dir = 1, transition = "slide"; for(i = 0; i < widgets.length; i++){ var w = widgets[i]; if("#"+moveTo == w.moveTo){ // found a widget that has the given moveTo transition = w.transition; dir = (w instanceof dm.Heading) ? -1 : 1; break; } } params = [ moveTo, dir, transition ]; } view.performTransition.apply(view, params); dm._params = null; }); } win.body().style.visibility = "visible"; }); // To search _parentNode first. TODO:1.8 reconsider this redefinition. registry.getEnclosingWidget = function(node){ while(node){ var id = node.getAttribute && node.getAttribute("widgetId"); if(id){ return registry.byId(id); } node = node._parentNode || node.parentNode; } return null; }; return dm; });