dojo非模态对话框

时间:2011-08-30 16:43:12

标签: dojo dialog non-modal

有没有办法使用dojo创建非模态对话框窗口? jQuery UI支持模态和非模态对话框。我正在尝试将SilverLight应用程序转换为HTML / javascript,并发现很难使用dojo创建非模态窗口。

4 个答案:

答案 0 :(得分:22)

您可以将参考底图的显示设置为“无”,并且您将拥有非模态对话框。为此,将Dialog的类设置为'nonModal'(这只是我现在正在创建的约定),并且在页面的CSS中,有一个.nonModal_underlay的条目。

require(['dijit/form/Button','dijit/Dialog'],
  function (Button, Dialog) {
    var d = new Dialog({
                    'title':'I am nonmodal',
                    'class':'nonModal'
                  });
  });

.nonModal_underlay {
    display:none;
}

答案 1 :(得分:3)

您可以尝试dojox.layout.FloatingPane

答案 2 :(得分:1)

您好,只需在您的CSS中添加

.dijitDialogUnderlayWrapper{
    display:none !important
}

答案 3 :(得分:0)

对于需要真正无模式Dialog的人来说,Roy J只提供部分解决方案,因为焦点仍然在Dialog中。要解决此问题,您可以将dijit / Dialog.js及其dijit / templates / Dialog.html复制到您自己的文件夹结构中,并将它们重命名为ModelessDialog。然后删除焦点处理程序,键处理程序和参考底图。以下是完整的结果:

