//>>built define("dojox/app/scene", ["dojo/_base/kernel", "dojo/_base/declare", "dojo/_base/connect", "dojo/_base/array", "dojo/_base/Deferred", "dojo/_base/lang", "dojo/_base/sniff", "dojo/dom-style", "dojo/dom-geometry", "dojo/dom-class", "dojo/dom-construct", "dojo/dom-attr", "dojo/query", "dijit", "dojox", "dijit/_WidgetBase", "dijit/_TemplatedMixin", "dijit/_WidgetsInTemplateMixin", "dojox/css3/transit", "./animation", "./model", "./view", "./bind"], function(dojo,declare,connect, array,deferred,dlang,has,dstyle,dgeometry,cls,dconstruct,dattr,query,dijit,dojox,WidgetBase,Templated,WidgetsInTemplate,transit, anim, model, baseView, bind){ var marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){ // summary: // Given the margin-box size of a node, return its content box size. // Functions like dojo.contentBox() but is more reliable since it doesn't have // to wait for the browser to compute sizes. var cs = dstyle.getComputedStyle(node); var me = dgeometry.getMarginExtents(node, cs); var pb = dgeometry.getPadBorderExtents(node, cs); return { l: dstyle.toPixelValue(node, cs.paddingLeft), t: dstyle.toPixelValue(node, cs.paddingTop), w: mb.w - (me.w + pb.w), h: mb.h - (me.h + pb.h) }; }; var capitalize = function(word){ return word.substring(0,1).toUpperCase() + word.substring(1); }; var size = function(widget, dim){ // size the child var newSize = widget.resize ? widget.resize(dim) : dgeometry.setMarginBox(widget.domNode, dim); // record child's size if(newSize){ // if the child returned it's new size then use that dojo.mixin(widget, newSize); }else{ // otherwise, call marginBox(), but favor our own numbers when we have them. // the browser lies sometimes dojo.mixin(widget, dgeometry.getMarginBox(widget.domNode)); dojo.mixin(widget, dim); } }; return declare("dojox.app.scene", [dijit._WidgetBase, dijit._TemplatedMixin, dijit._WidgetsInTemplateMixin], { isContainer: true, widgetsInTemplate: true, defaultView: "default", selectedChild: null, baseClass: "scene mblView", isFullScreen: false, defaultViewType: baseView, //Temporary work around for getting a null when calling getParent getParent: function(){return null;}, constructor: function(params,node){ this.children={}; if(params.parent){ this.parent=params.parent } if(params.app){ this.app = params.app; } }, buildRendering: function(){ this.inherited(arguments); dstyle.set(this.domNode, {width: "100%", "height": "100%"}); cls.add(this.domNode,"dijitContainer"); }, splitChildRef: function(childId){ var id = childId.split(","); if (id.length>0){ var to = id.shift(); }else{ console.warn("invalid child id passed to splitChildRef(): ", childId); } return { id:to || this.defaultView, next: id.join(',') } }, loadChild: function(childId,subIds){ // if no childId, load the default view if (!childId) { var parts = this.defaultView ? this.defaultView.split(",") : "default"; childId = parts.shift(); subIds = parts.join(','); } var cid = this.id+"_" + childId; if (this.children[cid]){ return this.children[cid]; } if (this.views&& this.views[childId]){ var conf = this.views[childId]; if (!conf.dependencies){conf.dependencies=[];} var deps = conf.template? conf.dependencies.concat(["dojo/text!app/"+conf.template]) : conf.dependencies.concat([]); var def = new deferred(); if (deps.length>0) { require(deps,function(){ def.resolve.call(def, arguments); }); }else{ def.resolve(true); } var loadChildDeferred = new deferred(); var self = this; deferred.when(def, function(){ var ctor; if (conf.type){ ctor=dojo.getObject(conf.type); }else if (self.defaultViewType){ ctor=self.defaultViewType; }else{ throw Error("Unable to find appropriate ctor for the base child class"); } var params = dojo.mixin({}, conf, { id: self.id + "_" + childId, templateString: conf.template?arguments[0][arguments[0].length-1]:"
", parent: self, app: self.app }) if (subIds){ params.defaultView=subIds; } var child = new ctor(params); //load child's model if it is not loaded before if(!child.loadedModels){ child.loadedModels = model(conf.models, self.loadedModels) //TODO need to find out a better way to get all bindable controls in a view bind([child], child.loadedModels); } var addResult = self.addChild(child); //publish /app/loadchild event //application can subscript this event to do user define operation like select TabBarButton, add dynamic script text etc. connect.publish("/app/loadchild", [child]); var promise; subIds = subIds.split(','); if ((subIds[0].length > 0) && (subIds.length > 1)) {//TODO join subIds promise = child.loadChild(subIds[0], subIds[1]); } else if (subIds[0].length > 0) { promise = child.loadChild(subIds[0], ""); } dojo.when(promise, function(){ loadChildDeferred.resolve(addResult) }); }); return loadChildDeferred; } throw Error("Child '" + childId + "' not found."); }, resize: function(changeSize,resultSize){ var node = this.domNode; // set margin box size, unless it wasn't specified, in which case use current size if(changeSize){ dgeometry.setMarginBox(node, changeSize); // set offset of the node if(changeSize.t){ node.style.top = changeSize.t + "px"; } if(changeSize.l){ node.style.left = changeSize.l + "px"; } } // If either height or width wasn't specified by the user, then query node for it. // But note that setting the margin box and then immediately querying dimensions may return // inaccurate results, so try not to depend on it. var mb = resultSize || {}; dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize if( !("h" in mb) || !("w" in mb) ){ mb = dojo.mixin(dgeometry.getMarginBox(node), mb); // just use dojo.marginBox() to fill in missing values } // Compute and save the size of my border box and content box // (w/out calling dojo.contentBox() since that may fail if size was recently set) var cs = dstyle.getComputedStyle(node); var me = dgeometry.getMarginExtents(node, cs); var be = dgeometry.getBorderExtents(node, cs); var bb = (this._borderBox = { w: mb.w - (me.w + be.w), h: mb.h - (me.h + be.h) }); var pe = dgeometry.getPadExtents(node, cs); this._contentBox = { l: dstyle.toPixelValue(node, cs.paddingLeft), t: dstyle.toPixelValue(node, cs.paddingTop), w: bb.w - pe.w, h: bb.h - pe.h }; // Callback for widget to adjust size of its children this.layout(); }, layout: function(){ var fullScreenScene,children,hasCenter; //console.log("fullscreen: ", this.selectedChild && this.selectedChild.isFullScreen); if (this.selectedChild && this.selectedChild.isFullScreen) { console.warn("fullscreen sceen layout"); /* fullScreenScene=true; children=[{domNode: this.selectedChild.domNode,region: "center"}]; dojo.query("> [region]",this.domNode).forEach(function(c){ if(this.selectedChild.domNode!==c.domNode){ dojo.style(c.domNode,"display","none"); } }) */ }else{ children = query("> [region]", this.domNode).map(function(node){ var w = dijit.getEnclosingWidget(node); if (w){return w;} return { domNode: node, region: dattr.get(node,"region") } }); if (this.selectedChild){ children = array.filter(children, function(c){ if (c.region=="center" && this.selectedChild && this.selectedChild.domNode!==c.domNode){ dstyle.set(c.domNode,"zIndex",25); dstyle.set(c.domNode,'display','none'); return false; }else if (c.region!="center"){ dstyle.set(c.domNode,"display",""); dstyle.set(c.domNode,"zIndex",100); } return c.domNode && c.region; },this); // this.selectedChild.region="center"; // dojo.attr(this.selectedChild.domNode,"region","center"); // dojo.style(this.selectedChild.domNode, "display",""); // dojo.style(this.selectedChild.domNode,"zIndex",50); // children.push({domNode: this.selectedChild.domNode, region: "center"}); // children.push(this.selectedChild); // console.log("children: ", children); }else{ array.forEach(children, function(c){ if (c && c.domNode && c.region=="center"){ dstyle.set(c.domNode,"zIndex",25); dstyle.set(c.domNode,'display','none'); } }); } } // We don't need to layout children if this._contentBox is null for the operation will do nothing. if (this._contentBox) { this.layoutChildren(this.domNode, this._contentBox, children); } array.forEach(this.getChildren(), function(child){ if (!child._started && child.startup){ child.startup(); } }); }, layoutChildren: function(/*DomNode*/ container, /*Object*/ dim, /*Widget[]*/ children, /*String?*/ changedRegionId, /*Number?*/ changedRegionSize){ // summary // Layout a bunch of child dom nodes within a parent dom node // container: // parent node // dim: // {l, t, w, h} object specifying dimensions of container into which to place children // children: // an array of Widgets or at least objects containing: // * domNode: pointer to DOM node to position // * region or layoutAlign: position to place DOM node // * resize(): (optional) method to set size of node // * id: (optional) Id of widgets, referenced from resize object, below. // changedRegionId: // If specified, the slider for the region with the specified id has been dragged, and thus // the region's height or width should be adjusted according to changedRegionSize // changedRegionSize: // See changedRegionId. // copy dim because we are going to modify it dim = dojo.mixin({}, dim); cls.add(container, "dijitLayoutContainer"); // Move "client" elements to the end of the array for layout. a11y dictates that the author // needs to be able to put them in the document in tab-order, but this algorithm requires that // client be last. TODO: move these lines to LayoutContainer? Unneeded other places I think. children = array.filter(children, function(item){ return item.region != "center" && item.layoutAlign != "client"; }) .concat(array.filter(children, function(item){ return item.region == "center" || item.layoutAlign == "client"; })); // set positions/sizes array.forEach(children, function(child){ var elm = child.domNode, pos = (child.region || child.layoutAlign); // set elem to upper left corner of unused space; may move it later var elmStyle = elm.style; elmStyle.left = dim.l+"px"; elmStyle.top = dim.t+"px"; elmStyle.position = "absolute"; cls.add(elm, "dijitAlign" + capitalize(pos)); // Size adjustments to make to this child widget var sizeSetting = {}; // Check for optional size adjustment due to splitter drag (height adjustment for top/bottom align // panes and width adjustment for left/right align panes. if(changedRegionId && changedRegionId == child.id){ sizeSetting[child.region == "top" || child.region == "bottom" ? "h" : "w"] = changedRegionSize; } // set size && adjust record of remaining space. // note that setting the width of a
may affect its height. if(pos == "top" || pos == "bottom"){ sizeSetting.w = dim.w; size(child, sizeSetting); dim.h -= child.h; if(pos == "top"){ dim.t += child.h; }else{ elmStyle.top = dim.t + dim.h + "px"; } }else if(pos == "left" || pos == "right"){ sizeSetting.h = dim.h; size(child, sizeSetting); dim.w -= child.w; if(pos == "left"){ dim.l += child.w; }else{ elmStyle.left = dim.l + dim.w + "px"; } }else if(pos == "client" || pos == "center"){ size(child, dim); } }); }, getChildren: function(){ return this._supportingWidgets; }, startup: function(){ if(this._started){ return; } this._started=true; var parts = this.defaultView?this.defaultView.split(","):"default"; var toId, subIds; toId= parts.shift(); subIds = parts.join(','); if(this.views[this.defaultView] && this.views[this.defaultView]["defaultView"]){ subIds = this.views[this.defaultView]["defaultView"]; } if(this.models && !this.loadedModels){ //if there is this.models config data and the models has not been loaded yet, //load models at here using the configuration data and load model logic in model.js this.loadedModels = model(this.models); bind(this.getChildren(), this.loadedModels); } //startup assumes all children are loaded into DOM before startup is called //startup will only start the current available children. var cid = this.id + "_" + toId; if (this.children[cid]) { var next = this.children[cid]; this.set("selectedChild", next); // If I am a not being controlled by a parent layout widget... var parent = this.getParent && this.getParent(); if (!(parent && parent.isLayoutContainer)) { // Do recursive sizing and layout of all my descendants // (passing in no argument to resize means that it has to glean the size itself) this.resize(); // Since my parent isn't a layout container, and my style *may be* width=height=100% // or something similar (either set directly or via a CSS class), // monitor when my size changes so that I can re-layout. // For browsers where I can't directly monitor when my size changes, // monitor when the viewport changes size, which *may* indicate a size change for me. this.connect(has("ie") ? this.domNode : dojo.global, 'onresize', function(){ // Using function(){} closure to ensure no arguments to resize. this.resize(); }); } array.forEach(this.getChildren(), function(child){ child.startup(); }); //transition to _startView if (this._startView && (this._startView != this.defaultView)) { this.transition(this._startView, {}); } } }, addChild: function(widget){ cls.add(widget.domNode, this.baseClass + "_child"); widget.region = "center";; dattr.set(widget.domNode,"region","center"); this._supportingWidgets.push(widget); dconstruct.place(widget.domNode,this.domNode); this.children[widget.id] = widget; return widget; }, removeChild: function(widget){ // summary: // Removes the passed widget instance from this widget but does // not destroy it. You can also pass in an integer indicating // the index within the container to remove if(widget){ var node = widget.domNode; if(node && node.parentNode){ node.parentNode.removeChild(node); // detach but don't destroy } return widget; } }, _setSelectedChildAttr: function(child,opts){ if (child !== this.selectedChild) { return deferred.when(child, dlang.hitch(this, function(child){ if (this.selectedChild){ if (this.selectedChild.deactivate){ this.selectedChild.deactivate(); } dstyle.set(this.selectedChild.domNode,"zIndex",25); } //dojo.style(child.domNode, { // "display": "", // "zIndex": 50, // "overflow": "auto" //}); this.selectedChild = child; dstyle.set(child.domNode, "display", ""); dstyle.set(child.domNode,"zIndex",50); this.selectedChild=child; if (this._started) { if (child.startup && !child._started){ child.startup(); }else if (child.activate){ child.activate(); } } this.layout(); })); } }, transition: function(transitionTo,opts){ //summary: // transitions from the currently visible scene to the defined scene. // it should determine what would be the best transition unless // an override in opts tells it to use a specific transitioning methodology // the transitionTo is a string in the form of [view]@[scene]. If // view is left of, the current scene will be transitioned to the default // view of the specified scene (eg @scene2), if the scene is left off // the app controller will instruct the active scene to the view (eg view1). If both // are supplied (view1@scene2), then the application should transition to the scene, // and instruct the scene to navigate to the view. var toId,subIds,next, current = this.selectedChild; console.log("scene", this.id, transitionTo); if (transitionTo){ var parts = transitionTo.split(","); toId= parts.shift(); subIds = parts.join(','); }else{ toId = this.defaultView; if(this.views[this.defaultView] && this.views[this.defaultView]["defaultView"]){ subIds = this.views[this.defaultView]["defaultView"]; } } next = this.loadChild(toId,subIds); if (!current){ //assume this.set(...) will return a promise object if child is first loaded //return nothing if child is already in array of this.children return this.set("selectedChild",next); } var transitionDeferred = new deferred(); deferred.when(next, dlang.hitch(this, function(next){ var promise; if (next!==current){ //TODO need to refactor here, when clicking fast, current will not be the //view we want to start transition. For example, during transition 1 -> 2 //if user click button to transition to 3 and then transition to 1. It will //perform transition 2 -> 3 and 2 -> 1 because current is always point to //2 during 1 -> 2 transition. var waitingList = anim.getWaitingList([next.domNode, current.domNode]); //update registry with deferred objects in animations of args. var transitionDefs = {}; transitionDefs[current.domNode.id] = anim.playing[current.domNode.id] = new deferred(); transitionDefs[next.domNode.id] = anim.playing[current.domNode.id] = new deferred(); deferred.when(waitingList, dojo.hitch(this, function(){ //assume next is already loaded so that this.set(...) will not return //a promise object. this.set(...) will handles the this.selectedChild, //activate or deactivate views and refresh layout. this.set("selectedChild", next); //publish /app/transition event //application can subscript this event to do user define operation like select TabBarButton, etc. connect.publish("/app/transition", [next, toId]); transit(current.domNode,next.domNode,dojo.mixin({},opts,{transition: this.defaultTransition || "none", transitionDefs: transitionDefs})).then(dlang.hitch(this, function(){ //dojo.style(current.domNode, "display", "none"); if (subIds && next.transition){ promise = next.transition(subIds,opts); } deferred.when(promise, function(){ transitionDeferred.resolve(); }); })); })); return; } //we didn't need to transition, but continue to propogate. if (subIds && next.transition){ promise = next.transition(subIds,opts); } deferred.when(promise, function(){ transitionDeferred.resolve(); }); })); return transitionDeferred; }, toString: function(){return this.id}, activate: function(){}, deactive: function(){} }); });