Javascript 使用 angularjs 检测未保存的更改并提醒用户

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14852802/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 18:08:54  来源:igfitidea点击:

Detect unsaved changes and alert user using angularjs

javascriptangularjsangularjs-directive

提问by iJade

Below is the code so far

以下是到目前为止的代码

    <!doctype html>
<html ng-app>
<head>
    <script src="http://code.angularjs.org/1.1.2/angular.min.js"></script>
    <script type="text/javascript">
    function Ctrl($scope) {
        var initial = {text: 'initial value'};
        $scope.myModel = angular.copy(initial);
        $scope.revert = function() {
            $scope.myModel = angular.copy(initial);
            $scope.myForm.$setPristine();
        }
    }
    </script>
</head>
<body>
    <form name="myForm" ng-controller="Ctrl">
        myModel.text: <input name="input" ng-model="myModel.text">
        <p>myModel.text = {{myModel.text}}</p>
        <p>$pristine = {{myForm.$pristine}}</p>
        <p>$dirty = {{myForm.$dirty}}</p>
        <button ng-click="revert()">Set pristine</button>
    </form>
</body>
</html>

How to alert on browser closeor url redirectin case there is some unsaved data, so that user can decide whether to continue?

如何在警戒browser closeurl redirect万一有一些未保存的数据,使用户可以决定是否继续?

回答by Anders Ekdahl

Something like this should do it:

像这样的事情应该这样做:

<!doctype html>
<html ng-app="myApp">
<head>
    <script src="http://code.angularjs.org/1.1.2/angular.min.js"></script>
    <script type="text/javascript">
    function Ctrl($scope) {
        var initial = {text: 'initial value'};
        $scope.myModel = angular.copy(initial);
        $scope.revert = function() {
            $scope.myModel = angular.copy(initial);
            $scope.myForm.$setPristine();
        }
    }

    angular.module("myApp", []).directive('confirmOnExit', function() {
        return {
            link: function($scope, elem, attrs) {
                window.onbeforeunload = function(){
                    if ($scope.myForm.$dirty) {
                        return "The form is dirty, do you want to stay on the page?";
                    }
                }
                $scope.$on('$locationChangeStart', function(event, next, current) {
                    if ($scope.myForm.$dirty) {
                        if(!confirm("The form is dirty, do you want to stay on the page?")) {
                            event.preventDefault();
                        }
                    }
                });
            }
        };
    });
    </script>
</head>
<body>
    <form name="myForm" ng-controller="Ctrl" confirm-on-exit>
        myModel.text: <input name="input" ng-model="myModel.text">
        <p>myModel.text = {{myModel.text}}</p>
        <p>$pristine = {{myForm.$pristine}}</p>
        <p>$dirty = {{myForm.$dirty}}</p>
        <button ng-click="revert()">Set pristine</button>
    </form>
</body>
</html>

Note that the listener for $locationChangeStart isn't triggered in this example since AngularJS doesn't handle any routing in such a simple example, but it should work in an actual Angular application.

请注意,在此示例中没有触发 $locationChangeStart 的侦听器,因为 AngularJS 在这样一个简单的示例中不处理任何路由,但它应该可以在实际的 Angular 应用程序中工作。

回答by manikanta

I've extended the @Anders answer to clean up listeners (unbind listers) when directive is destroyed (ex: when route changes), and added some syntactic sugar to generalise the usage.

我已经扩展了@Anders 的答案,以在指令被销毁时(例如:路由更改时)清理侦听器(解除绑定列表),并添加了一些语法糖来概括用法。

confirmOnExit Directive:

确认退出指令

/**
 * @name confirmOnExit
 * 
 * @description
 * Prompts user while he navigating away from the current route (or, as long as this directive 
 * is not destroyed) if any unsaved form changes present.
 * 
 * @element Attribute
 * @scope
 * @param confirmOnExit Scope function which will be called on window refresh/close or AngularS $route change to
 *                          decide whether to display the prompt or not.
 * @param confirmMessageWindow Custom message to display before browser refresh or closed.
 * @param confirmMessageRoute Custom message to display before navigating to other route.
 * @param confirmMessage Custom message to display when above specific message is not set.
 * 
 * @example
 * Usage:
 * Example Controller: (using controllerAs syntax in this example)
 * 
 *      angular.module('AppModule', []).controller('pageCtrl', [function () {
 *          this.isDirty = function () {
 *              // do your logic and return 'true' to display the prompt, or 'false' otherwise.
 *              return true;
 *          };
 *      }]);
 * 
 * Template:
 * 
 *      <div confirm-on-exit="pageCtrl.isDirty()" 
 *          confirm-message-window="All your changes will be lost."
 *          confirm-message-route="All your changes will be lost. Are you sure you want to do this?">
 * 
 * @see
 * http://stackoverflow.com/a/28905954/340290
 * 
 * @author Manikanta G
 */
