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