1 if(!dojo._hasResource["dijit.layout.BorderContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dijit.layout.BorderContainer"] = true;
3 dojo.provide("dijit.layout.BorderContainer");
5 dojo.require("dijit.layout._LayoutWidget");
6 dojo.require("dojo.cookie");
9 "dijit.layout.BorderContainer",
10 // [dijit._Widget, dijit._Container, dijit._Contained],
11 dijit.layout._LayoutWidget,
14 // Provides layout in 5 regions, a center and borders along its 4 sides.
17 // A BorderContainer is a box with a specified size (like style="width: 500px; height: 500px;"),
18 // that contains a child widget marked region="center" and optionally children widgets marked
19 // region equal to "top", "bottom", "leading", "trailing", "left" or "right".
20 // Children along the edges will be laid out according to width or height dimensions. The remaining
21 // space is designated for the center region.
22 // The outer size must be specified on the BorderContainer node. Width must be specified for the sides
23 // and height for the top and bottom, respectively. No dimensions should be specified on the center;
24 // it will fill the remaining space. Regions named "leading" and "trailing" may be used just like
25 // "left" and "right" except that they will be reversed in right-to-left environments.
26 // Optional splitters may be specified on the edge widgets only to make them resizable by the user.
30 // | html, body { height: 100%; width: 100%; }
32 // | <div dojoType="BorderContainer" design="sidebar" style="width: 100%; height: 100%">
33 // | <div dojoType="ContentPane" region="top">header text</div>
34 // | <div dojoType="ContentPane" region="right" style="width: 200px;">table of contents</div>
35 // | <div dojoType="ContentPane" region="center">client area</div>
39 // choose which design is used for the layout: "headline" (default) where the top and bottom extend
40 // the full width of the container, or "sidebar" where the left and right sides extend from top to bottom.
43 // liveSplitters: Boolean
44 // specifies whether splitters resize as you drag (true) or only upon mouseup (false)
48 // Save splitter positions in a cookie.
49 persist: false, // Boolean
51 // _splitterClass: String
52 // Optional hook to override the default Splitter widget used by BorderContainer
53 _splitterClass: "dijit.layout._Splitter",
55 postCreate: function(){
56 this.inherited(arguments);
59 this._splitterThickness = {};
60 dojo.addClass(this.domNode, "dijitBorderContainer");
64 if(this._started){ return; }
65 dojo.forEach(this.getChildren(), this._setupChild, this);
66 this.inherited(arguments);
69 _setupChild: function(/*Widget*/child){
70 var region = child.region;
72 // dojo.addClass(child.domNode, "dijitBorderContainerPane");
73 child.domNode.style.position = "absolute"; // bill says not to set this in CSS, since we can't keep others
74 // from destroying the class list
76 var ltr = this.isLeftToRight();
77 if(region == "leading"){ region = ltr ? "left" : "right"; }
78 if(region == "trailing"){ region = ltr ? "right" : "left"; }
80 this["_"+region] = child.domNode;
81 this["_"+region+"Widget"] = child;
84 var _Splitter = dojo.getObject(this._splitterClass);
85 var flip = {left:'right', right:'left', top:'bottom', bottom:'top', leading:'trailing', trailing:'leading'};
86 var oppNodeList = dojo.query('[region=' + flip[child.region] + ']', this.domNode);
87 var splitter = new _Splitter({ container: this, child: child, region: region,
88 oppNode: oppNodeList[0], live: this.liveSplitters });
89 this._splitters[region] = splitter.domNode;
90 dojo.place(splitter.domNode, child.domNode, "after");
91 this._computeSplitterThickness(region);
93 child.region = region;
97 _computeSplitterThickness: function(region){
98 var re = new RegExp("top|bottom");
99 this._splitterThickness[region] =
100 dojo.marginBox(this._splitters[region])[(re.test(region) ? 'h' : 'w')];
104 this._layoutChildren();
107 addChild: function(/*Widget*/ child, /*Integer?*/ insertIndex){
108 this.inherited(arguments);
109 this._setupChild(child);
111 this._layoutChildren(); //OPT
115 removeChild: function(/*Widget*/ child){
116 var region = child.region;
117 var splitter = this._splitters[region];
119 dijit.byNode(splitter).destroy();
120 delete this._splitters[region];
121 delete this._splitterThickness[region];
123 this.inherited(arguments);
124 delete this["_"+region];
125 delete this["_" +region+"Widget"];
127 this._layoutChildren(child.region);
131 _layoutChildren: function(/*String?*/changedRegion){
132 var sidebarLayout = (this.design == "sidebar");
133 var topHeight = 0, bottomHeight = 0, leftWidth = 0, rightWidth = 0;
134 var topStyle = {}, leftStyle = {}, rightStyle = {}, bottomStyle = {},
135 centerStyle = (this._center && this._center.style) || {};
137 var changedSide = /left|right/.test(changedRegion);
139 var layoutSides = !changedRegion || (!changedSide && !sidebarLayout);
140 var layoutTopBottom = !changedRegion || (changedSide && sidebarLayout);
142 topStyle = layoutTopBottom && this._top.style;
143 topHeight = dojo.marginBox(this._top).h;
146 leftStyle = layoutSides && this._left.style;
147 leftWidth = dojo.marginBox(this._left).w;
150 rightStyle = layoutSides && this._right.style;
151 rightWidth = dojo.marginBox(this._right).w;
154 bottomStyle = layoutTopBottom && this._bottom.style;
155 bottomHeight = dojo.marginBox(this._bottom).h;
158 var splitters = this._splitters;
159 var topSplitter = splitters.top;
160 var bottomSplitter = splitters.bottom;
161 var leftSplitter = splitters.left;
162 var rightSplitter = splitters.right;
163 var splitterThickness = this._splitterThickness;
164 var topSplitterThickness = splitterThickness.top || 0;
165 var leftSplitterThickness = splitterThickness.left || 0;
166 var rightSplitterThickness = splitterThickness.right || 0;
167 var bottomSplitterThickness = splitterThickness.bottom || 0;
169 // Check for race condition where CSS hasn't finished loading, so
170 // the splitter width == the viewport width (#5824)
171 if(leftSplitterThickness > 50 || rightSplitterThickness > 50){
172 setTimeout(dojo.hitch(this, function(){
173 for(var region in this._splitters){
174 this._computeSplitterThickness(region);
176 this._layoutChildren();
181 var splitterBounds = {
182 left: (sidebarLayout ? leftWidth + leftSplitterThickness: "0") + "px",
183 right: (sidebarLayout ? rightWidth + rightSplitterThickness: "0") + "px"
187 dojo.mixin(topSplitter.style, splitterBounds);
188 topSplitter.style.top = topHeight + "px";
192 dojo.mixin(bottomSplitter.style, splitterBounds);
193 bottomSplitter.style.bottom = bottomHeight + "px";
197 top: (sidebarLayout ? "0" : topHeight + topSplitterThickness) + "px",
198 bottom: (sidebarLayout ? "0" : bottomHeight + bottomSplitterThickness) + "px"
202 dojo.mixin(leftSplitter.style, splitterBounds);
203 leftSplitter.style.left = leftWidth + "px";
207 dojo.mixin(rightSplitter.style, splitterBounds);
208 rightSplitter.style.right = rightWidth + "px";
211 dojo.mixin(centerStyle, {
212 top: topHeight + topSplitterThickness + "px",
213 left: leftWidth + leftSplitterThickness + "px",
214 right: rightWidth + rightSplitterThickness + "px",
215 bottom: bottomHeight + bottomSplitterThickness + "px"
219 top: sidebarLayout ? "0" : centerStyle.top,
220 bottom: sidebarLayout ? "0" : centerStyle.bottom
222 dojo.mixin(leftStyle, bounds);
223 dojo.mixin(rightStyle, bounds);
224 leftStyle.left = rightStyle.right = topStyle.top = bottomStyle.bottom = "0";
226 topStyle.left = bottomStyle.left = leftWidth + (this.isLeftToRight() ? leftSplitterThickness : 0) + "px";
227 topStyle.right = bottomStyle.right = rightWidth + (this.isLeftToRight() ? 0 : rightSplitterThickness) + "px";
229 topStyle.left = topStyle.right = bottomStyle.left = bottomStyle.right = "0";
232 // Nodes in IE respond to t/l/b/r, and TEXTAREA doesn't respond in any browser
233 var janky = dojo.isIE || dojo.some(this.getChildren(), function(child){
234 return child.domNode.tagName == "TEXTAREA";
237 // Set the size of the children the old fashioned way, by calling
238 // childNode.resize({h: int, w: int}) for each child node)
240 var borderBox = function(n, b){
242 var s = dojo.getComputedStyle(n);
243 if(!b){ return dojo._getBorderBox(n, s); }
244 var me = dojo._getMarginExtents(n, s);
245 dojo._setMarginBox(n, b.l, b.t, b.w + me.w, b.h + me.h, s);
249 var resizeWidget = function(widget, dim){
251 widget.resize ? widget.resize(dim) : dojo.marginBox(widget.domNode, dim);
255 // TODO: use dim passed in to resize() (see _LayoutWidget.js resize())
256 // Then can make borderBox setBorderBox(), since no longer need to ever get the borderBox() size
257 var thisBorderBox = borderBox(this.domNode);
259 var containerHeight = thisBorderBox.h;
260 var middleHeight = containerHeight;
261 if(this._top){ middleHeight -= topHeight; }
262 if(this._bottom){ middleHeight -= bottomHeight; }
263 if(topSplitter){ middleHeight -= topSplitterThickness; }
264 if(bottomSplitter){ middleHeight -= bottomSplitterThickness; }
265 var centerDim = { h: middleHeight };
267 var sidebarHeight = sidebarLayout ? containerHeight : middleHeight;
268 if(leftSplitter){ leftSplitter.style.height = sidebarHeight; }
269 if(rightSplitter){ rightSplitter.style.height = sidebarHeight; }
270 resizeWidget(this._leftWidget, {h: sidebarHeight});
271 resizeWidget(this._rightWidget, {h: sidebarHeight});
273 var containerWidth = thisBorderBox.w;
274 var middleWidth = containerWidth;
275 if(this._left){ middleWidth -= leftWidth; }
276 if(this._right){ middleWidth -= rightWidth; }
277 if(leftSplitter){ middleWidth -= leftSplitterThickness; }
278 if(rightSplitter){ middleWidth -= rightSplitterThickness; }
279 centerDim.w = middleWidth;
281 var sidebarWidth = sidebarLayout ? middleWidth : containerWidth;
282 if(topSplitter){ topSplitter.style.width = sidebarWidth; }
283 if(bottomSplitter){ bottomSplitter.style.width = sidebarWidth; }
284 resizeWidget(this._topWidget, {w: sidebarWidth});
285 resizeWidget(this._bottomWidget, {w: sidebarWidth});
287 resizeWidget(this._centerWidget, centerDim);
290 // We've already sized the children by setting style.top/bottom/left/right...
291 // Now just need to call resize() on those children so they can re-layout themselves
293 // TODO: calling child.resize() without an argument is bad, because it forces
294 // the child to query it's own size (even though this function already knows
295 // the size), plus which querying the size of a node right after setting it
296 // is known to cause problems (incorrect answer or an exception).
297 // This is a setback from older layout widgets, which
298 // don't do that. See #3399, #2678, #3624 and #2955, #1988
302 resizeList[changedRegion] = resizeList.center = true;
303 if(/top|bottom/.test(changedRegion) && this.design != "sidebar"){
304 resizeList.left = resizeList.right = true;
305 }else if(/left|right/.test(changedRegion) && this.design == "sidebar"){
306 resizeList.top = resizeList.bottom = true;
310 dojo.forEach(this.getChildren(), function(child){
311 if(child.resize && (!changedRegion || child.region in resizeList)){
312 // console.log(this.id, ": resizing child id=" + child.id + " (region=" + child.region + "), style before resize is " +
313 // "{ t: " + child.domNode.style.top +
314 // ", b: " + child.domNode.style.bottom +
315 // ", l: " + child.domNode.style.left +
316 // ", r: " + child.domNode.style.right +
317 // ", w: " + child.domNode.style.width +
318 // ", h: " + child.domNode.style.height +
322 // console.log(this.id, ": after resize of child id=" + child.id + " (region=" + child.region + ") " +
323 // "{ t: " + child.domNode.style.top +
324 // ", b: " + child.domNode.style.bottom +
325 // ", l: " + child.domNode.style.left +
326 // ", r: " + child.domNode.style.right +
327 // ", w: " + child.domNode.style.width +
328 // ", h: " + child.domNode.style.height +
337 // This argument can be specified for the children of a BorderContainer.
338 // Since any widget can be specified as a LayoutContainer child, mix it
339 // into the base widget class. (This is a hack, but it's effective.)
340 dojo.extend(dijit._Widget, {
342 // "top", "bottom", "leading", "trailing", "left", "right", "center".
343 // See the BorderContainer description for details on this parameter.
356 dojo.require("dijit._Templated");
358 dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ],
367 // If true, the child's size changes and the child widget is redrawn as you drag the splitter;
368 // otherwise, the size doesn't change until you drop the splitter (by mouse-up)
371 // summary: A draggable spacer between two items in a BorderContainer
372 templateString: '<div class="dijitSplitter" dojoAttachEvent="onkeypress:_onKeyPress,onmousedown:_startDrag" tabIndex="0" waiRole="separator"><div class="dijitSplitterThumb"></div></div>',
374 postCreate: function(){
375 this.inherited(arguments);
376 this.horizontal = /top|bottom/.test(this.region);
377 dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V"));
378 // dojo.addClass(this.child.domNode, "dijitSplitterPane");
379 // dojo.setSelectable(this.domNode, false); //TODO is this necessary?
381 this._factor = /top|left/.test(this.region) ? 1 : -1;
382 this._minSize = this.child.minSize;
384 this._computeMaxSize();
385 //TODO: might be more accurate to recompute constraints on resize?
386 this.connect(this.container, "layout", dojo.hitch(this, this._computeMaxSize));
388 this._cookieName = this.container.id + "_" + this.region;
389 if(this.container.persist){
391 var persistSize = dojo.cookie(this._cookieName);
393 this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize;
398 _computeMaxSize: function(){
399 var dim = this.horizontal ? 'h' : 'w';
400 var available = dojo.contentBox(this.container.domNode)[dim] - (this.oppNode ? dojo.marginBox(this.oppNode)[dim] : 0);
401 this._maxSize = Math.min(this.child.maxSize, available);
404 _startDrag: function(e){
406 this.cover = dojo.doc.createElement('div');
407 dojo.addClass(this.cover, "dijitSplitterCover");
408 dojo.place(this.cover, this.child.domNode, "after");
410 this.cover.style.zIndex = 1;
413 // Safeguard in case the stop event was missed. Shouldn't be necessary if we always get the mouse up.
414 if(this.fake){ dojo._destroyElement(this.fake); }
415 if(!(this._resize = this.live)){ //TODO: disable live for IE6?
416 // create fake splitter to display at old position while we drag
417 (this.fake = this.domNode.cloneNode(true)).removeAttribute("id");
418 dojo.addClass(this.domNode, "dijitSplitterShadow");
419 dojo.place(this.fake, this.domNode, "after");
421 dojo.addClass(this.domNode, "dijitSplitterActive");
423 //Performance: load data info local vars for onmousevent function closure
424 var factor = this._factor,
426 min = this._minSize || 10;
427 var axis = this.horizontal ? "pageY" : "pageX";
428 var pageStart = e[axis];
429 var splitterStyle = this.domNode.style;
430 var dim = this.horizontal ? 'h' : 'w';
431 var childStart = dojo.marginBox(this.child.domNode)[dim];
432 var splitterStart = parseInt(this.domNode.style[this.region]);
433 var resize = this._resize;
434 var region = this.region;
436 var childNode = this.child.domNode;
437 var layoutFunc = dojo.hitch(this.container, this.container._layoutChildren);
439 var de = dojo.doc.body;
440 this._handlers = (this._handlers || []).concat([
441 dojo.connect(de, "onmousemove", this._drag = function(e, forceResize){
442 var delta = e[axis] - pageStart,
443 childSize = factor * delta + childStart,
444 boundChildSize = Math.max(Math.min(childSize, max), min);
446 if(resize || forceResize){
447 mb[dim] = boundChildSize;
448 // TODO: inefficient; we set the marginBox here and then immediately layoutFunc() needs to query it
449 dojo.marginBox(childNode, mb);
452 splitterStyle[region] = factor * delta + splitterStart + (boundChildSize - childSize) + "px";
454 dojo.connect(de, "onmouseup", this, "_stopDrag")
459 _stopDrag: function(e){
461 if(this.cover){ this.cover.style.zIndex = -1; }
462 if(this.fake){ dojo._destroyElement(this.fake); }
463 dojo.removeClass(this.domNode, "dijitSplitterActive");
464 dojo.removeClass(this.domNode, "dijitSplitterShadow");
465 this._drag(e); //TODO: redundant with onmousemove?
468 this._cleanupHandlers();
472 if(this.container.persist){
473 dojo.cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"]);
477 _cleanupHandlers: function(){
478 dojo.forEach(this._handlers, dojo.disconnect);
479 delete this._handlers;
482 _onKeyPress: function(/*Event*/ e){
483 // should we apply typematic to this?
485 var horizontal = this.horizontal;
489 case horizontal ? dk.UP_ARROW : dk.LEFT_ARROW:
492 case horizontal ? dk.DOWN_ARROW : dk.RIGHT_ARROW:
495 // this.inherited(arguments);
498 var childSize = dojo.marginBox(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick;
500 mb[ this.horizontal ? "h" : "w"] = Math.max(Math.min(childSize, this._maxSize), this._minSize);
501 dojo.marginBox(this.child.domNode, mb);
502 this.container._layoutChildren(this.region);
507 this._cleanupHandlers();
509 delete this.container;
511 this.inherited(arguments);