]> git.wh0rd.org - tt-rss.git/blame - lib/dijit/Dialog.js
upgrade Dojo to 1.6.1
[tt-rss.git] / lib / dijit / Dialog.js
CommitLineData
2f01fe57 1/*
81bea17a 2 Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
2f01fe57
AD
3 Available via Academic Free License >= 2.1 OR the modified BSD license.
4 see: http://dojotoolkit.org/license for details
5*/
6
7
81bea17a
AD
8if(!dojo._hasResource["dijit.Dialog"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9dojo._hasResource["dijit.Dialog"] = true;
2f01fe57
AD
10dojo.provide("dijit.Dialog");
11dojo.require("dojo.dnd.move");
12dojo.require("dojo.dnd.TimedMoveable");
13dojo.require("dojo.fx");
14dojo.require("dojo.window");
15dojo.require("dijit._Widget");
16dojo.require("dijit._Templated");
17dojo.require("dijit._CssStateMixin");
18dojo.require("dijit.form._FormMixin");
19dojo.require("dijit._DialogMixin");
20dojo.require("dijit.DialogUnderlay");
21dojo.require("dijit.layout.ContentPane");
81bea17a 22dojo.requireLocalization("dijit", "common", null, "ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw");
2f01fe57 23dojo.require("dijit.TooltipDialog");
81bea17a
AD
24
25
26// dijit/TooltipDialog required for back-compat. TODO: remove in 2.0
27
28/*=====
29dijit._underlay = function(kwArgs){
30 // summary:
31 // A shared instance of a `dijit.DialogUnderlay`
32 //
33 // description:
34 // A shared instance of a `dijit.DialogUnderlay` created and
35 // used by `dijit.Dialog`, though never created until some Dialog
36 // or subclass thereof is shown.
37};
38=====*/
39dojo.declare(
40 "dijit._DialogBase",
41 [dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin, dijit._CssStateMixin],
42 {
43 // summary:
44 // A modal dialog Widget
45 //
46 // description:
47 // Pops up a modal dialog window, blocking access to the screen
48 // and also graying out the screen Dialog is extended from
49 // ContentPane so it supports all the same parameters (href, etc.)
50 //
51 // example:
52 // | <div dojoType="dijit.Dialog" href="test.html"></div>
53 //
54 // example:
55 // | var foo = new dijit.Dialog({ title: "test dialog", content: "test content" };
56 // | dojo.body().appendChild(foo.domNode);
57 // | foo.startup();
58
59 templateString: dojo.cache("dijit", "templates/Dialog.html", "<div class=\"dijitDialog\" role=\"dialog\" aria-labelledby=\"${id}_title\">\n\t<div dojoAttachPoint=\"titleBar\" class=\"dijitDialogTitleBar\">\n\t<span dojoAttachPoint=\"titleNode\" class=\"dijitDialogTitle\" id=\"${id}_title\"></span>\n\t<span dojoAttachPoint=\"closeButtonNode\" class=\"dijitDialogCloseIcon\" dojoAttachEvent=\"ondijitclick: onCancel\" title=\"${buttonCancel}\" role=\"button\" tabIndex=\"-1\">\n\t\t<span dojoAttachPoint=\"closeText\" class=\"closeText\" title=\"${buttonCancel}\">x</span>\n\t</span>\n\t</div>\n\t\t<div dojoAttachPoint=\"containerNode\" class=\"dijitDialogPaneContent\"></div>\n</div>\n"),
60
61 baseClass: "dijitDialog",
62
63 cssStateNodes: {
64 closeButtonNode: "dijitDialogCloseIcon"
65 },
66
67 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
68 title: [
69 { node: "titleNode", type: "innerHTML" },
70 { node: "titleBar", type: "attribute" }
71 ],
72 "aria-describedby":""
73 }),
74
75 // open: [readonly] Boolean
76 // True if Dialog is currently displayed on screen.
77 open: false,
78
79 // duration: Integer
80 // The time in milliseconds it takes the dialog to fade in and out
81 duration: dijit.defaultDuration,
82
83 // refocus: Boolean
84 // A Toggle to modify the default focus behavior of a Dialog, which
85 // is to re-focus the element which had focus before being opened.
86 // False will disable refocusing. Default: true
87 refocus: true,
88
89 // autofocus: Boolean
90 // A Toggle to modify the default focus behavior of a Dialog, which
91 // is to focus on the first dialog element after opening the dialog.
92 // False will disable autofocusing. Default: true
93 autofocus: true,
94
95 // _firstFocusItem: [private readonly] DomNode
96 // The pointer to the first focusable node in the dialog.
97 // Set by `dijit._DialogMixin._getFocusItems`.
98 _firstFocusItem: null,
99
100 // _lastFocusItem: [private readonly] DomNode
101 // The pointer to which node has focus prior to our dialog.
102 // Set by `dijit._DialogMixin._getFocusItems`.
103 _lastFocusItem: null,
104
105 // doLayout: [protected] Boolean
106 // Don't change this parameter from the default value.
107 // This ContentPane parameter doesn't make sense for Dialog, since Dialog
108 // is never a child of a layout container, nor can you specify the size of
109 // Dialog in order to control the size of an inner widget.
110 doLayout: false,
111
112 // draggable: Boolean
113 // Toggles the moveable aspect of the Dialog. If true, Dialog
114 // can be dragged by it's title. If false it will remain centered
115 // in the viewport.
116 draggable: true,
117
118 //aria-describedby: String
119 // Allows the user to add an aria-describedby attribute onto the dialog. The value should
120 // be the id of the container element of text that describes the dialog purpose (usually
121 // the first text in the dialog).
122 // <div dojoType="dijit.Dialog" aria-describedby="intro" .....>
123 // <div id="intro">Introductory text</div>
124 // <div>rest of dialog contents</div>
125 // </div>
126 "aria-describedby":"",
127
128 postMixInProperties: function(){
129 var _nlsResources = dojo.i18n.getLocalization("dijit", "common");
130 dojo.mixin(this, _nlsResources);
131 this.inherited(arguments);
132 },
133
134 postCreate: function(){
135 dojo.style(this.domNode, {
136 display: "none",
137 position:"absolute"
138 });
139 dojo.body().appendChild(this.domNode);
140
141 this.inherited(arguments);
142
143 this.connect(this, "onExecute", "hide");
144 this.connect(this, "onCancel", "hide");
145 this._modalconnects = [];
146 },
147
148 onLoad: function(){
149 // summary:
150 // Called when data has been loaded from an href.
151 // Unlike most other callbacks, this function can be connected to (via `dojo.connect`)
152 // but should *not* be overridden.
153 // tags:
154 // callback
155
156 // when href is specified we need to reposition the dialog after the data is loaded
157 // and find the focusable elements
158 this._position();
159 if(this.autofocus && dijit._DialogLevelManager.isTop(this)){
160 this._getFocusItems(this.domNode);
161 dijit.focus(this._firstFocusItem);
162 }
163 this.inherited(arguments);
164 },
165
166 _endDrag: function(e){
167 // summary:
168 // Called after dragging the Dialog. Saves the position of the dialog in the viewport.
169 // tags:
170 // private
171 if(e && e.node && e.node === this.domNode){
172 this._relativePosition = dojo.position(e.node);
173 }
174 },
175
176 _setup: function(){
177 // summary:
178 // Stuff we need to do before showing the Dialog for the first
179 // time (but we defer it until right beforehand, for
180 // performance reasons).
181 // tags:
182 // private
183
184 var node = this.domNode;
185
186 if(this.titleBar && this.draggable){
187 this._moveable = (dojo.isIE == 6) ?
188 new dojo.dnd.TimedMoveable(node, { handle: this.titleBar }) : // prevent overload, see #5285
189 new dojo.dnd.Moveable(node, { handle: this.titleBar, timeout: 0 });
190 this._dndListener = dojo.subscribe("/dnd/move/stop",this,"_endDrag");
191 }else{
192 dojo.addClass(node,"dijitDialogFixed");
193 }
194
195 this.underlayAttrs = {
196 dialogId: this.id,
197 "class": dojo.map(this["class"].split(/\s/), function(s){ return s+"_underlay"; }).join(" ")
198 };
199 },
200
201 _size: function(){
202 // summary:
203 // If necessary, shrink dialog contents so dialog fits in viewport
204 // tags:
205 // private
206
207 this._checkIfSingleChild();
208
209 // If we resized the dialog contents earlier, reset them back to original size, so
210 // that if the user later increases the viewport size, the dialog can display w/out a scrollbar.
211 // Need to do this before the dojo.marginBox(this.domNode) call below.
212 if(this._singleChild){
213 if(this._singleChildOriginalStyle){
214 this._singleChild.domNode.style.cssText = this._singleChildOriginalStyle;
215 }
216 delete this._singleChildOriginalStyle;
217 }else{
218 dojo.style(this.containerNode, {
219 width:"auto",
220 height:"auto"
221 });
222 }
223
224 var mb = dojo._getMarginSize(this.domNode);
225 var viewport = dojo.window.getBox();
226 if(mb.w >= viewport.w || mb.h >= viewport.h){
227 // Reduce size of dialog contents so that dialog fits in viewport
228
229 var w = Math.min(mb.w, Math.floor(viewport.w * 0.75)),
230 h = Math.min(mb.h, Math.floor(viewport.h * 0.75));
231
232 if(this._singleChild && this._singleChild.resize){
233 this._singleChildOriginalStyle = this._singleChild.domNode.style.cssText;
234 this._singleChild.resize({w: w, h: h});
235 }else{
236 dojo.style(this.containerNode, {
237 width: w + "px",
238 height: h + "px",
239 overflow: "auto",
240 position: "relative" // workaround IE bug moving scrollbar or dragging dialog
241 });
242 }
243 }else{
244 if(this._singleChild && this._singleChild.resize){
245 this._singleChild.resize();
246 }
247 }
248 },
249
250 _position: function(){
251 // summary:
252 // Position modal dialog in the viewport. If no relative offset
253 // in the viewport has been determined (by dragging, for instance),
254 // center the node. Otherwise, use the Dialog's stored relative offset,
255 // and position the node to top: left: values based on the viewport.
256 // tags:
257 // private
258 if(!dojo.hasClass(dojo.body(),"dojoMove")){
259 var node = this.domNode,
260 viewport = dojo.window.getBox(),
261 p = this._relativePosition,
262 bb = p ? null : dojo._getBorderBox(node),
263 l = Math.floor(viewport.l + (p ? p.x : (viewport.w - bb.w) / 2)),
264 t = Math.floor(viewport.t + (p ? p.y : (viewport.h - bb.h) / 2))
265 ;
266 dojo.style(node,{
267 left: l + "px",
268 top: t + "px"
269 });
270 }
271 },
272
273 _onKey: function(/*Event*/ evt){
274 // summary:
275 // Handles the keyboard events for accessibility reasons
276 // tags:
277 // private
278
279 if(evt.charOrCode){
280 var dk = dojo.keys;
281 var node = evt.target;
282 if(evt.charOrCode === dk.TAB){
283 this._getFocusItems(this.domNode);
284 }
285 var singleFocusItem = (this._firstFocusItem == this._lastFocusItem);
286 // see if we are shift-tabbing from first focusable item on dialog
287 if(node == this._firstFocusItem && evt.shiftKey && evt.charOrCode === dk.TAB){
288 if(!singleFocusItem){
289 dijit.focus(this._lastFocusItem); // send focus to last item in dialog
290 }
291 dojo.stopEvent(evt);
292 }else if(node == this._lastFocusItem && evt.charOrCode === dk.TAB && !evt.shiftKey){
293 if(!singleFocusItem){
294 dijit.focus(this._firstFocusItem); // send focus to first item in dialog
295 }
296 dojo.stopEvent(evt);
297 }else{
298 // see if the key is for the dialog
299 while(node){
300 if(node == this.domNode || dojo.hasClass(node, "dijitPopup")){
301 if(evt.charOrCode == dk.ESCAPE){
302 this.onCancel();
303 }else{
304 return; // just let it go
305 }
306 }
307 node = node.parentNode;
308 }
309 // this key is for the disabled document window
310 if(evt.charOrCode !== dk.TAB){ // allow tabbing into the dialog for a11y
311 dojo.stopEvent(evt);
312 // opera won't tab to a div
313 }else if(!dojo.isOpera){
314 try{
315 this._firstFocusItem.focus();
316 }catch(e){ /*squelch*/ }
317 }
318 }
319 }
320 },
321
322 show: function(){
323 // summary:
324 // Display the dialog
325 // returns: dojo.Deferred
326 // Deferred object that resolves when the display animation is complete
327
328 if(this.open){ return; }
329
330 if(!this._started){
331 this.startup();
332 }
333
334 // first time we show the dialog, there's some initialization stuff to do
335 if(!this._alreadyInitialized){
336 this._setup();
337 this._alreadyInitialized=true;
338 }
339
340 if(this._fadeOutDeferred){
341 this._fadeOutDeferred.cancel();
342 }
343
344 this._modalconnects.push(dojo.connect(window, "onscroll", this, "layout"));
345 this._modalconnects.push(dojo.connect(window, "onresize", this, function(){
346 // IE gives spurious resize events and can actually get stuck
347 // in an infinite loop if we don't ignore them
348 var viewport = dojo.window.getBox();
349 if(!this._oldViewport ||
350 viewport.h != this._oldViewport.h ||
351 viewport.w != this._oldViewport.w){
352 this.layout();
353 this._oldViewport = viewport;
354 }
355 }));
356 this._modalconnects.push(dojo.connect(this.domNode, "onkeypress", this, "_onKey"));
357
358 dojo.style(this.domNode, {
359 opacity:0,
360 display:""
361 });
362
363 this._set("open", true);
364 this._onShow(); // lazy load trigger
365
366 this._size();
367 this._position();
368
369 // fade-in Animation object, setup below
370 var fadeIn;
371
372 this._fadeInDeferred = new dojo.Deferred(dojo.hitch(this, function(){
373 fadeIn.stop();
374 delete this._fadeInDeferred;
375 }));
376
377 fadeIn = dojo.fadeIn({
378 node: this.domNode,
379 duration: this.duration,
380 beforeBegin: dojo.hitch(this, function(){
381 dijit._DialogLevelManager.show(this, this.underlayAttrs);
382 }),
383 onEnd: dojo.hitch(this, function(){
384 if(this.autofocus && dijit._DialogLevelManager.isTop(this)){
385 // find focusable items each time dialog is shown since if dialog contains a widget the
386 // first focusable items can change
387 this._getFocusItems(this.domNode);
388 dijit.focus(this._firstFocusItem);
389 }
390 this._fadeInDeferred.callback(true);
391 delete this._fadeInDeferred;
392 })
393 }).play();
394
395 return this._fadeInDeferred;
396 },
397
398 hide: function(){
399 // summary:
400 // Hide the dialog
401 // returns: dojo.Deferred
402 // Deferred object that resolves when the hide animation is complete
403
404 // if we haven't been initialized yet then we aren't showing and we can just return
405 if(!this._alreadyInitialized){
406 return;
407 }
408 if(this._fadeInDeferred){
409 this._fadeInDeferred.cancel();
410 }
411
412 // fade-in Animation object, setup below
413 var fadeOut;
414
415 this._fadeOutDeferred = new dojo.Deferred(dojo.hitch(this, function(){
416 fadeOut.stop();
417 delete this._fadeOutDeferred;
418 }));
419
420 fadeOut = dojo.fadeOut({
421 node: this.domNode,
422 duration: this.duration,
423 onEnd: dojo.hitch(this, function(){
424 this.domNode.style.display = "none";
425 dijit._DialogLevelManager.hide(this);
426 this.onHide();
427 this._fadeOutDeferred.callback(true);
428 delete this._fadeOutDeferred;
429 })
430 }).play();
431
432 if(this._scrollConnected){
433 this._scrollConnected = false;
434 }
435 dojo.forEach(this._modalconnects, dojo.disconnect);
436 this._modalconnects = [];
437
438 if(this._relativePosition){
439 delete this._relativePosition;
440 }
441 this._set("open", false);
442
443 return this._fadeOutDeferred;
444 },
445
446 layout: function(){
447 // summary:
448 // Position the Dialog and the underlay
449 // tags:
450 // private
451 if(this.domNode.style.display != "none"){
452 if(dijit._underlay){ // avoid race condition during show()
453 dijit._underlay.layout();
454 }
455 this._position();
456 }
457 },
458
459 destroy: function(){
460 if(this._fadeInDeferred){
461 this._fadeInDeferred.cancel();
462 }
463 if(this._fadeOutDeferred){
464 this._fadeOutDeferred.cancel();
465 }
466 if(this._moveable){
467 this._moveable.destroy();
468 }
469 if(this._dndListener){
470 dojo.unsubscribe(this._dndListener);
471 }
472 dojo.forEach(this._modalconnects, dojo.disconnect);
473
474 dijit._DialogLevelManager.hide(this);
475
476 this.inherited(arguments);
477 }
478 }
479);
480
481dojo.declare(
482 "dijit.Dialog",
483 [dijit.layout.ContentPane, dijit._DialogBase],
484 {}
485);
486
487dijit._DialogLevelManager = {
488 // summary:
489 // Controls the various active "levels" on the page, starting with the
490 // stuff initially visible on the page (at z-index 0), and then having an entry for
491 // each Dialog shown.
492
493 show: function(/*dijit._Widget*/ dialog, /*Object*/ underlayAttrs){
494 // summary:
495 // Call right before fade-in animation for new dialog.
496 // Saves current focus, displays/adjusts underlay for new dialog,
497 // and sets the z-index of the dialog itself.
498 //
499 // New dialog will be displayed on top of all currently displayed dialogs.
500 //
501 // Caller is responsible for setting focus in new dialog after the fade-in
502 // animation completes.
503
504 var ds = dijit._dialogStack;
505
506 // Save current focus
507 ds[ds.length-1].focus = dijit.getFocus(dialog);
508
509 // Display the underlay, or if already displayed then adjust for this new dialog
510 var underlay = dijit._underlay;
511 if(!underlay || underlay._destroyed){
512 underlay = dijit._underlay = new dijit.DialogUnderlay(underlayAttrs);
513 }else{
514 underlay.set(dialog.underlayAttrs);
515 }
516
517 // Set z-index a bit above previous dialog
518 var zIndex = ds[ds.length-1].dialog ? ds[ds.length-1].zIndex + 2 : 950;
519 if(ds.length == 1){ // first dialog
520 underlay.show();
521 }
522 dojo.style(dijit._underlay.domNode, 'zIndex', zIndex - 1);
523
524 // Dialog
525 dojo.style(dialog.domNode, 'zIndex', zIndex);
526
527 ds.push({dialog: dialog, underlayAttrs: underlayAttrs, zIndex: zIndex});
528 },
529
530 hide: function(/*dijit._Widget*/ dialog){
531 // summary:
532 // Called when the specified dialog is hidden/destroyed, after the fade-out
533 // animation ends, in order to reset page focus, fix the underlay, etc.
534 // If the specified dialog isn't open then does nothing.
535 //
536 // Caller is responsible for either setting display:none on the dialog domNode,
537 // or calling dijit.popup.hide(), or removing it from the page DOM.
538
539 var ds = dijit._dialogStack;
540
541 if(ds[ds.length-1].dialog == dialog){
542 // Removing the top (or only) dialog in the stack, return focus
543 // to previous dialog
544
545 ds.pop();
546
547 var pd = ds[ds.length-1]; // the new active dialog (or the base page itself)
548
549 // Adjust underlay
550 if(ds.length == 1){
551 // Returning to original page.
552 // Hide the underlay, unless the underlay widget has already been destroyed
553 // because we are being called during page unload (when all widgets are destroyed)
554 if(!dijit._underlay._destroyed){
555 dijit._underlay.hide();
556 }
557 }else{
558 // Popping back to previous dialog, adjust underlay
559 dojo.style(dijit._underlay.domNode, 'zIndex', pd.zIndex - 1);
560 dijit._underlay.set(pd.underlayAttrs);
561 }
562
563 // Adjust focus
564 if(dialog.refocus){
565 // If we are returning control to a previous dialog but for some reason
566 // that dialog didn't have a focused field, set focus to first focusable item.
567 // This situation could happen if two dialogs appeared at nearly the same time,
568 // since a dialog doesn't set it's focus until the fade-in is finished.
569 var focus = pd.focus;
570 if(!focus || (pd.dialog && !dojo.isDescendant(focus.node, pd.dialog.domNode))){
571 pd.dialog._getFocusItems(pd.dialog.domNode);
572 focus = pd.dialog._firstFocusItem;
573 }
574
575 try{
576 dijit.focus(focus);
577 }catch(e){
578 /* focus() will fail if user opened the dialog by clicking a non-focusable element */
579 }
580 }
581 }else{
582 // Removing a dialog out of order (#9944, #10705).
583 // Don't need to mess with underlay or z-index or anything.
584 var idx = dojo.indexOf(dojo.map(ds, function(elem){return elem.dialog}), dialog);
585 if(idx != -1){
586 ds.splice(idx, 1);
587 }
588 }
589 },
590
591 isTop: function(/*dijit._Widget*/ dialog){
592 // summary:
593 // Returns true if specified Dialog is the top in the task
594 var ds = dijit._dialogStack;
595 return ds[ds.length-1].dialog == dialog;
596 }
597};
598
599// Stack representing the various active "levels" on the page, starting with the
600// stuff initially visible on the page (at z-index 0), and then having an entry for
601// each Dialog shown.
602// Each element in stack has form {
603// dialog: dialogWidget,
604// focus: returnFromGetFocus(),
605// underlayAttrs: attributes to set on underlay (when this widget is active)
606// }
607dijit._dialogStack = [
608 {dialog: null, focus: null, underlayAttrs: null} // entry for stuff at z-index: 0
609];
610
2f01fe57 611}