//>>built define("dijit/layout/BorderContainer", [ "dojo/_base/array", // array.filter array.forEach array.map "dojo/cookie", // cookie "dojo/_base/declare", // declare "dojo/dom-class", // domClass.add domClass.remove domClass.toggle "dojo/dom-construct", // domConstruct.destroy domConstruct.place "dojo/dom-geometry", // domGeometry.marginBox "dojo/dom-style", // domStyle.style "dojo/_base/event", // event.stop "dojo/keys", "dojo/_base/lang", // lang.getObject lang.hitch "dojo/on", "dojo/touch", "dojo/_base/window", // win.body win.doc win.doc.createElement "../_WidgetBase", "../_Widget", "../_TemplatedMixin", "./_LayoutWidget", "./utils" // layoutUtils.layoutChildren ], function(array, cookie, declare, domClass, domConstruct, domGeometry, domStyle, event, keys, lang, on, touch, win, _WidgetBase, _Widget, _TemplatedMixin, _LayoutWidget, layoutUtils){ /*===== var _WidgetBase = dijit._WidgetBase; var _Widget = dijit._Widget; var _TemplatedMixin = dijit._TemplatedMixin; var _LayoutWidget = dijit.layout._LayoutWidget; =====*/ // module: // dijit/layout/BorderContainer // summary: // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides. var _Splitter = declare("dijit.layout._Splitter", [_Widget, _TemplatedMixin ], { // summary: // A draggable spacer between two items in a `dijit.layout.BorderContainer`. // description: // This is instantiated by `dijit.layout.BorderContainer`. Users should not // create it directly. // tags: // private /*===== // container: [const] dijit.layout.BorderContainer // Pointer to the parent BorderContainer container: null, // child: [const] dijit.layout._LayoutWidget // Pointer to the pane associated with this splitter child: null, // region: [const] String // Region of pane associated with this splitter. // "top", "bottom", "left", "right". region: null, =====*/ // live: [const] Boolean // If true, the child's size changes and the child widget is redrawn as you drag the splitter; // otherwise, the size doesn't change until you drop the splitter (by mouse-up) live: true, templateString: '', constructor: function(){ this._handlers = []; }, postMixInProperties: function(){ this.inherited(arguments); this.horizontal = /top|bottom/.test(this.region); this._factor = /top|left/.test(this.region) ? 1 : -1; this._cookieName = this.container.id + "_" + this.region; }, buildRendering: function(){ this.inherited(arguments); domClass.add(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V")); if(this.container.persist){ // restore old size var persistSize = cookie(this._cookieName); if(persistSize){ this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize; } } }, _computeMaxSize: function(){ // summary: // Return the maximum size that my corresponding pane can be set to var dim = this.horizontal ? 'h' : 'w', childSize = domGeometry.getMarginBox(this.child.domNode)[dim], center = array.filter(this.container.getChildren(), function(child){ return child.region == "center";})[0], spaceAvailable = domGeometry.getMarginBox(center.domNode)[dim]; // can expand until center is crushed to 0 return Math.min(this.child.maxSize, childSize + spaceAvailable); }, _startDrag: function(e){ if(!this.cover){ this.cover = win.doc.createElement('div'); domClass.add(this.cover, "dijitSplitterCover"); domConstruct.place(this.cover, this.child.domNode, "after"); } domClass.add(this.cover, "dijitSplitterCoverActive"); // Safeguard in case the stop event was missed. Shouldn't be necessary if we always get the mouse up. if(this.fake){ domConstruct.destroy(this.fake); } if(!(this._resize = this.live)){ //TODO: disable live for IE6? // create fake splitter to display at old position while we drag (this.fake = this.domNode.cloneNode(true)).removeAttribute("id"); domClass.add(this.domNode, "dijitSplitterShadow"); domConstruct.place(this.fake, this.domNode, "after"); } domClass.add(this.domNode, "dijitSplitterActive dijitSplitter" + (this.horizontal ? "H" : "V") + "Active"); if(this.fake){ domClass.remove(this.fake, "dijitSplitterHover dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover"); } //Performance: load data info local vars for onmousevent function closure var factor = this._factor, isHorizontal = this.horizontal, axis = isHorizontal ? "pageY" : "pageX", pageStart = e[axis], splitterStyle = this.domNode.style, dim = isHorizontal ? 'h' : 'w', childStart = domGeometry.getMarginBox(this.child.domNode)[dim], max = this._computeMaxSize(), min = this.child.minSize || 20, region = this.region, splitterAttr = region == "top" || region == "bottom" ? "top" : "left", // style attribute of splitter to adjust splitterStart = parseInt(splitterStyle[splitterAttr], 10), resize = this._resize, layoutFunc = lang.hitch(this.container, "_layoutChildren", this.child.id), de = win.doc; this._handlers = this._handlers.concat([ on(de, touch.move, this._drag = function(e, forceResize){ var delta = e[axis] - pageStart, childSize = factor * delta + childStart, boundChildSize = Math.max(Math.min(childSize, max), min); if(resize || forceResize){ layoutFunc(boundChildSize); } // TODO: setting style directly (usually) sets content box size, need to set margin box size splitterStyle[splitterAttr] = delta + splitterStart + factor*(boundChildSize - childSize) + "px"; }), on(de, "dragstart", event.stop), on(win.body(), "selectstart", event.stop), on(de, touch.release, lang.hitch(this, "_stopDrag")) ]); event.stop(e); }, _onMouse: function(e){ // summary: // Handler for onmouseenter / onmouseleave events var o = (e.type == "mouseover" || e.type == "mouseenter"); domClass.toggle(this.domNode, "dijitSplitterHover", o); domClass.toggle(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover", o); }, _stopDrag: function(e){ try{ if(this.cover){ domClass.remove(this.cover, "dijitSplitterCoverActive"); } if(this.fake){ domConstruct.destroy(this.fake); } domClass.remove(this.domNode, "dijitSplitterActive dijitSplitter" + (this.horizontal ? "H" : "V") + "Active dijitSplitterShadow"); this._drag(e); //TODO: redundant with onmousemove? this._drag(e, true); }finally{ this._cleanupHandlers(); delete this._drag; } if(this.container.persist){ cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"], {expires:365}); } }, _cleanupHandlers: function(){ var h; while(h = this._handlers.pop()){ h.remove(); } }, _onKeyPress: function(/*Event*/ e){ // should we apply typematic to this? this._resize = true; var horizontal = this.horizontal; var tick = 1; switch(e.charOrCode){ case horizontal ? keys.UP_ARROW : keys.LEFT_ARROW: tick *= -1; // break; case horizontal ? keys.DOWN_ARROW : keys.RIGHT_ARROW: break; default: // this.inherited(arguments); return; } var childSize = domGeometry.getMarginSize(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick; this.container._layoutChildren(this.child.id, Math.max(Math.min(childSize, this._computeMaxSize()), this.child.minSize)); event.stop(e); }, destroy: function(){ this._cleanupHandlers(); delete this.child; delete this.container; delete this.cover; delete this.fake; this.inherited(arguments); } }); var _Gutter = declare("dijit.layout._Gutter", [_Widget, _TemplatedMixin], { // summary: // Just a spacer div to separate side pane from center pane. // Basically a trick to lookup the gutter/splitter width from the theme. // description: // Instantiated by `dijit.layout.BorderContainer`. Users should not // create directly. // tags: // private templateString: '', postMixInProperties: function(){ this.inherited(arguments); this.horizontal = /top|bottom/.test(this.region); }, buildRendering: function(){ this.inherited(arguments); domClass.add(this.domNode, "dijitGutter" + (this.horizontal ? "H" : "V")); } }); var BorderContainer = declare("dijit.layout.BorderContainer", _LayoutWidget, { // summary: // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides. // // description: // A BorderContainer is a box with a specified size, such as style="width: 500px; height: 500px;", // that contains a child widget marked region="center" and optionally children widgets marked // region equal to "top", "bottom", "leading", "trailing", "left" or "right". // Children along the edges will be laid out according to width or height dimensions and may // include optional splitters (splitter="true") to make them resizable by the user. The remaining // space is designated for the center region. // // The outer size must be specified on the BorderContainer node. Width must be specified for the sides // and height for the top and bottom, respectively. No dimensions should be specified on the center; // it will fill the remaining space. Regions named "leading" and "trailing" may be used just like // "left" and "right" except that they will be reversed in right-to-left environments. // // For complex layouts, multiple children can be specified for a single region. In this case, the // layoutPriority flag on the children determines which child is closer to the edge (low layoutPriority) // and which child is closer to the center (high layoutPriority). layoutPriority can also be used // instead of the design attribute to control layout precedence of horizontal vs. vertical panes. // example: // |
// |
header text
// |
table of contents
// |
client area
// |
// design: String // Which design is used for the layout: // - "headline" (default) where the top and bottom extend // the full width of the container // - "sidebar" where the left and right sides extend from top to bottom. design: "headline", // gutters: [const] Boolean // Give each pane a border and margin. // Margin determined by domNode.paddingLeft. // When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing. gutters: true, // liveSplitters: [const] Boolean // Specifies whether splitters resize as you drag (true) or only upon mouseup (false) liveSplitters: true, // persist: Boolean // Save splitter positions in a cookie. persist: false, baseClass: "dijitBorderContainer", // _splitterClass: Function||String // Optional hook to override the default Splitter widget used by BorderContainer _splitterClass: _Splitter, postMixInProperties: function(){ // change class name to indicate that BorderContainer is being used purely for // layout (like LayoutContainer) rather than for pretty formatting. if(!this.gutters){ this.baseClass += "NoGutter"; } this.inherited(arguments); }, startup: function(){ if(this._started){ return; } array.forEach(this.getChildren(), this._setupChild, this); this.inherited(arguments); }, _setupChild: function(/*dijit._Widget*/ child){ // Override _LayoutWidget._setupChild(). var region = child.region; if(region){ this.inherited(arguments); domClass.add(child.domNode, this.baseClass+"Pane"); var ltr = this.isLeftToRight(); if(region == "leading"){ region = ltr ? "left" : "right"; } if(region == "trailing"){ region = ltr ? "right" : "left"; } // Create draggable splitter for resizing pane, // or alternately if splitter=false but BorderContainer.gutters=true then // insert dummy div just for spacing if(region != "center" && (child.splitter || this.gutters) && !child._splitterWidget){ var _Splitter = child.splitter ? this._splitterClass : _Gutter; if(lang.isString(_Splitter)){ _Splitter = lang.getObject(_Splitter); // for back-compat, remove in 2.0 } var splitter = new _Splitter({ id: child.id + "_splitter", container: this, child: child, region: region, live: this.liveSplitters }); splitter.isSplitter = true; child._splitterWidget = splitter; domConstruct.place(splitter.domNode, child.domNode, "after"); // Splitters aren't added as Contained children, so we need to call startup explicitly splitter.startup(); } child.region = region; // TODO: technically wrong since it overwrites "trailing" with "left" etc. } }, layout: function(){ // Implement _LayoutWidget.layout() virtual method. this._layoutChildren(); }, addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ // Override _LayoutWidget.addChild(). this.inherited(arguments); if(this._started){ this.layout(); //OPT } }, removeChild: function(/*dijit._Widget*/ child){ // Override _LayoutWidget.removeChild(). var region = child.region; var splitter = child._splitterWidget; if(splitter){ splitter.destroy(); delete child._splitterWidget; } this.inherited(arguments); if(this._started){ this._layoutChildren(); } // Clean up whatever style changes we made to the child pane. // Unclear how height and width should be handled. domClass.remove(child.domNode, this.baseClass+"Pane"); domStyle.set(child.domNode, { top: "auto", bottom: "auto", left: "auto", right: "auto", position: "static" }); domStyle.set(child.domNode, region == "top" || region == "bottom" ? "width" : "height", "auto"); }, getChildren: function(){ // Override _LayoutWidget.getChildren() to only return real children, not the splitters. return array.filter(this.inherited(arguments), function(widget){ return !widget.isSplitter; }); }, // TODO: remove in 2.0 getSplitter: function(/*String*/region){ // summary: // Returns the widget responsible for rendering the splitter associated with region // tags: // deprecated return array.filter(this.getChildren(), function(child){ return child.region == region; })[0]._splitterWidget; }, resize: function(newSize, currentSize){ // Overrides _LayoutWidget.resize(). // resetting potential padding to 0px to provide support for 100% width/height + padding // TODO: this hack doesn't respect the box model and is a temporary fix if(!this.cs || !this.pe){ var node = this.domNode; this.cs = domStyle.getComputedStyle(node); this.pe = domGeometry.getPadExtents(node, this.cs); this.pe.r = domStyle.toPixelValue(node, this.cs.paddingRight); this.pe.b = domStyle.toPixelValue(node, this.cs.paddingBottom); domStyle.set(node, "padding", "0px"); } this.inherited(arguments); }, _layoutChildren: function(/*String?*/ changedChildId, /*Number?*/ changedChildSize){ // summary: // This is the main routine for setting size/position of each child. // description: // With no arguments, measures the height of top/bottom panes, the width // of left/right panes, and then sizes all panes accordingly. // // With changedRegion specified (as "left", "top", "bottom", or "right"), // it changes that region's width/height to changedRegionSize and // then resizes other regions that were affected. // changedChildId: // Id of the child which should be resized because splitter was dragged. // changedChildSize: // The new width/height (in pixels) to make specified child if(!this._borderBox || !this._borderBox.h){ // We are currently hidden, or we haven't been sized by our parent yet. // Abort. Someone will resize us later. return; } // Generate list of wrappers of my children in the order that I want layoutChildren() // to process them (i.e. from the outside to the inside) var wrappers = array.map(this.getChildren(), function(child, idx){ return { pane: child, weight: [ child.region == "center" ? Infinity : 0, child.layoutPriority, (this.design == "sidebar" ? 1 : -1) * (/top|bottom/.test(child.region) ? 1 : -1), idx ] }; }, this); wrappers.sort(function(a, b){ var aw = a.weight, bw = b.weight; for(var i=0; i