source: Dev/branches/rest-dojo-ui/client/dijit/popup.js @ 274

Last change on this file since 274 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 13.0 KB
Line 
1define([
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});
Note: See TracBrowser for help on using the repository browser.