define([
    "require",
    "dojo/_base/array", // array.forEach array.indexOf array.map
    "dojo/aspect",
    "dojo/_base/declare", // declare
    "dojo/Deferred", // Deferred
    "dojo/dom", // dom.isDescendant
    "dojo/dom-class", // domClass.add domClass.contains
    "dojo/dom-geometry", // domGeometry.position
    "dojo/dom-style", // domStyle.set
    "dojo/_base/fx", // fx.fadeIn fx.fadeOut
    "dojo/i18n", // i18n.getLocalization
    "dojo/keys",
    "dojo/_base/lang", // lang.mixin lang.hitch
    "dojo/on",
    "dojo/ready",
    "dojo/sniff", // has("ie") has("opera") has("dijit-legacy-requires")
    "dojo/window", // winUtils.getBox, winUtils.get
    "dojo/dnd/Moveable", // Moveable
    "dojo/dnd/TimedMoveable", // TimedMoveable
    "dijit/focus",
    "dijit/_base/manager", // manager.defaultDuration
    "dijit/_Widget",
    "dijit/_TemplatedMixin",
    "dijit/_CssStateMixin",
    "dijit/form/_FormMixin",
    "dijit/_DialogMixin",
    "dijit/layout/ContentPane",
    "dijit/layout/utils",
    "dojo/text!./templates/ModelessDialog.html",
    "dijit/a11yclick",  // template uses ondijitclick
    "dojo/i18n!dijit/nls/common"
], function(require, array, aspect, declare, Deferred,
            dom, domClass, domGeometry, domStyle, fx, i18n, keys, lang, on, ready, has, winUtils,
            Moveable, TimedMoveable, focus, manager, _Widget, _TemplatedMixin, _CssStateMixin, _FormMixin, _DialogMixin,
            ContentPane, utils, template){

    // module:
    //      company/common/ModelessDialog

    var resolvedDeferred = new Deferred(), _DialogBase, ds = null, DialogLevelManager = null, ModelessDialog,
       _currentDialog = null;
    resolvedDeferred.resolve(true);

    _DialogBase = declare("dijit._DialogBase" + (has("dojo-bidi") ? "_NoBidi" : ""),
                           [_TemplatedMixin, _FormMixin, _DialogMixin, _CssStateMixin], {
        templateString: template,

        baseClass: "dijitDialog",

        cssStateNodes: {
            closeButtonNode: "dijitDialogCloseIcon"
        },

        // Map widget attributes to DOMNode attributes.
        _setTitleAttr: { node: "titleNode", type: "innerHTML" },

        // open: [readonly] Boolean
        //      True if ModelessDialog is currently displayed on screen.
        open: false,

        // duration: Integer
        //      The time in milliseconds it takes the dialog to fade in and out
        duration: manager.defaultDuration,

        // refocus: Boolean
        //      A Toggle to modify the default focus behavior of a ModelessDialog, which
        //      is to re-focus the element which had focus before being opened.
        //      False will disable refocusing. Default: true
        refocus: true,

        // autofocus: Boolean
        //      A Toggle to modify the default focus behavior of a ModelessDialog, which
        //      is to focus on the first dialog element after opening the dialog.
        //      False will disable autofocusing. Default: true
        autofocus: true,

        // _firstFocusItem: [private readonly] DomNode
        //      The pointer to the first focusable node in the dialog.
        //      Set by `dijit/_DialogMixin._getFocusItems()`.
        _firstFocusItem: null,

        // _lastFocusItem: [private readonly] DomNode
        //      The pointer to which node has focus prior to our dialog.
        //      Set by `dijit/_DialogMixin._getFocusItems()`.
        _lastFocusItem: null,

        // draggable: Boolean
        //      Toggles the movable aspect of the ModelessDialog. If true, ModelessDialog
        //      can be dragged by it's title. If false it will remain centered
        //      in the viewport.
        draggable: true,
        _setDraggableAttr: function(/*Boolean*/ val){
            // Avoid _WidgetBase behavior of copying draggable attribute to this.domNode,
            // as that prevents text select on modern browsers (#14452)
            this._set("draggable", val);
        },

        // maxRatio: Number
        //      Maximum size to allow the dialog to expand to, relative to viewport size
        maxRatio: 0.8,

        // closable: Boolean
        //      ModelessDialog show [x] icon to close itself, and ESC key will close the dialog.
        closable: true,
        _setClosableAttr: function(val){
            this.closeButtonNode.style.display = val ? "" : "none";
            this._set("closable", val);
        },

        postMixInProperties: function(){
            var _nlsResources = i18n.getLocalization("dijit", "common");
            lang.mixin(this, _nlsResources);
            this.inherited(arguments);
        },

        postCreate: function(){
            domStyle.set(this.domNode, {
                display: "none",
                position: "absolute"
            });
            this.ownerDocumentBody.appendChild(this.domNode);

            this.inherited(arguments);

            aspect.after(this, "onExecute", lang.hitch(this, "hide"), true);
            aspect.after(this, "onCancel", lang.hitch(this, "hide"), true);

            this._modalconnects = [];
        },

        onLoad: function(){
            // summary:
            //      Called when data has been loaded from an href.
            //      Unlike most other callbacks, this function can be connected to (via `dojo.connect`)
            //      but should *not* be overridden.
            // tags:
            //      callback

            // when href is specified we need to reposition the dialog after the data is loaded
            // and find the focusable elements
            this.resize();
            this._position();

            if(this.autofocus && DialogLevelManager.isTop(this)){
                this._getFocusItems();
                focus.focus(this._firstFocusItem);
            }

            this.inherited(arguments);
        },

        focus: function(){
            this._getFocusItems();
            focus.focus(this._firstFocusItem);
        },

        _endDrag: function(){
            // summary:
            //      Called after dragging the ModelessDialog. Saves the position of the dialog in the viewport,
            //      and also adjust position to be fully within the viewport, so user doesn't lose access to handle
            var nodePosition = domGeometry.position(this.domNode),
                viewport = winUtils.getBox(this.ownerDocument);
            nodePosition.y = Math.min(Math.max(nodePosition.y, 0), (viewport.h - nodePosition.h));
            nodePosition.x = Math.min(Math.max(nodePosition.x, 0), (viewport.w - nodePosition.w));
            this._relativePosition = nodePosition;
            this._position();
        },

        _setup: function(){
            // summary:
            //      Stuff we need to do before showing the ModelessDialog for the first
            //      time (but we defer it until right beforehand, for
            //      performance reasons).
            // tags:
            //      private

            var node = this.domNode;

            if(this.titleBar && this.draggable){
                // prevent overload, see #5285
                if(has("ie") == 6) {
                    this._moveable = new TimedMoveable(node, { handle: this.titleBar });
                } else {
                    this._moveable = new Moveable(node, { handle: this.titleBar });
                }
                aspect.after(this._moveable, "onMoveStop", lang.hitch(this, "_endDrag"), true);
            }else{
                domClass.add(node, "dijitDialogFixed");
            }
        },

        _size: function(){
            // TODO: remove for 2.0
            this.resize();
        },

        _position: function(){
            // summary:
            //      Position the dialog in the viewport.  If no relative offset
            //      in the viewport has been determined (by dragging, for instance),
            //      center the dialog.  Otherwise, use the ModelessDialog's stored relative offset,
            //      adjusted by the viewport's scroll.
            if(!domClass.contains(this.ownerDocumentBody, "dojoMove")){    // don't do anything if called during auto-scroll
                var node = this.domNode,
                    viewport = winUtils.getBox(this.ownerDocument),
                    p = this._relativePosition,
                    bb = p ? null : domGeometry.position(node),
                    l = Math.floor(viewport.l + (p ? p.x : (viewport.w - bb.w) / 2)),
                    t = Math.floor(viewport.t + (p ? p.y : (viewport.h - bb.h) / 2))
                    ;
                domStyle.set(node, {
                    left: l + "px",
                    top: t + "px"
                });
            }
        },
        show: function(){
            // summary:
            //      Display the dialog
            // returns: dojo/promise/Promise
            //      Promise object that resolves when the display animation is complete

            if(this.open){
                return resolvedDeferred.promise;
            }
            if(_currentDialog && _currentDialog != this) {
                _currentDialog.destroy();
            }
            _currentDialog = this;
            if(!this._started){
                this.startup();
            }

            // first time we show the dialog, there's some initialization stuff to do
            if(!this._alreadyInitialized){
                this._setup();
                this._alreadyInitialized = true;
            }

            if(this._fadeOutDeferred){
                // There's a hide() operation in progress, so cancel it, but still call DialogLevelManager.hide()
                // as though the hide() completed, in preparation for the DialogLevelManager.show() call below.
                this._fadeOutDeferred.cancel();
                DialogLevelManager.hide(this);
            }

            // Recenter ModelessDialog if user scrolls browser.  Connecting to document doesn't work on IE, need to use window.
            // Be sure that event object doesn't get passed to resize() method, because it's expecting an optional
            // {w: ..., h:...} arg.
            var win = winUtils.get(this.ownerDocument),
                 // fade-in Animation object, setup below
                 fadeIn = null, promise;
            this._modalconnects.push(on(win, "scroll", lang.hitch(this, "resize", null)));

            //this._modalconnects.push(on(this.domNode, "keydown", lang.hitch(this, "_onKey")));

            domStyle.set(this.domNode, {
                opacity: 0,
                display: ""
            });

            this._set("open", true);
            this._onShow(); // lazy load trigger

            this.resize();
            this._position();

            this._fadeInDeferred = new Deferred(lang.hitch(this, function(){
                fadeIn.stop();
                delete this._fadeInDeferred;
            }));

            // If delay is 0, code below will delete this._fadeInDeferred instantly, so grab promise while we can.
            promise = this._fadeInDeferred.promise;

            fadeIn = fx.fadeIn({
                node: this.domNode,
                duration: this.duration,
                beforeBegin: lang.hitch(this, function(){
                    DialogLevelManager.show(this/*, this.underlayAttrs*/);
                }),
                onEnd: lang.hitch(this, function(){
                    if(this.autofocus && DialogLevelManager.isTop(this)){
                        // find focusable items each time dialog is shown since if dialog contains a widget the
                        // first focusable items can change
                        this._getFocusItems();
                        focus.focus(this._firstFocusItem);
                    }
                    this._fadeInDeferred.resolve(true);
                    delete this._fadeInDeferred;
                })
            }).play();

            return promise;
        },

        hide: function(){
            // summary:
            //      Hide the dialog
            // returns: dojo/promise/Promise
            //      Promise object that resolves when the display animation is complete

            // If we haven't been initialized yet then we aren't showing and we can just return.
            // Likewise if we are already hidden, or are currently fading out.
            _currentDialog = null;
            if(!this._alreadyInitialized || !this.open){
                return resolvedDeferred.promise;
            }
            if(this._fadeInDeferred){
                this._fadeInDeferred.cancel();
            }

            // fade-in Animation object, setup below
            var fadeOut = null, promise, h = null;

            this._fadeOutDeferred = new Deferred(lang.hitch(this, function(){
                fadeOut.stop();
                delete this._fadeOutDeferred;
            }));

            // fire onHide when the promise resolves.
            this._fadeOutDeferred.then(lang.hitch(this, 'onHide'));

            // If delay is 0, code below will delete this._fadeOutDeferred instantly, so grab promise while we can.
            promise = this._fadeOutDeferred.promise;
            fadeOut = fx.fadeOut({
                node: this.domNode,
                duration: this.duration,
                onEnd: lang.hitch(this, function(){
                    this.domNode.style.display = "none";
                    DialogLevelManager.hide(this);
                    this._fadeOutDeferred.resolve(true);
                    delete this._fadeOutDeferred;
                })
            }).play();

            if(this._scrollConnected){
                this._scrollConnected = false;
            }
            while(h = this._modalconnects.pop()){
                h.remove();
            }

            if(this._relativePosition){
                delete this._relativePosition;
            }
            this._set("open", false);

            return promise;
        },

        resize: function(dim){
            // summary:
            //      Called with no argument when viewport scrolled or viewport size changed.  Adjusts ModelessDialog as
            //      necessary to keep it visible.
            //
            //      Can also be called with an argument (by dojox/layout/ResizeHandle etc.) to explicitly set the
            //      size of the dialog.
            // dim: Object?
            //      Optional dimension object like {w: 200, h: 300}

            if(this.domNode.style.display != "none"){

                this._checkIfSingleChild();

                if(!dim){
                    if(this._shrunk){
                        // If we earlier shrunk the dialog to fit in the viewport, reset it to its natural size
                        if(this._singleChild){
                            if(typeof this._singleChildOriginalStyle != "undefined"){
                                this._singleChild.domNode.style.cssText = this._singleChildOriginalStyle;
                                delete this._singleChildOriginalStyle;
                            }
                        }
                        array.forEach([this.domNode, this.containerNode, this.titleBar], function(node){
                            domStyle.set(node, {
                                position: "static",
                                width: "auto",
                                height: "auto"
                            });
                        });
                        this.domNode.style.position = "absolute";
                    }

                    // If necessary, shrink ModelessDialog to fit in viewport and have some space around it
                    // to indicate that it's a popup.  This will also compensate for possible scrollbars on viewport.
                    var viewport = winUtils.getBox(this.ownerDocument), bb, contentDim, centerSize, cb;
                    viewport.w *= this.maxRatio;
                    viewport.h *= this.maxRatio;

                    bb = domGeometry.position(this.domNode);
                    if(bb.w >= viewport.w || bb.h >= viewport.h){
                        dim = {
                            w: Math.min(bb.w, viewport.w),
                            h: Math.min(bb.h, viewport.h)
                        };
                        this._shrunk = true;
                    }else{
                        this._shrunk = false;
                    }
                }

                // Code to run if user has requested an explicit size, or the shrinking code above set an implicit size
                if(dim){
                    // Set this.domNode to specified size
                    domGeometry.setMarginBox(this.domNode, dim);

                    // And then size this.containerNode
                    contentDim = utils.marginBox2contentBox(this.domNode, dim);
                    centerSize = {domNode: this.containerNode, region: "center"};
                    utils.layoutChildren(this.domNode, contentDim,
                        [ {domNode: this.titleBar, region: "top"}, centerSize ]);

                    // And then if this.containerNode has a single layout widget child, size it too.
                    // Otherwise, make this.containerNode show a scrollbar if it's overflowing.
                    if(this._singleChild){
                        cb = utils.marginBox2contentBox(this.containerNode, centerSize);
                        // note: if containerNode has padding singleChildSize will have l and t set,
                        // but don't pass them to resize() or it will doubly-offset the child
                        this._singleChild.resize({w: cb.w, h: cb.h});
                        // TODO: save original size for restoring it on another show()?
                    }else{
                        this.containerNode.style.overflow = "auto";
                        this._layoutChildren();     // send resize() event to all child widgets
                    }
                }else{
                    this._layoutChildren();     // send resize() event to all child widgets
                }

                if(!has("touch") && !dim){
                    // If the user has scrolled the viewport then reposition the ModelessDialog.  But don't do it for touch
                    // devices, because it will counteract when a keyboard pops up and then the browser auto-scrolls
                    // the focused node into view.
                    this._position();
                }
            }
        },

        _layoutChildren: function(){
            // Override _ContentPaneResizeMixin._layoutChildren because even when there's just a single layout child
            // widget, sometimes we don't want to size it explicitly (i.e. to pass a dim argument to resize())

            array.forEach(this.getChildren(), function(widget){
                if(widget.resize){
                    widget.resize();
                }
            });
        },

        destroy: function(){
            if(this._fadeInDeferred){
                this._fadeInDeferred.cancel();
            }
            if(this._fadeOutDeferred){
                this._fadeOutDeferred.cancel();
            }
            if(this._moveable){
                this._moveable.destroy();
            }
            var h = null;
            while(h = this._modalconnects.pop()){
                h.remove();
            }

            DialogLevelManager.hide(this);

            _currentDialog = null;

            this.inherited(arguments);

        }
    });

    if(has("dojo-bidi")){
        _DialogBase = declare("dijit._DialogBase", _DialogBase, {
            _setTitleAttr: function(/*String*/ title){
                this._set("title", title);
                this.titleNode.innerHTML = title;
                this.applyTextDir(this.titleNode);
            },

            _setTextDirAttr: function(textDir){
                if(this._created && this.textDir != textDir){
                    this._set("textDir", textDir);
                    this.set("title", this.title);
                }
            }
        });
    }

    ModelessDialog = declare("beacons.common.ModelessDialog", [ContentPane, _DialogBase], {
        // summary:
        //      A modal dialog Widget.
        // description:
        //      Pops up a modeless dialog window. ModelessDialog is extended from
        //      ContentPane so it supports all the same parameters (href, etc.).
        // example:
        // |    <div data-dojo-type="beacons/common/ModelessDialog" data-dojo-props="href: 'test.html'"></div>
        // example:
        // |    var foo = new ModelessDialog({ title: "test dialog", content: "test content" });
        // |    foo.placeAt(win.body());
        // |    foo.startup();
    });
    ModelessDialog._DialogBase = _DialogBase;   // for monkey patching and dojox/widget/DialogSimple

    DialogLevelManager = ModelessDialog._DialogLevelManager = {
        // summary:
        //      Controls the various active "levels" on the page, starting with the
        //      stuff initially visible on the page (at z-index 0), and then having an entry for
        //      each ModelessDialog shown.

        _beginZIndex: 750,

        show: function(/*dijit/_WidgetBase*/ dialog/*, / *Object* / underlayAttrs*/){
            // summary:
            //      Call right before fade-in animation for new dialog.
            //      Saves current focus, displays/adjusts underlay for new dialog,
            //      and sets the z-index of the dialog itself.
            //
            //      New dialog will be displayed on top of all currently displayed dialogs.
            //
            //      Caller is responsible for setting focus in new dialog after the fade-in
            //      animation completes.

            // Save current focus
            ds[ds.length - 1].focus = focus.curNode;

            // Set z-index a bit above previous dialog
            var zIndex = ds[ds.length - 1].dialog ? ds[ds.length - 1].zIndex + 2 : ModelessDialog._DialogLevelManager._beginZIndex;
            domStyle.set(dialog.domNode, 'zIndex', zIndex);


            ds.push({dialog: dialog, /*underlayAttrs: underlayAttrs, */zIndex: zIndex});
        },

        hide: function(/*dijit/_WidgetBase*/ dialog){
            // summary:
            //      Called when the specified dialog is hidden/destroyed, after the fade-out
            //      animation ends, in order to reset page focus, fix the underlay, etc.
            //      If the specified dialog isn't open then does nothing.
            //
            //      Caller is responsible for either setting display:none on the dialog domNode,
            //      or calling dijit/popup.hide(), or removing it from the page DOM.
            var pd, focus, idx;
            if(ds[ds.length - 1].dialog == dialog){
                // Removing the top (or only) dialog in the stack, return focus
                // to previous dialog

                ds.pop();

                pd = ds[ds.length - 1]; // the new active dialog (or the base page itself)

                // Adjust focus.
                // TODO: regardless of setting of dialog.refocus, if the exeucte() method set focus somewhere,
                // don't shift focus back to button.  Note that execute() runs at the start of the fade-out but
                // this code runs later, at the end of the fade-out.  Menu has code like this.
                if(dialog.refocus){
                    // If we are returning control to a previous dialog but for some reason
                    // that dialog didn't have a focused field, set focus to first focusable item.
                    // This situation could happen if two dialogs appeared at nearly the same time,
                    // since a dialog doesn't set it's focus until the fade-in is finished.
                    focus = pd.focus;
                    if(pd.dialog && (!focus || !dom.isDescendant(focus, pd.dialog.domNode))){
                        pd.dialog._getFocusItems();
                        focus = pd.dialog._firstFocusItem;
                    }

                    if(focus){
                        // Refocus the button that spawned the ModelessDialog.   This will fail in corner cases including
                        // page unload on IE, because the dijit/form/Button that launched the ModelessDialog may get destroyed
                        // before this code runs.  (#15058)
                        try{
                            focus.focus();
                        }catch(e){
                            ;
                        }
                    }
                }
            }else{
                // Removing a dialog out of order (#9944, #10705).
                // Don't need to mess with underlay or z-index or anything.
                idx = array.indexOf(array.map(ds, function(elem){
                    return elem.dialog;
                }), dialog);
                if(idx != -1){
                    ds.splice(idx, 1);
                }
            }
        },

        isTop: function(/*dijit/_WidgetBase*/ dialog){
            // summary:
            //      Returns true if specified ModelessDialog is the top in the task
            return ds[ds.length - 1].dialog == dialog;
        }
    };

    // Stack representing the various active "levels" on the page, starting with the
    // stuff initially visible on the page (at z-index 0), and then having an entry for
    // each ModelessDialog shown.
    // Each element in stack has form {
    //      dialog: dialogWidget,
    //      focus: returnFromGetFocus(),
    //      underlayAttrs: attributes to set on underlay (when this widget is active)
    // }
    ds = ModelessDialog._dialogStack = [
        {dialog: null, focus: null/*, underlayAttrs: null*/}    // entry for stuff at z-index: 0
    ];

    // Back compat w/1.6, remove for 2.0
    if(has("dijit-legacy-requires")){
        ready(0, function(){
            var requires = ["dijit/TooltipDialog"];
            require(requires);  // use indirection so modules not rolled into a build
        });
    }

    ModelessDialog.closeCurrent = function() {
        if(_currentDialog) {
            _currentDialog.destroy();
        }
    };
    return ModelessDialog;
});