source: Dev/branches/rest-dojo-ui/client/dojox/mobile/View.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: 17.7 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/declare",
7        "dojo/_base/lang",
8        "dojo/_base/sniff",
9        "dojo/_base/window",
10        "dojo/_base/Deferred",
11        "dojo/dom",
12        "dojo/dom-class",
13        "dojo/dom-geometry",
14        "dojo/dom-style",
15//      "dojo/hash", // optionally prereq'ed
16        "dijit/registry",       // registry.byNode
17        "dijit/_Contained",
18        "dijit/_Container",
19        "dijit/_WidgetBase",
20        "./ViewController", // to load ViewController for you (no direct references)
21        "./transition"
22], function(dojo, array, config, connect, declare, lang, has, win, Deferred, dom, domClass, domGeometry, domStyle, registry, Contained, Container, WidgetBase, ViewController, transitDeferred){
23
24/*=====
25        var Contained = dijit._Contained;
26        var Container = dijit._Container;
27        var WidgetBase = dijit._WidgetBase;
28        var ViewController = dojox.mobile.ViewController;
29=====*/
30
31        // module:
32        //              dojox/mobile/View
33        // summary:
34        //              A widget that represents a view that occupies the full screen
35
36        var dm = lang.getObject("dojox.mobile", true);
37
38        return declare("dojox.mobile.View", [WidgetBase, Container, Contained], {
39                // summary:
40                //              A widget that represents a view that occupies the full screen
41                // description:
42                //              View acts as a container for any HTML and/or widgets. An entire
43                //              HTML page can have multiple View widgets and the user can
44                //              navigate through the views back and forth without page
45                //              transitions.
46       
47                // selected: Boolean
48                //              If true, the view is displayed at startup time.
49                selected: false,
50
51                // keepScrollPos: Boolean
52                //              If true, the scroll position is kept between views.
53                keepScrollPos: true,
54       
55                constructor: function(params, node){
56                        if(node){
57                                dom.byId(node).style.visibility = "hidden";
58                        }
59                        this._aw = has('android') >= 2.2 && has('android') < 3; // flag for android animation workaround
60                },
61       
62                buildRendering: function(){
63                        this.domNode = this.containerNode = this.srcNodeRef || win.doc.createElement("DIV");
64                        this.domNode.className = "mblView";
65                        this.connect(this.domNode, "webkitAnimationEnd", "onAnimationEnd");
66                        this.connect(this.domNode, "webkitAnimationStart", "onAnimationStart");
67                        if(!config['mblCSS3Transition']){
68                            this.connect(this.domNode, "webkitTransitionEnd", "onAnimationEnd");
69                        }
70                        var id = location.href.match(/#(\w+)([^\w=]|$)/) ? RegExp.$1 : null;
71       
72                        this._visible = this.selected && !id || this.id == id;
73       
74                        if(this.selected){
75                                dm._defaultView = this;
76                        }
77                },
78
79                startup: function(){
80                        if(this._started){ return; }
81                        var siblings = [];
82                        var children = this.domNode.parentNode.childNodes;
83                        var visible = false;
84                        // check if a visible view exists
85                        for(var i = 0; i < children.length; i++){
86                                var c = children[i];
87                                if(c.nodeType === 1 && domClass.contains(c, "mblView")){
88                                        siblings.push(c);
89                                        visible = visible || registry.byNode(c)._visible;
90                                }
91                        }
92                        var _visible = this._visible;
93                        // if no visible view exists, make the first view visible
94                        if(siblings.length === 1 || (!visible && siblings[0] === this.domNode)){
95                                _visible = true;
96                        }
97                        var _this = this;
98                        setTimeout(function(){ // necessary to render the view correctly
99                                if(!_visible){
100                                        _this.domNode.style.display = "none";
101                                }else{
102                                        dm.currentView = _this; //TODO:1.8 reconsider this. currentView may not have a currently showing view when views are nested.
103                                        _this.onStartView();
104                                        connect.publish("/dojox/mobile/startView", [_this]);
105                                }
106                                if(_this.domNode.style.visibility != "visible"){ // this check is to avoid screen flickers
107                                        _this.domNode.style.visibility = "visible";
108                                }
109                                var parent = _this.getParent && _this.getParent();
110                                if(!parent || !parent.resize){ // top level widget
111                                        _this.resize();
112                                }
113                        }, has("ie") ? 100 : 0); // give IE a little time to complete drawing
114                        this.inherited(arguments);
115                },
116       
117                resize: function(){
118                        // summary:
119                        //              Calls resize() of each child widget.
120                        array.forEach(this.getChildren(), function(child){
121                                if(child.resize){ child.resize(); }
122                        });
123                },
124
125                onStartView: function(){
126                        // summary:
127                        //              Stub function to connect to from your application.
128                        // description:
129                        //              Called only when this view is shown at startup time.
130                },
131       
132                onBeforeTransitionIn: function(moveTo, dir, transition, context, method){
133                        // summary:
134                        //              Stub function to connect to from your application.
135                        // description:
136                        //              Called before the arriving transition occurs.
137                },
138       
139                onAfterTransitionIn: function(moveTo, dir, transition, context, method){
140                        // summary:
141                        //              Stub function to connect to from your application.
142                        // description:
143                        //              Called after the arriving transition occurs.
144                },
145       
146                onBeforeTransitionOut: function(moveTo, dir, transition, context, method){
147                        // summary:
148                        //              Stub function to connect to from your application.
149                        // description:
150                        //              Called before the leaving transition occurs.
151                },
152       
153                onAfterTransitionOut: function(moveTo, dir, transition, context, method){
154                        // summary:
155                        //              Stub function to connect to from your application.
156                        // description:
157                        //              Called after the leaving transition occurs.
158                },
159       
160                _saveState: function(moveTo, dir, transition, context, method){
161                        this._context = context;
162                        this._method = method;
163                        if(transition == "none"){
164                                transition = null;
165                        }
166                        this._moveTo = moveTo;
167                        this._dir = dir;
168                        this._transition = transition;
169                        this._arguments = lang._toArray(arguments);
170                        this._args = [];
171                        if(context || method){
172                                for(var i = 5; i < arguments.length; i++){
173                                        this._args.push(arguments[i]);
174                                }
175                        }
176                },
177               
178                _fixViewState: function(/*DomNode*/toNode){
179                        // summary:
180                        //              Sanity check for view transition states.
181                        // description:
182                        //              Sometimes uninitialization of Views fails after making view transition,
183                        //              and that results in failure of subsequent view transitions.
184                        //              This function does the uninitialization for all the sibling views.
185                        var nodes = this.domNode.parentNode.childNodes;
186                        for(var i = 0; i < nodes.length; i++){
187                                var n = nodes[i];
188                                if(n.nodeType === 1 && domClass.contains(n, "mblView")){
189                                        n.className = "mblView"; //TODO: Should remove classes one by one. This would clear user defined classes or even mblScrollableView.
190                                }
191                        }
192                        toNode.className = "mblView"; // just in case toNode is a sibling of an ancestor.
193                },
194       
195                convertToId: function(moveTo){
196                        if(typeof(moveTo) == "string"){
197                                // removes a leading hash mark (#) and params if exists
198                                // ex. "#bar&myParam=0003" -> "bar"
199                                moveTo.match(/^#?([^&?]+)/);
200                                return RegExp.$1;
201                        }
202                        return moveTo;
203                },
204       
205                performTransition: function(/*String*/moveTo, /*Number*/dir, /*String*/transition,
206                                                                        /*Object|null*/context, /*String|Function*/method /*optional args*/){
207                        // summary:
208                        //              Function to perform the various types of view transitions, such as fade, slide, and flip.
209                        // moveTo: String
210                        //              The id of the transition destination view which resides in
211                        //              the current page.
212                        //              If the value has a hash sign ('#') before the id
213                        //              (e.g. #view1) and the dojo.hash module is loaded by the user
214                        //              application, the view transition updates the hash in the
215                        //              browser URL so that the user can bookmark the destination
216                        //              view. In this case, the user can also use the browser's
217                        //              back/forward button to navigate through the views in the
218                        //              browser history.
219                        //              If null, transitions to a blank view.
220                        //              If '#', returns immediately without transition.
221                        // dir: Number
222                        //              The transition direction. If 1, transition forward. If -1, transition backward.
223                        //              For example, the slide transition slides the view from right to left when dir == 1,
224                        //              and from left to right when dir == -1.
225                        // transition: String
226                        //              A type of animated transition effect. You can choose from
227                        //              the standard transition types, "slide", "fade", "flip", or
228                        //              from the extended transition types, "cover", "coverv",
229                        //              "dissolve", "reveal", "revealv", "scaleIn",
230                        //              "scaleOut", "slidev", "swirl", "zoomIn", "zoomOut". If
231                        //              "none" is specified, transition occurs immediately without
232                        //              animation.
233                        // context: Object
234                        //              The object that the callback function will receive as "this".
235                        // method: String|Function
236                        //              A callback function that is called when the transition has been finished.
237                        //              A function reference, or name of a function in context.
238                        // tags:
239                        //              public
240                        //
241                        // example:
242                        //              Transition backward to a view whose id is "foo" with the slide animation.
243                        //      |       performTransition("foo", -1, "slide");
244                        //
245                        // example:
246                        //              Transition forward to a blank view, and then open another page.
247                        //      |       performTransition(null, 1, "slide", null, function(){location.href = href;});
248                        if(moveTo === "#"){ return; }
249                        if(dojo.hash){
250                                if(typeof(moveTo) == "string" && moveTo.charAt(0) == '#' && !dm._params){
251                                        dm._params = [];
252                                        for(var i = 0; i < arguments.length; i++){
253                                                dm._params.push(arguments[i]);
254                                        }
255                                        dojo.hash(moveTo);
256                                        return;
257                                }
258                        }
259                        this._saveState.apply(this, arguments);
260                        var toNode;
261                        if(moveTo){
262                                toNode = this.convertToId(moveTo);
263                        }else{
264                                if(!this._dummyNode){
265                                        this._dummyNode = win.doc.createElement("DIV");
266                                        win.body().appendChild(this._dummyNode);
267                                }
268                                toNode = this._dummyNode;
269                        }
270                        var fromNode = this.domNode;
271                        var fromTop = fromNode.offsetTop;
272                        toNode = this.toNode = dom.byId(toNode);
273                        if(!toNode){ console.log("dojox.mobile.View#performTransition: destination view not found: "+moveTo); return; }
274                        toNode.style.visibility = this._aw ? "visible" : "hidden";
275                        toNode.style.display = "";
276                        this._fixViewState(toNode);
277                        var toWidget = registry.byNode(toNode);
278                        if(toWidget){
279                                // Now that the target view became visible, it's time to run resize()
280                                if(config["mblAlwaysResizeOnTransition"] || !toWidget._resized){
281                                        dm.resizeAll(null, toWidget);
282                                        toWidget._resized = true;
283                                }
284       
285                                if(transition && transition != "none"){
286                                        // Temporarily add padding to align with the fromNode while transition
287                                        toWidget.containerNode.style.paddingTop = fromTop + "px";
288                                }
289
290                                toWidget.movedFrom = fromNode.id;
291                        }
292       
293                        this.onBeforeTransitionOut.apply(this, arguments);
294                        connect.publish("/dojox/mobile/beforeTransitionOut", [this].concat(lang._toArray(arguments)));
295                        if(toWidget){
296                                // perform view transition keeping the scroll position
297                                if(this.keepScrollPos && !this.getParent()){
298                                        var scrollTop = win.body().scrollTop || win.doc.documentElement.scrollTop || win.global.pageYOffset || 0;
299                                        fromNode._scrollTop = scrollTop;
300                                        var toTop = (dir == 1) ? 0 : (toNode._scrollTop || 0);
301                                        toNode.style.top = "0px";
302                                        if(scrollTop > 1 || toTop !== 0){
303                                                fromNode.style.top = toTop - scrollTop + "px";
304                                                if(config["mblHideAddressBar"] !== false){
305                                                        setTimeout(function(){ // iPhone needs setTimeout
306                                                                win.global.scrollTo(0, (toTop || 1));
307                                                        }, 0);
308                                                }
309                                        }
310                                }else{
311                                        toNode.style.top = "0px";
312                                }
313                                toWidget.onBeforeTransitionIn.apply(toWidget, arguments);
314                                connect.publish("/dojox/mobile/beforeTransitionIn", [toWidget].concat(lang._toArray(arguments)));
315                        }
316                        if(!this._aw){
317                                toNode.style.display = "none";
318                                toNode.style.visibility = "visible";
319                        }
320                       
321                        if(dm._iw && dm.scrollable){ // Workaround for iPhone flicker issue (only when scrollable.js is loaded)
322                                var ss = dm.getScreenSize();
323                                // Show cover behind the view.
324                                // cover's z-index is set to -10000, lower than z-index value specified in transition css.
325                                win.body().appendChild(dm._iwBgCover);
326                                domStyle.set(dm._iwBgCover, {
327                                        position: "absolute",
328                                        top: "0px",
329                                        left: "0px",
330                                        height: (ss.h + 1) + "px", // "+1" means the height of scrollTo(0,1)
331                                        width: ss.w + "px",
332                                        backgroundColor: domStyle.get(win.body(), "background-color"),
333                                        zIndex: -10000,
334                                        display: ""
335                                });
336                                // Show toNode behind the cover.
337                                domStyle.set(toNode, {
338                                        position: "absolute",
339                                        zIndex: -10001,
340                                        visibility: "visible",
341                                        display: ""
342                                });
343                                // setTimeout seems to be necessary to avoid flicker.
344                                // Also the duration of setTimeout should be long enough to avoid flicker.
345                                // 0 is not effective. 50 sometimes causes flicker.
346                                setTimeout(lang.hitch(this, function(){
347                                        this._doTransition(fromNode, toNode, transition, dir);
348                                }), 80);
349                        }else{
350                                this._doTransition(fromNode, toNode, transition, dir);
351                        }
352                },
353                _toCls: function(s){
354                        // convert from transition name to corresponding class name
355                        // ex. "slide" -> "mblSlide"
356                        return "mbl"+s.charAt(0).toUpperCase() + s.substring(1);
357                },
358       
359                _doTransition: function(fromNode, toNode, transition, dir){
360                        var rev = (dir == -1) ? " mblReverse" : "";
361                        if(dm._iw && dm.scrollable){ // Workaround for iPhone flicker issue (only when scrollable.js is loaded)
362                                // Show toNode after flicker ends
363                                domStyle.set(toNode, {
364                                        position: "",
365                                        zIndex: ""
366                                });
367                                // Remove cover
368                                win.body().removeChild(dm._iwBgCover);
369                        }else if(!this._aw){
370                                toNode.style.display = "";
371                        }
372                        if(!transition || transition == "none"){
373                                this.domNode.style.display = "none";
374                                this.invokeCallback();
375                        }else if(config['mblCSS3Transition']){
376                                //get dojox/css3/transit first
377                                Deferred.when(transitDeferred, lang.hitch(this, function(transit){
378                                        //follow the style of .mblView.mblIn in View.css
379                                        //need to set the toNode to absolute position
380                                        var toPosition = domStyle.get(toNode, "position");
381                                        domStyle.set(toNode, "position", "absolute");
382                                        Deferred.when(transit(fromNode, toNode, {transition: transition, reverse: (dir===-1)?true:false}),lang.hitch(this,function(){
383                                                domStyle.set(toNode, "position", toPosition);
384                                                this.invokeCallback();
385                                        }));
386                                }));
387                        }else{
388                                var s = this._toCls(transition);
389                                domClass.add(fromNode, s + " mblOut" + rev);
390                                domClass.add(toNode, s + " mblIn" + rev);
391                                setTimeout(function(){
392                                        domClass.add(fromNode, "mblTransition");
393                                        domClass.add(toNode, "mblTransition");
394                                }, 100);
395                                // set transform origin
396                                var fromOrigin = "50% 50%";
397                                var toOrigin = "50% 50%";
398                                var scrollTop, posX, posY;
399                                if(transition.indexOf("swirl") != -1 || transition.indexOf("zoom") != -1){
400                                        if(this.keepScrollPos && !this.getParent()){
401                                                scrollTop = win.body().scrollTop || win.doc.documentElement.scrollTop || win.global.pageYOffset || 0;
402                                        }else{
403                                                scrollTop = -domGeometry.position(fromNode, true).y;
404                                        }
405                                        posY = win.global.innerHeight / 2 + scrollTop;
406                                        fromOrigin = "50% " + posY + "px";
407                                        toOrigin = "50% " + posY + "px";
408                                }else if(transition.indexOf("scale") != -1){
409                                        var viewPos = domGeometry.position(fromNode, true);
410                                        posX = ((this.clickedPosX !== undefined) ? this.clickedPosX : win.global.innerWidth / 2) - viewPos.x;
411                                        if(this.keepScrollPos && !this.getParent()){
412                                                scrollTop = win.body().scrollTop || win.doc.documentElement.scrollTop || win.global.pageYOffset || 0;
413                                        }else{
414                                                scrollTop = -viewPos.y;
415                                        }
416                                        posY = ((this.clickedPosY !== undefined) ? this.clickedPosY : win.global.innerHeight / 2) + scrollTop;
417                                        fromOrigin = posX + "px " + posY + "px";
418                                        toOrigin = posX + "px " + posY + "px";
419                                }
420                                domStyle.set(fromNode, {webkitTransformOrigin:fromOrigin});
421                                domStyle.set(toNode, {webkitTransformOrigin:toOrigin});
422                        }
423                        dm.currentView = registry.byNode(toNode);
424                },
425       
426                onAnimationStart: function(e){
427                },
428
429
430                onAnimationEnd: function(e){
431                        var name = e.animationName || e.target.className;
432                        if(name.indexOf("Out") === -1 &&
433                                name.indexOf("In") === -1 &&
434                                name.indexOf("Shrink") === -1){ return; }
435                        var isOut = false;
436                        if(domClass.contains(this.domNode, "mblOut")){
437                                isOut = true;
438                                this.domNode.style.display = "none";
439                                domClass.remove(this.domNode, [this._toCls(this._transition), "mblIn", "mblOut", "mblReverse"]);
440                        }else{
441                                // Reset the temporary padding
442                                this.containerNode.style.paddingTop = "";
443                        }
444                        domStyle.set(this.domNode, {webkitTransformOrigin:""});
445                        if(name.indexOf("Shrink") !== -1){
446                                var li = e.target;
447                                li.style.display = "none";
448                                domClass.remove(li, "mblCloseContent");
449                        }
450                        if(isOut){
451                                this.invokeCallback();
452                        }
453                        // this.domNode may be destroyed as a result of invoking the callback,
454                        // so check for that before accessing it.
455                        this.domNode && (this.domNode.className = "mblView");
456
457                        // clear the clicked position
458                        this.clickedPosX = this.clickedPosY = undefined;
459                },
460
461                invokeCallback: function(){
462                        this.onAfterTransitionOut.apply(this, this._arguments);
463                        connect.publish("/dojox/mobile/afterTransitionOut", [this].concat(this._arguments));
464                        var toWidget = registry.byNode(this.toNode);
465                        if(toWidget){
466                                toWidget.onAfterTransitionIn.apply(toWidget, this._arguments);
467                                connect.publish("/dojox/mobile/afterTransitionIn", [toWidget].concat(this._arguments));
468                                toWidget.movedFrom = undefined;
469                        }
470
471                        var c = this._context, m = this._method;
472                        if(!c && !m){ return; }
473                        if(!m){
474                                m = c;
475                                c = null;
476                        }
477                        c = c || win.global;
478                        if(typeof(m) == "string"){
479                                c[m].apply(c, this._args);
480                        }else{
481                                m.apply(c, this._args);
482                        }
483                },
484       
485                getShowingView: function(){
486                        // summary:
487                        //              Find the currently showing view from my sibling views.
488                        // description:
489                        //              Note that dojox.mobile.currentView is the last shown view.
490                        //              If the page consists of a splitter, there are multiple showing views.
491                        var nodes = this.domNode.parentNode.childNodes;
492                        for(var i = 0; i < nodes.length; i++){
493                                var n = nodes[i];
494                                if(n.nodeType === 1 && domClass.contains(n, "mblView") && domStyle.get(n, "display") !== "none"){
495                                        return registry.byNode(n);
496                                }
497                        }
498                        return null;
499                },
500       
501                show: function(){
502                        // summary:
503                        //              Shows this view without a transition animation.
504                        var view = this.getShowingView();
505                        if(view){
506                                view.domNode.style.display = "none"; // from-style
507                        }
508                        this.domNode.style.display = ""; // to-style
509                        dm.currentView = this;
510                }
511        });
512});
Note: See TracBrowser for help on using the repository browser.