web-dev-qa-db-de.com

Erkennen Sie nicht gespeicherte Änderungen und warnen Sie Benutzer mithilfe von eckigejs

Unten ist der Code so weit

    <!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>

Wie kann auf browser close oder url redirect hingewiesen werden, falls nicht gespeicherte Daten vorhanden sind, sodass der Benutzer entscheiden kann, ob er fortfahren möchte?

35
iJade

So etwas sollte es tun:

<!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>

Beachten Sie, dass der Listener für $ locationChangeStart in diesem Beispiel nicht ausgelöst wird, da AngularJS in einem solchen einfachen Beispiel kein Routing verarbeitet. Es sollte jedoch in einer tatsächlichen Angular-Anwendung funktionieren.

70
Anders Ekdahl

Ich habe die Antwort von @Anders um die Bereinigung der Listener (Unbind-Listener) erweitert, wenn die Direktive zerstört wird (z. B. wenn die Route geändert wird), und ein wenig syntaktischer Zucker hinzugefügt, um die Verwendung zu generalisieren.

ConfirmOnExit-Richtlinie :

/**
 * @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();
            });
        }
    };
});

Verwendung: Beispielcontroller : (unter Verwendung der ControllerAs-Syntax in diesem Beispiel)

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

        return true;
    };
}]);

Vorlage :

<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?">
33
manikanta

Anders 'Antwort funktioniert gut. Für Leute, die Angular UI-Router verwenden, sollten Sie jedoch '$stateChangeStart' anstelle von '$locationChangeStart' verwenden.

16
Razan Paul

Ich habe die Antwort von @Anders so geändert, dass die Direktive den Formularnamen nicht fest codiert enthält:

    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.";
                    }
                }
            }
        };
    });

Hier ist der HTML-Code dafür:

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

Vielleicht ist es hilfreich für jemanden . https://github.com/umbrella-web/Angular-unsavedChanges

Mit diesem Dienst können Sie ungespeicherte Änderungen für jedes Objekt im Geltungsbereich überwachen (nicht nur das Formular).

3
Mikhail

Um die exzellente Antwort von Anders Ekdahl mit einer Angular 1.5-Komponente zu verwenden, fügen Sie den $scope in den Controller der Komponente ein:

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();
          }
        });
      }
    ]
  });
0
Wtower

Die akzeptierte Antwort ist großartig, aber ich hatte ein Problem damit, dass ich meinen Form Controller richtig in den Griff bekam, da ich bei einigen Formularen den Tag form mit dem Attribut name und zu anderen Zeiten die Direktive ng-form verwende. Wenn Sie TypeScript-Stilfunktionen verwenden, die das Muster this oder vm verwenden, z. <form name='$ctrl.myForm'...

Ich bin überrascht, dass niemand sonst dies erwähnt hat, aber mein Fix bestand darin, die Anforderungseigenschaft der Direktive zu nutzen und mir von wink einen Hinweis auf den Formularcontroller selbst zu geben.

Ich habe die akzeptierte Antwort unten aktualisiert, um meine Änderungen anzuzeigen. Beachten Sie die Requise-Eigenschaft und die zusätzlichen Parameter für die Link-Funktion.

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();
                        }
                    }
                });
            }
        };
    });

Damit kann ich garantieren, dass ich den Formularcontroller gut im Griff habe, da winklig einen Fehler auslöst, wenn es keinen Formularcontroller auf dem Element findet.

Sie können auch Modifikatoren wie ^ und hinzufügen. z. B. require='^form' zum Abrufen eines Vorfahrformulars oder require='?form', wenn das Formular optional ist (dadurch wird die Direktive nicht verletzt, Sie müssen jedoch selbst prüfen, ob Sie über einen gültigen Formularcontroller verfügen).

0
Dillon