]> git.wh0rd.org - tt-rss.git/blame - lib/dijit/_HasDropDown.js.uncompressed.js
modify dojo rebuild script to remove uncompressed files
[tt-rss.git] / lib / dijit / _HasDropDown.js.uncompressed.js
CommitLineData
f0cfe83e
AD
1define("dijit/_HasDropDown", [
2 "dojo/_base/declare", // declare
3 "dojo/_base/Deferred",
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
13 "dojo/on",
14 "dojo/window", // winUtils.getBox
15 "./registry", // registry.byNode()
16 "./focus",
17 "./popup",
18 "./_FocusMixin"
19], function(declare, Deferred, event,dom, domAttr, domClass, domGeometry, domStyle, has, keys, lang, on,
20 winUtils, registry, focus, popup, _FocusMixin){
21
22
23 // module:
24 // dijit/_HasDropDown
25
26 return declare("dijit._HasDropDown", _FocusMixin, {
27 // summary:
28 // Mixin for widgets that need drop down ability.
29
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.
34 _buttonNode: null,
35
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,
42
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,
48
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.
53 _aroundNode: null,
54
55 // dropDown: [protected] Widget
56 // The widget to display as a popup. This widget *must* be
57 // defined before the startup function is called.
58 dropDown: null,
59
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
63 // default width
64 autoWidth: true,
65
66 // forceWidth: [protected] Boolean
67 // Set to true to make the drop down exactly as wide as this
68 // widget. Overrides autoWidth.
69 forceWidth: false,
70
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
75 maxHeight: 0,
76
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:
80 //
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
87 //
88 // The list is positions is tried, in order, until a position is found where the drop down fits
89 // within the viewport.
90 //
91 dropDownPosition: ["below","above"],
92
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,
97
98 _onDropDownMouseDown: function(/*Event*/ e){
99 // summary:
100 // Callback when the user mousedown's on the arrow icon
101 if(this.disabled || this.readOnly){ return; }
102
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
107 e.preventDefault();
108
109 this._docHandler = this.connect(this.ownerDocument, "mouseup", "_onDropDownMouseUp");
110
111 this.toggleDropDown();
112 },
113
114 _onDropDownMouseUp: function(/*Event?*/ e){
115 // summary:
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
119 // a mouseup event.
120 //
121 // This is useful for the common mouse movement pattern
122 // with native browser `<select>` nodes:
123 //
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);
129 }
130 var dropDown = this.dropDown, overMenu = false;
131
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)){
140 var t = e.target;
141 while(t && !overMenu){
142 if(domClass.contains(t, "dijitPopup")){
143 overMenu = true;
144 }else{
145 t = t.parentNode;
146 }
147 }
148 if(overMenu){
149 t = e.target;
150 if(dropDown.onItemClick){
151 var menuItem;
152 while(t && !(menuItem = registry.byNode(t))){
153 t = t.parentNode;
154 }
155 if(menuItem && menuItem.onClick && menuItem.getParent){
156 menuItem.getParent().onItemClick(menuItem, e);
157 }
158 }
159 return;
160 }
161 }
162 }
163 if(this._opened){
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(){
168 dropDown.focus();
169 delete this._focusDropDownTimer;
170 });
171 }
172 }else{
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)
175 this.defer("focus");
176 }
177
178 if(has("touch")){
179 this._justGotMouseUp = true;
180 this.defer(function(){
181 this._justGotMouseUp = false;
182 });
183 }
184 },
185
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.
190 //
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);
196 }
197
198 // The drop down was already opened on mousedown/keydown; just need to call stopEvent().
199 if(this._stopClickEvents){
200 event.stop(e);
201 }
202 },
203
204 buildRendering: function(){
205 this.inherited(arguments);
206
207 this._buttonNode = this._buttonNode || this.focusNode || this.domNode;
208 this._popupStateNode = this._popupStateNode || this.focusNode || this._buttonNode;
209
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
212 var defaultPos = {
213 "after" : this.isLeftToRight() ? "Right" : "Left",
214 "before" : this.isLeftToRight() ? "Left" : "Right",
215 "above" : "Up",
216 "below" : "Down",
217 "left" : "Left",
218 "right" : "Right"
219 }[this.dropDownPosition[0]] || this.dropDownPosition[0] || "Down";
220 domClass.add(this._arrowWrapperNode || this._buttonNode, "dijit" + defaultPos + "ArrowButton");
221 },
222
223 postCreate: function(){
224 // summary:
225 // set up nodes and connect our mouse and keyboard events
226
227 this.inherited(arguments);
228
229 var keyboardEventNode = this.focusNode || this.domNode;
230 this.own(
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"))
235 );
236 },
237
238 destroy: function(){
239 if(this.dropDown){
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();
244 }
245 delete this.dropDown;
246 }
247 this.inherited(arguments);
248 },
249
250 _onKey: function(/*Event*/ e){
251 // summary:
252 // Callback when the user presses a key while focused on the button node
253
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 */
259 event.stop(e);
260 return;
261 }
262 }
263 if(d && this._opened && e.keyCode == keys.ESCAPE){
264 this.closeDropDown();
265 event.stop(e);
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;
276 event.stop(e);
277 }
278 },
279
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
285 if(d && d.focus){
286 this.defer(lang.hitch(d, "focus"), 1);
287 }
288 }
289 },
290
291 _onBlur: function(){
292 // summary:
293 // Called magically when focus has shifted away from this widget and it's dropdown
294
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);
300
301 this.closeDropDown(focusMe);
302
303 this.inherited(arguments);
304 },
305
306 isLoaded: function(){
307 // summary:
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().
310 // tags:
311 // protected
312
313 return true;
314 },
315
316 loadDropDown: function(/*Function*/ loadCallback){
317 // summary:
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.
321 // tags:
322 // protected
323
324 // TODO: for 2.0, change API to return a Deferred, instead of calling loadCallback?
325 loadCallback();
326 },
327
328 loadAndOpenDropDown: function(){
329 // summary:
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.
334 // returns: Deferred
335 // Deferred for the drop down widget that
336 // fires when drop down is created and loaded
337 // tags:
338 // protected
339 var d = new Deferred(),
340 afterLoad = lang.hitch(this, function(){
341 this.openDropDown();
342 d.resolve(this.dropDown);
343 });
344 if(!this.isLoaded()){
345 this.loadDropDown(afterLoad);
346 }else{
347 afterLoad();
348 }
349 return d;
350 },
351
352 toggleDropDown: function(){
353 // summary:
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
357 // tags:
358 // protected
359
360 if(this.disabled || this.readOnly){ return; }
361 if(!this._opened){
362 this.loadAndOpenDropDown();
363 }else{
364 this.closeDropDown();
365 }
366 },
367
368 openDropDown: function(){
369 // summary:
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).
372 // returns:
373 // return value of dijit/popup.open()
374 // tags:
375 // protected
376
377 var dropDown = this.dropDown,
378 ddNode = dropDown.domNode,
379 aroundNode = this._aroundNode || this.domNode,
380 self = this;
381
382 // Prepare our popup's height and honor maxHeight if it exists.
383
384 // TODO: isn't maxHeight dependent on the return value from dijit/popup.open(),
385 // ie, dependent on how much space is available (BK)
386
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;
392 }
393 if(ddNode.style.height){
394 this._explicitDDHeight = true;
395 }
396 }
397
398 // Code for resizing dropdown (height limitation, or increasing width to match my width)
399 if(this.maxHeight || this.forceWidth || this.autoWidth){
400 var myStyle = {
401 display: "",
402 visibility: "hidden"
403 };
404 if(!this._explicitDDWidth){
405 myStyle.width = "";
406 }
407 if(!this._explicitDDHeight){
408 myStyle.height = "";
409 }
410 domStyle.set(ddNode, myStyle);
411
412 // Figure out maximum height allowed (if there is a height restriction)
413 var maxHeight = this.maxHeight;
414 if(maxHeight == -1){
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)));
420 }
421
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);
425
426 if(dropDown.startup && !dropDown._started){
427 dropDown.startup(); // this has to be done after being added to the DOM
428 }
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"
436 });
437 if(overHeight){
438 mb.h = maxHeight;
439 if("w" in mb){
440 mb.w += 16; // room for vertical scrollbar
441 }
442 }else{
443 delete mb.h;
444 }
445
446 // Adjust dropdown width to match or be larger than my width
447 if(this.forceWidth){
448 mb.w = aroundNode.offsetWidth;
449 }else if(this.autoWidth){
450 mb.w = Math.max(mb.w, aroundNode.offsetWidth);
451 }else{
452 delete mb.w;
453 }
454
455 // And finally, resize the dropdown to calculated height and width
456 if(lang.isFunction(dropDown.resize)){
457 dropDown.resize(mb);
458 }else{
459 domGeometry.setMarginBox(ddNode, mb);
460 }
461 }
462
463 var retVal = popup.open({
464 parent: this,
465 popup: dropDown,
466 around: aroundNode,
467 orient: this.dropDownPosition,
468 onExecute: function(){
469 self.closeDropDown(true);
470 },
471 onCancel: function(){
472 self.closeDropDown(true);
473 },
474 onClose: function(){
475 domAttr.set(self._popupStateNode, "popupActive", false);
476 domClass.remove(self._popupStateNode, "dijitHasDropDownOpen");
477 self._set("_opened", false); // use set() because _CssStateMixin is watching
478 }
479 });
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");
484
485 return retVal;
486 },
487
488 closeDropDown: function(/*Boolean*/ focus){
489 // summary:
490 // Closes the drop down on this widget
491 // focus:
492 // If true, refocuses the button widget
493 // tags:
494 // protected
495
496 if(this._focusDropDownTimer){
497 this._focusDropDownTimer.remove();
498 delete this._focusDropDownTimer;
499 }
500 if(this._opened){
501 this.domNode.setAttribute("aria-expanded", "false");
502 if(focus){ this.focus(); }
503 popup.close(this.dropDown);
504 this._opened = false;
505 }
506 }
507
508 });
509});