]>
git.wh0rd.org - tt-rss.git/blob - lib/dijit/layout/BorderContainer.js.uncompressed.js
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
;