ngxDirectivesModule.directive('confirmOnExit', function() {
    return {
        scope: {
            confirmOnExit: '&',
            confirmMessageWindow: '@',
            confirmMessageRoute: '@',
            confirmMessage: '@'
        },
        link: function($scope, elem, attrs) {
            window.onbeforeunload = function(){
                if ($scope.confirmOnExit()) {
                    return $scope.confirmMessageWindow || $scope.confirmMessage;
                }
            }
            var $locationChangeStartUnbind = $scope.$on('$locationChangeStart', function(event, next, current) {
                if ($scope.confirmOnExit()) {
                    if(! confirm($scope.confirmMessageRoute || $scope.confirmMessage)) {
                        event.preventDefault();
                    }
                }
            });

            $scope.$on('$destroy', function() {
                window.onbeforeunload = null;
                $locationChangeStartUnbind();
            });
        }
    };
});

Usage: Example Controller: (using controllerAs syntax in this example)

用法: 示例控制器:(在本示例中使用 controllerAs 语法)

angular.module('AppModule', []).controller('pageCtrl', [function () {
    this.isDirty = function () {
        // do your logic and return 'true' to display the prompt, or 'false' otherwise.

        return true;
    };
}]);

Template:

模板

<div confirm-on-exit="pageCtrl.isDirty()" 
    confirm-message-window="All your changes will be lost." 
    confirm-message-route="All your changes will be lost. Are you sure you want to do this?">

回答by Razan Paul

Anders's answer works fine, However, for people who uses Angular ui-router, you should use '$stateChangeStart'instead of '$locationChangeStart'.

安德斯的回答工作正常,但是,谁使用角UI路由器的人,你应该使用'$stateChangeStart'来代替'$locationChangeStart'

回答by polina-c

I modified the @Anders answer so that the directive does not contain the form name hard coded:

我修改了@Anders 的答案,以便指令不包含硬编码的表单名称:

    app.directive('confirmOnExit', function() {
        return {
            link: function($scope, elem, attrs, ctrl) {
                window.onbeforeunload = function(){
                    if ($scope[attrs["name"]].$dirty) {
                        return "Your edits will be lost.";
                    }
                }
            }
        };
    });

Here is the html code for it:

这是它的html代码:

<form name="myForm" confirm-on-exit> 

回答by Mikhail

Maybe it will be helpful for someone. https://github.com/umbrella-web/Angular-unsavedChanges

也许这对某人有帮助。 https://github.com/umbrella-web/Angular-unsavedChanges

Using this service you can listen unsaved changes for any object in the scope (not only the form)

使用此服务,您可以侦听范围内任何对象(不仅是表单)的未保存更改

回答by Wtower

To use Anders Ekdahl's excellent answer with an Angular 1.5 component, inject the $scopein the component's controller:

要将Anders Ekdahl的优秀答案与 Angular 1.5 组件结合使用,请$scope在组件的控制器中注入:

angular
  .module('myModule')
  .component('myComponent', {
    controller: ['$routeParams', '$scope',
      function MyController($routeParams, $scope) {
        var self = this;

        $scope.$on('$locationChangeStart', function (event, next, current) {
          if (self.productEdit.$dirty && !confirm('There are unsaved changes. Would you like to close the form?')) {
            event.preventDefault();
          }
        });
      }
    ]
  });

回答by Dillon

The accepted answer is great, however I had an issue with properly getting a handle on my form controller consistently since some forms I use the formtag with the nameattribute and at other times i use the ng-formdirective. Also, if you're using typescript style functions utilizing the thisor vmtype pattern e.g. <form name='$ctrl.myForm'...

接受的答案很好,但是我在正确处理表单控制器时遇到了问题,因为有些表单我使用form带有name属性的标签,而其他时候我使用ng-form指令。此外,如果您正在使用使用thisvm类型模式的打字稿样式函数,例如<form name='$ctrl.myForm'...

I'm surprised no one else has mentioned this, but my fix was to utilize the require property of the directive and let angular give me a reference to the form controller itself.

我很惊讶没有其他人提到这一点,但我的解决方法是利用指令的 require 属性,让 angular 给我一个对表单控制器本身的引用。

I've updated the accepted answer below to show my changes, notice the require property and the additional parameter to the link function.

我已经更新了下面接受的答案以显示我的更改,注意 require 属性和链接函数的附加参数。

angular.module("myApp", []).directive('confirmOnExit', function() {
        return {
            restrict: 'A',
            require: 'form',
            link: function($scope, elem, attrs, form) {
                window.onbeforeunload = function(){
                    if (form.$dirty) {
                        return "The form is dirty, do you want to stay on the page?";
                    }
                }
                $scope.$on('$locationChangeStart', function(event, next, current) {
                    if (form.$dirty) {
                        if(!confirm("The form is dirty, do you want to stay on the page?")) {
                            event.preventDefault();
                        }
                    }
                });
            }
        };
    });

With this I can guarantee that I have a good handle on the form controller because angular will throw an error if it cannot find a form controller on the element.

有了这个,我可以保证我对表单控制器有一个很好的处理,因为如果在元素上找不到表单控制器,angular 会抛出一个错误。

You can also add modifiers like ^and ?such as require='^form'to pull an ancestor form or require='?form'if the form is optional (this wont break the directive, but you will need to check that you have a handle on a valid form controller yourself).

您还可以添加修饰符,如^? 例如require='^form'拉一个祖先表单或者require='?form'表单是可选的(这不会破坏指令,但您需要检查自己是否拥有有效表单控制器的句柄)。