dojo.provide("dijit.layout._LayoutWidget"); dojo.require("dijit._Widget"); dojo.require("dijit._Container"); dojo.require("dijit._Contained"); dojo.declare("dijit.layout._LayoutWidget", [dijit._Widget, dijit._Container, dijit._Contained], { // summary: // Base class for a _Container widget which is responsible for laying out its children. // Widgets which mixin this code must define layout() to lay out the children. // baseClass: [protected extension] String // This class name is applied to the widget's domNode // and also may be used to generate names for sub nodes, // like for example dijitTabContainer-content. baseClass: "dijitLayoutContainer", // isLayoutContainer: [private deprecated] Boolean // TODO: this is unused, but maybe it *should* be used for a child to // detect whether the parent is going to call resize() on it or not // (see calls to getParent() and resize() in this file) isLayoutContainer: true, postCreate: function(){ dojo.addClass(this.domNode, "dijitContainer"); dojo.addClass(this.domNode, this.baseClass); // TODO: this.inherited() }, startup: function(){ // summary: // Called after all the widgets have been instantiated and their // dom nodes have been inserted somewhere under dojo.doc.body. // // Widgets should override this method to do any initialization // dependent on other widgets existing, and then call // this superclass method to finish things off. // // startup() in subclasses shouldn't do anything // size related because the size of the widget hasn't been set yet. if(this._started){ return; } // TODO: seems like this code should be in _Container.startup(). // Then things that don't extend LayoutContainer (like GridContainer) // would get the behavior for free. dojo.forEach(this.getChildren(), function(child){ child.startup(); }); // If I am a top level widget if(!this.getParent || !this.getParent()){ // 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 is width=height=100% (or something similar), // then I need to watch when the window resizes, and size myself accordingly. // (Passing in no arguments to resize means that it has to glean the size itself.) // TODO: make one global listener to avoid getViewport() per widget. this._viewport = dijit.getViewport(); this.connect(dojo.global, 'onresize', function(){ var newViewport = dijit.getViewport(); if(newViewport.w != this._viewport.w || newViewport.h != this._viewport.h){ this._viewport = newViewport; this.resize(); } }); } this.inherited(arguments); }, resize: function(changeSize, resultSize){ // summary: // Call this to resize a widget, or after its size has changed. // description: // Change size mode: // When changeSize is specified, changes the marginBox of this widget // and forces it to relayout its contents accordingly. // changeSize may specify height, width, or both. // // If resultSize is specified it indicates the size the widget will // become after changeSize has been applied. // // Notification mode: // When changeSize is null, indicates that the caller has already changed // the size of the widget, or perhaps it changed because the browser // window was resized. Tells widget to relayout it's contents accordingly. // // If resultSize is also specified it indicates the size the widget has // become. // // In either mode, this method also: // 1. Sets this._borderBox and this._contentBox to the new size of // the widget. Queries the current domNode size if necessary. // 2. Calls layout() to resize contents (and maybe adjust child widgets). // // changeSize: Object? // Sets the widget to this margin-box size and position. // May include any/all of the following properties: // | {w: int, h: int, l: int, t: int} // // resultSize: Object? // The margin-box size of this widget after applying changeSize (if // changeSize is specified). If caller knows this size and // passes it in, we don't need to query the browser to get the size. // | {w: int, h: int} var node = this.domNode; // set margin box size, unless it wasn't specified, in which case use current size if(changeSize){ dojo.marginBox(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(dojo.marginBox(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 = dojo.getComputedStyle(node); var me = dojo._getMarginExtents(node, cs); var be = dojo._getBorderExtents(node, cs); var bb = (this._borderBox = { w: mb.w - (me.w + be.w), h: mb.h - (me.h + be.h) }); var pe = dojo._getPadExtents(node, cs); this._contentBox = { l: dojo._toPixelValue(node, cs.paddingLeft), t: dojo._toPixelValue(node, cs.paddingTop), w: bb.w - pe.w, h: bb.h - pe.h }; // Callback for widget to adjust size of it's children this.layout(); }, layout: function(){ // summary: // Widgets override this method to size and position their contents/children. // When this is called this._contentBox is guaranteed to be set (see resize()). // // This is called after startup(), and also when the widget's size has been // changed. // tags: // protected extension }, _setupChild: function(/*Widget*/child){ // summary: // Common setup for initial children and children which are added after startup // tags: // protected extension dojo.addClass(child.domNode, this.baseClass+"-child"); if(child.baseClass){ dojo.addClass(child.domNode, this.baseClass+"-"+child.baseClass); } }, addChild: function(/*Widget*/ child, /*Integer?*/ insertIndex){ // Overrides _Container.addChild() to call _setupChild() this.inherited(arguments); if(this._started){ this._setupChild(child); } }, removeChild: function(/*Widget*/ child){ // Overrides _Container.removeChild() to remove class added by _setupChild() dojo.removeClass(child.domNode, this.baseClass+"-child"); if(child.baseClass){ dojo.removeClass(child.domNode, this.baseClass+"-"+child.baseClass); } this.inherited(arguments); } } ); dijit.layout.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 = dojo.getComputedStyle(node); var me = dojo._getMarginExtents(node, cs); var pb = dojo._getPadBorderExtents(node, cs); return { l: dojo._toPixelValue(node, cs.paddingLeft), t: dojo._toPixelValue(node, cs.paddingTop), w: mb.w - (me.w + pb.w), h: mb.h - (me.h + pb.h) }; }; (function(){ var capitalize = function(word){ return word.substring(0,1).toUpperCase() + word.substring(1); }; var size = function(widget, dim){ // size the child widget.resize ? widget.resize(dim) : dojo.marginBox(widget.domNode, dim); // record child's size, but favor our own numbers when we have them. // the browser lies sometimes dojo.mixin(widget, dojo.marginBox(widget.domNode)); dojo.mixin(widget, dim); }; dijit.layout.layoutChildren = function(/*DomNode*/ container, /*Object*/ dim, /*Object[]*/ children){ // 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 like [ {domNode: foo, layoutAlign: "bottom" }, {domNode: bar, layoutAlign: "client"} ] // copy dim because we are going to modify it dim = dojo.mixin({}, dim); dojo.addClass(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. children = dojo.filter(children, function(item){ return item.layoutAlign != "client"; }) .concat(dojo.filter(children, function(item){ return item.layoutAlign == "client"; })); // set positions/sizes dojo.forEach(children, function(child){ var elm = child.domNode, pos = 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.bottom = elmStyle.right = "auto"; dojo.addClass(elm, "dijitAlign" + capitalize(pos)); // set size && adjust record of remaining space. // note that setting the width of a
may affect it's height. if(pos == "top" || pos == "bottom"){ size(child, { w: dim.w }); 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"){ size(child, { h: dim.h }); dim.w -= child.w; if(pos == "left"){ dim.l += child.w; }else{ elmStyle.left = dim.l + dim.w + "px"; } }else if(pos == "client"){ size(child, dim); } }); }; })();