source: Dev/branches/rest-dojo-ui/client/dojox/app/scene.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: 19.7 KB
Line 
1define(["dojo/_base/kernel",
2        "dojo/_base/declare",
3        "dojo/_base/connect",
4        "dojo/_base/array",
5        "dojo/_base/Deferred",
6        "dojo/_base/lang",
7        "dojo/_base/sniff",
8        "dojo/dom-style",
9        "dojo/dom-geometry",
10        "dojo/dom-class",
11        "dojo/dom-construct",
12        "dojo/dom-attr",
13        "dojo/query",
14        "dijit",
15        "dojox",
16        "dijit/_WidgetBase",
17        "dijit/_TemplatedMixin",
18        "dijit/_WidgetsInTemplateMixin",
19        "dojox/css3/transit",
20        "./animation",
21        "./model",
22        "./view",
23        "./bind"],
24        function(dojo,declare,connect, array,deferred,dlang,has,dstyle,dgeometry,cls,dconstruct,dattr,query,dijit,dojox,WidgetBase,Templated,WidgetsInTemplate,transit, anim, model, baseView, bind){
25       
26        var marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){
27                // summary:
28                //              Given the margin-box size of a node, return its content box size.
29                //              Functions like dojo.contentBox() but is more reliable since it doesn't have
30                //              to wait for the browser to compute sizes.
31                var cs = dstyle.getComputedStyle(node);
32                var me = dgeometry.getMarginExtents(node, cs);
33                var pb = dgeometry.getPadBorderExtents(node, cs);
34                return {
35                        l: dstyle.toPixelValue(node, cs.paddingLeft),
36                        t: dstyle.toPixelValue(node, cs.paddingTop),
37                        w: mb.w - (me.w + pb.w),
38                        h: mb.h - (me.h + pb.h)
39                };
40        };
41
42        var capitalize = function(word){
43                return word.substring(0,1).toUpperCase() + word.substring(1);
44        };
45
46        var size = function(widget, dim){
47                // size the child
48                var newSize = widget.resize ? widget.resize(dim) : dgeometry.setMarginBox(widget.domNode, dim);
49                // record child's size
50                if(newSize){
51                        // if the child returned it's new size then use that
52                        dojo.mixin(widget, newSize);
53                }else{
54                        // otherwise, call marginBox(), but favor our own numbers when we have them.
55                        // the browser lies sometimes
56                        dojo.mixin(widget, dgeometry.getMarginBox(widget.domNode));
57
58                        dojo.mixin(widget, dim);
59                }
60        };
61
62        return declare("dojox.app.scene", [dijit._WidgetBase, dijit._TemplatedMixin, dijit._WidgetsInTemplateMixin], {
63                isContainer: true,
64                widgetsInTemplate: true,
65                defaultView: "default",
66
67                selectedChild: null,
68                baseClass: "scene mblView",
69                isFullScreen: false,
70                defaultViewType: baseView,
71               
72                //Temporary work around for getting a null when calling getParent
73                getParent: function(){return null;},
74
75
76                constructor: function(params,node){
77                        this.children={};
78                        if(params.parent){
79                                this.parent=params.parent
80                        }
81                        if(params.app){
82                                this.app = params.app;
83                        }
84                },
85
86                buildRendering: function(){
87                        this.inherited(arguments);
88                        dstyle.set(this.domNode, {width: "100%", "height": "100%"});
89                        cls.add(this.domNode,"dijitContainer");
90                },
91
92                splitChildRef: function(childId){
93                        var id = childId.split(",");
94                        if (id.length>0){
95                                var to = id.shift();
96                        }else{
97                                console.warn("invalid child id passed to splitChildRef(): ", childId);
98                        }
99
100                        return {
101                                id:to || this.defaultView,
102                                next: id.join(',')
103                        }
104                },
105
106                loadChild: function(childId,subIds){
107                        // if no childId, load the default view
108            if (!childId) {
109                var parts = this.defaultView ? this.defaultView.split(",") : "default";
110                childId = parts.shift();
111                subIds = parts.join(',');
112            }
113
114                        var cid = this.id+"_" + childId;
115                        if (this.children[cid]){
116                                return this.children[cid];
117                        }
118
119                        if (this.views&& this.views[childId]){
120                                var conf = this.views[childId];
121                                if (!conf.dependencies){conf.dependencies=[];}
122                                var deps = conf.template? conf.dependencies.concat(["dojo/text!app/"+conf.template]) :
123                                                conf.dependencies.concat([]);
124                       
125                                var def = new deferred();
126                                if (deps.length>0) {
127                                        require(deps,function(){
128                                                def.resolve.call(def, arguments);                       
129                                        });
130                                }else{
131                                        def.resolve(true);
132                                }
133               
134                           var loadChildDeferred = new deferred();
135                           var self = this;
136                                deferred.when(def, function(){
137                                        var ctor;
138                                        if (conf.type){
139                                                ctor=dojo.getObject(conf.type);
140                                        }else if (self.defaultViewType){
141                                                ctor=self.defaultViewType;
142                                        }else{
143                                                throw Error("Unable to find appropriate ctor for the base child class");
144                                        }
145
146                                        var params = dojo.mixin({}, conf, {
147                                                id: self.id + "_" + childId,
148                                                templateString: conf.template?arguments[0][arguments[0].length-1]:"<div></div>",
149                                                parent: self,
150                                                app: self.app
151                                        })
152                                        if (subIds){
153                                                params.defaultView=subIds;
154                                        }
155                    var child = new ctor(params);
156                    //load child's model if it is not loaded before
157                    if(!child.loadedModels){
158                        child.loadedModels = model(conf.models, self.loadedModels)
159                        //TODO need to find out a better way to get all bindable controls in a view
160                        bind([child], child.loadedModels);
161                    }
162                                        var addResult = self.addChild(child);
163                                        //publish /app/loadchild event
164                                        //application can subscript this event to do user define operation like select TabBarButton, add dynamic script text etc.
165                                        connect.publish("/app/loadchild", [child]);
166
167                 var promise;
168
169                 subIds = subIds.split(',');
170                 if ((subIds[0].length > 0) && (subIds.length > 1)) {//TODO join subIds
171                     promise = child.loadChild(subIds[0], subIds[1]);
172                 }
173                 else
174                     if (subIds[0].length > 0) {
175                         promise = child.loadChild(subIds[0], "");
176                     }
177                 
178                 dojo.when(promise, function(){
179                     loadChildDeferred.resolve(addResult)
180                 });
181                                });
182              return loadChildDeferred;
183                        }
184       
185                        throw Error("Child '" + childId + "' not found.");
186                },
187
188                resize: function(changeSize,resultSize){
189                        var node = this.domNode;
190
191                        // set margin box size, unless it wasn't specified, in which case use current size
192                        if(changeSize){
193                                dgeometry.setMarginBox(node, changeSize);
194
195                                // set offset of the node
196                                if(changeSize.t){ node.style.top = changeSize.t + "px"; }
197                                if(changeSize.l){ node.style.left = changeSize.l + "px"; }
198                        }
199
200                        // If either height or width wasn't specified by the user, then query node for it.
201                        // But note that setting the margin box and then immediately querying dimensions may return
202                        // inaccurate results, so try not to depend on it.
203                        var mb = resultSize || {};
204                        dojo.mixin(mb, changeSize || {});       // changeSize overrides resultSize
205                        if( !("h" in mb) || !("w" in mb) ){
206                                mb = dojo.mixin(dgeometry.getMarginBox(node), mb);      // just use dojo.marginBox() to fill in missing values
207                        }
208
209                        // Compute and save the size of my border box and content box
210                        // (w/out calling dojo.contentBox() since that may fail if size was recently set)
211                        var cs = dstyle.getComputedStyle(node);
212                        var me = dgeometry.getMarginExtents(node, cs);
213                        var be = dgeometry.getBorderExtents(node, cs);
214                        var bb = (this._borderBox = {
215                                w: mb.w - (me.w + be.w),
216                                h: mb.h - (me.h + be.h)
217                        });
218                        var pe = dgeometry.getPadExtents(node, cs);
219                        this._contentBox = {
220                                l: dstyle.toPixelValue(node, cs.paddingLeft),
221                                t: dstyle.toPixelValue(node, cs.paddingTop),
222                                w: bb.w - pe.w,
223                                h: bb.h - pe.h
224                        };
225
226                        // Callback for widget to adjust size of its children
227                        this.layout();
228                },
229
230                layout: function(){
231                        var fullScreenScene,children,hasCenter;
232                        //console.log("fullscreen: ", this.selectedChild && this.selectedChild.isFullScreen);
233                        if (this.selectedChild && this.selectedChild.isFullScreen) {
234                                console.warn("fullscreen sceen layout");
235                                /*
236                                fullScreenScene=true;           
237                                children=[{domNode: this.selectedChild.domNode,region: "center"}];
238                                dojo.query("> [region]",this.domNode).forEach(function(c){
239                                        if(this.selectedChild.domNode!==c.domNode){
240                                                dojo.style(c.domNode,"display","none");
241                                        }
242                                })
243                                */
244                        }else{
245                                children = query("> [region]", this.domNode).map(function(node){
246                                        var w = dijit.getEnclosingWidget(node);
247                                        if (w){return w;}
248
249                                        return {               
250                                                domNode: node,
251                                                region: dattr.get(node,"region")
252                                        }
253                                               
254                                });
255                                if (this.selectedChild){
256                                        children = array.filter(children, function(c){
257                                                if (c.region=="center" && this.selectedChild && this.selectedChild.domNode!==c.domNode){
258                                                        dstyle.set(c.domNode,"zIndex",25);
259                                                        dstyle.set(c.domNode,'display','none');
260                                                        return false;
261                                                }else if (c.region!="center"){
262                                                        dstyle.set(c.domNode,"display","");
263                                                        dstyle.set(c.domNode,"zIndex",100);
264                                                }
265                                       
266                                                return c.domNode && c.region;
267                                        },this);
268
269                                //      this.selectedChild.region="center";     
270                                //      dojo.attr(this.selectedChild.domNode,"region","center");
271                                //      dojo.style(this.selectedChild.domNode, "display","");
272                                //      dojo.style(this.selectedChild.domNode,"zIndex",50);
273
274                                //      children.push({domNode: this.selectedChild.domNode, region: "center"});
275                                //      children.push(this.selectedChild);
276                                //      console.log("children: ", children);
277                                }else{
278                                        array.forEach(children, function(c){
279                                                if (c && c.domNode && c.region=="center"){
280                                                        dstyle.set(c.domNode,"zIndex",25);
281                                                        dstyle.set(c.domNode,'display','none');
282                                                }       
283                                        });
284                                }
285                       
286                        }       
287                        // We don't need to layout children if this._contentBox is null for the operation will do nothing.
288                        if (this._contentBox) {
289                                this.layoutChildren(this.domNode, this._contentBox, children);
290                        }
291                        array.forEach(this.getChildren(), function(child){
292                                if (!child._started && child.startup){
293                                        child.startup();
294                                }
295
296                        });
297
298                },
299
300
301                layoutChildren: function(/*DomNode*/ container, /*Object*/ dim, /*Widget[]*/ children,
302                        /*String?*/ changedRegionId, /*Number?*/ changedRegionSize){
303                        // summary
304                        //              Layout a bunch of child dom nodes within a parent dom node
305                        // container:
306                        //              parent node
307                        // dim:
308                        //              {l, t, w, h} object specifying dimensions of container into which to place children
309                        // children:
310                        //              an array of Widgets or at least objects containing:
311                        //                      * domNode: pointer to DOM node to position
312                        //                      * region or layoutAlign: position to place DOM node
313                        //                      * resize(): (optional) method to set size of node
314                        //                      * id: (optional) Id of widgets, referenced from resize object, below.
315                        // changedRegionId:
316                        //              If specified, the slider for the region with the specified id has been dragged, and thus
317                        //              the region's height or width should be adjusted according to changedRegionSize
318                        // changedRegionSize:
319                        //              See changedRegionId.
320       
321                        // copy dim because we are going to modify it
322                        dim = dojo.mixin({}, dim);
323       
324                        cls.add(container, "dijitLayoutContainer");
325       
326                        // Move "client" elements to the end of the array for layout.  a11y dictates that the author
327                        // needs to be able to put them in the document in tab-order, but this algorithm requires that
328                        // client be last.    TODO: move these lines to LayoutContainer?   Unneeded other places I think.
329                        children = array.filter(children, function(item){ return item.region != "center" && item.layoutAlign != "client"; })
330                                .concat(array.filter(children, function(item){ return item.region == "center" || item.layoutAlign == "client"; }));
331       
332                        // set positions/sizes
333                        array.forEach(children, function(child){
334                                var elm = child.domNode,
335                                        pos = (child.region || child.layoutAlign);
336       
337                                // set elem to upper left corner of unused space; may move it later
338                                var elmStyle = elm.style;
339                                elmStyle.left = dim.l+"px";
340                                elmStyle.top = dim.t+"px";
341                                elmStyle.position = "absolute";
342       
343                                cls.add(elm, "dijitAlign" + capitalize(pos));
344       
345                                // Size adjustments to make to this child widget
346                                var sizeSetting = {};
347       
348                                // Check for optional size adjustment due to splitter drag (height adjustment for top/bottom align
349                                // panes and width adjustment for left/right align panes.
350                                if(changedRegionId && changedRegionId == child.id){
351                                        sizeSetting[child.region == "top" || child.region == "bottom" ? "h" : "w"] = changedRegionSize;
352                                }
353       
354                                // set size && adjust record of remaining space.
355                                // note that setting the width of a <div> may affect its height.
356                                if(pos == "top" || pos == "bottom"){
357                                        sizeSetting.w = dim.w;
358                                        size(child, sizeSetting);
359                                        dim.h -= child.h;
360                                        if(pos == "top"){
361                                                dim.t += child.h;
362                                        }else{
363                                                elmStyle.top = dim.t + dim.h + "px";
364                                        }
365                                }else if(pos == "left" || pos == "right"){
366                                        sizeSetting.h = dim.h;
367                                        size(child, sizeSetting);
368                                        dim.w -= child.w;
369                                        if(pos == "left"){
370                                                dim.l += child.w;
371                                        }else{
372                                                elmStyle.left = dim.l + dim.w + "px";
373                                        }
374                                }else if(pos == "client" || pos == "center"){
375                                        size(child, dim);
376                                }
377                        });
378                },
379
380                getChildren: function(){
381                        return this._supportingWidgets;
382                },
383
384                startup: function(){
385                        if(this._started){ return; }
386                        this._started=true;
387
388                        var parts = this.defaultView?this.defaultView.split(","):"default";
389                        var toId, subIds;
390                        toId= parts.shift();
391                        subIds = parts.join(',');
392
393                        if(this.views[this.defaultView] && this.views[this.defaultView]["defaultView"]){
394                                subIds =  this.views[this.defaultView]["defaultView"];
395                        }       
396                       
397                        if(this.models && !this.loadedModels){
398                                //if there is this.models config data and the models has not been loaded yet,
399                                //load models at here using the configuration data and load model logic in model.js
400                                this.loadedModels = model(this.models);
401                                bind(this.getChildren(), this.loadedModels);
402                        }
403                       
404                        //startup assumes all children are loaded into DOM before startup is called
405                        //startup will only start the current available children.
406                        var cid = this.id + "_" + toId;
407            if (this.children[cid]) {
408                                var next = this.children[cid];
409
410                                this.set("selectedChild", next);
411                               
412                                // If I am a not being controlled by a parent layout widget...
413                                var parent = this.getParent && this.getParent();
414                                if (!(parent && parent.isLayoutContainer)) {
415                                        // Do recursive sizing and layout of all my descendants
416                                        // (passing in no argument to resize means that it has to glean the size itself)
417                                        this.resize();
418                                       
419                                        // Since my parent isn't a layout container, and my style *may be* width=height=100%
420                                        // or something similar (either set directly or via a CSS class),
421                                        // monitor when my size changes so that I can re-layout.
422                                        // For browsers where I can't directly monitor when my size changes,
423                                        // monitor when the viewport changes size, which *may* indicate a size change for me.
424                                        this.connect(has("ie") ? this.domNode : dojo.global, 'onresize', function(){
425                                                // Using function(){} closure to ensure no arguments to resize.
426                                                this.resize();
427                                        });
428                                       
429                                }
430                               
431                                array.forEach(this.getChildren(), function(child){
432                                        child.startup();
433                                });
434
435                                //transition to _startView
436              if (this._startView && (this._startView != this.defaultView)) {
437                  this.transition(this._startView, {});
438              }
439                        }
440                },
441
442                addChild: function(widget){
443                        cls.add(widget.domNode, this.baseClass + "_child");
444                        widget.region = "center";;
445                        dattr.set(widget.domNode,"region","center");
446                        this._supportingWidgets.push(widget);
447                        dconstruct.place(widget.domNode,this.domNode);
448                        this.children[widget.id] = widget;
449                        return widget;
450                },
451
452                removeChild: function(widget){
453                        // summary:
454                        //              Removes the passed widget instance from this widget but does
455                        //              not destroy it.  You can also pass in an integer indicating
456                        //              the index within the container to remove
457
458                        if(widget){
459                                var node = widget.domNode;
460                                if(node && node.parentNode){
461                                        node.parentNode.removeChild(node); // detach but don't destroy
462                                }
463                                return widget;
464                        }
465                },
466
467                _setSelectedChildAttr: function(child,opts){
468                        if (child !== this.selectedChild) {
469                                return deferred.when(child, dlang.hitch(this, function(child){
470                                        if (this.selectedChild){
471                                                if (this.selectedChild.deactivate){
472                                                        this.selectedChild.deactivate();
473                                                }
474
475                                                dstyle.set(this.selectedChild.domNode,"zIndex",25);
476                                        }
477               
478                                        //dojo.style(child.domNode, {
479                                        //      "display": "",
480                                        //      "zIndex": 50,
481                                        //      "overflow": "auto"
482                                        //});
483                                        this.selectedChild = child;
484                                        dstyle.set(child.domNode, "display", "");
485                                        dstyle.set(child.domNode,"zIndex",50);
486                                        this.selectedChild=child;
487                                        if (this._started) {   
488                                                if (child.startup && !child._started){
489                                                        child.startup();
490                                                }else if (child.activate){
491                                                        child.activate();
492                                                }
493               
494                                        }
495                                        this.layout();
496                                }));
497                        }
498                },
499
500
501                transition: function(transitionTo,opts){
502                        //summary:
503                        //  transitions from the currently visible scene to the defined scene.
504                        //  it should determine what would be the best transition unless
505                        //  an override in opts tells it to use a specific transitioning methodology
506                        //  the transitionTo is a string in the form of [view]@[scene].  If
507                        //  view is left of, the current scene will be transitioned to the default
508                        //  view of the specified scene (eg @scene2), if the scene is left off
509                        //  the app controller will instruct the active scene to the view (eg view1).  If both
510                        //  are supplied (view1@scene2), then the application should transition to the scene,
511                        //  and instruct the scene to navigate to the view.
512                        var toId,subIds,next, current = this.selectedChild;
513                        console.log("scene", this.id, transitionTo);
514                        if (transitionTo){     
515                                var parts = transitionTo.split(",");
516                                toId= parts.shift();
517                                subIds = parts.join(',');
518
519                        }else{
520                                toId = this.defaultView;
521                                if(this.views[this.defaultView] && this.views[this.defaultView]["defaultView"]){
522                                        subIds =  this.views[this.defaultView]["defaultView"];
523                                }       
524                        }
525               
526                        next = this.loadChild(toId,subIds);
527
528                        if (!current){
529                                //assume this.set(...) will return a promise object if child is first loaded
530                                //return nothing if child is already in array of this.children
531                                return this.set("selectedChild",next); 
532                        }       
533
534                        var transitionDeferred  = new deferred();
535                        deferred.when(next, dlang.hitch(this, function(next){
536                                var promise;
537                           
538                                if (next!==current){
539                                    //TODO need to refactor here, when clicking fast, current will not be the
540                                    //view we want to start transition. For example, during transition 1 -> 2
541                                    //if user click button to transition to 3 and then transition to 1. It will
542                                    //perform transition 2 -> 3 and 2 -> 1 because current is always point to
543                                    //2 during 1 -> 2 transition.
544                                   
545                                    var waitingList = anim.getWaitingList([next.domNode, current.domNode]);
546                                    //update registry with deferred objects in animations of args.
547                                    var transitionDefs = {};
548                                    transitionDefs[current.domNode.id] = anim.playing[current.domNode.id] = new deferred();
549                                    transitionDefs[next.domNode.id] = anim.playing[current.domNode.id] = new deferred();
550                                               
551                                    deferred.when(waitingList, dojo.hitch(this, function(){
552                                        //assume next is already loaded so that this.set(...) will not return
553                                        //a promise object. this.set(...) will handles the this.selectedChild,
554                                        //activate or deactivate views and refresh layout.
555                                        this.set("selectedChild", next);
556                                       
557                                        //publish /app/transition event
558                                        //application can subscript this event to do user define operation like select TabBarButton, etc.
559                                        connect.publish("/app/transition", [next, toId]);
560                                        transit(current.domNode,next.domNode,dojo.mixin({},opts,{transition: this.defaultTransition || "none", transitionDefs: transitionDefs})).then(dlang.hitch(this, function(){
561                                                //dojo.style(current.domNode, "display", "none");
562                                                if (subIds && next.transition){
563                                                        promise = next.transition(subIds,opts);
564                                                }
565                                                deferred.when(promise, function(){
566                                                    transitionDeferred.resolve();
567                                                });
568                                        }));
569                                    }));
570                                    return;
571                                }
572
573                                //we didn't need to transition, but continue to propogate.
574                                if (subIds && next.transition){
575                                        promise = next.transition(subIds,opts);
576                                }
577                                deferred.when(promise, function(){
578                                    transitionDeferred.resolve();
579                                });
580                        }));
581                        return transitionDeferred;
582                },
583                toString: function(){return this.id},
584
585                activate: function(){},
586                deactive: function(){}
587        });
588});
Note: See TracBrowser for help on using the repository browser.