548 lines
19 KiB
JavaScript
548 lines
19 KiB
JavaScript
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",
|
|
"../_WidgetBase",
|
|
"../_Widget",
|
|
"../_TemplatedMixin",
|
|
"./_LayoutWidget",
|
|
"./utils" // layoutUtils.layoutChildren
|
|
], function(array, cookie, declare, domClass, domConstruct, domGeometry, domStyle, event, keys, lang, on, touch,
|
|
_WidgetBase, _Widget, _TemplatedMixin, _LayoutWidget, layoutUtils){
|
|
|
|
// module:
|
|
// dijit/layout/BorderContainer
|
|
|
|
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: '<div class="dijitSplitter" data-dojo-attach-event="onkeypress:_onKeyPress,press:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" role="separator"><div class="dijitSplitterThumb"></div></div>',
|
|
|
|
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 = domConstruct.place("<div class=dijitSplitterCover></div>", 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 = this.ownerDocument;
|
|
|
|
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(this.ownerDocumentBody, "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: '<div class="dijitGutter" role="presentation"></div>',
|
|
|
|
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.
|
|
//
|
|
// See `BorderContainer.ChildWidgetProperties` for details on the properties that can be set on
|
|
// children of a `BorderContainer`.
|
|
// example:
|
|
// | <div data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="design: 'sidebar', gutters: false"
|
|
// | style="width: 400px; height: 300px;">
|
|
// | <div data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: 'top'">header text</div>
|
|
// | <div data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: 'right', splitter: true" style="width: 200px;">table of contents</div>
|
|
// | <div data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: 'center'">client area</div>
|
|
// | </div>
|
|
|
|
// 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/_WidgetBase*/ 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/_WidgetBase*/ child, /*Integer?*/ insertIndex){
|
|
// Override _LayoutWidget.addChild().
|
|
this.inherited(arguments);
|
|
if(this._started){
|
|
this.layout(); //OPT
|
|
}
|
|
},
|
|
|
|
removeChild: function(/*dijit/_WidgetBase*/ 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<aw.length; i++){
|
|
if(aw[i] != bw[i]){
|
|
return aw[i] - bw[i];
|
|
}
|
|
}
|
|
return 0;
|
|
});
|
|
|
|
// Make new list, combining the externally specified children with splitters and gutters
|
|
var childrenAndSplitters = [];
|
|
array.forEach(wrappers, function(wrapper){
|
|
var pane = wrapper.pane;
|
|
childrenAndSplitters.push(pane);
|
|
if(pane._splitterWidget){
|
|
childrenAndSplitters.push(pane._splitterWidget);
|
|
}
|
|
});
|
|
|
|
// Compute the box in which to lay out my children
|
|
var dim = {
|
|
l: this.pe.l,
|
|
t: this.pe.t,
|
|
w: this._borderBox.w - this.pe.w,
|
|
h: this._borderBox.h - this.pe.h
|
|
};
|
|
|
|
// Layout the children, possibly changing size due to a splitter drag
|
|
layoutUtils.layoutChildren(this.domNode, dim, childrenAndSplitters,
|
|
changedChildId, changedChildSize);
|
|
},
|
|
|
|
destroyRecursive: function(){
|
|
// Destroy splitters first, while getChildren() still works
|
|
array.forEach(this.getChildren(), function(child){
|
|
var splitter = child._splitterWidget;
|
|
if(splitter){
|
|
splitter.destroy();
|
|
}
|
|
delete child._splitterWidget;
|
|
});
|
|
|
|
// Then destroy the real children, and myself
|
|
this.inherited(arguments);
|
|
}
|
|
});
|
|
|
|
BorderContainer.ChildWidgetProperties = {
|
|
// summary:
|
|
// These properties can be specified for the children of a BorderContainer.
|
|
|
|
// region: [const] String
|
|
// Values: "top", "bottom", "leading", "trailing", "left", "right", "center".
|
|
// See the `dijit/layout/BorderContainer` description for details.
|
|
region: '',
|
|
|
|
// layoutPriority: [const] Number
|
|
// Children with a higher layoutPriority will be placed closer to the BorderContainer center,
|
|
// between children with a lower layoutPriority.
|
|
layoutPriority: 0,
|
|
|
|
// splitter: [const] Boolean
|
|
// Parameter for children where region != "center".
|
|
// If true, enables user to resize the widget by putting a draggable splitter between
|
|
// this widget and the region=center widget.
|
|
splitter: false,
|
|
|
|
// minSize: [const] Number
|
|
// Specifies a minimum size (in pixels) for this widget when resized by a splitter.
|
|
minSize: 0,
|
|
|
|
// maxSize: [const] Number
|
|
// Specifies a maximum size (in pixels) for this widget when resized by a splitter.
|
|
maxSize: Infinity
|
|
};
|
|
|
|
// Since any widget can be specified as a LayoutContainer child, mix it
|
|
// into the base widget class. (This is a hack, but it's effective.)
|
|
// This is for the benefit of the parser. Remove for 2.0. Also, hide from doc viewer.
|
|
lang.extend(_WidgetBase, /*===== {} || =====*/ BorderContainer.ChildWidgetProperties);
|
|
|
|
// For monkey patching
|
|
BorderContainer._Splitter = _Splitter;
|
|
BorderContainer._Gutter = _Gutter;
|
|
|
|
return BorderContainer;
|
|
});
|