AngularJS:重构确认模态指令

时间:2013-11-14 14:29:39

标签: angularjs angularjs-directive

我需要一些关于重构我所拥有的模态指令的建议。我刚开始使用指令,所以欢迎任何其他解决我问题的方法。

我的程序需要一个确认模式,我们可以确认或取消所需的操作。它将出现在许多地方,需要能够有一个可编程按钮。取消是一致的,因为它只会隐藏模态,确认按钮需要执行任何所需的操作。

我目前正在使用$rootScope来显示/隐藏/配置模态。这是一个坏主意吗?请告诉我。

这就是我现在正在使用的(粗略地说,因为我已经删除了很多其他不必要的代码):

的index.html

<!doctype html>
<html lang="en">
    <head>
        <title>My App</title>
    </head>
    <body ng-controller="MenuCtrl">

        <confirmmodal ng-show="$root.confirmModal.isVisible"></confirmmodal>

        <ul>
            <li>Home</li>
            <li>About</li>
            <li>Contact</li>
        </ul>

        <div ng-view></div>

        <!-- build:js scripts/main.js -->
        <script data-main="scripts/main" src="lib/requirejs/require.js"></script>
        <!-- endbuild -->
    </body>
</html>

所以我的模态位于ng-view之上,可以从任何地方调用。它位于伪全局控制器内,名为MenuCtrl

这是模态指令代码:

directives.js

/* Confirm Modal */
.directive('confirmmodal', [function() {
    return {
        restrict: 'E',
        templateUrl: 'view/templates/modal-confirm.tpl.html'
    };
}])

它用作以下代码的模板:

模态-confirm.tpl.html

<!-- Confirm Modal Template -->
<div class="overlay">
    <div class="overlay-content extended">
        <span>{{$root.confirmModal.content}}</span>
        <div class="buttons">
            <button class="btn btn-default" ng-click="$root.confirmModal.secondary.action()">{{$root.confirmModal.secondary.content}}</button>
            <button class="btn btn-primary" ng-click="$root.confirmModal.primary.action()">{{$root.confirmModal.primary.content}}</button>
        </div>
    </div>
</div>

我在app.run函数中设置了一堆默认值:

app.js

app.run(['$rootScope', function ($rootScope) {
    _.extend($rootScope, {
        confirmModal: {
            isVisible: false,
            content: '',
            primary: {
                action: function() {
                    console.log('hello world');
                },
                content: 'Submit'
            },
            secondary: {
                action: function() {
                    $rootScope.confirmModal.isVisible = false;
                },
                content: 'Cancel'
            }
        }
    });
}]);

所以我还编写了一个模态触发器指令,这个想法是我可以创建不同的触发器,用模态执行不同的操作。

directives.js

/* Resolve Event */
.directive('resolveevent', ['RequestService', '$location', function (RequestService, $location) {
    return {
        restrict: 'A',
        scope: {
            eventtype: '@',
            eventid: '@',
            index: '@'
        },
        controller: ['$scope', function($scope) {

            $scope.remove = function(id) {
                // remove the event from the events array
                $scope.$parent.$parent.$parent.$parent.events.splice(id, 1);
            },

            $scope.config = function(config) {
                _.extend($scope.$root.confirmModal, config);
            },

            $scope.isVisible = function() {
                $scope.$apply(function() {
                    $scope.$root.confirmModal.isVisible = true;
                });
            }
        }],
        link: function( $scope, element, attrs ) {
            var config = {
                content: 'Are you sure you wish to resolve this event?',
                primary: {
                    action: function() {
                        var config = {
                            url: '/Events/' + $scope.eventid,
                            method: 'PUT',
                            data: {
                                event_status: 'resolved'
                            },
                            cache: false
                        }

                        /* Update event with resolved status */
                        RequestService.makeApiRequest(config).success(function(response) {
                            $scope.$root.confirmModal.isVisible = false;
                            $scope.remove($scope.index);
                        });
                    },
                    content: 'Resolve Event'
                }
            }

            element.on('click', function() {
                if (!$scope.$root.confirmModal.isVisible) {
                    $scope.config(config);
                    $scope.isVisible();
                }
            });
        }
    }
}]);

然后我在视图上使用了一个按钮,我的ng-repeat找到了能够触发模态的按钮:

eventlist.html

<li ng-repeat="event in events">

    <p>Event: {{ event.number }}</p>
    <p>Group: {{ event.group_name }}</p>
    <p>Record Date: {{ event.event_date | moment: 'MM/DD/YYYY h:mm A' }}</p>

    <button resolveevent index="{{$index}}" eventid="{{ event.number }}" class="btn btn-default">Resolve</button>
</li>

这就是我所拥有的,而且它正在发挥作用,但它似乎有点矫枉过正,低效率,以及维持的噩梦。任何人都可以提出改进方法吗?我感谢任何帮助,提前谢谢。

4 个答案:

答案 0 :(得分:1)

您可以查看bootstrap-ui项目:http://angular-ui.github.io/bootstrap/

如果您使用的是Bootstrap 3,请注意模板,并使用不带它们的版本。您可以在此处下载与bootstrap3兼容的模板:https://github.com/angular-ui/bootstrap/tree/bootstrap3_bis2_modalPatch

答案 1 :(得分:1)

确认的简单指令:

/**
 * A generic confirmation for risky actions.
 * Usage: Add attributes: ng-really-message="Really?" ng-really-click="takeAction()" function
 */
angular.module('app').directive('ngReallyClick', [function() {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            element.bind('click', function() {
                var message = attrs.ngReallyMessage;
                if (message && confirm(message)) {
                    scope.$apply(attrs.ngReallyClick);
                }
            });
        }
    }
}]);

