]>
git.wh0rd.org - tt-rss.git/blob - lib/dijit/_HasDropDown.js.uncompressed.js
1 define("dijit/_HasDropDown", [
2 "dojo/_base/declare", // declare
4 "dojo/_base/event", // event.stop
5 "dojo/dom", // dom.isDescendant
6 "dojo/dom-attr", // domAttr.set
7 "dojo/dom-class", // domClass.add domClass.contains domClass.remove
8 "dojo/dom-geometry", // domGeometry.marginBox domGeometry.position
9 "dojo/dom-style", // domStyle.set
10 "dojo/has", // has("touch")
11 "dojo/keys", // keys.DOWN_ARROW keys.ENTER keys.ESCAPE
12 "dojo/_base/lang", // lang.hitch lang.isFunction
14 "dojo/window", // winUtils.getBox
15 "./registry", // registry.byNode()
19 ], function(declare
, Deferred
, event
,dom
, domAttr
, domClass
, domGeometry
, domStyle
, has
, keys
, lang
, on
,
20 winUtils
, registry
, focus
, popup
, _FocusMixin
){
26 return declare("dijit._HasDropDown", _FocusMixin
, {
28 // Mixin for widgets that need drop down ability.
30 // _buttonNode: [protected] DomNode
31 // The button/icon/node to click to display the drop down.
32 // Can be set via a data-dojo-attach-point assignment.
33 // If missing, then either focusNode or domNode (if focusNode is also missing) will be used.
36 // _arrowWrapperNode: [protected] DomNode
37 // Will set CSS class dijitUpArrow, dijitDownArrow, dijitRightArrow etc. on this node depending
38 // on where the drop down is set to be positioned.
39 // Can be set via a data-dojo-attach-point assignment.
40 // If missing, then _buttonNode will be used.
41 _arrowWrapperNode
: null,
43 // _popupStateNode: [protected] DomNode
44 // The node to set the popupActive class on.
45 // Can be set via a data-dojo-attach-point assignment.
46 // If missing, then focusNode or _buttonNode (if focusNode is missing) will be used.
47 _popupStateNode
: null,
49 // _aroundNode: [protected] DomNode
50 // The node to display the popup around.
51 // Can be set via a data-dojo-attach-point assignment.
52 // If missing, then domNode will be used.
55 // dropDown: [protected] Widget
56 // The widget to display as a popup. This widget *must* be
57 // defined before the startup function is called.
60 // autoWidth: [protected] Boolean
61 // Set to true to make the drop down at least as wide as this
62 // widget. Set to false if the drop down should just be its
66 // forceWidth: [protected] Boolean
67 // Set to true to make the drop down exactly as wide as this
68 // widget. Overrides autoWidth.
71 // maxHeight: [protected] Integer
72 // The max height for our dropdown.
73 // Any dropdown taller than this will have scrollbars.
74 // Set to 0 for no max height, or -1 to limit height to available space in viewport
77 // dropDownPosition: [const] String[]
78 // This variable controls the position of the drop down.
79 // It's an array of strings with the following values:
81 // - before: places drop down to the left of the target node/widget, or to the right in
82 // the case of RTL scripts like Hebrew and Arabic
83 // - after: places drop down to the right of the target node/widget, or to the left in
84 // the case of RTL scripts like Hebrew and Arabic
85 // - above: drop down goes above target node
86 // - below: drop down goes below target node
88 // The list is positions is tried, in order, until a position is found where the drop down fits
89 // within the viewport.
91 dropDownPosition
: ["below","above"],
93 // _stopClickEvents: Boolean
94 // When set to false, the click events will not be stopped, in
95 // case you want to use them in your subclass
96 _stopClickEvents
: true,
98 _onDropDownMouseDown: function(/*Event*/ e
){
100 // Callback when the user mousedown's on the arrow icon
101 if(this.disabled
|| this.readOnly
){ return; }
103 // Prevent default to stop things like text selection, but don't stop propagation, so that:
104 // 1. TimeTextBox etc. can focus the <input> on mousedown
105 // 2. dropDownButtonActive class applied by _CssStateMixin (on button depress)
106 // 3. user defined onMouseDown handler fires
109 this._docHandler
= this.connect(this.ownerDocument
, "mouseup", "_onDropDownMouseUp");
111 this.toggleDropDown();
114 _onDropDownMouseUp: function(/*Event?*/ e
){
116 // Callback when the user lifts their mouse after mouse down on the arrow icon.
117 // If the drop down is a simple menu and the mouse is over the menu, we execute it, otherwise, we focus our
118 // drop down widget. If the event is missing, then we are not
121 // This is useful for the common mouse movement pattern
122 // with native browser `<select>` nodes:
124 // 1. mouse down on the select node (probably on the arrow)
125 // 2. move mouse to a menu item while holding down the mouse button
126 // 3. mouse up. this selects the menu item as though the user had clicked it.
127 if(e
&& this._docHandler
){
128 this.disconnect(this._docHandler
);
130 var dropDown
= this.dropDown
, overMenu
= false;
132 if(e
&& this._opened
){
133 // This code deals with the corner-case when the drop down covers the original widget,
134 // because it's so large. In that case mouse-up shouldn't select a value from the menu.
135 // Find out if our target is somewhere in our dropdown widget,
136 // but not over our _buttonNode (the clickable node)
137 var c
= domGeometry
.position(this._buttonNode
, true);
138 if(!(e
.pageX
>= c
.x
&& e
.pageX
<= c
.x
+ c
.w
) ||
139 !(e
.pageY
>= c
.y
&& e
.pageY
<= c
.y
+ c
.h
)){
141 while(t
&& !overMenu
){
142 if(domClass
.contains(t
, "dijitPopup")){
150 if(dropDown
.onItemClick
){
152 while(t
&& !(menuItem
= registry
.byNode(t
))){
155 if(menuItem
&& menuItem
.onClick
&& menuItem
.getParent
){
156 menuItem
.getParent().onItemClick(menuItem
, e
);
164 if(dropDown
.focus
&& dropDown
.autoFocus
!== false){
165 // Focus the dropdown widget - do it on a delay so that we
166 // don't steal back focus from the dropdown.
167 this._focusDropDownTimer
= this.defer(function(){
169 delete this._focusDropDownTimer
;
173 // The drop down arrow icon probably can't receive focus, but widget itself should get focus.
174 // defer() needed to make it work on IE (test DateTextBox)
179 this._justGotMouseUp
= true;
180 this.defer(function(){
181 this._justGotMouseUp
= false;
186 _onDropDownClick: function(/*Event*/ e
){
187 if(has("touch") && !this._justGotMouseUp
){
188 // If there was no preceding mousedown/mouseup (like on android), then simulate them to
189 // toggle the drop down.
191 // The if(has("touch") is necessary since IE and desktop safari get spurious onclick events
192 // when there are nested tables (specifically, clicking on a table that holds a dijit/form/Select,
193 // but not on the Select itself, causes an onclick event on the Select)
194 this._onDropDownMouseDown(e
);
195 this._onDropDownMouseUp(e
);
198 // The drop down was already opened on mousedown/keydown; just need to call stopEvent().
199 if(this._stopClickEvents
){
204 buildRendering: function(){
205 this.inherited(arguments
);
207 this._buttonNode
= this._buttonNode
|| this.focusNode
|| this.domNode
;
208 this._popupStateNode
= this._popupStateNode
|| this.focusNode
|| this._buttonNode
;
210 // Add a class to the "dijitDownArrowButton" type class to _buttonNode so theme can set direction of arrow
211 // based on where drop down will normally appear
213 "after" : this.isLeftToRight() ? "Right" : "Left",
214 "before" : this.isLeftToRight() ? "Left" : "Right",
219 }[this.dropDownPosition
[0]] || this.dropDownPosition
[0] || "Down";
220 domClass
.add(this._arrowWrapperNode
|| this._buttonNode
, "dijit" + defaultPos
+ "ArrowButton");
223 postCreate: function(){
225 // set up nodes and connect our mouse and keyboard events
227 this.inherited(arguments
);
229 var keyboardEventNode
= this.focusNode
|| this.domNode
;
231 on(this._buttonNode
, "mousedown", lang
.hitch(this, "_onDropDownMouseDown")),
232 on(this._buttonNode
, "click", lang
.hitch(this, "_onDropDownClick")),
233 on(keyboardEventNode
, "keydown", lang
.hitch(this, "_onKey")),
234 on(keyboardEventNode
, "keyup", lang
.hitch(this, "_onKeyUp"))
240 // Destroy the drop down, unless it's already been destroyed. This can happen because
241 // the drop down is a direct child of <body> even though it's logically my child.
242 if(!this.dropDown
._destroyed
){
243 this.dropDown
.destroyRecursive();
245 delete this.dropDown
;
247 this.inherited(arguments
);
250 _onKey: function(/*Event*/ e
){
252 // Callback when the user presses a key while focused on the button node
254 if(this.disabled
|| this.readOnly
){ return; }
255 var d
= this.dropDown
, target
= e
.target
;
256 if(d
&& this._opened
&& d
.handleKey
){
257 if(d
.handleKey(e
) === false){
258 /* false return code means that the drop down handled the key */
263 if(d
&& this._opened
&& e
.keyCode
== keys
.ESCAPE
){
264 this.closeDropDown();
266 }else if(!this._opened
&&
267 (e
.keyCode
== keys
.DOWN_ARROW
||
268 ( (e
.keyCode
== keys
.ENTER
|| e
.keyCode
== keys
.SPACE
) &&
269 //ignore enter and space if the event is for a text input
270 ((target
.tagName
|| "").toLowerCase() !== 'input' ||
271 (target
.type
&& target
.type
.toLowerCase() !== 'text'))))){
272 // Toggle the drop down, but wait until keyup so that the drop down doesn't
273 // get a stray keyup event, or in the case of key-repeat (because user held
274 // down key for too long), stray keydown events
275 this._toggleOnKeyUp
= true;
280 _onKeyUp: function(){
281 if(this._toggleOnKeyUp
){
282 delete this._toggleOnKeyUp
;
283 this.toggleDropDown();
284 var d
= this.dropDown
; // drop down may not exist until toggleDropDown() call
286 this.defer(lang
.hitch(d
, "focus"), 1);
293 // Called magically when focus has shifted away from this widget and it's dropdown
295 // Don't focus on button if the user has explicitly focused on something else (happens
296 // when user clicks another control causing the current popup to close)..
297 // But if focus is inside of the drop down then reset focus to me, because IE doesn't like
298 // it when you display:none a node with focus.
299 var focusMe
= focus
.curNode
&& this.dropDown
&& dom
.isDescendant(focus
.curNode
, this.dropDown
.domNode
);
301 this.closeDropDown(focusMe
);
303 this.inherited(arguments
);
306 isLoaded: function(){
308 // Returns true if the dropdown exists and it's data is loaded. This can
309 // be overridden in order to force a call to loadDropDown().
316 loadDropDown: function(/*Function*/ loadCallback
){
318 // Creates the drop down if it doesn't exist, loads the data
319 // if there's an href and it hasn't been loaded yet, and then calls
320 // the given callback.
324 // TODO: for 2.0, change API to return a Deferred, instead of calling loadCallback?
328 loadAndOpenDropDown: function(){
330 // Creates the drop down if it doesn't exist, loads the data
331 // if there's an href and it hasn't been loaded yet, and
332 // then opens the drop down. This is basically a callback when the
333 // user presses the down arrow button to open the drop down.
335 // Deferred for the drop down widget that
336 // fires when drop down is created and loaded
339 var d
= new Deferred(),
340 afterLoad
= lang
.hitch(this, function(){
342 d
.resolve(this.dropDown
);
344 if(!this.isLoaded()){
345 this.loadDropDown(afterLoad
);
352 toggleDropDown: function(){
354 // Callback when the user presses the down arrow button or presses
355 // the down arrow key to open/close the drop down.
356 // Toggle the drop-down widget; if it is up, close it, if not, open it
360 if(this.disabled
|| this.readOnly
){ return; }
362 this.loadAndOpenDropDown();
364 this.closeDropDown();
368 openDropDown: function(){
370 // Opens the dropdown for this widget. To be called only when this.dropDown
371 // has been created and is ready to display (ie, it's data is loaded).
373 // return value of dijit/popup.open()
377 var dropDown
= this.dropDown
,
378 ddNode
= dropDown
.domNode
,
379 aroundNode
= this._aroundNode
|| this.domNode
,
382 // Prepare our popup's height and honor maxHeight if it exists.
384 // TODO: isn't maxHeight dependent on the return value from dijit/popup.open(),
385 // ie, dependent on how much space is available (BK)
387 if(!this._preparedNode
){
388 this._preparedNode
= true;
389 // Check if we have explicitly set width and height on the dropdown widget dom node
390 if(ddNode
.style
.width
){
391 this._explicitDDWidth
= true;
393 if(ddNode
.style
.height
){
394 this._explicitDDHeight
= true;
398 // Code for resizing dropdown (height limitation, or increasing width to match my width)
399 if(this.maxHeight
|| this.forceWidth
|| this.autoWidth
){
404 if(!this._explicitDDWidth
){
407 if(!this._explicitDDHeight
){
410 domStyle
.set(ddNode
, myStyle
);
412 // Figure out maximum height allowed (if there is a height restriction)
413 var maxHeight
= this.maxHeight
;
415 // limit height to space available in viewport either above or below my domNode
416 // (whichever side has more room)
417 var viewport
= winUtils
.getBox(this.ownerDocument
),
418 position
= domGeometry
.position(aroundNode
, false);
419 maxHeight
= Math
.floor(Math
.max(position
.y
, viewport
.h
- (position
.y
+ position
.h
)));
422 // Attach dropDown to DOM and make make visibility:hidden rather than display:none
423 // so we call startup() and also get the size
424 popup
.moveOffScreen(dropDown
);
426 if(dropDown
.startup
&& !dropDown
._started
){
427 dropDown
.startup(); // this has to be done after being added to the DOM
429 // Get size of drop down, and determine if vertical scroll bar needed. If no scroll bar needed,
430 // use overflow:visible rather than overflow:hidden so off-by-one errors don't hide drop down border.
431 var mb
= domGeometry
.getMarginSize(ddNode
);
432 var overHeight
= (maxHeight
&& mb
.h
> maxHeight
);
433 domStyle
.set(ddNode
, {
434 overflowX
: "visible",
435 overflowY
: overHeight
? "auto" : "visible"
440 mb
.w
+= 16; // room for vertical scrollbar
446 // Adjust dropdown width to match or be larger than my width
448 mb
.w
= aroundNode
.offsetWidth
;
449 }else if(this.autoWidth
){
450 mb
.w
= Math
.max(mb
.w
, aroundNode
.offsetWidth
);
455 // And finally, resize the dropdown to calculated height and width
456 if(lang
.isFunction(dropDown
.resize
)){
459 domGeometry
.setMarginBox(ddNode
, mb
);
463 var retVal
= popup
.open({
467 orient
: this.dropDownPosition
,
468 onExecute: function(){
469 self
.closeDropDown(true);
471 onCancel: function(){
472 self
.closeDropDown(true);
475 domAttr
.set(self
._popupStateNode
, "popupActive", false);
476 domClass
.remove(self
._popupStateNode
, "dijitHasDropDownOpen");
477 self
._set("_opened", false); // use set() because _CssStateMixin is watching
480 domAttr
.set(this._popupStateNode
, "popupActive", "true");
481 domClass
.add(this._popupStateNode
, "dijitHasDropDownOpen");
482 this._set("_opened", true); // use set() because _CssStateMixin is watching
483 this.domNode
.setAttribute("aria-expanded", "true");
488 closeDropDown: function(/*Boolean*/ focus
){
490 // Closes the drop down on this widget
492 // If true, refocuses the button widget
496 if(this._focusDropDownTimer
){
497 this._focusDropDownTimer
.remove();
498 delete this._focusDropDownTimer
;
501 this.domNode
.setAttribute("aria-expanded", "false");
502 if(focus
){ this.focus(); }
503 popup
.close(this.dropDown
);
504 this._opened
= false;