如何打开多个模态窗口并进行模态导航

时间:2014-07-28 15:11:35

标签: javascript angularjs twitter-bootstrap

我有一个问题,我假设使用ng-include,但我不确定。我将尝试尽可能地描述它。这是一点背景。我正在使用bootstrap的模态服务,我创建了一个视图和一个控制器,它应该作为一个包装器。想法是在模态中注入任何视图。这是“modaltemplate.html”

的html片段
<div data-ng-controller='modalCtrl' >
    <div data-ng-include="templateUrl" >
    </div>
</div>

就是这样,真的没什么。然后在$ rootScope上,我有以下代码,根据路由名称和传递给它的参数打开一个新模式。

 app.run(function($rootScope,$modal) {
        $rootScope.openEditModal = function (startRouteName, parameters, scope) {
            $modal.open({
                templateUrl: 'app/common/modaltemplate.html',
                scope: scope,
                controller: function($scope, $modalInstance) {
                    $modalInstance.opened.then(function () {
                    $scope.startRouteName = startRouteName;
                    $scope.routeParams = parameters;

                    });
                }
            });
        };
    });

因此,当modal打开时,routeName和参数将在modalCtrl.js中处理。部分代码是从角度代码库中劫持的。简而言之,它将模板的url定位为绑定到ng-include之上,并将所有参数设置为$ scope上的routeParams

$scope.routeParams = next.params;
//ep: set the templateUrl for the 'view' so that modal.html will load it in.
$scope.templateUrl = next.$$route.templateUrl;

这一切都很好,我可以打开任何路线,传递参数,在模态打开时来回路线之间的进展等等。基本上,我在模态工作中有一个路由,好像它是单独的应用程序。例如,我可以这样做

var startRouteName = 'InsuredExceptionAdd';
var params = {
       mode: 'add',
        viewTitle: "Add Insured Exception",
        insuredId: insuredId,
};

$scope.openEditModal(startRouteName, params, $scope);

然后在保存时我可以导航到下一个路线等。现在,我正在尝试从模态中弹出另一个模态,这就是我遇到以下问题的地方。一个新的模态在第一个,但是,新打开的模态和第一个模态现在共享相同的控制器,但不是控制器的相同实例的顶部加速。我使用快速uid生成器来确认控制器不是同一个实例,而它们不是。会发生什么,是第一个模态的包含视图的控​​制器被控制器和新弹出模式的视图替换。 如果我查看DOM,我现在有两个包含的视图。我从第一个模态看到的第一个视图已经消失并被替换为新视图,第二个模态保持相同的视图,但没有发生任何绑定。在下面的示例中,inInsuredEditCtrl和视图已被替换为 像这样的inRelatedInsuredEditCtrl

<div tabindex="-1" class="modal fade  in" ng-class="{in: animate}" ng-style="{'z-index': 1050 + index*10, display: 'block'}" ng-click="close($event)" modal-window="" index="0" animate="animate" style="z-index: 1050; display: block;">
    <div class="modal-dialog">
        <div class="modal-content" ng-transclude="">
            <div data-ng-controller="modalCtrl" class="ng-scope">
            <!-- ngInclude: templateUrl -->
                <div data-ng-include="templateUrl" class="ng-scope">
                    <div id="editRelatedInsured" data-ng-controller="inRelatedInsuredEditCtrl" class="ng-scope">
            <div class="modal-header">
                <h3 class="lineset ng-binding">Add Related Insured</h3> 
            </div>
            <form role="form" name="editRelatedInsuredForm" class="ng-pristine ng-valid">            
            </form>
        </div>
    </div>
</div>

并且现在位于第一个模态之上的那个具有正确的控制器但没有发生绑定所以html看起来像这样

<div tabindex="-1" class="modal fade  in" ng-class="{in: animate}" ng-style="{'z-index': 1050 + index*10, display: 'block'}" ng-click="close($event)" modal-window="" index="1" animate="animate" style="z-index: 1060; display: block;">
    <div class="modal-dialog"><div class="modal-content" ng-transclude="">
        <div data-ng-controller="modalCtrl" class="ng-scope">

        <!-- ngInclude: templateUrl -->
        <div data-ng-include="templateUrl" class="ng-scope">
            <div id="editRelatedInsured" data-ng-controller="inRelatedInsuredEditCtrl" class="ng-scope">
                <div class="modal-header">
                <h3 class="lineset ng-binding">{{routeParams.viewTitle}}</h3> 
            </div>

            <form role="form" name="editRelatedInsuredForm" class="ng-pristine ng-valid">           
            </form>
        </div>
    </div>
