webui-aria2/js/libs/dojox/app/scene.js.uncompressed.js
2012-05-01 19:52:07 +08:00

590 lines
20 KiB
JavaScript

//>>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]:"<div></div>",
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 <div> 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(){}
});
});