答案 2 :(得分:0)

我的方法可能不符合最佳实践,但我通常最终会创建专用服务,它们都可以访问模态的范围并操作dom。将其视为自我注射指令。

这是模态的容器html(使用bootstrap的样式):

<div class="modal-backdrop"></div>
<div class="modal fade">
    <div class="modal-dialog" ng-style="{width: width}">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" ng-click="close()" aria-hidden="true">&times;</button>
                <h4 class="modal-title">{{title}}</h4>
            </div>
            <div class="modal-body">

            </div>
            <div class="modal-footer">
                <button ng-repeat="(name, callback) in buttons" type="button" ng-click="callback()">{{name}}</button>
            </div>
        </div>
    </div>
</div>

然后是DialogService的伪代码:

.service('DialogService', function($compile, $http, $rootScope) {
  this.open = function(options) {
     //options contain various properties
     //e.g. title, width, template or templateUrl, button map with callbacks
     loadModalContainer()
     .then(loadModalBody)
     .then(init);

     function init() {
       modal = $('body').append(containerHtml).find('.modal');
       modal.append(bodyHtml);
       scope = (options.scope || $rootScope).$new();
       if (options.controller) $controller(options.controller, {$scope: scope});
       $compile(modal)(scope);
       listenForEscKey();
     }
     function close() {
       //clean up event listeners
       //
       if (options.onClose) options.onClose();
       scope.$destroy();
       $('body').find('.modal,.modal-backdrop').remove();
     }
  }
});

当然,由于服务的异步特性,如果第二个模式弹出,你必须实现一些自动关闭逻辑。从非常简单,您可以将具体对话框定义为单独的服务来抽象出细节:

 .service('TermsModal', function(DialogService) {
    this.open = function(acceptCallback, declineCallback, scope) {
       DialogService.open({
         templateUrl: '',
         width: '',
         buttons: {
           accept: acceptCallback,
           decline: declineCallback
         }, 
         scope: scope
       });
    }
 })

然后,您可以从任何控制器打开带有单行的模态:TermsModal.open(acceptCallback, declineCallback, $scope)

有几个问题。首先,使用转换会很棒,因为现在modal的子范围充满了title, buttons, width属性。

另一件事是我绕过模态体的宽度,但这只是我的懒惰(我无法正确设计模式体模宽度,因为它是硬编码的)。

此外,我从控制器传递局部范围,因为模态的主体内容通常与调用模态的控制器有一种或另一种方式相关。例如,如果我们将ItemController与item作为范围属性,并且我们有一个编辑按钮来编辑模态中的项目值,则子范围必须知道它正在处理的模型。因此要么是在范围内传递,要么直接在选项中传递所需的值。我更喜欢范围,因为它提供了更大的灵活性,并且在子范围初始化的情况下,很难弄乱原始模型。

总而言之,这种设置的强大功能和灵活性证明了服务与DOM有点混乱的事实。您的rootScope没有全局状态(该服务管理自己的状态而不向外界提供详细信息),并且您的主模板没有模态部分/指令/无论可能使用也可能不使用。

答案 3 :(得分:0)

我已经创建了一个小的确认指令,如果确认了模态,它会打开一个模态并执行你想要的代码:

<强> app.html

<button type="button" class="btn btn-default"
 nait-confirm-click
 confirm="Do you really want to remove this record?"
 confirm-if="user.disabled == true"
 do="remove(user)">
    Remove
</button>

<强>的script.js

angular
    .module('xyz', ['ui.bootstrap'])
    .directive('naitConfirmClick', function($modal, $parse) {
        return {
            restrict: 'EA',
            link: function(scope, element, attrs) {
                if (!attrs.do) {
                    return;
                }

                // register the confirmation event
                var confirmButtonText = attrs.confirmButtonText ? attrs.confirmButtonText : 'OK';
                var cancelButtonText = attrs.cancelButtonText ? attrs.cancelButtonText : 'Cancel';
                element.click(function() {
                    // action that should be executed if user confirms
                    var doThis = $parse(attrs.do);

                    // condition for confirmation
                    if (attrs.confirmIf) {
                        var confirmationCondition = $parse(attrs.confirmIf);
                        if (!confirmationCondition(scope)) {
                            // if no confirmation is needed, we can execute the action and leave
                            doThis(scope);
                            scope.$apply();
                            return;
                        }
                    }
                    $modal
                        .open({
                            template: '<div class="modal-body">' + attrs.confirm + '</div>'
                                + '<div class="modal-footer">'
                                +     '<button type="button" class="btn btn-default btn-naitsirch-confirm pull-right" ng-click="$close(\'ok\')">' + confirmButtonText + '</button>'
                                +     '<button type="button" class="btn btn-default btn-naitsirch-cancel pull-right" ng-click="$dismiss(\'cancel\')">' + cancelButtonText + '</button>'
                                + '</div>'
                        })
                        .result.then(function() {
                            doThis(scope);
                            scope.$apply()
                        })
                    ;
                });
            }
        };
    })
;

现在,如果您点击带有 nait-confirm-click 的按钮,它会打开一个带有两个按钮的模式以及您通过 confirm 属性传递的文本。如果单击取消按钮,则不会发生任何操作。如果您通过单击“确定”进行确认,则将执行执行属性所传递的表达式。

如果在可选的 confirm-if 属性中传递表达式,则只有在表达式为真时才会打开模态。如果表达式为false,则将在不询问的情况下执行操作。

我希望这段代码可以帮助某人;)