diff options
Diffstat (limited to 'lib/dijit/_HasDropDown.js.uncompressed.js')
-rw-r--r-- | lib/dijit/_HasDropDown.js.uncompressed.js | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/lib/dijit/_HasDropDown.js.uncompressed.js b/lib/dijit/_HasDropDown.js.uncompressed.js new file mode 100644 index 000000000..6cc6b8731 --- /dev/null +++ b/lib/dijit/_HasDropDown.js.uncompressed.js @@ -0,0 +1,509 @@ +define("dijit/_HasDropDown", [ + "dojo/_base/declare", // declare + "dojo/_base/Deferred", + "dojo/_base/event", // event.stop + "dojo/dom", // dom.isDescendant + "dojo/dom-attr", // domAttr.set + "dojo/dom-class", // domClass.add domClass.contains domClass.remove + "dojo/dom-geometry", // domGeometry.marginBox domGeometry.position + "dojo/dom-style", // domStyle.set + "dojo/has", // has("touch") + "dojo/keys", // keys.DOWN_ARROW keys.ENTER keys.ESCAPE + "dojo/_base/lang", // lang.hitch lang.isFunction + "dojo/on", + "dojo/window", // winUtils.getBox + "./registry", // registry.byNode() + "./focus", + "./popup", + "./_FocusMixin" +], function(declare, Deferred, event,dom, domAttr, domClass, domGeometry, domStyle, has, keys, lang, on, + winUtils, registry, focus, popup, _FocusMixin){ + + + // module: + // dijit/_HasDropDown + + return declare("dijit._HasDropDown", _FocusMixin, { + // 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 data-dojo-attach-point 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 data-dojo-attach-point 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 data-dojo-attach-point 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 data-dojo-attach-point 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 subclass + _stopClickEvents: true, + + _onDropDownMouseDown: function(/*Event*/ e){ + // summary: + // Callback when the user mousedown's on the arrow icon + if(this.disabled || this.readOnly){ return; } + + // Prevent default to stop things like text selection, but don't stop propagation, so that: + // 1. TimeTextBox etc. can focus the <input> on mousedown + // 2. dropDownButtonActive class applied by _CssStateMixin (on button depress) + // 3. user defined onMouseDown handler fires + e.preventDefault(); + + this._docHandler = this.connect(this.ownerDocument, "mouseup", "_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 down is a simple menu and the mouse is over the menu, we execute it, otherwise, we focus our + // drop down widget. 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 = domGeometry.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(domClass.contains(t, "dijitPopup")){ + overMenu = true; + }else{ + t = t.parentNode; + } + } + if(overMenu){ + t = e.target; + if(dropDown.onItemClick){ + var menuItem; + while(t && !(menuItem = registry.byNode(t))){ + t = t.parentNode; + } + if(menuItem && menuItem.onClick && menuItem.getParent){ + menuItem.getParent().onItemClick(menuItem, e); + } + } + return; + } + } + } + if(this._opened){ + if(dropDown.focus && dropDown.autoFocus !== false){ + // Focus the dropdown widget - do it on a delay so that we + // don't steal back focus from the dropdown. + this._focusDropDownTimer = this.defer(function(){ + dropDown.focus(); + delete this._focusDropDownTimer; + }); + } + }else{ + // The drop down arrow icon probably can't receive focus, but widget itself should get focus. + // defer() needed to make it work on IE (test DateTextBox) + this.defer("focus"); + } + + if(has("touch")){ + this._justGotMouseUp = true; + this.defer(function(){ + this._justGotMouseUp = false; + }); + } + }, + + _onDropDownClick: function(/*Event*/ e){ + if(has("touch") && !this._justGotMouseUp){ + // If there was no preceding mousedown/mouseup (like on android), then simulate them to + // toggle the drop down. + // + // The if(has("touch") is necessary since IE and desktop safari get spurious onclick events + // when there are nested tables (specifically, clicking on a table that holds a dijit/form/Select, + // but not on the Select itself, causes an onclick event on the Select) + this._onDropDownMouseDown(e); + this._onDropDownMouseUp(e); + } + + // The drop down was already opened on mousedown/keydown; just need to call stopEvent(). + if(this._stopClickEvents){ + event.stop(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"; + domClass.add(this._arrowWrapperNode || this._buttonNode, "dijit" + defaultPos + "ArrowButton"); + }, + + postCreate: function(){ + // summary: + // set up nodes and connect our mouse and keyboard events + + this.inherited(arguments); + + var keyboardEventNode = this.focusNode || this.domNode; + this.own( + on(this._buttonNode, "mousedown", lang.hitch(this, "_onDropDownMouseDown")), + on(this._buttonNode, "click", lang.hitch(this, "_onDropDownClick")), + on(keyboardEventNode, "keydown", lang.hitch(this, "_onKey")), + on(keyboardEventNode, "keyup", lang.hitch(this, "_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 */ + event.stop(e); + return; + } + } + if(d && this._opened && e.keyCode == keys.ESCAPE){ + this.closeDropDown(); + event.stop(e); + }else if(!this._opened && + (e.keyCode == keys.DOWN_ARROW || + ( (e.keyCode == keys.ENTER || e.keyCode == keys.SPACE) && + //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; + event.stop(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){ + this.defer(lang.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 = focus.curNode && this.dropDown && dom.isDescendant(focus.curNode, this.dropDown.domNode); + + this.closeDropDown(focusMe); + + this.inherited(arguments); + }, + + isLoaded: function(){ + // summary: + // Returns true if the dropdown exists and it's data is loaded. This can + // be overridden in order to force a call to loadDropDown(). + // tags: + // protected + + return true; + }, + + loadDropDown: function(/*Function*/ loadCallback){ + // summary: + // Creates the drop down if it doesn't exist, loads the data + // if there's an href and it hasn't been loaded yet, and then calls + // the given callback. + // tags: + // protected + + // TODO: for 2.0, change API to return a Deferred, instead of calling loadCallback? + loadCallback(); + }, + + loadAndOpenDropDown: function(){ + // summary: + // Creates the drop down if it doesn't exist, loads the data + // if there's an href and it hasn't been loaded yet, and + // then opens the drop down. This is basically a callback when the + // user presses the down arrow button to open the drop down. + // returns: Deferred + // Deferred for the drop down widget that + // fires when drop down is created and loaded + // tags: + // protected + var d = new Deferred(), + afterLoad = lang.hitch(this, function(){ + this.openDropDown(); + d.resolve(this.dropDown); + }); + if(!this.isLoaded()){ + this.loadDropDown(afterLoad); + }else{ + afterLoad(); + } + return d; + }, + + 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){ + this.loadAndOpenDropDown(); + }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 = ""; + } + domStyle.set(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 = winUtils.getBox(this.ownerDocument), + position = domGeometry.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 + popup.moveOffScreen(dropDown); + + if(dropDown.startup && !dropDown._started){ + dropDown.startup(); // this has to be done after being added to the DOM + } + // Get size of drop down, and determine if vertical scroll bar needed. If no scroll bar needed, + // use overflow:visible rather than overflow:hidden so off-by-one errors don't hide drop down border. + var mb = domGeometry.getMarginSize(ddNode); + var overHeight = (maxHeight && mb.h > maxHeight); + domStyle.set(ddNode, { + overflowX: "visible", + overflowY: overHeight ? "auto" : "visible" + }); + 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(lang.isFunction(dropDown.resize)){ + dropDown.resize(mb); + }else{ + domGeometry.setMarginBox(ddNode, mb); + } + } + + var retVal = popup.open({ + parent: this, + popup: dropDown, + around: aroundNode, + orient: this.dropDownPosition, + onExecute: function(){ + self.closeDropDown(true); + }, + onCancel: function(){ + self.closeDropDown(true); + }, + onClose: function(){ + domAttr.set(self._popupStateNode, "popupActive", false); + domClass.remove(self._popupStateNode, "dijitHasDropDownOpen"); + self._set("_opened", false); // use set() because _CssStateMixin is watching + } + }); + domAttr.set(this._popupStateNode, "popupActive", "true"); + domClass.add(this._popupStateNode, "dijitHasDropDownOpen"); + this._set("_opened", true); // use set() because _CssStateMixin is watching + this.domNode.setAttribute("aria-expanded", "true"); + + 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._focusDropDownTimer){ + this._focusDropDownTimer.remove(); + delete this._focusDropDownTimer; + } + if(this._opened){ + this.domNode.setAttribute("aria-expanded", "false"); + if(focus){ this.focus(); } + popup.close(this.dropDown); + this._opened = false; + } + } + + }); +}); |