261 lines
8.2 KiB
JavaScript
261 lines
8.2 KiB
JavaScript
|
dojo.provide("dojox.layout.ToggleSplitter");
|
||
|
dojo.experimental("dojox.layout.ToggleSplitter");
|
||
|
|
||
|
dojo.require("dijit.layout.BorderContainer");
|
||
|
|
||
|
dojo.declare("dojox.layout.ToggleSplitter", [ dijit.layout._Splitter ],
|
||
|
{
|
||
|
// summary:
|
||
|
// A draggable and toggle-to-close/open spacer between two items in a BorderContainer
|
||
|
//
|
||
|
// description:
|
||
|
// Extends the dijit.layout._Splitter to add a toggling behavior
|
||
|
// on double-click
|
||
|
//
|
||
|
|
||
|
/*=====
|
||
|
container: null,
|
||
|
child: null,
|
||
|
region: null,
|
||
|
=====*/
|
||
|
|
||
|
// open: Boolean
|
||
|
// the initial and current state of the splitter (and its attached pane)
|
||
|
open: true,
|
||
|
|
||
|
// closedThreshold: Integer
|
||
|
// how small the attached pane can be before its considered closed
|
||
|
closedThreshold: 5,
|
||
|
|
||
|
// openSize: String
|
||
|
// the css height/width value to apply by default when the attached pane is open
|
||
|
openSize: "",
|
||
|
|
||
|
// _closedSize: String
|
||
|
// the css height/width value to apply by default when the attached pane is closed
|
||
|
_closedSize: "0",
|
||
|
|
||
|
templateString: '<div class="dijitSplitter dojoxToggleSplitter" dojoAttachEvent="onkeypress:_onKeyPress,onmousedown:_onMouseDown" tabIndex="0" waiRole="separator"><div dojoAttachPoint="toggleNode" class="dijitSplitterThumb dojoxToggleSplitterIcon"></div></div>',
|
||
|
|
||
|
postCreate: function(){
|
||
|
this._started = false;
|
||
|
|
||
|
this.inherited(arguments);
|
||
|
|
||
|
// add a region css hook
|
||
|
var region = this.region;
|
||
|
dojo.addClass(this.domNode, "dojoxToggleSplitter"+region.charAt(0).toUpperCase() + region.substring(1));
|
||
|
|
||
|
// hook up double-clicks to toggle the splitter -
|
||
|
this.connect(this, "onDblClick", "_toggleMe");
|
||
|
|
||
|
},
|
||
|
startup: function(){
|
||
|
this.inherited(arguments);
|
||
|
|
||
|
// we have to wait until startup to be sure the child exists in the dom
|
||
|
// and has non-zero size (if its supposed to be showing)
|
||
|
var paneNode = this.child.domNode,
|
||
|
intPaneSize = dojo.style(paneNode, (this.horizontal ? "height" : "width"));
|
||
|
|
||
|
// creation of splitters is an opaque process in BorderContainer,
|
||
|
// so if we want to get init params, we have to retrieve them from the attached BC child
|
||
|
// NOTE: for this to work we have to extend the prototype of dijit._Widget (some more)
|
||
|
dojo.forEach(["toggleSplitterOpen", "toggleSplitterClosedThreshold", "toggleSplitterOpenSize"], function(name){
|
||
|
var pname = name.substring("toggleSplitter".length);
|
||
|
pname = pname.charAt(0).toLowerCase() + pname.substring(1);
|
||
|
if(name in this.child){
|
||
|
this[pname] = this.child[name];
|
||
|
}
|
||
|
}, this);
|
||
|
|
||
|
if(!this.openSize){
|
||
|
// store the current size as the openSize if none was provided
|
||
|
|
||
|
// dojo.style always returns a integer (pixel) value for height/width
|
||
|
// use an arbirary default if a pane was initalized closed and no openSize provided
|
||
|
this.openSize = (this.open) ? intPaneSize + "px" : "75px";
|
||
|
}
|
||
|
this._openStyleProps = this._getStyleProps(paneNode, true);
|
||
|
|
||
|
// update state
|
||
|
this._started = true;
|
||
|
this.attr("open", this.open);
|
||
|
|
||
|
return this;
|
||
|
},
|
||
|
_onMouseUp: function(evt){
|
||
|
dojo.disconnect(this._onMoveHandle);
|
||
|
dojo.disconnect(this._onUpHandle);
|
||
|
delete this._onMoveHandle;
|
||
|
delete this._onUpHandle;
|
||
|
delete this._startPosn;
|
||
|
},
|
||
|
_onPrelimMouseMove: function(evt){
|
||
|
// only start dragging when a mouse down AND a significant mousemove occurs
|
||
|
var startPosn = this._startPosn || 0;
|
||
|
// allow a little fudging in a click before we consider a drag started
|
||
|
var dragThreshold = 3;
|
||
|
var offset = Math.abs( startPosn - (this.horizontal ? evt.clientY : evt.clientX) );
|
||
|
if(offset >= dragThreshold){
|
||
|
// treat as a drag and dismantle this preliminary handlers
|
||
|
dojo.disconnect(this._onMoveHandle);
|
||
|
this._startDrag(evt);
|
||
|
}
|
||
|
},
|
||
|
_onMouseDown: function(evt){
|
||
|
// summary:
|
||
|
// handle mousedown events from the domNode
|
||
|
if(!this.open){
|
||
|
// ignore mousedown while closed
|
||
|
// - this has the effect of preventing dragging while closed, which is the prefered behavior (for now)
|
||
|
return;
|
||
|
}
|
||
|
// Mousedown can fire more than once (!)
|
||
|
// ..so check before connecting
|
||
|
if(!this._onUpHandle){
|
||
|
this._onUpHandle = dojo.connect(dojo.body(), "onmouseup", this, "_onMouseUp");
|
||
|
}
|
||
|
if(!this._onMoveHandle){
|
||
|
this._startPosn = this.horizontal ? evt.clientY : evt.clientX;
|
||
|
// start listening for mousemove
|
||
|
this._onMoveHandle = dojo.connect(dojo.body(), "onmousemove", this, "_onPrelimMouseMove");
|
||
|
}
|
||
|
},
|
||
|
_handleOnChange: function(){
|
||
|
// summary
|
||
|
// effect the state change with the new value of this.open
|
||
|
|
||
|
// TODO: animate the open/close
|
||
|
|
||
|
var paneNode = this.child.domNode,
|
||
|
openProps,
|
||
|
dim = this.horizontal ? "height" : "width";
|
||
|
|
||
|
if(this.open){
|
||
|
// change to open state
|
||
|
var styleProps = dojo.mixin({
|
||
|
display: "block",
|
||
|
overflow: "auto",
|
||
|
visibility: "visible"
|
||
|
}, this._openStyleProps);
|
||
|
|
||
|
styleProps[dim] = (this._openStyleProps && this._openStyleProps[dim]) ? this._openStyleProps[dim] : this.openSize;
|
||
|
dojo.style(paneNode, styleProps);
|
||
|
|
||
|
// and re-hook up the mouse event handler
|
||
|
this.connect(this.domNode, "onmousedown", "_onMouseDown");
|
||
|
|
||
|
} else {
|
||
|
// change to closed state
|
||
|
// FIXME: this wont work in a drag-to-closed scenario
|
||
|
var paneStyle = dojo.getComputedStyle(paneNode);
|
||
|
|
||
|
openProps = this._getStyleProps(paneNode, true, paneStyle);
|
||
|
var closedProps = this._getStyleProps(paneNode, false, paneStyle);
|
||
|
|
||
|
this._openStyleProps = openProps;
|
||
|
dojo.style(paneNode, closedProps);
|
||
|
}
|
||
|
this._setStateClass();
|
||
|
if(this.container._started){
|
||
|
this.container._layoutChildren(this.region);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_getStyleProps: function(paneNode, open, paneStyle){
|
||
|
// summary:
|
||
|
// create an object with the style property name: values
|
||
|
// that will need to be applied to the child pane render the given state
|
||
|
if(!paneStyle){
|
||
|
paneStyle = dojo.getComputedStyle(paneNode);
|
||
|
}
|
||
|
var styleProps = {},
|
||
|
dim = this.horizontal ? "height" : "width";
|
||
|
|
||
|
styleProps["overflow"] = (open) ? paneStyle["overflow"] : "hidden";
|
||
|
styleProps["visibility"] = (open) ? paneStyle["visibility"] : "hidden";
|
||
|
|
||
|
// use the inline width/height style value, in preference to the computedStyle
|
||
|
// for the open width/height
|
||
|
styleProps[dim] = (open) ? paneNode.style[dim] || paneStyle[dim] : this._closedSize;
|
||
|
|
||
|
// We include the padding,border,margin width values for restoring on open
|
||
|
var edgeNames = ["Top", "Right", "Bottom", "Left"];
|
||
|
dojo.forEach(["padding","margin","border"], function(pname){
|
||
|
for(var i=0; i<edgeNames.length; i++){
|
||
|
var fullname = pname+edgeNames[i];
|
||
|
if(pname=="border"){
|
||
|
pname+="Width";
|
||
|
}
|
||
|
if(undefined !== paneStyle[fullname]){
|
||
|
styleProps[fullname] = (open) ?
|
||
|
paneStyle[fullname] : 0;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
return styleProps;
|
||
|
},
|
||
|
|
||
|
_setStateClass: function(){
|
||
|
// sumamry:
|
||
|
// apply the appropriate classes for the current open state
|
||
|
if(this.open){
|
||
|
dojo.removeClass(this.domNode, "dojoxToggleSplitterClosed");
|
||
|
dojo.addClass(this.domNode, "dojoxToggleSplitterOpen");
|
||
|
dojo.removeClass(this.toggleNode, "dojoxToggleSplitterIconClosed");
|
||
|
dojo.addClass(this.toggleNode, "dojoxToggleSplitterIconOpen");
|
||
|
} else {
|
||
|
dojo.addClass(this.domNode, "dojoxToggleSplitterClosed");
|
||
|
dojo.removeClass(this.domNode, "dojoxToggleSplitterOpen");
|
||
|
dojo.addClass(this.toggleNode, "dojoxToggleSplitterIconClosed");
|
||
|
dojo.removeClass(this.toggleNode, "dojoxToggleSplitterIconOpen");
|
||
|
}
|
||
|
},
|
||
|
_setOpenAttr: function(/*Boolean*/ value){
|
||
|
// summary:
|
||
|
// setter for the open property
|
||
|
if(!this._started) {
|
||
|
return;
|
||
|
}
|
||
|
this.open = value;
|
||
|
this._handleOnChange(value, true);
|
||
|
var evt = this.open ? "onOpen" : "onClose";
|
||
|
this[evt](this.child);
|
||
|
},
|
||
|
onOpen: function(){
|
||
|
// stub
|
||
|
},
|
||
|
onClose: function(){
|
||
|
// stub
|
||
|
},
|
||
|
|
||
|
_toggleMe: function(evt){
|
||
|
// summary:
|
||
|
// event handle, toggle the open state
|
||
|
if(evt){
|
||
|
dojo.stopEvent(evt);
|
||
|
}
|
||
|
this.attr("open", !this.open);
|
||
|
},
|
||
|
|
||
|
_onKeyPress: function(/*Event*/ e){
|
||
|
this.inherited(arguments);
|
||
|
// TODO: add support for space, enter to cause toggle
|
||
|
}
|
||
|
|
||
|
});
|
||
|
|
||
|
// As BC places no constraints on what kind of widgets can be children
|
||
|
// we have to extend the base class to ensure the properties we need can be set (both in markup and programatically)
|
||
|
dojo.extend(dijit._Widget, {
|
||
|
// toggleSplitterOpen: Boolean
|
||
|
toggleSplitterOpen: true,
|
||
|
|
||
|
// toggleSplitterClosedThreshold: Integer
|
||
|
toggleSplitterClosedThreshold: 5,
|
||
|
|
||
|
// toggleSplitterClosedThreshold: String
|
||
|
// a css size value (e.g. "100px")
|
||
|
toggleSplitterOpenSize: ""
|
||
|
});
|