]> git.wh0rd.org - tt-rss.git/blob - lib/dijit/_HasDropDown.js.uncompressed.js
update dojo to 1.7.3
[tt-rss.git] / lib / dijit / _HasDropDown.js.uncompressed.js
1 define("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",
11 "dojo/keys", // keys.DOWN_ARROW keys.ENTER keys.ESCAPE
12 "dojo/_base/lang", // lang.hitch lang.isFunction
13 "dojo/touch",
14 "dojo/_base/window", // win.doc
15 "dojo/window", // winUtils.getBox
16 "./registry", // registry.byNode()
17 "./focus",
18 "./popup",
19 "./_FocusMixin"
20 ], function(declare, Deferred, event,dom, domAttr, domClass, domGeometry, domStyle, has, keys, lang, touch,
21 win, winUtils, registry, focus, popup, _FocusMixin){
22
23 /*=====
24 var _FocusMixin = dijit._FocusMixin;
25 =====*/
26
27 // module:
28 // dijit/_HasDropDown
29 // summary:
30 // Mixin for widgets that need drop down ability.
31
32 return declare("dijit._HasDropDown", _FocusMixin, {
33 // summary:
34 // Mixin for widgets that need drop down ability.
35
36 // _buttonNode: [protected] DomNode
37 // The button/icon/node to click to display the drop down.
38 // Can be set via a data-dojo-attach-point assignment.
39 // If missing, then either focusNode or domNode (if focusNode is also missing) will be used.
40 _buttonNode: null,
41
42 // _arrowWrapperNode: [protected] DomNode
43 // Will set CSS class dijitUpArrow, dijitDownArrow, dijitRightArrow etc. on this node depending
44 // on where the drop down is set to be positioned.
45 // Can be set via a data-dojo-attach-point assignment.
46 // If missing, then _buttonNode will be used.
47 _arrowWrapperNode: null,
48
49 // _popupStateNode: [protected] DomNode
50 // The node to set the popupActive class on.
51 // Can be set via a data-dojo-attach-point assignment.
52 // If missing, then focusNode or _buttonNode (if focusNode is missing) will be used.
53 _popupStateNode: null,
54
55 // _aroundNode: [protected] DomNode
56 // The node to display the popup around.
57 // Can be set via a data-dojo-attach-point assignment.
58 // If missing, then domNode will be used.
59 _aroundNode: null,
60
61 // dropDown: [protected] Widget
62 // The widget to display as a popup. This widget *must* be
63 // defined before the startup function is called.
64 dropDown: null,
65
66 // autoWidth: [protected] Boolean
67 // Set to true to make the drop down at least as wide as this
68 // widget. Set to false if the drop down should just be its
69 // default width
70 autoWidth: true,
71
72 // forceWidth: [protected] Boolean
73 // Set to true to make the drop down exactly as wide as this
74 // widget. Overrides autoWidth.
75 forceWidth: false,
76
77 // maxHeight: [protected] Integer
78 // The max height for our dropdown.
79 // Any dropdown taller than this will have scrollbars.
80 // Set to 0 for no max height, or -1 to limit height to available space in viewport
81 maxHeight: 0,
82
83 // dropDownPosition: [const] String[]
84 // This variable controls the position of the drop down.
85 // It's an array of strings with the following values:
86 //
87 // * before: places drop down to the left of the target node/widget, or to the right in
88 // the case of RTL scripts like Hebrew and Arabic
89 // * after: places drop down to the right of the target node/widget, or to the left in
90 // the case of RTL scripts like Hebrew and Arabic
91 // * above: drop down goes above target node
92 // * below: drop down goes below target node
93 //
94 // The list is positions is tried, in order, until a position is found where the drop down fits
95 // within the viewport.
96 //
97 dropDownPosition: ["below","above"],
98
99 // _stopClickEvents: Boolean
100 // When set to false, the click events will not be stopped, in
101 // case you want to use them in your subwidget
102 _stopClickEvents: true,
103
104 _onDropDownMouseDown: function(/*Event*/ e){
105 // summary:
106 // Callback when the user mousedown's on the arrow icon
107 if(this.disabled || this.readOnly){ return; }
108
109 // Prevent default to stop things like text selection, but don't stop propogation, so that:
110 // 1. TimeTextBox etc. can focusthe <input> on mousedown
111 // 2. dropDownButtonActive class applied by _CssStateMixin (on button depress)
112 // 3. user defined onMouseDown handler fires
113 e.preventDefault();
114
115 this._docHandler = this.connect(win.doc, touch.release, "_onDropDownMouseUp");
116
117 this.toggleDropDown();
118 },
119
120 _onDropDownMouseUp: function(/*Event?*/ e){
121 // summary:
122 // Callback when the user lifts their mouse after mouse down on the arrow icon.
123 // If the drop down is a simple menu and the mouse is over the menu, we execute it, otherwise, we focus our
124 // drop down widget. If the event is missing, then we are not
125 // a mouseup event.
126 //
127 // This is useful for the common mouse movement pattern
128 // with native browser <select> nodes:
129 // 1. mouse down on the select node (probably on the arrow)
130 // 2. move mouse to a menu item while holding down the mouse button
131 // 3. mouse up. this selects the menu item as though the user had clicked it.
132 if(e && this._docHandler){
133 this.disconnect(this._docHandler);
134 }
135 var dropDown = this.dropDown, overMenu = false;
136
137 if(e && this._opened){
138 // This code deals with the corner-case when the drop down covers the original widget,
139 // because it's so large. In that case mouse-up shouldn't select a value from the menu.
140 // Find out if our target is somewhere in our dropdown widget,
141 // but not over our _buttonNode (the clickable node)
142 var c = domGeometry.position(this._buttonNode, true);
143 if(!(e.pageX >= c.x && e.pageX <= c.x + c.w) ||
144 !(e.pageY >= c.y && e.pageY <= c.y + c.h)){
145 var t = e.target;
146 while(t && !overMenu){
147 if(domClass.contains(t, "dijitPopup")){
148 overMenu = true;
149 }else{
150 t = t.parentNode;
151 }
152 }
153 if(overMenu){
154 t = e.target;
155 if(dropDown.onItemClick){
156 var menuItem;
157 while(t && !(menuItem = registry.byNode(t))){
158 t = t.parentNode;
159 }
160 if(menuItem && menuItem.onClick && menuItem.getParent){
161 menuItem.getParent().onItemClick(menuItem, e);
162 }
163 }
164 return;
165 }
166 }
167 }
168 if(this._opened){
169 if(dropDown.focus && dropDown.autoFocus !== false){
170 // Focus the dropdown widget - do it on a delay so that we
171 // don't steal our own focus.
172 window.setTimeout(lang.hitch(dropDown, "focus"), 1);
173 }
174 }else{
175 // The drop down arrow icon probably can't receive focus, but widget itself should get focus.
176 // setTimeout() needed to make it work on IE (test DateTextBox)
177 setTimeout(lang.hitch(this, "focus"), 0);
178 }
179
180 if(has("ios")){
181 this._justGotMouseUp = true;
182 setTimeout(lang.hitch(this, function(){
183 this._justGotMouseUp = false;
184 }), 0);
185 }
186 },
187
188 _onDropDownClick: function(/*Event*/ e){
189 if(has("ios") && !this._justGotMouseUp){
190 // This branch fires on iPhone for ComboBox, because the button node is an <input> and doesn't
191 // generate touchstart/touchend events. Pretend we just got a mouse down / mouse up.
192 // The if(has("ios") is necessary since IE and desktop safari get spurious onclick events
193 // when there are nested tables (specifically, clicking on a table that holds a dijit.form.Select,
194 // but not on the Select itself, causes an onclick event on the Select)
195 this._onDropDownMouseDown(e);
196 this._onDropDownMouseUp(e);
197 }
198
199 // The drop down was already opened on mousedown/keydown; just need to call stopEvent().
200 if(this._stopClickEvents){
201 event.stop(e);
202 }
203 },
204
205 buildRendering: function(){
206 this.inherited(arguments);
207
208 this._buttonNode = this._buttonNode || this.focusNode || this.domNode;
209 this._popupStateNode = this._popupStateNode || this.focusNode || this._buttonNode;
210
211 // Add a class to the "dijitDownArrowButton" type class to _buttonNode so theme can set direction of arrow
212 // based on where drop down will normally appear
213 var defaultPos = {
214 "after" : this.isLeftToRight() ? "Right" : "Left",
215 "before" : this.isLeftToRight() ? "Left" : "Right",
216 "above" : "Up",
217 "below" : "Down",
218 "left" : "Left",
219 "right" : "Right"
220 }[this.dropDownPosition[0]] || this.dropDownPosition[0] || "Down";
221 domClass.add(this._arrowWrapperNode || this._buttonNode, "dijit" + defaultPos + "ArrowButton");
222 },
223
224 postCreate: function(){
225 // summary:
226 // set up nodes and connect our mouse and keypress events
227
228 this.inherited(arguments);
229
230 this.connect(this._buttonNode, touch.press, "_onDropDownMouseDown");
231 this.connect(this._buttonNode, "onclick", "_onDropDownClick");
232 this.connect(this.focusNode, "onkeypress", "_onKey");
233 this.connect(this.focusNode, "onkeyup", "_onKeyUp");
234 },
235
236 destroy: function(){
237 if(this.dropDown){
238 // Destroy the drop down, unless it's already been destroyed. This can happen because
239 // the drop down is a direct child of <body> even though it's logically my child.
240 if(!this.dropDown._destroyed){
241 this.dropDown.destroyRecursive();
242 }
243 delete this.dropDown;
244 }
245 this.inherited(arguments);
246 },
247
248 _onKey: function(/*Event*/ e){
249 // summary:
250 // Callback when the user presses a key while focused on the button node
251
252 if(this.disabled || this.readOnly){ return; }
253
254 var d = this.dropDown, target = e.target;
255 if(d && this._opened && d.handleKey){
256 if(d.handleKey(e) === false){
257 /* false return code means that the drop down handled the key */
258 event.stop(e);
259 return;
260 }
261 }
262 if(d && this._opened && e.charOrCode == keys.ESCAPE){
263 this.closeDropDown();
264 event.stop(e);
265 }else if(!this._opened &&
266 (e.charOrCode == keys.DOWN_ARROW ||
267 ( (e.charOrCode == keys.ENTER || e.charOrCode == " ") &&
268 //ignore enter and space if the event is for a text input
269 ((target.tagName || "").toLowerCase() !== 'input' ||
270 (target.type && target.type.toLowerCase() !== 'text'))))){
271 // Toggle the drop down, but wait until keyup so that the drop down doesn't
272 // get a stray keyup event, or in the case of key-repeat (because user held
273 // down key for too long), stray keydown events
274 this._toggleOnKeyUp = true;
275 event.stop(e);
276 }
277 },
278
279 _onKeyUp: function(){
280 if(this._toggleOnKeyUp){
281 delete this._toggleOnKeyUp;
282 this.toggleDropDown();
283 var d = this.dropDown; // drop down may not exist until toggleDropDown() call
284 if(d && d.focus){
285 setTimeout(lang.hitch(d, "focus"), 1);
286 }
287 }
288 },
289
290 _onBlur: function(){
291 // summary:
292 // Called magically when focus has shifted away from this widget and it's dropdown
293
294 // Don't focus on button if the user has explicitly focused on something else (happens
295 // when user clicks another control causing the current popup to close)..
296 // But if focus is inside of the drop down then reset focus to me, because IE doesn't like
297 // it when you display:none a node with focus.
298 var focusMe = focus.curNode && this.dropDown && dom.isDescendant(focus.curNode, this.dropDown.domNode);
299
300 this.closeDropDown(focusMe);
301
302 this.inherited(arguments);
303 },
304
305 isLoaded: function(){
306 // summary:
307 // Returns true if the dropdown exists and it's data is loaded. This can
308 // be overridden in order to force a call to loadDropDown().
309 // tags:
310 // protected
311
312 return true;
313 },
314
315 loadDropDown: function(/*Function*/ loadCallback){
316 // summary:
317 // Creates the drop down if it doesn't exist, loads the data
318 // if there's an href and it hasn't been loaded yet, and then calls
319 // the given callback.
320 // tags:
321 // protected
322
323 // TODO: for 2.0, change API to return a Deferred, instead of calling loadCallback?
324 loadCallback();
325 },
326
327 loadAndOpenDropDown: function(){
328 // summary:
329 // Creates the drop down if it doesn't exist, loads the data
330 // if there's an href and it hasn't been loaded yet, and
331 // then opens the drop down. This is basically a callback when the
332 // user presses the down arrow button to open the drop down.
333 // returns: Deferred
334 // Deferred for the drop down widget that
335 // fires when drop down is created and loaded
336 // tags:
337 // protected
338 var d = new Deferred(),
339 afterLoad = lang.hitch(this, function(){
340 this.openDropDown();
341 d.resolve(this.dropDown);
342 });
343 if(!this.isLoaded()){
344 this.loadDropDown(afterLoad);
345 }else{
346 afterLoad();
347 }
348 return d;
349 },
350
351 toggleDropDown: function(){
352 // summary:
353 // Callback when the user presses the down arrow button or presses
354 // the down arrow key to open/close the drop down.
355 // Toggle the drop-down widget; if it is up, close it, if not, open it
356 // tags:
357 // protected
358
359 if(this.disabled || this.readOnly){ return; }
360 if(!this._opened){
361 this.loadAndOpenDropDown();
362 }else{
363 this.closeDropDown();
364 }
365 },
366
367 openDropDown: function(){
368 // summary:
369 // Opens the dropdown for this widget. To be called only when this.dropDown
370 // has been created and is ready to display (ie, it's data is loaded).
371 // returns:
372 // return value of dijit.popup.open()
373 // tags:
374 // protected
375
376 var dropDown = this.dropDown,
377 ddNode = dropDown.domNode,
378 aroundNode = this._aroundNode || this.domNode,
379 self = this;
380
381 // Prepare our popup's height and honor maxHeight if it exists.
382
383 // TODO: isn't maxHeight dependent on the return value from dijit.popup.open(),
384 // ie, dependent on how much space is available (BK)
385
386 if(!this._preparedNode){
387 this._preparedNode = true;
388 // Check if we have explicitly set width and height on the dropdown widget dom node
389 if(ddNode.style.width){
390 this._explicitDDWidth = true;
391 }
392 if(ddNode.style.height){
393 this._explicitDDHeight = true;
394 }
395 }
396
397 // Code for resizing dropdown (height limitation, or increasing width to match my width)
398 if(this.maxHeight || this.forceWidth || this.autoWidth){
399 var myStyle = {
400 display: "",
401 visibility: "hidden"
402 };
403 if(!this._explicitDDWidth){
404 myStyle.width = "";
405 }
406 if(!this._explicitDDHeight){
407 myStyle.height = "";
408 }
409 domStyle.set(ddNode, myStyle);
410
411 // Figure out maximum height allowed (if there is a height restriction)
412 var maxHeight = this.maxHeight;
413 if(maxHeight == -1){
414 // limit height to space available in viewport either above or below my domNode
415 // (whichever side has more room)
416 var viewport = winUtils.getBox(),
417 position = domGeometry.position(aroundNode, false);
418 maxHeight = Math.floor(Math.max(position.y, viewport.h - (position.y + position.h)));
419 }
420
421 // Attach dropDown to DOM and make make visibility:hidden rather than display:none
422 // so we call startup() and also get the size
423 popup.moveOffScreen(dropDown);
424
425 if(dropDown.startup && !dropDown._started){
426 dropDown.startup(); // this has to be done after being added to the DOM
427 }
428 // Get size of drop down, and determine if vertical scroll bar needed
429 var mb = domGeometry.getMarginSize(ddNode);
430 var overHeight = (maxHeight && mb.h > maxHeight);
431 domStyle.set(ddNode, {
432 overflowX: "hidden",
433 overflowY: overHeight ? "auto" : "hidden"
434 });
435 if(overHeight){
436 mb.h = maxHeight;
437 if("w" in mb){
438 mb.w += 16; // room for vertical scrollbar
439 }
440 }else{
441 delete mb.h;
442 }
443
444 // Adjust dropdown width to match or be larger than my width
445 if(this.forceWidth){
446 mb.w = aroundNode.offsetWidth;
447 }else if(this.autoWidth){
448 mb.w = Math.max(mb.w, aroundNode.offsetWidth);
449 }else{
450 delete mb.w;
451 }
452
453 // And finally, resize the dropdown to calculated height and width
454 if(lang.isFunction(dropDown.resize)){
455 dropDown.resize(mb);
456 }else{
457 domGeometry.setMarginBox(ddNode, mb);
458 }
459 }
460
461 var retVal = popup.open({
462 parent: this,
463 popup: dropDown,
464 around: aroundNode,
465 orient: this.dropDownPosition,
466 onExecute: function(){
467 self.closeDropDown(true);
468 },
469 onCancel: function(){
470 self.closeDropDown(true);
471 },
472 onClose: function(){
473 domAttr.set(self._popupStateNode, "popupActive", false);
474 domClass.remove(self._popupStateNode, "dijitHasDropDownOpen");
475 self._opened = false;
476 }
477 });
478 domAttr.set(this._popupStateNode, "popupActive", "true");
479 domClass.add(self._popupStateNode, "dijitHasDropDownOpen");
480 this._opened=true;
481
482 // TODO: set this.checked and call setStateClass(), to affect button look while drop down is shown
483 return retVal;
484 },
485
486 closeDropDown: function(/*Boolean*/ focus){
487 // summary:
488 // Closes the drop down on this widget
489 // focus:
490 // If true, refocuses the button widget
491 // tags:
492 // protected
493
494 if(this._opened){
495 if(focus){ this.focus(); }
496 popup.close(this.dropDown);
497 this._opened = false;
498 }
499 }
500
501 });
502 });