dojox
Version:
Dojo eXtensions, a rollup of many useful sub-projects and varying states of maturity – from very stable and robust, to alpha and experimental. See individual projects contain README files for details.
241 lines (221 loc) • 9.3 kB
JavaScript
define(["dojo/_base/declare", "dojo/_base/lang", "dojo/_base/array", "dojo/_base/window",
"dojo/query", "dojo/dom-geometry", "dojo/dom-attr", "dojo/dom-style", "dijit/registry",
"./LayoutBase", "../utils/layout", "../utils/constraints", "dojo/sniff"],
function(declare, lang, array, win, query, domGeom, domAttr, domStyle, registry, LayoutBase, layout, constraints, has){
// module:
// dojox/app/controllers/Layout
// summary:
// Extends LayoutBase which binds "app-initLayout", "app-layoutView" and "app-resize" events on application instance.
return declare("dojox.app.controllers.Layout", LayoutBase, {
constructor: function(app, events){
// summary:
// bind "app-initLayout" and "app-layoutView" events on application instance.
//
// app:
// dojox/app application instance.
// events:
// {event : handler}
},
onResize: function(){
this._doResize(this.app);
// this is needed to resize the children on an orientation change or a resize of the browser.
// it was being done in _doResize, but was not needed for every call to _doResize.
this.resizeSelectedChildren(this.app);
},
resizeSelectedChildren: function(w){
for(var hash in w.selectedChildren){ // need this to handle all selectedChildren
if(w.selectedChildren[hash] && w.selectedChildren[hash].domNode){
this.app.log("in Layout resizeSelectedChildren calling resizeSelectedChildren calling _doResize for w.selectedChildren[hash].id="+w.selectedChildren[hash].id);
this._doResize(w.selectedChildren[hash]);
// Call resize on child widgets, needed to get the scrollableView to resize correctly initially
array.forEach(w.selectedChildren[hash].domNode.children, function(child){
if(registry.byId(child.id) && registry.byId(child.id).resize){
registry.byId(child.id).resize();
}
});
this.resizeSelectedChildren(w.selectedChildren[hash]);
}
}
},
initLayout: function(event){
// summary:
// Response to dojox/app "app-initLayout" event.
//
// example:
// Use emit to trigger "app-initLayout" event, and this function will respond to the event. For example:
// | this.app.emit("app-initLayout", view);
//
// event: Object
// | {"view": view, "callback": function(){}};
this.app.log("in app/controllers/Layout.initLayout event=",event);
this.app.log("in app/controllers/Layout.initLayout event.view.parent.name=[",event.view.parent.name,"]");
if (!event.view.domNode.parentNode || (has("ie") == 8 && !event.view.domNode.parentElement)) {
if(this.app.useConfigOrder){
event.view.parent.domNode.appendChild(event.view.domNode);
}else{
this.addViewToParentDomByConstraint(event);
}
}
domAttr.set(event.view.domNode, "data-app-constraint", event.view.constraint);
this.inherited(arguments);
},
addViewToParentDomByConstraint: function(event){
// summary:
// Insert the view domNode into the parent domNode based upon the constraints.
// It should layout the children in this order: top, left, center, right, bottom
// Unless it is rtl then it should layout the children in this order: top, right, center, left, bottom
//
// event: Object
// | {"parent":parent, "view":view, "removeView": boolean}
var newViewConstraint = event.view.constraint;
if(newViewConstraint === "bottom"){ // if new is bottom always place last
event.view.parent.domNode.appendChild(event.view.domNode);
}else if(newViewConstraint === "top"){ // if new is top always place first
event.view.parent.domNode.insertBefore(event.view.domNode, event.view.parent.domNode.firstChild);
}else{ // need to compare new constraint to the previous ones
if(event.view.parent.domNode.children.length > 0){ // parent node has children, check constraints
// in this loop if previous is top or left skip it and look for next child, otherwise process it
for(var childIndex in event.view.parent.domNode.children){
var child = event.view.parent.domNode.children[childIndex];
var dir = domStyle.get(event.view.parent.domNode,"direction");
var isltr = (dir === "ltr");
var LEADING_VIEW = isltr ? "left" : "right";
var TRAILING_VIEW = isltr ? "right" : "left";
if(child.getAttribute && child.getAttribute("data-app-constraint")) {
var previousViewConstraint = child.getAttribute("data-app-constraint");
// if previous is bottom or previous is Trailing
// or previous is not top and newView is Leading we need to insert before this child
if(previousViewConstraint === "bottom" ||
(previousViewConstraint === TRAILING_VIEW) ||
(previousViewConstraint !== "top" &&
(newViewConstraint === LEADING_VIEW))){
event.view.parent.domNode.insertBefore(event.view.domNode, child);
break;
}
}
}
}
}
// if the domNode was not added to the parent yet add it to the end now
if (!event.view.domNode.parentNode || (has("ie") == 8 && !event.view.domNode.parentElement)) {
event.view.parent.domNode.appendChild(event.view.domNode);
}
},
_doResize: function(view){
// summary:
// resize view.
//
// view: Object
// view instance needs to do layout.
var node = view.domNode;
if(!node){
this.app.log("Warning - View has not been loaded, in Layout _doResize view.domNode is not set for view.id="+view.id+" view=",view);
return;
}
// 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 = {};
if( !("h" in mb) || !("w" in mb) ){
mb = lang.mixin(domGeom.getMarginBox(node), mb); // just use dojo/_base/html.marginBox() to fill in missing values
}
// Compute and save the size of my border box and content box
// (w/out calling dojo/_base/html.contentBox() since that may fail if size was recently set)
if(view !== this.app){
var cs = domStyle.getComputedStyle(node);
var me = domGeom.getMarginExtents(node, cs);
var be = domGeom.getBorderExtents(node, cs);
var bb = (view._borderBox = {
w: mb.w - (me.w + be.w),
h: mb.h - (me.h + be.h)
});
var pe = domGeom.getPadExtents(node, cs);
view._contentBox = {
l: domStyle.toPixelValue(node, cs.paddingLeft),
t: domStyle.toPixelValue(node, cs.paddingTop),
w: bb.w - pe.w,
h: bb.h - pe.h
};
}else{
// if we are layouting the top level app the above code does not work when hiding address bar
// so let's use similar code to dojo mobile.
view._contentBox = {
l: 0,
t: 0,
h: win.global.innerHeight || win.doc.documentElement.clientHeight,
w: win.global.innerWidth || win.doc.documentElement.clientWidth
};
}
this.inherited(arguments);
},
layoutView: function(event){
// summary:
// Response to dojox/app "app-layoutView" event.
//
// example:
// Use emit to trigger "app-layoutView" event, and this function will response the event. For example:
// | this.app.emit("app-layoutView", view);
//
// event: Object
// | {"parent":parent, "view":view, "removeView": boolean}
if(event.view){
this.inherited(arguments);
// normally when called from transition doResize will be false, and the resize will only be done when the app-resize event is fired
if(event.doResize){
this._doResize(event.parent || this.app);
this._doResize(event.view);
}
}
},
_doLayout: function(view){
// summary:
// do view layout.
//
// view: Object
// view instance needs to do layout.
if(!view){
console.warn("layout empty view.");
return;
}
this.app.log("in Layout _doLayout called for view.id="+view.id+" view=",view);
var children;
// TODO: probably need to handle selectedChildren here, not just selected child...
// TODO: why are we passing view here? not parent? This call does not seem logical?
var selectedChild = constraints.getSelectedChild(view, view.constraint);
if(selectedChild && selectedChild.isFullScreen){
console.warn("fullscreen sceen layout");
/*
fullScreenScene=true;
children=[{domNode: selectedChild.domNode,constraint: "center"}];
query("> [constraint]",this.domNode).forEach(function(c){
if(selectedChild.domNode!==c.domNode){
dstyle(c.domNode,"display","none");
}
})
*/
}else{
children = query("> [data-app-constraint]", view.domNode).map(function(node){
var w = registry.getEnclosingWidget(node);
if(w){
w._constraint = domAttr.get(node, "data-app-constraint");
return w;
}
return {
domNode: node,
_constraint: domAttr.get(node, "data-app-constraint")
};
});
if(selectedChild){
children = array.filter(children, function(c){
// do not need to set display none here it is set in select.
return c.domNode && c._constraint;
}, view);
}
}
// We don't need to layout children if this._contentBox is null for the operation will do nothing.
if(view._contentBox){
layout.layoutChildren(view.domNode, view._contentBox, children);
}
}
});
});