summaryrefslogtreecommitdiff
path: root/lib/dijit/_HasDropDown.js.uncompressed.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dijit/_HasDropDown.js.uncompressed.js')
-rw-r--r--lib/dijit/_HasDropDown.js.uncompressed.js509
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;
+ }
+ }
+
+ });
+});