自定义 AngularJS Material 模态框

自定义 AngularJS Material 模态框

Flying
2016-09-08 / 0 评论 / 122 阅读 / 正在检测是否收录...

AngularJS Material 是基于 Material Design 设计方式,使用 AngularJS 实现的,该项目提供了一套基于 Material Design 系统设计的可重复使用的,易于测试的 UI 组件。它的 layout 使用的是 flex 来布局的,因此大家就知道了他的兼容性了,但是这个是未来的方向。

angular-material.svg

AngularJS Material

和 AngularJS UI Bootstrap 相比,AngularJS Material 组件更丰富,比如统一外观的 Select(下拉框)、移动设备系统才有的 Switch(开关)、Swipe(侧滑)、Bottom Sheet(底部弹出)等组件。AngularJS Material 交互性更好、动感更强,更适合做响应式 Web App。

看了一下 AngularJS Material 官网的 Demo,不管是 Dialog 还是 Panel 组件似乎缺少封装。如果你和我一样在项目中需要自定义 AngularJS Material 模态框,请往下看。

自定义模态框

我觉得模态框的头部是最需要封装的。想一想模态框头部是不是都是标题加关闭按钮。标题最好能接受父作用域设置的参数,头部以外的区域很多时候会变动,因此我们可以使用 transclude 将指令内部的元素嵌入到你的模板中去。看下面代码:

/**
 * 模态窗指令:对应模态窗服务的视图
 * @param  dialogTitle {String} 设置障碍模态窗标题
 */
angular
  .module('myApp')
  .directive('riaDialog', function() {
    return {
      scope: {
        dialogTitle:'@'
      },
      restrict: "EA",
      transclude: true,
      replace: true,
      template: '<div class="dialog" role="dialog" aria-label="Dialog" layout="column" ng-cloak>\n' +
      '  <md-toolbar class="dialog-header">\n' +
      '    <div class="md-toolbar-tools" layout="row" layout-align="space-between center">\n' +
      '      <h4>{{::dialogTitle}}</h4>\n' +
      '      <md-button class="md-icon-button" title="Close"\n' +
      '        aria-label="Close dialog"\n' +
      '        ng-click="$parent.closeDialog();">\n' +
      '        <md-icon>\n' +
      '          <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fit="" preserveAspectRatio="xMidYMid meet" focusable="false">\n' +
      '            <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path>\n' +
      '          </svg>\n' +
      '        </md-icon>\n' +
      '      </md-button>\n' +
      '    </div>\n' +
      '  </md-toolbar>\n' +
      '  <ng-transclude></ng-transclude>\n' +
      '</div>\n'
    };
  });

我们只对 riaDialog 指令设置了一个独立 Scope(作用域)属性 dialogTitle。点击关闭按钮将会调用父作用域中的 closeDialog 方法,这种行为通常是统一的,因此我们用一个服务来封装它。看下面代码:

/**
 * 模态窗服务
 */

angular
  .module('myApp')
  .factory('dialog', ["$mdPanel", function($mdPanel) {
    return {

      /**
       * 打开带模板的模态窗
       * @param templateUrl {String} 内容模块地址 
       * @param locals {Object} 指定共享数据
       * @param clickOutsideToClose {Boolean} 点击模态窗外面关闭
       */
      open: function(templateUrl, locals, clickOutsideToClose) {
        var panelRef = null;
        var position = $mdPanel.newPanelPosition()
          .absolute()
          .center();

        var config = {
          attachTo: 'body',
          controller: ['$scope', function($scope) {
            $scope.locals = locals;
            $scope.closeDialog = function() {
              panelRef && panelRef.close().then(function() {
                panelRef.destroy();
              });
            }
          }],
          templateUrl: templateUrl,
          hasBackdrop: true,
          panelClass: 'ria-dialog',
          position: position,
          trapFocus: false,
          zIndex: 40,
          clickOutsideToClose: clickOutsideToClose || false,
          escapeToClose: true,
          focusOnOpen: true
        };

        return $mdPanel.open(config).then(function(mdPanelRef) {
          panelRef = mdPanelRef;
        });
      }
    }
  }]);

dialog 服务是对 $mdPanel 的封装,只有一个 open 方法,本质上是对 $mdPanel.open 方法的调用。为什么不选用 dialog?个人觉得 mdPanel 更底层一点,配置更灵活。我们这里使用了三个参数,只有第一个是必选的。模态框关联的控制器可以对返回的 panelRef 的注入引用, 这个引用可以关闭、隐藏和显示模态框。模态框头部与嵌入内容是平级的,因此关闭按钮要调用 closeDialog 函数,加上 $parent 属性就行了,没必要再加一个独立作用域属性。

注意:由于 codepen 对文件的限制,本来是可以通过 md-svg-icon 设置 svg 按钮图标,最终改成直接嵌入 svg 代码。还有本来中通过 templateUrl 弹出动态内容,最终使用 $templateCache 前将模板缓存到一个定义模板的 JavaScript 文件中,这样就不需要通过 XHR 来加载模板了。

打开模态框是一个异步的过程,因此如果模态框的内容没加载完,表单验证会有问题。这又是 AngularJS 人一个“坑”。怎样解决这一个问题呢?可以使用 angular.bootstrap 手动启动该 AngularJS 应用。

参考实例

访问 Codepen 查看代码及最终效果

5

评论 (0)

取消