]>
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
18 "./utils" // layoutUtils.layoutChildren
19 ], function(array
, cookie
, declare
, domClass
, domConstruct
, domGeometry
, domStyle
, event
, keys
, lang
, on
, touch
,
20 _WidgetBase
, _Widget
, _TemplatedMixin
, _LayoutWidget
, layoutUtils
){
23 // dijit/layout/BorderContainer
25 var _Splitter
= declare("dijit.layout._Splitter", [_Widget
, _TemplatedMixin
],
28 // A draggable spacer between two items in a `dijit/layout/BorderContainer`.
30 // This is instantiated by `dijit/layout/BorderContainer`. Users should not
31 // create it directly.
36 // container: [const] dijit/layout/BorderContainer
37 // Pointer to the parent BorderContainer
40 // child: [const] dijit/layout/_LayoutWidget
41 // Pointer to the pane associated with this splitter
44 // region: [const] String
45 // Region of pane associated with this splitter.
46 // "top", "bottom", "left", "right".
50 // live: [const] Boolean
51 // If true, the child's size changes and the child widget is redrawn as you drag the splitter;
52 // otherwise, the size doesn't change until you drop the splitter (by mouse-up)
55 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>',
57 constructor: function(){
61 postMixInProperties: function(){
62 this.inherited(arguments
);
64 this.horizontal
= /top|bottom/.test(this.region
);
65 this._factor
= /top|left/.test(this.region
) ? 1 : -1;
66 this._cookieName
= this.container
.id
+ "_" + this.region
;
69 buildRendering: function(){
70 this.inherited(arguments
);
72 domClass
.add(this.domNode
, "dijitSplitter" + (this.horizontal
? "H" : "V"));
74 if(this.container
.persist
){
76 var persistSize
= cookie(this._cookieName
);
78 this.child
.domNode
.style
[this.horizontal
? "height" : "width"] = persistSize
;
83 _computeMaxSize: function(){
85 // Return the maximum size that my corresponding pane can be set to
87 var dim
= this.horizontal
? 'h' : 'w',
88 childSize
= domGeometry
.getMarginBox(this.child
.domNode
)[dim
],
89 center
= array
.filter(this.container
.getChildren(), function(child
){ return child
.region
== "center";})[0],
90 spaceAvailable
= domGeometry
.getMarginBox(center
.domNode
)[dim
]; // can expand until center is crushed to 0
92 return Math
.min(this.child
.maxSize
, childSize
+ spaceAvailable
);
95 _startDrag: function(e
){
97 this.cover
= domConstruct
.place("<div class=dijitSplitterCover></div>", this.child
.domNode
, "after");
99 domClass
.add(this.cover
, "dijitSplitterCoverActive");
101 // Safeguard in case the stop event was missed. Shouldn't be necessary if we always get the mouse up.
102 if(this.fake
){ domConstruct
.destroy(this.fake
); }
103 if(!(this._resize
= this.live
)){ //TODO: disable live for IE6?
104 // create fake splitter to display at old position while we drag
105 (this.fake
= this.domNode
.cloneNode(true)).removeAttribute("id");
106 domClass
.add(this.domNode
, "dijitSplitterShadow");
107 domConstruct
.place(this.fake
, this.domNode
, "after");
109 domClass
.add(this.domNode
, "dijitSplitterActive dijitSplitter" + (this.horizontal
? "H" : "V") + "Active");
111 domClass
.remove(this.fake
, "dijitSplitterHover dijitSplitter" + (this.horizontal
? "H" : "V") + "Hover");
114 //Performance: load data info local vars for onmousevent function closure
115 var factor
= this._factor
,
116 isHorizontal
= this.horizontal
,
117 axis
= isHorizontal
? "pageY" : "pageX",
119 splitterStyle
= this.domNode
.style
,
120 dim
= isHorizontal
? 'h' : 'w',
121 childStart
= domGeometry
.getMarginBox(this.child
.domNode
)[dim
],
122 max
= this._computeMaxSize(),
123 min
= this.child
.minSize
|| 20,
124 region
= this.region
,
125 splitterAttr
= region
== "top" || region
== "bottom" ? "top" : "left", // style attribute of splitter to adjust
126 splitterStart
= parseInt(splitterStyle
[splitterAttr
], 10),
127 resize
= this._resize
,
128 layoutFunc
= lang
.hitch(this.container
, "_layoutChildren", this.child
.id
),
129 de
= this.ownerDocument
;
131 this._handlers
= this._handlers
.concat([
132 on(de
, touch
.move, this._drag = function(e
, forceResize
){
133 var delta
= e
[axis
] - pageStart
,
134 childSize
= factor
* delta
+ childStart
,
135 boundChildSize
= Math
.max(Math
.min(childSize
, max
), min
);
137 if(resize
|| forceResize
){
138 layoutFunc(boundChildSize
);
140 // TODO: setting style directly (usually) sets content box size, need to set margin box size
141 splitterStyle
[splitterAttr
] = delta
+ splitterStart
+ factor
*(boundChildSize
- childSize
) + "px";
143 on(de
, "dragstart", event
.stop
),
144 on(this.ownerDocumentBody
, "selectstart", event
.stop
),
145 on(de
, touch
.release
, lang
.hitch(this, "_stopDrag"))
150 _onMouse: function(e
){
152 // Handler for onmouseenter / onmouseleave events
153 var o
= (e
.type
== "mouseover" || e
.type
== "mouseenter");
154 domClass
.toggle(this.domNode
, "dijitSplitterHover", o
);
155 domClass
.toggle(this.domNode
, "dijitSplitter" + (this.horizontal
? "H" : "V") + "Hover", o
);
158 _stopDrag: function(e
){
161 domClass
.remove(this.cover
, "dijitSplitterCoverActive");
163 if(this.fake
){ domConstruct
.destroy(this.fake
); }
164 domClass
.remove(this.domNode
, "dijitSplitterActive dijitSplitter"
165 + (this.horizontal
? "H" : "V") + "Active dijitSplitterShadow");
166 this._drag(e
); //TODO: redundant with onmousemove?
169 this._cleanupHandlers();
173 if(this.container
.persist
){
174 cookie(this._cookieName
, this.child
.domNode
.style
[this.horizontal
? "height" : "width"], {expires
:365});
178 _cleanupHandlers: function(){
180 while(h
= this._handlers
.pop()){ h
.remove(); }
183 _onKeyPress: function(/*Event*/ e
){
184 // should we apply typematic to this?
186 var horizontal
= this.horizontal
;
188 switch(e
.charOrCode
){
189 case horizontal
? keys
.UP_ARROW
: keys
.LEFT_ARROW
:
192 case horizontal
? keys
.DOWN_ARROW
: keys
.RIGHT_ARROW
:
195 // this.inherited(arguments);
198 var childSize
= domGeometry
.getMarginSize(this.child
.domNode
)[ horizontal
? 'h' : 'w' ] + this._factor
* tick
;
199 this.container
._layoutChildren(this.child
.id
, Math
.max(Math
.min(childSize
, this._computeMaxSize()), this.child
.minSize
));
204 this._cleanupHandlers();
206 delete this.container
;
209 this.inherited(arguments
);
213 var _Gutter
= declare("dijit.layout._Gutter", [_Widget
, _TemplatedMixin
],
216 // Just a spacer div to separate side pane from center pane.
217 // Basically a trick to lookup the gutter/splitter width from the theme.
219 // Instantiated by `dijit/layout/BorderContainer`. Users should not
224 templateString
: '<div class="dijitGutter" role="presentation"></div>',
226 postMixInProperties: function(){
227 this.inherited(arguments
);
228 this.horizontal
= /top|bottom/.test(this.region
);
231 buildRendering: function(){
232 this.inherited(arguments
);
233 domClass
.add(this.domNode
, "dijitGutter" + (this.horizontal
? "H" : "V"));
237 var BorderContainer
= declare("dijit.layout.BorderContainer", _LayoutWidget
, {
239 // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides.
241 // A BorderContainer is a box with a specified size, such as style="width: 500px; height: 500px;",
242 // that contains a child widget marked region="center" and optionally children widgets marked
243 // region equal to "top", "bottom", "leading", "trailing", "left" or "right".
244 // Children along the edges will be laid out according to width or height dimensions and may
245 // include optional splitters (splitter="true") to make them resizable by the user. The remaining
246 // space is designated for the center region.
248 // The outer size must be specified on the BorderContainer node. Width must be specified for the sides
249 // and height for the top and bottom, respectively. No dimensions should be specified on the center;
250 // it will fill the remaining space. Regions named "leading" and "trailing" may be used just like
251 // "left" and "right" except that they will be reversed in right-to-left environments.
253 // For complex layouts, multiple children can be specified for a single region. In this case, the
254 // layoutPriority flag on the children determines which child is closer to the edge (low layoutPriority)
255 // and which child is closer to the center (high layoutPriority). layoutPriority can also be used
256 // instead of the design attribute to control layout precedence of horizontal vs. vertical panes.
258 // See `BorderContainer.ChildWidgetProperties` for details on the properties that can be set on
259 // children of a `BorderContainer`.
261 // | <div data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="design: 'sidebar', gutters: false"
262 // | style="width: 400px; height: 300px;">
263 // | <div data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: 'top'">header text</div>
264 // | <div data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: 'right', splitter: true" style="width: 200px;">table of contents</div>
265 // | <div data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: 'center'">client area</div>
269 // Which design is used for the layout:
271 // - "headline" (default) where the top and bottom extend the full width of the container
272 // - "sidebar" where the left and right sides extend from top to bottom.
275 // gutters: [const] Boolean
276 // Give each pane a border and margin.
277 // Margin determined by domNode.paddingLeft.
278 // When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing.
281 // liveSplitters: [const] Boolean
282 // Specifies whether splitters resize as you drag (true) or only upon mouseup (false)
286 // Save splitter positions in a cookie.
289 baseClass
: "dijitBorderContainer",
291 // _splitterClass: Function||String
292 // Optional hook to override the default Splitter widget used by BorderContainer
293 _splitterClass
: _Splitter
,
295 postMixInProperties: function(){
296 // change class name to indicate that BorderContainer is being used purely for
297 // layout (like LayoutContainer) rather than for pretty formatting.
299 this.baseClass
+= "NoGutter";
301 this.inherited(arguments
);
305 if(this._started
){ return; }
306 array
.forEach(this.getChildren(), this._setupChild
, this);
307 this.inherited(arguments
);
310 _setupChild: function(/*dijit/_WidgetBase*/ child){
311 // Override _LayoutWidget._setupChild().
313 var region = child.region;
315 this.inherited(arguments);
317 domClass.add(child.domNode, this.baseClass+"Pane");
319 var ltr = this.isLeftToRight();
320 if(region == "leading"){ region = ltr ? "left" : "right"; }
321 if(region == "trailing"){ region = ltr ? "right" : "left"; }
323 // Create draggable splitter for resizing pane,
324 // or alternately if splitter=false but BorderContainer.gutters=true then
325 // insert dummy div just for spacing
326 if(region != "center" && (child.splitter || this.gutters) && !child._splitterWidget){
327 var _Splitter = child.splitter ? this._splitterClass : _Gutter;
328 if(lang.isString(_Splitter)){
329 _Splitter = lang.getObject(_Splitter); // for back-compat, remove in 2.0
331 var splitter = new _Splitter({
332 id: child.id + "_splitter",
336 live: this.liveSplitters
338 splitter.isSplitter = true;
339 child._splitterWidget = splitter;
341 domConstruct.place(splitter.domNode, child.domNode, "after");
343 // Splitters aren't added as Contained children, so we need to call startup explicitly
346 child.region = region; // TODO: technically wrong since it overwrites "trailing" with "left" etc.
351 // Implement _LayoutWidget.layout() virtual method.
352 this._layoutChildren();
355 addChild: function(/*dijit/_WidgetBase*/ child, /*Integer?*/ insertIndex
){
356 // Override _LayoutWidget.addChild().
357 this.inherited(arguments
);
363 removeChild: function(/*dijit/_WidgetBase*/ child){
364 // Override _LayoutWidget.removeChild().
366 var region = child.region;
367 var splitter = child._splitterWidget;
370 delete child._splitterWidget;
372 this.inherited(arguments);
375 this._layoutChildren();
377 // Clean up whatever style changes we made to the child pane.
378 // Unclear how height and width should be handled.
379 domClass.remove(child.domNode, this.baseClass+"Pane");
380 domStyle.set(child.domNode, {
387 domStyle.set(child.domNode, region == "top" || region == "bottom" ? "width" : "height", "auto");
390 getChildren: function(){
391 // Override _LayoutWidget.getChildren() to only return real children, not the splitters.
392 return array.filter(this.inherited(arguments), function(widget){
393 return !widget.isSplitter;
397 // TODO: remove in 2.0
398 getSplitter: function(/*String*/region
){
400 // Returns the widget responsible for rendering the splitter associated with region
403 return array
.filter(this.getChildren(), function(child
){
404 return child
.region
== region
;
405 })[0]._splitterWidget
;
408 resize: function(newSize
, currentSize
){
409 // Overrides _LayoutWidget.resize().
411 // resetting potential padding to 0px to provide support for 100% width/height + padding
412 // TODO: this hack doesn't respect the box model and is a temporary fix
413 if(!this.cs
|| !this.pe
){
414 var node
= this.domNode
;
415 this.cs
= domStyle
.getComputedStyle(node
);
416 this.pe
= domGeometry
.getPadExtents(node
, this.cs
);
417 this.pe
.r
= domStyle
.toPixelValue(node
, this.cs
.paddingRight
);
418 this.pe
.b
= domStyle
.toPixelValue(node
, this.cs
.paddingBottom
);
420 domStyle
.set(node
, "padding", "0px");
423 this.inherited(arguments
);
426 _layoutChildren: function(/*String?*/ changedChildId
, /*Number?*/ changedChildSize
){
428 // This is the main routine for setting size/position of each child.
430 // With no arguments, measures the height of top/bottom panes, the width
431 // of left/right panes, and then sizes all panes accordingly.
433 // With changedRegion specified (as "left", "top", "bottom", or "right"),
434 // it changes that region's width/height to changedRegionSize and
435 // then resizes other regions that were affected.
437 // Id of the child which should be resized because splitter was dragged.
439 // The new width/height (in pixels) to make specified child
441 if(!this._borderBox
|| !this._borderBox
.h
){
442 // We are currently hidden, or we haven't been sized by our parent yet.
443 // Abort. Someone will resize us later.
447 // Generate list of wrappers of my children in the order that I want layoutChildren()
448 // to process them (i.e. from the outside to the inside)
449 var wrappers
= array
.map(this.getChildren(), function(child
, idx
){
453 child
.region
== "center" ? Infinity
: 0,
454 child
.layoutPriority
,
455 (this.design
== "sidebar" ? 1 : -1) * (/top|bottom/.test(child
.region
) ? 1 : -1),
460 wrappers
.sort(function(a
, b
){
461 var aw
= a
.weight
, bw
= b
.weight
;
462 for(var i
=0; i
<aw
.length
; i
++){
464 return aw
[i
] - bw
[i
];
470 // Make new list, combining the externally specified children with splitters and gutters
471 var childrenAndSplitters
= [];
472 array
.forEach(wrappers
, function(wrapper
){
473 var pane
= wrapper
.pane
;
474 childrenAndSplitters
.push(pane
);
475 if(pane
._splitterWidget
){
476 childrenAndSplitters
.push(pane
._splitterWidget
);
480 // Compute the box in which to lay out my children
484 w
: this._borderBox
.w
- this.pe
.w
,
485 h
: this._borderBox
.h
- this.pe
.h
488 // Layout the children, possibly changing size due to a splitter drag
489 layoutUtils
.layoutChildren(this.domNode
, dim
, childrenAndSplitters
,
490 changedChildId
, changedChildSize
);
493 destroyRecursive: function(){
494 // Destroy splitters first, while getChildren() still works
495 array
.forEach(this.getChildren(), function(child
){
496 var splitter
= child
._splitterWidget
;
500 delete child
._splitterWidget
;
503 // Then destroy the real children, and myself
504 this.inherited(arguments
);
508 BorderContainer
.ChildWidgetProperties
= {
510 // These properties can be specified for the children of a BorderContainer.
512 // region: [const] String
513 // Values: "top", "bottom", "leading", "trailing", "left", "right", "center".
514 // See the `dijit/layout/BorderContainer` description for details.
517 // layoutPriority: [const] Number
518 // Children with a higher layoutPriority will be placed closer to the BorderContainer center,
519 // between children with a lower layoutPriority.
522 // splitter: [const] Boolean
523 // Parameter for children where region != "center".
524 // If true, enables user to resize the widget by putting a draggable splitter between
525 // this widget and the region=center widget.
528 // minSize: [const] Number
529 // Specifies a minimum size (in pixels) for this widget when resized by a splitter.
532 // maxSize: [const] Number
533 // Specifies a maximum size (in pixels) for this widget when resized by a splitter.
537 // Since any widget can be specified as a LayoutContainer child, mix it
538 // into the base widget class. (This is a hack, but it's effective.)
539 // This is for the benefit of the parser. Remove for 2.0. Also, hide from doc viewer.
540 lang
.extend(_WidgetBase
, /*===== {} || =====*/ BorderContainer
.ChildWidgetProperties
);
542 // For monkey patching
543 BorderContainer
._Splitter
= _Splitter
;
544 BorderContainer
._Gutter
= _Gutter
;
546 return BorderContainer
;