]> git.wh0rd.org - tt-rss.git/blob - lib/dijit/popup.js.uncompressed.js
update dojo to 1.7.3
[tt-rss.git] / lib / dijit / popup.js.uncompressed.js
1 define("dijit/popup", [
2 "dojo/_base/array", // array.forEach array.some
3 "dojo/aspect",
4 "dojo/_base/connect", // connect._keypress
5 "dojo/_base/declare", // declare
6 "dojo/dom", // dom.isDescendant
7 "dojo/dom-attr", // domAttr.set
8 "dojo/dom-construct", // domConstruct.create domConstruct.destroy
9 "dojo/dom-geometry", // domGeometry.isBodyLtr
10 "dojo/dom-style", // domStyle.set
11 "dojo/_base/event", // event.stop
12 "dojo/keys",
13 "dojo/_base/lang", // lang.hitch
14 "dojo/on",
15 "dojo/_base/sniff", // has("ie") has("mozilla")
16 "dojo/_base/window", // win.body
17 "./place",
18 "./BackgroundIframe",
19 "." // dijit (defining dijit.popup to match API doc)
20 ], function(array, aspect, connect, declare, dom, domAttr, domConstruct, domGeometry, domStyle, event, keys, lang, on, has, win,
21 place, BackgroundIframe, dijit){
22
23 // module:
24 // dijit/popup
25 // summary:
26 // Used to show drop downs (ex: the select list of a ComboBox)
27 // or popups (ex: right-click context menus)
28
29
30 /*=====
31 dijit.popup.__OpenArgs = function(){
32 // popup: Widget
33 // widget to display
34 // parent: Widget
35 // the button etc. that is displaying this popup
36 // around: DomNode
37 // DOM node (typically a button); place popup relative to this node. (Specify this *or* "x" and "y" parameters.)
38 // x: Integer
39 // Absolute horizontal position (in pixels) to place node at. (Specify this *or* "around" parameter.)
40 // y: Integer
41 // Absolute vertical position (in pixels) to place node at. (Specify this *or* "around" parameter.)
42 // orient: Object|String
43 // When the around parameter is specified, orient should be a list of positions to try, ex:
44 // | [ "below", "above" ]
45 // For backwards compatibility it can also be an (ordered) hash of tuples of the form
46 // (around-node-corner, popup-node-corner), ex:
47 // | { "BL": "TL", "TL": "BL" }
48 // where BL means "bottom left" and "TL" means "top left", etc.
49 //
50 // dijit.popup.open() tries to position the popup according to each specified position, in order,
51 // until the popup appears fully within the viewport.
52 //
53 // The default value is ["below", "above"]
54 //
55 // When an (x,y) position is specified rather than an around node, orient is either
56 // "R" or "L". R (for right) means that it tries to put the popup to the right of the mouse,
57 // specifically positioning the popup's top-right corner at the mouse position, and if that doesn't
58 // fit in the viewport, then it tries, in order, the bottom-right corner, the top left corner,
59 // and the top-right corner.
60 // onCancel: Function
61 // callback when user has canceled the popup by
62 // 1. hitting ESC or
63 // 2. by using the popup widget's proprietary cancel mechanism (like a cancel button in a dialog);
64 // i.e. whenever popupWidget.onCancel() is called, args.onCancel is called
65 // onClose: Function
66 // callback whenever this popup is closed
67 // onExecute: Function
68 // callback when user "executed" on the popup/sub-popup by selecting a menu choice, etc. (top menu only)
69 // padding: dijit.__Position
70 // adding a buffer around the opening position. This is only useful when around is not set.
71 this.popup = popup;
72 this.parent = parent;
73 this.around = around;
74 this.x = x;
75 this.y = y;
76 this.orient = orient;
77 this.onCancel = onCancel;
78 this.onClose = onClose;
79 this.onExecute = onExecute;
80 this.padding = padding;
81 }
82 =====*/
83
84 /*=====
85 dijit.popup = {
86 // summary:
87 // Used to show drop downs (ex: the select list of a ComboBox)
88 // or popups (ex: right-click context menus).
89 //
90 // Access via require(["dijit/popup"], function(popup){ ... }).
91
92 moveOffScreen: function(widget){
93 // summary:
94 // Moves the popup widget off-screen.
95 // Do not use this method to hide popups when not in use, because
96 // that will create an accessibility issue: the offscreen popup is
97 // still in the tabbing order.
98 // widget: dijit._WidgetBase
99 // The widget
100 },
101
102 hide: function(widget){
103 // summary:
104 // Hide this popup widget (until it is ready to be shown).
105 // Initialization for widgets that will be used as popups
106 //
107 // Also puts widget inside a wrapper DIV (if not already in one)
108 //
109 // If popup widget needs to layout it should
110 // do so when it is made visible, and popup._onShow() is called.
111 // widget: dijit._WidgetBase
112 // The widget
113 },
114
115 open: function(args){
116 // summary:
117 // Popup the widget at the specified position
118 // example:
119 // opening at the mouse position
120 // | popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY});
121 // example:
122 // opening the widget as a dropdown
123 // | popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}});
124 //
125 // Note that whatever widget called dijit.popup.open() should also listen to its own _onBlur callback
126 // (fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed.
127 // args: dijit.popup.__OpenArgs
128 // Parameters
129 return {}; // Object specifying which position was chosen
130 },
131
132 close: function(popup){
133 // summary:
134 // Close specified popup and any popups that it parented.
135 // If no popup is specified, closes all popups.
136 // widget: dijit._WidgetBase?
137 // The widget, optional
138 }
139 };
140 =====*/
141
142 var PopupManager = declare(null, {
143 // _stack: dijit._Widget[]
144 // Stack of currently popped up widgets.
145 // (someone opened _stack[0], and then it opened _stack[1], etc.)
146 _stack: [],
147
148 // _beginZIndex: Number
149 // Z-index of the first popup. (If first popup opens other
150 // popups they get a higher z-index.)
151 _beginZIndex: 1000,
152
153 _idGen: 1,
154
155 _createWrapper: function(/*Widget*/ widget){
156 // summary:
157 // Initialization for widgets that will be used as popups.
158 // Puts widget inside a wrapper DIV (if not already in one),
159 // and returns pointer to that wrapper DIV.
160
161 var wrapper = widget._popupWrapper,
162 node = widget.domNode;
163
164 if(!wrapper){
165 // Create wrapper <div> for when this widget [in the future] will be used as a popup.
166 // This is done early because of IE bugs where creating/moving DOM nodes causes focus
167 // to go wonky, see tests/robot/Toolbar.html to reproduce
168 wrapper = domConstruct.create("div",{
169 "class":"dijitPopup",
170 style:{ display: "none"},
171 role: "presentation"
172 }, win.body());
173 wrapper.appendChild(node);
174
175 var s = node.style;
176 s.display = "";
177 s.visibility = "";
178 s.position = "";
179 s.top = "0px";
180
181 widget._popupWrapper = wrapper;
182 aspect.after(widget, "destroy", function(){
183 domConstruct.destroy(wrapper);
184 delete widget._popupWrapper;
185 });
186 }
187
188 return wrapper;
189 },
190
191 moveOffScreen: function(/*Widget*/ widget){
192 // summary:
193 // Moves the popup widget off-screen.
194 // Do not use this method to hide popups when not in use, because
195 // that will create an accessibility issue: the offscreen popup is
196 // still in the tabbing order.
197
198 // Create wrapper if not already there
199 var wrapper = this._createWrapper(widget);
200
201 domStyle.set(wrapper, {
202 visibility: "hidden",
203 top: "-9999px", // prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111)
204 display: ""
205 });
206 },
207
208 hide: function(/*Widget*/ widget){
209 // summary:
210 // Hide this popup widget (until it is ready to be shown).
211 // Initialization for widgets that will be used as popups
212 //
213 // Also puts widget inside a wrapper DIV (if not already in one)
214 //
215 // If popup widget needs to layout it should
216 // do so when it is made visible, and popup._onShow() is called.
217
218 // Create wrapper if not already there
219 var wrapper = this._createWrapper(widget);
220
221 domStyle.set(wrapper, "display", "none");
222 },
223
224 getTopPopup: function(){
225 // summary:
226 // Compute the closest ancestor popup that's *not* a child of another popup.
227 // Ex: For a TooltipDialog with a button that spawns a tree of menus, find the popup of the button.
228 var stack = this._stack;
229 for(var pi=stack.length-1; pi > 0 && stack[pi].parent === stack[pi-1].widget; pi--){
230 /* do nothing, just trying to get right value for pi */
231 }
232 return stack[pi];
233 },
234
235 open: function(/*dijit.popup.__OpenArgs*/ args){
236 // summary:
237 // Popup the widget at the specified position
238 //
239 // example:
240 // opening at the mouse position
241 // | popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY});
242 //
243 // example:
244 // opening the widget as a dropdown
245 // | popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}});
246 //
247 // Note that whatever widget called dijit.popup.open() should also listen to its own _onBlur callback
248 // (fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed.
249
250 var stack = this._stack,
251 widget = args.popup,
252 orient = args.orient || ["below", "below-alt", "above", "above-alt"],
253 ltr = args.parent ? args.parent.isLeftToRight() : domGeometry.isBodyLtr(),
254 around = args.around,
255 id = (args.around && args.around.id) ? (args.around.id+"_dropdown") : ("popup_"+this._idGen++);
256
257 // If we are opening a new popup that isn't a child of a currently opened popup, then
258 // close currently opened popup(s). This should happen automatically when the old popups
259 // gets the _onBlur() event, except that the _onBlur() event isn't reliable on IE, see [22198].
260 while(stack.length && (!args.parent || !dom.isDescendant(args.parent.domNode, stack[stack.length-1].widget.domNode))){
261 this.close(stack[stack.length-1].widget);
262 }
263
264 // Get pointer to popup wrapper, and create wrapper if it doesn't exist
265 var wrapper = this._createWrapper(widget);
266
267
268 domAttr.set(wrapper, {
269 id: id,
270 style: {
271 zIndex: this._beginZIndex + stack.length
272 },
273 "class": "dijitPopup " + (widget.baseClass || widget["class"] || "").split(" ")[0] +"Popup",
274 dijitPopupParent: args.parent ? args.parent.id : ""
275 });
276
277 if(has("ie") || has("mozilla")){
278 if(!widget.bgIframe){
279 // setting widget.bgIframe triggers cleanup in _Widget.destroy()
280 widget.bgIframe = new BackgroundIframe(wrapper);
281 }
282 }
283
284 // position the wrapper node and make it visible
285 var best = around ?
286 place.around(wrapper, around, orient, ltr, widget.orient ? lang.hitch(widget, "orient") : null) :
287 place.at(wrapper, args, orient == 'R' ? ['TR','BR','TL','BL'] : ['TL','BL','TR','BR'], args.padding);
288
289 wrapper.style.display = "";
290 wrapper.style.visibility = "visible";
291 widget.domNode.style.visibility = "visible"; // counteract effects from _HasDropDown
292
293 var handlers = [];
294
295 // provide default escape and tab key handling
296 // (this will work for any widget, not just menu)
297 handlers.push(on(wrapper, connect._keypress, lang.hitch(this, function(evt){
298 if(evt.charOrCode == keys.ESCAPE && args.onCancel){
299 event.stop(evt);
300 args.onCancel();
301 }else if(evt.charOrCode === keys.TAB){
302 event.stop(evt);
303 var topPopup = this.getTopPopup();
304 if(topPopup && topPopup.onCancel){
305 topPopup.onCancel();
306 }
307 }
308 })));
309
310 // watch for cancel/execute events on the popup and notify the caller
311 // (for a menu, "execute" means clicking an item)
312 if(widget.onCancel && args.onCancel){
313 handlers.push(widget.on("cancel", args.onCancel));
314 }
315
316 handlers.push(widget.on(widget.onExecute ? "execute" : "change", lang.hitch(this, function(){
317 var topPopup = this.getTopPopup();
318 if(topPopup && topPopup.onExecute){
319 topPopup.onExecute();
320 }
321 })));
322
323 stack.push({
324 widget: widget,
325 parent: args.parent,
326 onExecute: args.onExecute,
327 onCancel: args.onCancel,
328 onClose: args.onClose,
329 handlers: handlers
330 });
331
332 if(widget.onOpen){
333 // TODO: in 2.0 standardize onShow() (used by StackContainer) and onOpen() (used here)
334 widget.onOpen(best);
335 }
336
337 return best;
338 },
339
340 close: function(/*Widget?*/ popup){
341 // summary:
342 // Close specified popup and any popups that it parented.
343 // If no popup is specified, closes all popups.
344
345 var stack = this._stack;
346
347 // Basically work backwards from the top of the stack closing popups
348 // until we hit the specified popup, but IIRC there was some issue where closing
349 // a popup would cause others to close too. Thus if we are trying to close B in [A,B,C]
350 // closing C might close B indirectly and then the while() condition will run where stack==[A]...
351 // so the while condition is constructed defensively.
352 while((popup && array.some(stack, function(elem){return elem.widget == popup;})) ||
353 (!popup && stack.length)){
354 var top = stack.pop(),
355 widget = top.widget,
356 onClose = top.onClose;
357
358 if(widget.onClose){
359 // TODO: in 2.0 standardize onHide() (used by StackContainer) and onClose() (used here)
360 widget.onClose();
361 }
362
363 var h;
364 while(h = top.handlers.pop()){ h.remove(); }
365
366 // Hide the widget and it's wrapper unless it has already been destroyed in above onClose() etc.
367 if(widget && widget.domNode){
368 this.hide(widget);
369 }
370
371 if(onClose){
372 onClose();
373 }
374 }
375 }
376 });
377
378 return (dijit.popup = new PopupManager());
379 });