用于md对话框的自定义堆栈很少导致范围中断

时间:2016-12-02 18:22:28

标签: javascript angularjs dialog angular-material

PREMISE

从Bootstrap切换时我看到的角度材料的一个限制是,在单个工作流程中没有固有的机制可以有多个对话框(至少,不是从文档的角度来看)。为了解决这个问题,我创建了自己的堆栈以及我认为是有效使用它的合理抽象机制。该文件转载如下。

问题

大多数情况下,它可以按照您的预期运作。但是,如果最初使用现有范围创建对话框,则很少(并且看似没有一致性)关闭对话框也会破坏关联的范围。有时,这表现为后续对话框无法打开,或者如果它们打开,则无法填充嵌套在其中的绑定。在其他情况下,它会导致父页面上的每个角度元素失败。标准链接工作正常,但任何ng-click事件等都不再起作用。我不知道为什么会这样。我希望一双新鲜的眼睛能帮助我找到造成这种错误的愚蠢错误。

USAGE

可以通过两种方式使用堆栈: push replace 。如果将新对话框推送到堆栈,则会隐藏任何当前对话框并以保留其上下文的方式进行存储。这基本上意味着首先用于生成模态的所有参数都存储在一个对象中并被压入堆栈,这是一个本地存储在DialogStack服务中的数组。在此之后,渲染函数使用堆栈上的最高元素生成新模态。如果您选择替换,则该过程实际上是相同的,除了在添加新对话框之前完全清除堆栈。

实际的对话框参数存储在全局对象DIALOGS中。所有对话框中都使用默认参数(DIALOGS.DEFAULTS),然后为系统创建的每个对话框都有自己的一组属性,包括templateUrl和简要说明。偶尔他们也会覆盖标准属性。呈现对话框时,使用Lodash将与当前对话框关联的对象与DIALOGS.DEFAULTS对象混合,使自定义属性优先。特别值得注意的是preserveScope属性,它在DIALOGS.DEFAULTS中设置为true。我本来希望扩展这个标志的使用方式,但为了稳定起见,它基本上是孤立的。我看到的问题来自于使用preserveScope的对话框未触及(设置为true)。

请注意,还有一个实用程序函数可以使用内置的确认对话框,该对话框使用基本构造函数而不是DIALOGS对象生成其参数。

CODE

Plunker https://embed.plnkr.co/MZ74mV0FMGirJd1LYm64/

DialogStack:

'use strict';

/**
 * @ngdoc function
 * @name dialogsuite.service:DialogStack
 * @description
 * # DialogStack
 * Handles dialog management under angular-material
 */
angular.module('dialogsuite')
  .factory('DialogStack',
    ['$mdDialog', '$q', '$rootScope',
     'Util',
     'DIALOGS',
    function ($mdDialog, $q, $rootScope,
              Util,
              DIALOGS) {

    var stack;
    var processing;
    // var debugCount = 0;

    /**
     * Removes all dialogs from the stack
     **/
    function clear (hideAll)
    {
      if (!!hideAll)
      {
        stack = [];

        renderDialog ();
      }
      else
      {
        if (!!stack && stack.length === 1)
        {
          processing = false;
        }

        $mdDialog.hide ();
      }
    }

    /**
     * Closes the visible dialog
     **/
    function close ()
    {
      if (!stack || !stack.length) { return; }

      $mdDialog.hide ();
    }

    /**
     * Shows a confirm dialog and calls the appropriate callback
     **/
    function confirm (message, confirmCallback, cancelCallback)
    {
      if (!stack)
      {
        stack = [];
      }
      else if (!!stack.length)
      {
        processing = true;
      }

      var params = $mdDialog.confirm ()
                    //.title('Confirm Action')
                    .textContent (message)
                    .ariaLabel ('dialog')
                    .ok ('CONFIRM')
                    .cancel ('CANCEL')
                    .theme ('default')
                    ._options;

      params.onRemoving = function () { pop (); };

      stack.push (params);

      $rootScope.$evalAsync (function ()
      {
        $mdDialog.show (params).then (confirmCallback, cancelCallback);
      });
    }

    /**
     * Removes the dialogue at the top of the stack. If there are any dialogs
     * remaining, opens the next one down
     **/
    function pop (force)
    {
      if ((!!stack && !!stack.length && !processing) ||
          (!!stack && !!stack.length && force === true))
      {
        var removed = stack.pop ();

        $rootScope.$broadcast ('llDialogClosed', removed);

        renderDialog ();
      }

      processing = false;
    }

    /**
     * Adds a dialog to the stack and opens it immediately
     **/
    function push (dialog, scope, locals)
    {
      if (!dialog)
      {
        throw 'Exception: No dialog provided';
      }

      if (!stack)
      {
        stack = [];
      }

      processing = true;

      if (!!scope)
      {
        dialog.scope = scope;
      }

      //Note: Locals get injected into the dialog's controller
      if (!!locals)
      {
        dialog.locals = locals;
      }

      stack.push (dialog);

      renderDialog ();
    }

    /**
     * Closes any dialog that may currently be open, then shows the dialog at
     * this top of the stack
     **/
    function renderDialog ()
    {
      /*
      locals     {object=}:   An object containing key/value pairs. The keys will be used as names of values to inject into the controller. For example, `locals: {three: 3}` would inject three into the controller, with the value 3. If bindToController is true, they will be copied to the controller instead.
      onShowing  {function=}: Callback function used to announce the show() action is starting.
      onComplete {function=}: Callback function used to announce when the show() action is finished.
      onRemoving {function=}: Callback function used to announce the close/hide() action is starting. This allows developers to run custom animations in parallel the close animations.
      resolve    {object=}:   Similar to locals, except it takes promises as values, and the dialog will not open until all of the promises resolve.
      */

      if (!stack || !stack.length)
      {
        $mdDialog.hide ();
        processing = false;
        return;
      }

      var params = _.assign (DIALOGS.DEFAULTS, stack[stack.length - 1]);

      params.onRemoving = function () { pop (); };

      $rootScope.$evalAsync (function ()
      {
        //When a modal becomes visible, any previous modals are automatically
        //hidden. By doing this manually we can clear the processing flag without
        //destroying the rest of the stack, since onRemoving resolves before the
        //promise does
        $mdDialog.hide ()
        .then (function ()
        {
          processing = false;
          $mdDialog.show (params);
        });
      });
    }

    /**
     * Replaces the dialog stack with the given dialog
     **/
    function replace (dialog, scope, locals)
    {
      stack = [];

      push (dialog, scope, locals);
    }

    /* ********************************************************************** */

    //Service public-facing functions
    var DialogStack = {
      clear: clear,
      close: close,
      confirm: confirm,
      pop: pop,
      push: push,
      replace: replace
    };

    return DialogStack;
  }]);

对话框

app.value ('DIALOGS',
  {
    'DEFAULTS':
    {
      autoWrap: false,
      clickOutsideToClose: true,
      disableParentScroll: true,
      escapeToClose: true,
      focusOnOpen: true,
      fullscreen: true,
      hasBackdrop: true,
      parent: $('body'),
      preserveScope: true
    },
    'MODALROOT':
    {
      desc: 'multi-purpose container, usually for displaying messages',
      fullscreen: false,
      name: 'root',
      templateUrl: 'modal-root.html'
    }
  })

0 个答案:

没有答案