</div>

这是ng-include和scopes的问题吗?我不知道为什么ng-include被处理两次以及为什么它会影响以前的模态。它几乎就像更改模板url传递给两个modalCtrl实例一样。

1 个答案:

答案 0 :(得分:0)

好的,我终于明白了。不得不重写一些部分,但现在它工作正常。该控制器将允许打开多个模态,并且还将处理模态窗口内的导航。

editModal.js

(function () {
    'use strict';

    var app = angular.module('app');

    app.run(function ($modal, $modalStack, $rootScope, $route, $routeParams, $location, $q, $sce, $injector, $http, $templateCache) {

        $rootScope.openEditModal = function (startRouteName, parameters, scope) {

            function afterModalOpened($scope) {

                $scope.title = "modalCtrl";

                // ep: keep track of modal windows open for edit
                // $modalStack keeps list of all opened windows, but that is not good for us
                // saving... and loading... are also modals and they appear as top most window so
                // $modalStack.getTop().value.modalScope.$id does not work always , and in order to 
                // responsed to $locationChangeStart events by top most open edit modal we need this list
                $rootScope.editModalsScopes = $rootScope.editModalsScopes || [];

                $rootScope.editModalsScopes.push($scope);

                console.log("modalCtrl.afterModalOpened -  $scope.$id: " + $scope.$id + " " + "   startRouteName: " + startRouteName);

                //ep: overwrite default implementation os $scope.$close from modal window
                var closeModal = $scope.$close;
                $scope.$close = function (payload) {
                    console.log("scope closing the modal: " + $scope.$id);
                    // $scope.$emit("ModalClosing");
                    offLocationChangeStart();
                    closeModal(payload);
                    $rootScope.editModalsScopes.pop();
                };

                //ep: overwrite default implementation os $scope.$dismiss from modal window
                var dismissModal = $scope.$dismiss;
                $scope.$dismiss = function (reason) {
                    console.log("scope canceling the modal: " + $scope.$id);
                    // $scope.$emit("ModalCanceling");
                    offLocationChangeStart();
                    dismissModal(reason);
                    $rootScope.editModalsScopes.pop();
                };

                //ep: following section deals witn in-modal navigation
                // when we don't want to close window before progressing to a next window
                // one can use $scope.navigateTo(routeName, paramters) to switch the content within the modal window

                var callerLocation = {};
                angular.copy($location, callerLocation);

                //ep: remember the return function so we can de-register it
                var offLocationChangeStart = $scope.$on('$locationChangeStart', function (event) {

                    //ep: so that that angular base would not process this also,
                    // navigation is localized to modal window
                    event.preventDefault();

                    var topEditModalScope = $rootScope.editModalsScopes[$rootScope.editModalsScopes.length - 1];

                    console.log("in $locationChangeStart");
                    console.log("    $scope.$id = " + $scope.$id + " " + $scope.title);
                    console.log("    top most edit modal window $scope.$id " + topEditModalScope.$id);
                    console.log("    event.currentScope.$id = " + event.currentScope.$id);
                    console.log("    event.targetScope.$id = " + event.targetScope.$id);

                    //process locationChangeStart events only by the top edit modal window, currently active one
                    if (topEditModalScope.$id !== $scope.$id) {

                        console.log("$locationChangeStart - aborting processing due to scope id diferences");
                        return;
                    }

                    //ep: this could be problematic when there are search parameters and reload is true
                    //but for now it all works fine, address if and when needed
                    if ($location.$$path == callerLocation.$$path) {
                        $scope.$dismiss("back-button");
                    }

                    var route = $location.$$path;

                    console.log("   about to process route " + route);

                    //ep: this will ad entry on the $scope.routeParams and set templateUrl that modal's ng-include is bound to.
                    //  all objects that are on passed will be passed as route paramters. Complex objects will be converted to JSON
                    //  on receiving side one has to convert them back.
                    $q.when(processRoute()).then(function () {
                        console.log("   finished processing route " + route);
                    });
                });

                activate();

                function activate() {

                    $scope.routeParams = parameters;
                    $scope.templateUrl = $scope.getTemplateUrl(startRouteName);

                }

                //ep: this code was lifted from anuglar-route.js/updateRoute() and modified to process 
                //route, get the templateUrl and routeParameters, set them on the $scope, but not raise any events.
                function processRoute() {
                    var next = parseRoute(),
                        last = $route.current;

                    if (next || last) {
                        //forceReload = false;
                        //$rootScope.$broadcast('$routeChangeStart', next, last);
                        //$route.current = next;
                        if (next) {
                            if (next.redirectTo) {
                                if (angular.isString(next.redirectTo)) {
                                    $location.path(interpolate(next.redirectTo, next.params)).search(next.params)
                                             .replace();
                                } else {
                                    $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
                                             .replace();
                                }
                            }
                        }

                        $q.when(next).
                          then(function () {
                              if (next) {
                                  var locals = angular.extend({}, next.resolve),
                                      template, templateUrl;

                                  angular.forEach(locals, function (value, key) {
                                      locals[key] = angular.isString(value) ?
                                          $injector.get(value) : $injector.invoke(value);
                                  });

                                  if (angular.isDefined(template = next.template)) {
                                      if (angular.isFunction(template)) {
                                          template = template(next.params);
                                      }
                                  } else if (angular.isDefined(templateUrl = next.templateUrl)) {
                                      if (angular.isFunction(templateUrl)) {
                                          templateUrl = templateUrl(next.params);
                                      }
                                      templateUrl = $sce.getTrustedResourceUrl(templateUrl);
                                      if (angular.isDefined(templateUrl)) {
                                          next.loadedTemplateUrl = templateUrl;
                                          template = $http.get(templateUrl, { cache: $templateCache }).
                                              then(function (response) { return response.data; });
                                      }
                                  }
                                  if (angular.isDefined(template)) {
                                      locals['$template'] = template;
                                  }
                                  return $q.all(locals);
                              }
                          }).
                          // after route change
                          then(function (locals) {

                              console.log("processRoute.afterRouteChange $scope.id: " + $scope.$id + " - " + $scope.title);

                              //angular.copy(next.params, $scope.routeParams);

                              //ep: make routeParams available to the next "view" handled by the modal
                              $scope.routeParams = next.params;


                              //ep: set the templateUrl for the 'view' so that modal.html will load it in.
                              $scope.templateUrl = next.$$route.templateUrl;

                              //modalScope.templateUrl = next.$$route.templateUrl;    //^

                              //if (next == $route.current) {
                              //     if (next) {

                              //         next.locals = locals;

                              //           angular.copy(next.params, $routeParams);
                              //     }
                              //     $rootScope.$broadcast('$routeChangeSuccess', next, last);
                              // }
                          }, function (error) {
                              if (next == $route.current) {
                                  //$rootScope.$broadcast('$routeChangeError', next, last, error);
                              }
                          });
                    }
                }

                //ep:copied from anuglar-route.js 
                function parseRoute() {
                    // Match a route
                    var params, match;
                    angular.forEach($route.routes, function (route, path) {
                        if (!match && (params = switchRouteMatcher($location.path(), route))) {
                            match = inherit(route, {
                                params: angular.extend({}, $location.search(), params),
                                pathParams: params
                            });
                            match.$$route = route;
                        }
                    });
                    // No route matched; fallback to "otherwise" route
                    return match || routes[null] && inherit(routes[null], { params: {}, pathParams: {} });
                }

                    /**
               * @param on {string} current url
               * @param route {Object} route regexp to match the url against
               * @return {?Object}
               *
               * @description
               * Check if the route matches the current url.
               *
               * Inspired by match in
               * visionmedia/express/lib/router/router.js.
               */
                //ep:copied from anuglar-route.js 
                function switchRouteMatcher(on, route) {
                    var keys = route.keys,
                        params = {};

                    if (!route.regexp) return null;

                    var m = route.regexp.exec(on);
                    if (!m) return null;

                    for (var i = 1, len = m.length; i < len; ++i) {
                        var key = keys[i - 1];

                        var val = 'string' == typeof m[i]
                              ? decodeURIComponent(m[i])
                              : m[i];

                        if (key && val) {
                            params[key.name] = val;
                        }
                    }
                    return params;
                }

                ////ep: copied from angular-route core
                function inherit(parent, extra) {
                    return angular.extend(new (angular.extend(function () { }, { prototype: parent }))(), extra);
                }

            }

            //ep : open modal and process route and parameters
            $modal.open({
                template: "<div data-ng-include='templateUrl'></div>",
                //templateUrl: 'app/common/modaltemplate.html',
                // scope: scope,
                controller: function ($scope, $modalInstance) {

                    $modalInstance.opened
                                    .then(function () {
                                        afterModalOpened($scope);
                                    });
                }
            });
        };
    });

})();