diff options
author | Andrew Dolgov <[email protected]> | 2011-11-08 20:40:44 +0400 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2011-11-08 20:40:44 +0400 |
commit | 81bea17aefb26859f825b9293c7c99192874806e (patch) | |
tree | fb244408ca271affa2899adb634788802c9a89d8 /lib/dijit/_HasDropDown.js | |
parent | 870a70e109ac9e80a88047044530de53d0404ec7 (diff) |
upgrade Dojo to 1.6.1
Diffstat (limited to 'lib/dijit/_HasDropDown.js')
-rw-r--r-- | lib/dijit/_HasDropDown.js | 633 |
1 files changed, 434 insertions, 199 deletions
diff --git a/lib/dijit/_HasDropDown.js b/lib/dijit/_HasDropDown.js index 6606acb00..e1e9c1ca2 100644 --- a/lib/dijit/_HasDropDown.js +++ b/lib/dijit/_HasDropDown.js @@ -1,208 +1,443 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit._HasDropDown"]){ -dojo._hasResource["dijit._HasDropDown"]=true; +if(!dojo._hasResource["dijit._HasDropDown"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit._HasDropDown"] = true; dojo.provide("dijit._HasDropDown"); -dojo.require("dijit._base.place"); dojo.require("dijit._Widget"); -dojo.declare("dijit._HasDropDown",null,{_buttonNode:null,_arrowWrapperNode:null,_popupStateNode:null,_aroundNode:null,dropDown:null,autoWidth:true,forceWidth:false,maxHeight:0,dropDownPosition:["below","above"],_stopClickEvents:true,_onDropDownMouseDown:function(e){ -if(this.disabled||this.readOnly){ -return; -} -this._docHandler=this.connect(dojo.doc,"onmouseup","_onDropDownMouseUp"); -this.toggleDropDown(); -},_onDropDownMouseUp:function(e){ -if(e&&this._docHandler){ -this.disconnect(this._docHandler); -} -var _1=this.dropDown,_2=false; -if(e&&this._opened){ -var c=dojo.position(this._buttonNode,true); -if(!(e.pageX>=c.x&&e.pageX<=c.x+c.w)||!(e.pageY>=c.y&&e.pageY<=c.y+c.h)){ -var t=e.target; -while(t&&!_2){ -if(dojo.hasClass(t,"dijitPopup")){ -_2=true; -}else{ -t=t.parentNode; -} -} -if(_2){ -t=e.target; -if(_1.onItemClick){ -var _3; -while(t&&!(_3=dijit.byNode(t))){ -t=t.parentNode; -} -if(_3&&_3.onClick&&_3.getParent){ -_3.getParent().onItemClick(_3,e); -} -} -return; -} -} -} -if(this._opened&&_1.focus){ -window.setTimeout(dojo.hitch(_1,"focus"),1); -} -},_onDropDownClick:function(e){ -if(this._stopClickEvents){ -dojo.stopEvent(e); -} -},_setupDropdown:function(){ -this._buttonNode=this._buttonNode||this.focusNode||this.domNode; -this._popupStateNode=this._popupStateNode||this.focusNode||this._buttonNode; -this._aroundNode=this._aroundNode||this.domNode; -this.connect(this._buttonNode,"onmousedown","_onDropDownMouseDown"); -this.connect(this._buttonNode,"onclick","_onDropDownClick"); -this.connect(this._buttonNode,"onkeydown","_onDropDownKeydown"); -this.connect(this._buttonNode,"onkeyup","_onKey"); -if(this._setStateClass){ -this.connect(this,"openDropDown","_setStateClass"); -this.connect(this,"closeDropDown","_setStateClass"); -} -var _4={"after":this.isLeftToRight()?"Right":"Left","before":this.isLeftToRight()?"Left":"Right","above":"Up","below":"Down","left":"Left","right":"Right"}[this.dropDownPosition[0]]||this.dropDownPosition[0]||"Down"; -dojo.addClass(this._arrowWrapperNode||this._buttonNode,"dijit"+_4+"ArrowButton"); -},postCreate:function(){ -this._setupDropdown(); -this.inherited(arguments); -},destroyDescendants:function(){ -if(this.dropDown){ -if(!this.dropDown._destroyed){ -this.dropDown.destroyRecursive(); -} -delete this.dropDown; -} -this.inherited(arguments); -},_onDropDownKeydown:function(e){ -if(e.keyCode==dojo.keys.DOWN_ARROW||e.keyCode==dojo.keys.ENTER||e.keyCode==dojo.keys.SPACE){ -e.preventDefault(); -} -},_onKey:function(e){ -if(this.disabled||this.readOnly){ -return; -} -var d=this.dropDown; -if(d&&this._opened&&d.handleKey){ -if(d.handleKey(e)===false){ -return; -} -} -if(d&&this._opened&&e.keyCode==dojo.keys.ESCAPE){ -this.toggleDropDown(); -}else{ -if(d&&!this._opened&&(e.keyCode==dojo.keys.DOWN_ARROW||e.keyCode==dojo.keys.ENTER||e.keyCode==dojo.keys.SPACE)){ -this.toggleDropDown(); -if(d.focus){ -setTimeout(dojo.hitch(d,"focus"),1); -} -} -} -},_onBlur:function(){ -this.closeDropDown(); -this.inherited(arguments); -},isLoaded:function(){ -return true; -},loadDropDown:function(_5){ -_5(); -},toggleDropDown:function(){ -if(this.disabled||this.readOnly){ -return; -} -this.focus(); -var _6=this.dropDown; -if(!_6){ -return; -} -if(!this._opened){ -if(!this.isLoaded()){ -this.loadDropDown(dojo.hitch(this,"openDropDown")); -return; -}else{ -this.openDropDown(); -} -}else{ -this.closeDropDown(); -} -},openDropDown:function(){ -var _7=this.dropDown; -var _8=_7.domNode; -var _9=this; -if(!this._preparedNode){ -dijit.popup.moveOffScreen(_8); -this._preparedNode=true; -if(_8.style.width){ -this._explicitDDWidth=true; -} -if(_8.style.height){ -this._explicitDDHeight=true; -} -} -if(this.maxHeight||this.forceWidth||this.autoWidth){ -var _a={display:"",visibility:"hidden"}; -if(!this._explicitDDWidth){ -_a.width=""; -} -if(!this._explicitDDHeight){ -_a.height=""; -} -dojo.style(_8,_a); -var mb=dojo.marginBox(_8); -var _b=(this.maxHeight&&mb.h>this.maxHeight); -dojo.style(_8,{overflowX:"hidden",overflowY:_b?"auto":"hidden"}); -if(_b){ -mb.h=this.maxHeight; -if("w" in mb){ -mb.w+=16; -} -}else{ -delete mb.h; -} -delete mb.t; -delete mb.l; -if(this.forceWidth){ -mb.w=this.domNode.offsetWidth; -}else{ -if(this.autoWidth){ -mb.w=Math.max(mb.w,this.domNode.offsetWidth); -}else{ -delete mb.w; -} -} -if(dojo.isFunction(_7.resize)){ -_7.resize(mb); -}else{ -dojo.marginBox(_8,mb); -} -} -var _c=dijit.popup.open({parent:this,popup:_7,around:this._aroundNode,orient:dijit.getPopupAroundAlignment((this.dropDownPosition&&this.dropDownPosition.length)?this.dropDownPosition:["below"],this.isLeftToRight()),onExecute:function(){ -_9.closeDropDown(true); -},onCancel:function(){ -_9.closeDropDown(true); -},onClose:function(){ -dojo.attr(_9._popupStateNode,"popupActive",false); -dojo.removeClass(_9._popupStateNode,"dijitHasDropDownOpen"); -_9._opened=false; -_9.state=""; -}}); -dojo.attr(this._popupStateNode,"popupActive","true"); -dojo.addClass(_9._popupStateNode,"dijitHasDropDownOpen"); -this._opened=true; -this.state="Opened"; -return _c; -},closeDropDown:function(_d){ -if(this._opened){ -if(_d){ -this.focus(); -} -dijit.popup.close(this.dropDown); -this._opened=false; -this.state=""; -} -}}); + + +dojo.declare("dijit._HasDropDown", + null, + { + // summary: + // Mixin for widgets that need drop down ability. + + // _buttonNode: [protected] DomNode + // The button/icon/node to click to display the drop down. + // Can be set via a dojoAttachPoint assignment. + // If missing, then either focusNode or domNode (if focusNode is also missing) will be used. + _buttonNode: null, + + // _arrowWrapperNode: [protected] DomNode + // Will set CSS class dijitUpArrow, dijitDownArrow, dijitRightArrow etc. on this node depending + // on where the drop down is set to be positioned. + // Can be set via a dojoAttachPoint assignment. + // If missing, then _buttonNode will be used. + _arrowWrapperNode: null, + + // _popupStateNode: [protected] DomNode + // The node to set the popupActive class on. + // Can be set via a dojoAttachPoint assignment. + // If missing, then focusNode or _buttonNode (if focusNode is missing) will be used. + _popupStateNode: null, + + // _aroundNode: [protected] DomNode + // The node to display the popup around. + // Can be set via a dojoAttachPoint assignment. + // If missing, then domNode will be used. + _aroundNode: null, + + // dropDown: [protected] Widget + // The widget to display as a popup. This widget *must* be + // defined before the startup function is called. + dropDown: null, + + // autoWidth: [protected] Boolean + // Set to true to make the drop down at least as wide as this + // widget. Set to false if the drop down should just be its + // default width + autoWidth: true, + + // forceWidth: [protected] Boolean + // Set to true to make the drop down exactly as wide as this + // widget. Overrides autoWidth. + forceWidth: false, + + // maxHeight: [protected] Integer + // The max height for our dropdown. + // Any dropdown taller than this will have scrollbars. + // Set to 0 for no max height, or -1 to limit height to available space in viewport + maxHeight: 0, + + // dropDownPosition: [const] String[] + // This variable controls the position of the drop down. + // It's an array of strings with the following values: + // + // * before: places drop down to the left of the target node/widget, or to the right in + // the case of RTL scripts like Hebrew and Arabic + // * after: places drop down to the right of the target node/widget, or to the left in + // the case of RTL scripts like Hebrew and Arabic + // * above: drop down goes above target node + // * below: drop down goes below target node + // + // The list is positions is tried, in order, until a position is found where the drop down fits + // within the viewport. + // + dropDownPosition: ["below","above"], + + // _stopClickEvents: Boolean + // When set to false, the click events will not be stopped, in + // case you want to use them in your subwidget + _stopClickEvents: true, + + _onDropDownMouseDown: function(/*Event*/ e){ + // summary: + // Callback when the user mousedown's on the arrow icon + + if(this.disabled || this.readOnly){ return; } + + dojo.stopEvent(e); + + this._docHandler = this.connect(dojo.doc, "onmouseup", "_onDropDownMouseUp"); + + this.toggleDropDown(); + }, + + _onDropDownMouseUp: function(/*Event?*/ e){ + // summary: + // Callback when the user lifts their mouse after mouse down on the arrow icon. + // If the drop is a simple menu and the mouse is over the menu, we execute it, otherwise, we focus our + // dropDown node. If the event is missing, then we are not + // a mouseup event. + // + // This is useful for the common mouse movement pattern + // with native browser <select> nodes: + // 1. mouse down on the select node (probably on the arrow) + // 2. move mouse to a menu item while holding down the mouse button + // 3. mouse up. this selects the menu item as though the user had clicked it. + if(e && this._docHandler){ + this.disconnect(this._docHandler); + } + var dropDown = this.dropDown, overMenu = false; + + if(e && this._opened){ + // This code deals with the corner-case when the drop down covers the original widget, + // because it's so large. In that case mouse-up shouldn't select a value from the menu. + // Find out if our target is somewhere in our dropdown widget, + // but not over our _buttonNode (the clickable node) + var c = dojo.position(this._buttonNode, true); + if(!(e.pageX >= c.x && e.pageX <= c.x + c.w) || + !(e.pageY >= c.y && e.pageY <= c.y + c.h)){ + var t = e.target; + while(t && !overMenu){ + if(dojo.hasClass(t, "dijitPopup")){ + overMenu = true; + }else{ + t = t.parentNode; + } + } + if(overMenu){ + t = e.target; + if(dropDown.onItemClick){ + var menuItem; + while(t && !(menuItem = dijit.byNode(t))){ + t = t.parentNode; + } + if(menuItem && menuItem.onClick && menuItem.getParent){ + menuItem.getParent().onItemClick(menuItem, e); + } + } + return; + } + } + } + if(this._opened && dropDown.focus && dropDown.autoFocus !== false){ + // Focus the dropdown widget - do it on a delay so that we + // don't steal our own focus. + window.setTimeout(dojo.hitch(dropDown, "focus"), 1); + } + }, + + _onDropDownClick: function(/*Event*/ e){ + // the drop down was already opened on mousedown/keydown; just need to call stopEvent() + if(this._stopClickEvents){ + dojo.stopEvent(e); + } + }, + + buildRendering: function(){ + this.inherited(arguments); + + this._buttonNode = this._buttonNode || this.focusNode || this.domNode; + this._popupStateNode = this._popupStateNode || this.focusNode || this._buttonNode; + + // Add a class to the "dijitDownArrowButton" type class to _buttonNode so theme can set direction of arrow + // based on where drop down will normally appear + var defaultPos = { + "after" : this.isLeftToRight() ? "Right" : "Left", + "before" : this.isLeftToRight() ? "Left" : "Right", + "above" : "Up", + "below" : "Down", + "left" : "Left", + "right" : "Right" + }[this.dropDownPosition[0]] || this.dropDownPosition[0] || "Down"; + dojo.addClass(this._arrowWrapperNode || this._buttonNode, "dijit" + defaultPos + "ArrowButton"); + }, + + postCreate: function(){ + // summary: + // set up nodes and connect our mouse and keypress events + + this.inherited(arguments); + + this.connect(this._buttonNode, "onmousedown", "_onDropDownMouseDown"); + this.connect(this._buttonNode, "onclick", "_onDropDownClick"); + this.connect(this.focusNode, "onkeypress", "_onKey"); + this.connect(this.focusNode, "onkeyup", "_onKeyUp"); + }, + + destroy: function(){ + if(this.dropDown){ + // Destroy the drop down, unless it's already been destroyed. This can happen because + // the drop down is a direct child of <body> even though it's logically my child. + if(!this.dropDown._destroyed){ + this.dropDown.destroyRecursive(); + } + delete this.dropDown; + } + this.inherited(arguments); + }, + + _onKey: function(/*Event*/ e){ + // summary: + // Callback when the user presses a key while focused on the button node + + if(this.disabled || this.readOnly){ return; } + + var d = this.dropDown, target = e.target; + if(d && this._opened && d.handleKey){ + if(d.handleKey(e) === false){ + /* false return code means that the drop down handled the key */ + dojo.stopEvent(e); + return; + } + } + if(d && this._opened && e.charOrCode == dojo.keys.ESCAPE){ + this.closeDropDown(); + dojo.stopEvent(e); + }else if(!this._opened && + (e.charOrCode == dojo.keys.DOWN_ARROW || + ( (e.charOrCode == dojo.keys.ENTER || e.charOrCode == " ") && + //ignore enter and space if the event is for a text input + ((target.tagName || "").toLowerCase() !== 'input' || + (target.type && target.type.toLowerCase() !== 'text'))))){ + // Toggle the drop down, but wait until keyup so that the drop down doesn't + // get a stray keyup event, or in the case of key-repeat (because user held + // down key for too long), stray keydown events + this._toggleOnKeyUp = true; + dojo.stopEvent(e); + } + }, + + _onKeyUp: function(){ + if(this._toggleOnKeyUp){ + delete this._toggleOnKeyUp; + this.toggleDropDown(); + var d = this.dropDown; // drop down may not exist until toggleDropDown() call + if(d && d.focus){ + setTimeout(dojo.hitch(d, "focus"), 1); + } + } + }, + + _onBlur: function(){ + // summary: + // Called magically when focus has shifted away from this widget and it's dropdown + + // Don't focus on button if the user has explicitly focused on something else (happens + // when user clicks another control causing the current popup to close).. + // But if focus is inside of the drop down then reset focus to me, because IE doesn't like + // it when you display:none a node with focus. + var focusMe = dijit._curFocus && this.dropDown && dojo.isDescendant(dijit._curFocus, this.dropDown.domNode); + + this.closeDropDown(focusMe); + + this.inherited(arguments); + }, + + isLoaded: function(){ + // summary: + // Returns whether or not the dropdown is loaded. This can + // be overridden in order to force a call to loadDropDown(). + // tags: + // protected + + return true; + }, + + loadDropDown: function(/* Function */ loadCallback){ + // summary: + // Loads the data for the dropdown, and at some point, calls + // the given callback. This is basically a callback when the + // user presses the down arrow button to open the drop down. + // tags: + // protected + + loadCallback(); + }, + + toggleDropDown: function(){ + // summary: + // Callback when the user presses the down arrow button or presses + // the down arrow key to open/close the drop down. + // Toggle the drop-down widget; if it is up, close it, if not, open it + // tags: + // protected + + if(this.disabled || this.readOnly){ return; } + if(!this._opened){ + // If we aren't loaded, load it first so there isn't a flicker + if(!this.isLoaded()){ + this.loadDropDown(dojo.hitch(this, "openDropDown")); + return; + }else{ + this.openDropDown(); + } + }else{ + this.closeDropDown(); + } + }, + + openDropDown: function(){ + // summary: + // Opens the dropdown for this widget. To be called only when this.dropDown + // has been created and is ready to display (ie, it's data is loaded). + // returns: + // return value of dijit.popup.open() + // tags: + // protected + + var dropDown = this.dropDown, + ddNode = dropDown.domNode, + aroundNode = this._aroundNode || this.domNode, + self = this; + + // Prepare our popup's height and honor maxHeight if it exists. + + // TODO: isn't maxHeight dependent on the return value from dijit.popup.open(), + // ie, dependent on how much space is available (BK) + + if(!this._preparedNode){ + this._preparedNode = true; + // Check if we have explicitly set width and height on the dropdown widget dom node + if(ddNode.style.width){ + this._explicitDDWidth = true; + } + if(ddNode.style.height){ + this._explicitDDHeight = true; + } + } + + // Code for resizing dropdown (height limitation, or increasing width to match my width) + if(this.maxHeight || this.forceWidth || this.autoWidth){ + var myStyle = { + display: "", + visibility: "hidden" + }; + if(!this._explicitDDWidth){ + myStyle.width = ""; + } + if(!this._explicitDDHeight){ + myStyle.height = ""; + } + dojo.style(ddNode, myStyle); + + // Figure out maximum height allowed (if there is a height restriction) + var maxHeight = this.maxHeight; + if(maxHeight == -1){ + // limit height to space available in viewport either above or below my domNode + // (whichever side has more room) + var viewport = dojo.window.getBox(), + position = dojo.position(aroundNode, false); + maxHeight = Math.floor(Math.max(position.y, viewport.h - (position.y + position.h))); + } + + // Attach dropDown to DOM and make make visibility:hidden rather than display:none + // so we call startup() and also get the size + if(dropDown.startup && !dropDown._started){ + dropDown.startup(); + } + + dijit.popup.moveOffScreen(dropDown); + // Get size of drop down, and determine if vertical scroll bar needed + var mb = dojo._getMarginSize(ddNode); + var overHeight = (maxHeight && mb.h > maxHeight); + dojo.style(ddNode, { + overflowX: "hidden", + overflowY: overHeight ? "auto" : "hidden" + }); + if(overHeight){ + mb.h = maxHeight; + if("w" in mb){ + mb.w += 16; // room for vertical scrollbar + } + }else{ + delete mb.h; + } + + // Adjust dropdown width to match or be larger than my width + if(this.forceWidth){ + mb.w = aroundNode.offsetWidth; + }else if(this.autoWidth){ + mb.w = Math.max(mb.w, aroundNode.offsetWidth); + }else{ + delete mb.w; + } + + // And finally, resize the dropdown to calculated height and width + if(dojo.isFunction(dropDown.resize)){ + dropDown.resize(mb); + }else{ + dojo.marginBox(ddNode, mb); + } + } + + var retVal = dijit.popup.open({ + parent: this, + popup: dropDown, + around: aroundNode, + orient: dijit.getPopupAroundAlignment((this.dropDownPosition && this.dropDownPosition.length) ? this.dropDownPosition : ["below"],this.isLeftToRight()), + onExecute: function(){ + self.closeDropDown(true); + }, + onCancel: function(){ + self.closeDropDown(true); + }, + onClose: function(){ + dojo.attr(self._popupStateNode, "popupActive", false); + dojo.removeClass(self._popupStateNode, "dijitHasDropDownOpen"); + self._opened = false; + } + }); + dojo.attr(this._popupStateNode, "popupActive", "true"); + dojo.addClass(self._popupStateNode, "dijitHasDropDownOpen"); + this._opened=true; + + // TODO: set this.checked and call setStateClass(), to affect button look while drop down is shown + return retVal; + }, + + closeDropDown: function(/*Boolean*/ focus){ + // summary: + // Closes the drop down on this widget + // focus: + // If true, refocuses the button widget + // tags: + // protected + + if(this._opened){ + if(focus){ this.focus(); } + dijit.popup.close(this.dropDown); + this._opened = false; + } + } + + } +); + } |