1 define("dijit/layout/BorderContainer", [
2 "dojo/_base/array", // array.filter array.forEach array.map
3 "dojo/cookie", // cookie
4 "dojo/_base/declare", // declare
5 "dojo/dom-class", // domClass.add domClass.remove domClass.toggle
6 "dojo/dom-construct", // domConstruct.destroy domConstruct.place
7 "dojo/dom-geometry", // domGeometry.marginBox
8 "dojo/dom-style", // domStyle.style
9 "dojo/_base/event", // event.stop
11 "dojo/_base/lang", // lang.getObject lang.hitch
14 "dojo/_base/window", // win.body win.doc win.doc.createElement
19 "./utils" // layoutUtils.layoutChildren
20 ], function(array, cookie, declare, domClass, domConstruct, domGeometry, domStyle, event, keys, lang, on, touch, win,
21 _WidgetBase, _Widget, _TemplatedMixin, _LayoutWidget, layoutUtils){
24 var _WidgetBase = dijit._WidgetBase;
25 var _Widget = dijit._Widget;
26 var _TemplatedMixin = dijit._TemplatedMixin;
27 var _LayoutWidget = dijit.layout._LayoutWidget;
31 // dijit/layout/BorderContainer
33 // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides.
35 var _Splitter = declare("dijit.layout._Splitter", [_Widget, _TemplatedMixin ],
38 // A draggable spacer between two items in a `dijit.layout.BorderContainer`.
40 // This is instantiated by `dijit.layout.BorderContainer`. Users should not
41 // create it directly.
46 // container: [const] dijit.layout.BorderContainer
47 // Pointer to the parent BorderContainer
50 // child: [const] dijit.layout._LayoutWidget
51 // Pointer to the pane associated with this splitter
54 // region: [const] String
55 // Region of pane associated with this splitter.
56 // "top", "bottom", "left", "right".
60 // live: [const] Boolean
61 // If true, the child's size changes and the child widget is redrawn as you drag the splitter;
62 // otherwise, the size doesn't change until you drop the splitter (by mouse-up)
65 templateString: '<div class="dijitSplitter" data-dojo-attach-event="onkeypress:_onKeyPress,press:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" role="separator"><div class="dijitSplitterThumb"></div></div>',
67 constructor: function(){
71 postMixInProperties: function(){
72 this.inherited(arguments);
74 this.horizontal = /top|bottom/.test(this.region);
75 this._factor = /top|left/.test(this.region) ? 1 : -1;
76 this._cookieName = this.container.id + "_" + this.region;
79 buildRendering: function(){
80 this.inherited(arguments);
82 domClass.add(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V"));
84 if(this.container.persist){
86 var persistSize = cookie(this._cookieName);
88 this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize;
93 _computeMaxSize: function(){
95 // Return the maximum size that my corresponding pane can be set to
97 var dim = this.horizontal ? 'h' : 'w',
98 childSize = domGeometry.getMarginBox(this.child.domNode)[dim],
99 center = array.filter(this.container.getChildren(), function(child){ return child.region == "center";})[0],
100 spaceAvailable = domGeometry.getMarginBox(center.domNode)[dim]; // can expand until center is crushed to 0
102 return Math.min(this.child.maxSize, childSize + spaceAvailable);
105 _startDrag: function(e){
107 this.cover = win.doc.createElement('div');
108 domClass.add(this.cover, "dijitSplitterCover");
109 domConstruct.place(this.cover, this.child.domNode, "after");
111 domClass.add(this.cover, "dijitSplitterCoverActive");
113 // Safeguard in case the stop event was missed. Shouldn't be necessary if we always get the mouse up.
114 if(this.fake){ domConstruct.destroy(this.fake); }
115 if(!(this._resize = this.live)){ //TODO: disable live for IE6?
116 // create fake splitter to display at old position while we drag
117 (this.fake = this.domNode.cloneNode(true)).removeAttribute("id");
118 domClass.add(this.domNode, "dijitSplitterShadow");
119 domConstruct.place(this.fake, this.domNode, "after");
121 domClass.add(this.domNode, "dijitSplitterActive dijitSplitter" + (this.horizontal ? "H" : "V") + "Active");
123 domClass.remove(this.fake, "dijitSplitterHover dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover");
126 //Performance: load data info local vars for onmousevent function closure
127 var factor = this._factor,
128 isHorizontal = this.horizontal,
129 axis = isHorizontal ? "pageY" : "pageX",
131 splitterStyle = this.domNode.style,
132 dim = isHorizontal ? 'h' : 'w',
133 childStart = domGeometry.getMarginBox(this.child.domNode)[dim],
134 max = this._computeMaxSize(),
135 min = this.child.minSize || 20,
136 region = this.region,
137 splitterAttr = region == "top" || region == "bottom" ? "top" : "left", // style attribute of splitter to adjust
138 splitterStart = parseInt(splitterStyle[splitterAttr], 10),
139 resize = this._resize,
140 layoutFunc = lang.hitch(this.container, "_layoutChildren", this.child.id),
143 this._handlers = this._handlers.concat([
144 on(de, touch.move, this._drag = function(e, forceResize){
145 var delta = e[axis] - pageStart,
146 childSize = factor * delta + childStart,
147 boundChildSize = Math.max(Math.min(childSize, max), min);
149 if(resize || forceResize){
150 layoutFunc(boundChildSize);
152 // TODO: setting style directly (usually) sets content box size, need to set margin box size
153 splitterStyle[splitterAttr] = delta + splitterStart + factor*(boundChildSize - childSize) + "px";
155 on(de, "dragstart", event.stop),
156 on(win.body(), "selectstart", event.stop),
157 on(de, touch.release, lang.hitch(this, "_stopDrag"))
162 _onMouse: function(e){
164 // Handler for onmouseenter / onmouseleave events
165 var o = (e.type == "mouseover" || e.type == "mouseenter");
166 domClass.toggle(this.domNode, "dijitSplitterHover", o);
167 domClass.toggle(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover", o);
170 _stopDrag: function(e){
173 domClass.remove(this.cover, "dijitSplitterCoverActive");
175 if(this.fake){ domConstruct.destroy(this.fake); }
176 domClass.remove(this.domNode, "dijitSplitterActive dijitSplitter"
177 + (this.horizontal ? "H" : "V") + "Active dijitSplitterShadow");
178 this._drag(e); //TODO: redundant with onmousemove?
181 this._cleanupHandlers();
185 if(this.container.persist){
186 cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"], {expires:365});
190 _cleanupHandlers: function(){
192 while(h = this._handlers.pop()){ h.remove(); }
195 _onKeyPress: function(/*Event*/ e){
196 // should we apply typematic to this?
198 var horizontal = this.horizontal;
200 switch(e.charOrCode){
201 case horizontal ? keys.UP_ARROW : keys.LEFT_ARROW:
204 case horizontal ? keys.DOWN_ARROW : keys.RIGHT_ARROW:
207 // this.inherited(arguments);
210 var childSize = domGeometry.getMarginSize(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick;
211 this.container._layoutChildren(this.child.id, Math.max(Math.min(childSize, this._computeMaxSize()), this.child.minSize));
216 this._cleanupHandlers();
218 delete this.container;
221 this.inherited(arguments);
225 var _Gutter = declare("dijit.layout._Gutter", [_Widget, _TemplatedMixin],
228 // Just a spacer div to separate side pane from center pane.
229 // Basically a trick to lookup the gutter/splitter width from the theme.
231 // Instantiated by `dijit.layout.BorderContainer`. Users should not
236 templateString: '<div class="dijitGutter" role="presentation"></div>',
238 postMixInProperties: function(){
239 this.inherited(arguments);
240 this.horizontal = /top|bottom/.test(this.region);
243 buildRendering: function(){
244 this.inherited(arguments);
245 domClass.add(this.domNode, "dijitGutter" + (this.horizontal ? "H" : "V"));
249 var BorderContainer = declare("dijit.layout.BorderContainer", _LayoutWidget, {
251 // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides.
254 // A BorderContainer is a box with a specified size, such as style="width: 500px; height: 500px;",
255 // that contains a child widget marked region="center" and optionally children widgets marked
256 // region equal to "top", "bottom", "leading", "trailing", "left" or "right".
257 // Children along the edges will be laid out according to width or height dimensions and may
258 // include optional splitters (splitter="true") to make them resizable by the user. The remaining
259 // space is designated for the center region.
261 // The outer size must be specified on the BorderContainer node. Width must be specified for the sides
262 // and height for the top and bottom, respectively. No dimensions should be specified on the center;
263 // it will fill the remaining space. Regions named "leading" and "trailing" may be used just like
264 // "left" and "right" except that they will be reversed in right-to-left environments.
266 // For complex layouts, multiple children can be specified for a single region. In this case, the
267 // layoutPriority flag on the children determines which child is closer to the edge (low layoutPriority)
268 // and which child is closer to the center (high layoutPriority). layoutPriority can also be used
269 // instead of the design attribute to control layout precedence of horizontal vs. vertical panes.
271 // | <div data-dojo-type="dijit.layout.BorderContainer" data-dojo-props="design: 'sidebar', gutters: false"
272 // | style="width: 400px; height: 300px;">
273 // | <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region: 'top'">header text</div>
274 // | <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region: 'right', splitter: true" style="width: 200px;">table of contents</div>
275 // | <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region: 'center'">client area</div>
279 // Which design is used for the layout:
280 // - "headline" (default) where the top and bottom extend
281 // the full width of the container
282 // - "sidebar" where the left and right sides extend from top to bottom.
285 // gutters: [const] Boolean
286 // Give each pane a border and margin.
287 // Margin determined by domNode.paddingLeft.
288 // When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing.
291 // liveSplitters: [const] Boolean
292 // Specifies whether splitters resize as you drag (true) or only upon mouseup (false)
296 // Save splitter positions in a cookie.
299 baseClass: "dijitBorderContainer",
301 // _splitterClass: Function||String
302 // Optional hook to override the default Splitter widget used by BorderContainer
303 _splitterClass: _Splitter,
305 postMixInProperties: function(){
306 // change class name to indicate that BorderContainer is being used purely for
307 // layout (like LayoutContainer) rather than for pretty formatting.
309 this.baseClass += "NoGutter";
311 this.inherited(arguments);
315 if(this._started){ return; }
316 array.forEach(this.getChildren(), this._setupChild, this);
317 this.inherited(arguments);
320 _setupChild: function(/*dijit._Widget*/ child){
321 // Override _LayoutWidget._setupChild().
323 var region = child.region;
325 this.inherited(arguments);
327 domClass.add(child.domNode, this.baseClass+"Pane");
329 var ltr = this.isLeftToRight();
330 if(region == "leading"){ region = ltr ? "left" : "right"; }
331 if(region == "trailing"){ region = ltr ? "right" : "left"; }
333 // Create draggable splitter for resizing pane,
334 // or alternately if splitter=false but BorderContainer.gutters=true then
335 // insert dummy div just for spacing
336 if(region != "center" && (child.splitter || this.gutters) && !child._splitterWidget){
337 var _Splitter = child.splitter ? this._splitterClass : _Gutter;
338 if(lang.isString(_Splitter)){
339 _Splitter = lang.getObject(_Splitter); // for back-compat, remove in 2.0
341 var splitter = new _Splitter({
342 id: child.id + "_splitter",
346 live: this.liveSplitters
348 splitter.isSplitter = true;
349 child._splitterWidget = splitter;
351 domConstruct.place(splitter.domNode, child.domNode, "after");
353 // Splitters aren't added as Contained children, so we need to call startup explicitly
356 child.region = region; // TODO: technically wrong since it overwrites "trailing" with "left" etc.
361 // Implement _LayoutWidget.layout() virtual method.
362 this._layoutChildren();
365 addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
366 // Override _LayoutWidget.addChild().
367 this.inherited(arguments);
373 removeChild: function(/*dijit._Widget*/ child){
374 // Override _LayoutWidget.removeChild().
376 var region = child.region;
377 var splitter = child._splitterWidget;
380 delete child._splitterWidget;
382 this.inherited(arguments);
385 this._layoutChildren();
387 // Clean up whatever style changes we made to the child pane.
388 // Unclear how height and width should be handled.
389 domClass.remove(child.domNode, this.baseClass+"Pane");
390 domStyle.set(child.domNode, {
397 domStyle.set(child.domNode, region == "top" || region == "bottom" ? "width" : "height", "auto");
400 getChildren: function(){
401 // Override _LayoutWidget.getChildren() to only return real children, not the splitters.
402 return array.filter(this.inherited(arguments), function(widget){
403 return !widget.isSplitter;
407 // TODO: remove in 2.0
408 getSplitter: function(/*String*/region){
410 // Returns the widget responsible for rendering the splitter associated with region
413 return array.filter(this.getChildren(), function(child){
414 return child.region == region;
415 })[0]._splitterWidget;
418 resize: function(newSize, currentSize){
419 // Overrides _LayoutWidget.resize().
421 // resetting potential padding to 0px to provide support for 100% width/height + padding
422 // TODO: this hack doesn't respect the box model and is a temporary fix
423 if(!this.cs || !this.pe){
424 var node = this.domNode;
425 this.cs = domStyle.getComputedStyle(node);
426 this.pe = domGeometry.getPadExtents(node, this.cs);
427 this.pe.r = domStyle.toPixelValue(node, this.cs.paddingRight);
428 this.pe.b = domStyle.toPixelValue(node, this.cs.paddingBottom);
430 domStyle.set(node, "padding", "0px");
433 this.inherited(arguments);
436 _layoutChildren: function(/*String?*/ changedChildId, /*Number?*/ changedChildSize){
438 // This is the main routine for setting size/position of each child.
440 // With no arguments, measures the height of top/bottom panes, the width
441 // of left/right panes, and then sizes all panes accordingly.
443 // With changedRegion specified (as "left", "top", "bottom", or "right"),
444 // it changes that region's width/height to changedRegionSize and
445 // then resizes other regions that were affected.
447 // Id of the child which should be resized because splitter was dragged.
449 // The new width/height (in pixels) to make specified child
451 if(!this._borderBox || !this._borderBox.h){
452 // We are currently hidden, or we haven't been sized by our parent yet.
453 // Abort. Someone will resize us later.
457 // Generate list of wrappers of my children in the order that I want layoutChildren()
458 // to process them (i.e. from the outside to the inside)
459 var wrappers = array.map(this.getChildren(), function(child, idx){
463 child.region == "center" ? Infinity : 0,
464 child.layoutPriority,
465 (this.design == "sidebar" ? 1 : -1) * (/top|bottom/.test(child.region) ? 1 : -1),
470 wrappers.sort(function(a, b){
471 var aw = a.weight, bw = b.weight;
472 for(var i=0; i<aw.length; i++){
474 return aw[i] - bw[i];
480 // Make new list, combining the externally specified children with splitters and gutters
481 var childrenAndSplitters = [];
482 array.forEach(wrappers, function(wrapper){
483 var pane = wrapper.pane;
484 childrenAndSplitters.push(pane);
485 if(pane._splitterWidget){
486 childrenAndSplitters.push(pane._splitterWidget);
490 // Compute the box in which to lay out my children
494 w: this._borderBox.w - this.pe.w,
495 h: this._borderBox.h - this.pe.h
498 // Layout the children, possibly changing size due to a splitter drag
499 layoutUtils.layoutChildren(this.domNode, dim, childrenAndSplitters,
500 changedChildId, changedChildSize);
503 destroyRecursive: function(){
504 // Destroy splitters first, while getChildren() still works
505 array.forEach(this.getChildren(), function(child){
506 var splitter = child._splitterWidget;
510 delete child._splitterWidget;
513 // Then destroy the real children, and myself
514 this.inherited(arguments);
518 // This argument can be specified for the children of a BorderContainer.
519 // Since any widget can be specified as a LayoutContainer child, mix it
520 // into the base widget class. (This is a hack, but it's effective.)
521 lang.extend(_WidgetBase, {
522 // region: [const] String
523 // Parameter for children of `dijit.layout.BorderContainer`.
524 // Values: "top", "bottom", "leading", "trailing", "left", "right", "center".
525 // See the `dijit.layout.BorderContainer` description for details.
528 // layoutPriority: [const] Number
529 // Parameter for children of `dijit.layout.BorderContainer`.
530 // Children with a higher layoutPriority will be placed closer to the BorderContainer center,
531 // between children with a lower layoutPriority.
534 // splitter: [const] Boolean
535 // Parameter for child of `dijit.layout.BorderContainer` where region != "center".
536 // If true, enables user to resize the widget by putting a draggable splitter between
537 // this widget and the region=center widget.
540 // minSize: [const] Number
541 // Parameter for children of `dijit.layout.BorderContainer`.
542 // Specifies a minimum size (in pixels) for this widget when resized by a splitter.
545 // maxSize: [const] Number
546 // Parameter for children of `dijit.layout.BorderContainer`.
547 // Specifies a maximum size (in pixels) for this widget when resized by a splitter.
551 // For monkey patching
552 BorderContainer._Splitter = _Splitter;
553 BorderContainer._Gutter = _Gutter;
555 return BorderContainer;