common.js 16 KB

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