]> git.wh0rd.org Git - 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 });