source: Dev/branches/rest-dojo-ui/client/dojox/mobile/common.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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