UNPKG

dijit

Version:

Dijit provides a complete collection of user interface controls based on Dojo, giving you the power to create web applications that are highly optimized for usability, performance, internationalization, accessibility, but above all deliver an incredible u

592 lines (491 loc) 17.1 kB
define([ "dojo/_base/array", // array.forEach array.indexOf array.some "dojo/cookie", // cookie "dojo/_base/declare", // declare "dojo/dom", // dom.setSelectable "dojo/dom-class", // domClass.add "dojo/dom-construct", // domConstruct.create domConstruct.destroy "dojo/dom-geometry", // domGeometry.marginBox domGeometry.position "dojo/dom-style", // domStyle.style "dojo/_base/event", // event.stop "dojo/_base/kernel", // kernel.deprecated "dojo/_base/lang", // lang.extend lang.hitch "dojo/on", "dojo/sniff", // has("mozilla") "../registry", // registry.getUniqueId() "../_WidgetBase", "./_LayoutWidget" ], function(array, cookie, declare, dom, domClass, domConstruct, domGeometry, domStyle, event, kernel, lang, on, has, registry, _WidgetBase, _LayoutWidget){ // module: // dijit/layout/SplitContainer // // FIXME: make it prettier // FIXME: active dragging upwards doesn't always shift other bars (direction calculation is wrong in this case) // FIXME: sizeWidth should be a CSS attribute (at 7 because css wants it to be 7 until we fix to css) // var SplitContainer = declare("dijit.layout.SplitContainer", _LayoutWidget, { // summary: // Deprecated. Use `dijit/layout/BorderContainer` instead. // description: // A Container widget with sizing handles in-between each child. // Contains multiple children widgets, all of which are displayed side by side // (either horizontally or vertically); there's a bar between each of the children, // and you can adjust the relative size of each child by dragging the bars. // // You must specify a size (width and height) for the SplitContainer. // // See `SplitContainer.ChildWidgetProperties` for details on the properties that can be set on // children of a `SplitContainer`. // tags: // deprecated constructor: function(){ kernel.deprecated("dijit.layout.SplitContainer is deprecated", "use BorderContainer with splitter instead", 2.0); }, // activeSizing: Boolean // If true, the children's size changes as you drag the bar; // otherwise, the sizes don't change until you drop the bar (by mouse-up) activeSizing: false, // sizerWidth: Integer // Size in pixels of the bar between each child sizerWidth: 7, // orientation: String // either 'horizontal' or vertical; indicates whether the children are // arranged side-by-side or up/down. orientation: 'horizontal', // persist: Boolean // Save splitter positions in a cookie persist: true, baseClass: "dijitSplitContainer", postMixInProperties: function(){ this.inherited("postMixInProperties",arguments); this.isHorizontal = (this.orientation == 'horizontal'); }, postCreate: function(){ this.inherited(arguments); this.sizers = []; // overflow has to be explicitly hidden for splitContainers using gekko (trac #1435) // to keep other combined css classes from inadvertantly making the overflow visible if(has("mozilla")){ this.domNode.style.overflow = '-moz-scrollbars-none'; // hidden doesn't work } // create the fake dragger if(typeof this.sizerWidth == "object"){ try{ //FIXME: do this without a try/catch this.sizerWidth = parseInt(this.sizerWidth.toString()); }catch(e){ this.sizerWidth = 7; } } var sizer = this.ownerDocument.createElement('div'); this.virtualSizer = sizer; sizer.style.position = 'relative'; // #1681: work around the dreaded 'quirky percentages in IE' layout bug // If the splitcontainer's dimensions are specified in percentages, it // will be resized when the virtualsizer is displayed in _showSizingLine // (typically expanding its bounds unnecessarily). This happens because // we use position: relative for .dijitSplitContainer. // The workaround: instead of changing the display style attribute, // switch to changing the zIndex (bring to front/move to back) sizer.style.zIndex = 10; sizer.className = this.isHorizontal ? 'dijitSplitContainerVirtualSizerH' : 'dijitSplitContainerVirtualSizerV'; this.domNode.appendChild(sizer); dom.setSelectable(sizer, false); }, destroy: function(){ delete this.virtualSizer; if(this._ownconnects){ var h; while(h = this._ownconnects.pop()){ h.remove(); } } this.inherited(arguments); }, startup: function(){ if(this._started){ return; } array.forEach(this.getChildren(), function(child, i, children){ // attach the children and create the draggers this._setupChild(child); if(i < children.length-1){ this._addSizer(); } }, this); if(this.persist){ this._restoreState(); } this.inherited(arguments); }, _setupChild: function(/*dijit/_WidgetBase*/ child){ this.inherited(arguments); child.domNode.style.position = "absolute"; domClass.add(child.domNode, "dijitSplitPane"); }, _onSizerMouseDown: function(e){ if(e.target.id){ for(var i=0;i<this.sizers.length;i++){ if(this.sizers[i].id == e.target.id){ break; } } if(i<this.sizers.length){ this.beginSizing(e,i); } } }, _addSizer: function(index){ index = index === undefined ? this.sizers.length : index; // TODO: use a template for this!!! var sizer = this.ownerDocument.createElement('div'); sizer.id=registry.getUniqueId('dijit_layout_SplitterContainer_Splitter'); this.sizers.splice(index,0,sizer); this.domNode.appendChild(sizer); sizer.className = this.isHorizontal ? 'dijitSplitContainerSizerH' : 'dijitSplitContainerSizerV'; // add the thumb div var thumb = this.ownerDocument.createElement('div'); thumb.className = 'thumb'; sizer.appendChild(thumb); // FIXME: are you serious? why aren't we using mover start/stop combo? this.connect(sizer, "onmousedown", '_onSizerMouseDown'); dom.setSelectable(sizer, false); }, removeChild: function(widget){ // summary: // Remove sizer, but only if widget is really our child and // we have at least one sizer to throw away if(this.sizers.length){ var i = array.indexOf(this.getChildren(), widget); if(i != -1){ if(i == this.sizers.length){ i--; } domConstruct.destroy(this.sizers[i]); this.sizers.splice(i,1); } } // Remove widget and repaint this.inherited(arguments); if(this._started){ this.layout(); } }, addChild: function(/*dijit/_WidgetBase*/ child, /*Integer?*/ insertIndex){ // summary: // Add a child widget to the container // child: // a widget to add // insertIndex: // position in the "stack" to add the child widget // SplitContainer puts all the child widgets first, and all the splitters at the end. // (This is not ideal for accessibility but not going to fix because the widget is deprecated.) // So, just need to maintain that order so that _Container.addChild() puts the widgets where expected. if(typeof insertIndex == "undefined" || insertIndex == "last"){ insertIndex = this.getChildren().length; } this.inherited(arguments, [child, insertIndex]); if(this._started){ // Do the stuff that startup() does for each widget var children = this.getChildren(); if(children.length > 1){ this._addSizer(insertIndex); } // and then reposition (ie, shrink) every pane to make room for the new guy this.layout(); } }, layout: function(){ // summary: // Do layout of panels // base class defines this._contentBox on initial creation and also // on resize this.paneWidth = this._contentBox.w; this.paneHeight = this._contentBox.h; var children = this.getChildren(); if(!children.length){ return; } // // calculate space // var space = this.isHorizontal ? this.paneWidth : this.paneHeight; if(children.length > 1){ space -= this.sizerWidth * (children.length - 1); } // // calculate total of SizeShare values // var outOf = 0; array.forEach(children, function(child){ outOf += child.sizeShare; }); // // work out actual pixels per sizeshare unit // var pixPerUnit = space / outOf; // // set the SizeActual member of each pane // var totalSize = 0; array.forEach(children.slice(0, children.length - 1), function(child){ var size = Math.round(pixPerUnit * child.sizeShare); child.sizeActual = size; totalSize += size; }); children[children.length-1].sizeActual = space - totalSize; // // make sure the sizes are ok // this._checkSizes(); // // now loop, positioning each pane and letting children resize themselves // var pos = 0; var size = children[0].sizeActual; this._movePanel(children[0], pos, size); children[0].position = pos; pos += size; // if we don't have any sizers, our layout method hasn't been called yet // so bail until we are called..TODO: REVISIT: need to change the startup // algorithm to guaranteed the ordering of calls to layout method if(!this.sizers){ return; } array.some(children.slice(1), function(child, i){ // error-checking if(!this.sizers[i]){ return true; } // first we position the sizing handle before this pane this._moveSlider(this.sizers[i], pos, this.sizerWidth); this.sizers[i].position = pos; pos += this.sizerWidth; size = child.sizeActual; this._movePanel(child, pos, size); child.position = pos; pos += size; }, this); }, _movePanel: function(panel, pos, size){ var box; if(this.isHorizontal){ panel.domNode.style.left = pos + 'px'; // TODO: resize() takes l and t parameters too, don't need to set manually panel.domNode.style.top = 0; box = {w: size, h: this.paneHeight}; if(panel.resize){ panel.resize(box); }else{ domGeometry.setMarginBox(panel.domNode, box); } }else{ panel.domNode.style.left = 0; // TODO: resize() takes l and t parameters too, don't need to set manually panel.domNode.style.top = pos + 'px'; box = {w: this.paneWidth, h: size}; if(panel.resize){ panel.resize(box); }else{ domGeometry.setMarginBox(panel.domNode, box); } } }, _moveSlider: function(slider, pos, size){ if(this.isHorizontal){ slider.style.left = pos + 'px'; slider.style.top = 0; domGeometry.setMarginBox(slider, { w: size, h: this.paneHeight }); }else{ slider.style.left = 0; slider.style.top = pos + 'px'; domGeometry.setMarginBox(slider, { w: this.paneWidth, h: size }); } }, _growPane: function(growth, pane){ if(growth > 0){ if(pane.sizeActual > pane.sizeMin){ if((pane.sizeActual - pane.sizeMin) > growth){ // stick all the growth in this pane pane.sizeActual = pane.sizeActual - growth; growth = 0; }else{ // put as much growth in here as we can growth -= pane.sizeActual - pane.sizeMin; pane.sizeActual = pane.sizeMin; } } } return growth; }, _checkSizes: function(){ var totalMinSize = 0; var totalSize = 0; var children = this.getChildren(); array.forEach(children, function(child){ totalSize += child.sizeActual; totalMinSize += child.sizeMin; }); // only make adjustments if we have enough space for all the minimums if(totalMinSize <= totalSize){ var growth = 0; array.forEach(children, function(child){ if(child.sizeActual < child.sizeMin){ growth += child.sizeMin - child.sizeActual; child.sizeActual = child.sizeMin; } }); if(growth > 0){ var list = this.isDraggingLeft ? children.reverse() : children; array.forEach(list, function(child){ growth = this._growPane(growth, child); }, this); } }else{ array.forEach(children, function(child){ child.sizeActual = Math.round(totalSize * (child.sizeMin / totalMinSize)); }); } }, beginSizing: function(e, i){ // summary: // Begin dragging the splitter between child[i] and child[i+1] var children = this.getChildren(); this.paneBefore = children[i]; this.paneAfter = children[i+1]; this.paneBefore.sizeBeforeDrag = this.paneBefore.sizeActual; this.paneAfter.sizeBeforeDrag = this.paneAfter.sizeActual; this.paneAfter.positionBeforeDrag = this.paneAfter.position; this.isSizing = true; this.sizingSplitter = this.sizers[i]; this.sizingSplitter.positionBeforeDrag = domStyle.get(this.sizingSplitter,(this.isHorizontal ? "left" : "top")); if(!this.cover){ this.cover = domConstruct.create('div', { style: { position:'absolute', zIndex:5, top: 0, left: 0, width: "100%", height: "100%" } }, this.domNode); }else{ this.cover.style.zIndex = 5; } this.sizingSplitter.style.zIndex = 6; // startPoint is the e.pageX or e.pageY at start of drag this.startPoint = this.lastPoint = (this.isHorizontal ? e.pageX : e.pageY); // Calculate maximum to the left or right that splitter is allowed to be dragged // minDelta is negative to indicate left/upward drag where end.pageX < start.pageX. this.maxDelta = this.paneAfter.sizeActual - this.paneAfter.sizeMin; this.minDelta = -1 * (this.paneBefore.sizeActual - this.paneBefore.sizeMin); if(!this.activeSizing){ this._showSizingLine(); } // attach mouse events this._ownconnects = [ on(this.ownerDocument.documentElement, "mousemove", lang.hitch(this, "changeSizing")), on(this.ownerDocument.documentElement, "mouseup", lang.hitch(this, "endSizing")) ]; event.stop(e); }, changeSizing: function(e){ // summary: // Called on mousemove while dragging the splitter if(!this.isSizing){ return; } // lastPoint is the most recent e.pageX or e.pageY during the drag this.lastPoint = this.isHorizontal ? e.pageX : e.pageY; var delta = Math.max(Math.min(this.lastPoint - this.startPoint, this.maxDelta), this.minDelta); if(this.activeSizing){ this._updateSize(delta); }else{ this._moveSizingLine(delta); } event.stop(e); }, endSizing: function(){ if(!this.isSizing){ return; } if(this.cover){ this.cover.style.zIndex = -1; } if(!this.activeSizing){ this._hideSizingLine(); } var delta = Math.max(Math.min(this.lastPoint - this.startPoint, this.maxDelta), this.minDelta); this._updateSize(delta); this.isSizing = false; if(this.persist){ this._saveState(this); } var h; while(h = this._ownconnects.pop()){ h.remove(); } }, _updateSize: function(/*Number*/ delta){ // summary: // Resets sizes of panes before and after splitter being dragged. // Called during a drag, for active sizing, or at the end of a drag otherwise. // delta: Number // Change in slider position compared to start of drag. But note that // this function may be called multiple times during drag. this.paneBefore.sizeActual = this.paneBefore.sizeBeforeDrag + delta; this.paneAfter.position = this.paneAfter.positionBeforeDrag + delta; this.paneAfter.sizeActual = this.paneAfter.sizeBeforeDrag - delta; array.forEach(this.getChildren(), function(child){ child.sizeShare = child.sizeActual; }); if(this._started){ this.layout(); } }, _showSizingLine: function(){ // summary: // Show virtual splitter, for non-active resizing this._moveSizingLine(0); domGeometry.setMarginBox(this.virtualSizer, this.isHorizontal ? { w: this.sizerWidth, h: this.paneHeight } : { w: this.paneWidth, h: this.sizerWidth }); this.virtualSizer.style.display = 'block'; }, _hideSizingLine: function(){ this.virtualSizer.style.display = 'none'; }, _moveSizingLine: function(/*Number*/ delta){ // summary: // Called for non-active resizing, to move the virtual splitter without adjusting the size of the panes var pos = delta + this.sizingSplitter.positionBeforeDrag; domStyle.set(this.virtualSizer,(this.isHorizontal ? "left" : "top"),pos+"px"); }, _getCookieName: function(i){ return this.id + "_" + i; }, _restoreState: function(){ array.forEach(this.getChildren(), function(child, i){ var cookieName = this._getCookieName(i); var cookieValue = cookie(cookieName); if(cookieValue){ var pos = parseInt(cookieValue); if(typeof pos == "number"){ child.sizeShare = pos; } } }, this); }, _saveState: function(){ if(!this.persist){ return; } array.forEach(this.getChildren(), function(child, i){ cookie(this._getCookieName(i), child.sizeShare, {expires:365}); }, this); } }); SplitContainer.ChildWidgetProperties = { // summary: // These properties can be specified for the children of a SplitContainer. // sizeMin: [deprecated] Integer // Minimum size (width or height) of a child of a SplitContainer. // The value is relative to other children's sizeShare properties. sizeMin: 10, // sizeShare: [deprecated] Integer // Size (width or height) of a child of a SplitContainer. // The value is relative to other children's sizeShare properties. // For example, if there are two children and each has sizeShare=10, then // each takes up 50% of the available space. sizeShare: 10 }; // Since any widget can be specified as a SplitContainer child, mix them // 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, /*===== {} || =====*/ SplitContainer.ChildWidgetProperties); return SplitContainer; });