1 define("dijit/layout/SplitContainer", [
2 "dojo/_base/array", // array.forEach array.indexOf array.some
3 "dojo/cookie", // cookie
4 "dojo/_base/declare", // declare
5 "dojo/dom", // dom.setSelectable
6 "dojo/dom-class", // domClass.add
7 "dojo/dom-construct", // domConstruct.create domConstruct.destroy
8 "dojo/dom-geometry", // domGeometry.marginBox domGeometry.position
9 "dojo/dom-style", // domStyle.style
10 "dojo/_base/event", // event.stop
11 "dojo/_base/kernel", // kernel.deprecated
12 "dojo/_base/lang", // lang.extend lang.hitch
14 "dojo/sniff", // has("mozilla")
15 "../registry", // registry.getUniqueId()
18 ], function(array, cookie, declare, dom, domClass, domConstruct, domGeometry, domStyle,
19 event, kernel, lang, on, has, registry, _WidgetBase, _LayoutWidget){
22 // dijit/layout/SplitContainer
25 // FIXME: make it prettier
26 // FIXME: active dragging upwards doesn't always shift other bars (direction calculation is wrong in this case)
27 // FIXME: sizeWidth should be a CSS attribute (at 7 because css wants it to be 7 until we fix to css)
30 var SplitContainer = declare("dijit.layout.SplitContainer", _LayoutWidget, {
32 // Deprecated. Use `dijit/layout/BorderContainer` instead.
34 // A Container widget with sizing handles in-between each child.
35 // Contains multiple children widgets, all of which are displayed side by side
36 // (either horizontally or vertically); there's a bar between each of the children,
37 // and you can adjust the relative size of each child by dragging the bars.
39 // You must specify a size (width and height) for the SplitContainer.
41 // See `SplitContainer.ChildWidgetProperties` for details on the properties that can be set on
42 // children of a `SplitContainer`.
46 constructor: function(){
47 kernel.deprecated("dijit.layout.SplitContainer is deprecated", "use BorderContainer with splitter instead", 2.0);
50 // activeSizing: Boolean
51 // If true, the children's size changes as you drag the bar;
52 // otherwise, the sizes don't change until you drop the bar (by mouse-up)
55 // sizerWidth: Integer
56 // Size in pixels of the bar between each child
59 // orientation: String
60 // either 'horizontal' or vertical; indicates whether the children are
61 // arranged side-by-side or up/down.
62 orientation: 'horizontal',
65 // Save splitter positions in a cookie
68 baseClass: "dijitSplitContainer",
70 postMixInProperties: function(){
71 this.inherited("postMixInProperties",arguments);
72 this.isHorizontal = (this.orientation == 'horizontal');
75 postCreate: function(){
76 this.inherited(arguments);
79 // overflow has to be explicitly hidden for splitContainers using gekko (trac #1435)
80 // to keep other combined css classes from inadvertantly making the overflow visible
82 this.domNode.style.overflow = '-moz-scrollbars-none'; // hidden doesn't work
85 // create the fake dragger
86 if(typeof this.sizerWidth == "object"){
87 try{ //FIXME: do this without a try/catch
88 this.sizerWidth = parseInt(this.sizerWidth.toString());
89 }catch(e){ this.sizerWidth = 7; }
91 var sizer = this.ownerDocument.createElement('div');
92 this.virtualSizer = sizer;
93 sizer.style.position = 'relative';
95 // #1681: work around the dreaded 'quirky percentages in IE' layout bug
96 // If the splitcontainer's dimensions are specified in percentages, it
97 // will be resized when the virtualsizer is displayed in _showSizingLine
98 // (typically expanding its bounds unnecessarily). This happens because
99 // we use position: relative for .dijitSplitContainer.
100 // The workaround: instead of changing the display style attribute,
101 // switch to changing the zIndex (bring to front/move to back)
103 sizer.style.zIndex = 10;
104 sizer.className = this.isHorizontal ? 'dijitSplitContainerVirtualSizerH' : 'dijitSplitContainerVirtualSizerV';
105 this.domNode.appendChild(sizer);
106 dom.setSelectable(sizer, false);
110 delete this.virtualSizer;
111 if(this._ownconnects){
113 while(h = this._ownconnects.pop()){ h.remove(); }
115 this.inherited(arguments);
118 if(this._started){ return; }
120 array.forEach(this.getChildren(), function(child, i, children){
121 // attach the children and create the draggers
122 this._setupChild(child);
124 if(i < children.length-1){
130 this._restoreState();
133 this.inherited(arguments);
136 _setupChild: function(/*dijit/_WidgetBase*/ child){
137 this.inherited(arguments);
138 child.domNode.style.position = "absolute";
139 domClass.add(child.domNode, "dijitSplitPane");
142 _onSizerMouseDown: function(e){
144 for(var i=0;i<this.sizers.length;i++){
145 if(this.sizers[i].id == e.target.id){
149 if(i<this.sizers.length){
150 this.beginSizing(e,i);
154 _addSizer: function(index){
155 index = index === undefined ? this.sizers.length : index;
157 // TODO: use a template for this!!!
158 var sizer = this.ownerDocument.createElement('div');
159 sizer.id=registry.getUniqueId('dijit_layout_SplitterContainer_Splitter');
160 this.sizers.splice(index,0,sizer);
161 this.domNode.appendChild(sizer);
163 sizer.className = this.isHorizontal ? 'dijitSplitContainerSizerH' : 'dijitSplitContainerSizerV';
166 var thumb = this.ownerDocument.createElement('div');
167 thumb.className = 'thumb';
168 sizer.appendChild(thumb);
170 // FIXME: are you serious? why aren't we using mover start/stop combo?
171 this.connect(sizer, "onmousedown", '_onSizerMouseDown');
173 dom.setSelectable(sizer, false);
176 removeChild: function(widget){
178 // Remove sizer, but only if widget is really our child and
179 // we have at least one sizer to throw away
180 if(this.sizers.length){
181 var i = array.indexOf(this.getChildren(), widget);
183 if(i == this.sizers.length){
186 domConstruct.destroy(this.sizers[i]);
187 this.sizers.splice(i,1);
191 // Remove widget and repaint
192 this.inherited(arguments);
198 addChild: function(/*dijit/_WidgetBase*/ child, /*Integer?*/ insertIndex){
200 // Add a child widget to the container
204 // postion in the "stack" to add the child widget
206 this.inherited(arguments);
209 // Do the stuff that startup() does for each widget
210 var children = this.getChildren();
211 if(children.length > 1){
212 this._addSizer(insertIndex);
215 // and then reposition (ie, shrink) every pane to make room for the new guy
222 // Do layout of panels
224 // base class defines this._contentBox on initial creation and also
226 this.paneWidth = this._contentBox.w;
227 this.paneHeight = this._contentBox.h;
229 var children = this.getChildren();
230 if(!children.length){ return; }
236 var space = this.isHorizontal ? this.paneWidth : this.paneHeight;
237 if(children.length > 1){
238 space -= this.sizerWidth * (children.length - 1);
242 // calculate total of SizeShare values
245 array.forEach(children, function(child){
246 outOf += child.sizeShare;
250 // work out actual pixels per sizeshare unit
252 var pixPerUnit = space / outOf;
255 // set the SizeActual member of each pane
258 array.forEach(children.slice(0, children.length - 1), function(child){
259 var size = Math.round(pixPerUnit * child.sizeShare);
260 child.sizeActual = size;
264 children[children.length-1].sizeActual = space - totalSize;
267 // make sure the sizes are ok
272 // now loop, positioning each pane and letting children resize themselves
276 var size = children[0].sizeActual;
277 this._movePanel(children[0], pos, size);
278 children[0].position = pos;
281 // if we don't have any sizers, our layout method hasn't been called yet
282 // so bail until we are called..TODO: REVISIT: need to change the startup
283 // algorithm to guaranteed the ordering of calls to layout method
288 array.some(children.slice(1), function(child, i){
293 // first we position the sizing handle before this pane
294 this._moveSlider(this.sizers[i], pos, this.sizerWidth);
295 this.sizers[i].position = pos;
296 pos += this.sizerWidth;
298 size = child.sizeActual;
299 this._movePanel(child, pos, size);
300 child.position = pos;
305 _movePanel: function(panel, pos, size){
307 if(this.isHorizontal){
308 panel.domNode.style.left = pos + 'px'; // TODO: resize() takes l and t parameters too, don't need to set manually
309 panel.domNode.style.top = 0;
310 box = {w: size, h: this.paneHeight};
314 domGeometry.setMarginBox(panel.domNode, box);
317 panel.domNode.style.left = 0; // TODO: resize() takes l and t parameters too, don't need to set manually
318 panel.domNode.style.top = pos + 'px';
319 box = {w: this.paneWidth, h: size};
323 domGeometry.setMarginBox(panel.domNode, box);
328 _moveSlider: function(slider, pos, size){
329 if(this.isHorizontal){
330 slider.style.left = pos + 'px';
331 slider.style.top = 0;
332 domGeometry.setMarginBox(slider, { w: size, h: this.paneHeight });
334 slider.style.left = 0;
335 slider.style.top = pos + 'px';
336 domGeometry.setMarginBox(slider, { w: this.paneWidth, h: size });
340 _growPane: function(growth, pane){
342 if(pane.sizeActual > pane.sizeMin){
343 if((pane.sizeActual - pane.sizeMin) > growth){
345 // stick all the growth in this pane
346 pane.sizeActual = pane.sizeActual - growth;
349 // put as much growth in here as we can
350 growth -= pane.sizeActual - pane.sizeMin;
351 pane.sizeActual = pane.sizeMin;
358 _checkSizes: function(){
360 var totalMinSize = 0;
362 var children = this.getChildren();
364 array.forEach(children, function(child){
365 totalSize += child.sizeActual;
366 totalMinSize += child.sizeMin;
369 // only make adjustments if we have enough space for all the minimums
371 if(totalMinSize <= totalSize){
375 array.forEach(children, function(child){
376 if(child.sizeActual < child.sizeMin){
377 growth += child.sizeMin - child.sizeActual;
378 child.sizeActual = child.sizeMin;
383 var list = this.isDraggingLeft ? children.reverse() : children;
384 array.forEach(list, function(child){
385 growth = this._growPane(growth, child);
389 array.forEach(children, function(child){
390 child.sizeActual = Math.round(totalSize * (child.sizeMin / totalMinSize));
395 beginSizing: function(e, i){
397 // Begin dragging the splitter between child[i] and child[i+1]
399 var children = this.getChildren();
401 this.paneBefore = children[i];
402 this.paneAfter = children[i+1];
404 this.paneBefore.sizeBeforeDrag = this.paneBefore.sizeActual;
405 this.paneAfter.sizeBeforeDrag = this.paneAfter.sizeActual;
406 this.paneAfter.positionBeforeDrag = this.paneAfter.position;
408 this.isSizing = true;
409 this.sizingSplitter = this.sizers[i];
410 this.sizingSplitter.positionBeforeDrag = domStyle.get(this.sizingSplitter,(this.isHorizontal ? "left" : "top"));
413 this.cover = domConstruct.create('div', {
424 this.cover.style.zIndex = 5;
426 this.sizingSplitter.style.zIndex = 6;
428 // startPoint is the e.pageX or e.pageY at start of drag
429 this.startPoint = this.lastPoint = (this.isHorizontal ? e.pageX : e.pageY);
431 // Calculate maximum to the left or right that splitter is allowed to be dragged
432 // minDelta is negative to indicate left/upward drag where end.pageX < start.pageX.
433 this.maxDelta = this.paneAfter.sizeActual - this.paneAfter.sizeMin;
434 this.minDelta = -1 * (this.paneBefore.sizeActual - this.paneBefore.sizeMin);
436 if(!this.activeSizing){
437 this._showSizingLine();
440 // attach mouse events
441 this._ownconnects = [
442 on(this.ownerDocument.documentElement, "mousemove", lang.hitch(this, "changeSizing")),
443 on(this.ownerDocument.documentElement, "mouseup", lang.hitch(this, "endSizing"))
449 changeSizing: function(e){
451 // Called on mousemove while dragging the splitter
453 if(!this.isSizing){ return; }
455 // lastPoint is the most recent e.pageX or e.pageY during the drag
456 this.lastPoint = this.isHorizontal ? e.pageX : e.pageY;
457 var delta = Math.max(Math.min(this.lastPoint - this.startPoint, this.maxDelta), this.minDelta);
459 if(this.activeSizing){
460 this._updateSize(delta);
462 this._moveSizingLine(delta);
467 endSizing: function(){
468 if(!this.isSizing){ return; }
470 this.cover.style.zIndex = -1;
472 if(!this.activeSizing){
473 this._hideSizingLine();
476 var delta = Math.max(Math.min(this.lastPoint - this.startPoint, this.maxDelta), this.minDelta);
477 this._updateSize(delta);
479 this.isSizing = false;
482 this._saveState(this);
486 while(h = this._ownconnects.pop()){ h.remove(); }
489 _updateSize: function(/*Number*/ delta){
491 // Resets sizes of panes before and after splitter being dragged.
492 // Called during a drag, for active sizing, or at the end of a drag otherwise.
494 // Change in slider position compared to start of drag. But note that
495 // this function may be called multiple times during drag.
497 this.paneBefore.sizeActual = this.paneBefore.sizeBeforeDrag + delta;
498 this.paneAfter.position = this.paneAfter.positionBeforeDrag + delta;
499 this.paneAfter.sizeActual = this.paneAfter.sizeBeforeDrag - delta;
501 array.forEach(this.getChildren(), function(child){
502 child.sizeShare = child.sizeActual;
510 _showSizingLine: function(){
512 // Show virtual splitter, for non-active resizing
514 this._moveSizingLine(0);
516 domGeometry.setMarginBox(this.virtualSizer,
517 this.isHorizontal ? { w: this.sizerWidth, h: this.paneHeight } : { w: this.paneWidth, h: this.sizerWidth });
519 this.virtualSizer.style.display = 'block';
522 _hideSizingLine: function(){
523 this.virtualSizer.style.display = 'none';
526 _moveSizingLine: function(/*Number*/ delta){
528 // Called for non-active resizing, to move the virtual splitter without adjusting the size of the panes
529 var pos = delta + this.sizingSplitter.positionBeforeDrag;
530 domStyle.set(this.virtualSizer,(this.isHorizontal ? "left" : "top"),pos+"px");
533 _getCookieName: function(i){
534 return this.id + "_" + i;
537 _restoreState: function(){
538 array.forEach(this.getChildren(), function(child, i){
539 var cookieName = this._getCookieName(i);
540 var cookieValue = cookie(cookieName);
542 var pos = parseInt(cookieValue);
543 if(typeof pos == "number"){
544 child.sizeShare = pos;
550 _saveState: function(){
554 array.forEach(this.getChildren(), function(child, i){
555 cookie(this._getCookieName(i), child.sizeShare, {expires:365});
560 SplitContainer.ChildWidgetProperties = {
562 // These properties can be specified for the children of a SplitContainer.
564 // sizeMin: [deprecated] Integer
565 // Minimum size (width or height) of a child of a SplitContainer.
566 // The value is relative to other children's sizeShare properties.
569 // sizeShare: [deprecated] Integer
570 // Size (width or height) of a child of a SplitContainer.
571 // The value is relative to other children's sizeShare properties.
572 // For example, if there are two children and each has sizeShare=10, then
573 // each takes up 50% of the available space.
577 // Since any widget can be specified as a SplitContainer child, mix them
578 // into the base widget class. (This is a hack, but it's effective.)
579 // This is for the benefit of the parser. Remove for 2.0. Also, hide from doc viewer.
580 lang.extend(_WidgetBase, /*===== {} || =====*/ SplitContainer.ChildWidgetProperties);
582 return SplitContainer;