在AngularJS模态对话框中有处理“取消”的模式吗?

时间:2022-03-21 10:25:07

Note: This is not about showing a modal dialog with AngularJS, that topic has plenty of questions and answers!

注意:这并不是显示一个带有AngularJS的模态对话框,这个主题有很多问题和答案!

This question is about how to react to both OK and Cancel within a modal dialog on a page. Let's say you've got a scope with just one variable in it:

这个问题是关于如何在页面的模态对话框中对OK和Cancel做出反应。假设你有一个范围只有一个变量

$scope.description = "Oh, how I love porcupines..."

If I provide you with a modal dialog on the page and use ng-model="description" within that dialog, all of the changes you make are actually made in real time to the description itself as you type. That's bad, because then how do you cancel out of that dialog?

如果我在页面上提供一个模态对话框,并在该对话框中使用ng-model="description",那么您所做的所有更改实际上都是在您输入描述时对描述本身进行的实时更改。这很糟糕,因为你怎么消掉这个对话框呢?

There's this question that says to do what I explain below. The accepted answer for it is the same "solution" I came up with: AngularJS: Data-bound modal - save changes only when "Save" is clicked, or forget changes if "Cancel" is clicked

这里有一个问题,说要做我下面解释的事情。接受的答案与我提出的“解决方案”相同:AngularJS:数据绑定模式——只在单击“save”时保存更改,或者在单击“Cancel”时忘记更改

I can see how to do it if clicking the button to bring up a modal goes back to a function in the back and that creates a temporary copy of the relevant data for the modal and then pops up the modal. Then "OK" (or "Save" or whatever) could copy the temporary values to the actual model values.

我可以看到如果点击按钮打开一个模态回到后面的一个函数,它会为模态创建一个相关数据的临时拷贝,然后弹出模态。然后“OK”(或“Save”或其他)可以将临时值复制到实际的模型值。

main.js (excerpt):

主要。js(节选):

$scope.descriptionUncommitted = $scope.description;

$scope.commitChanges = function () {
  $scope.description = $scope.descriptionUncommitted;
}

main.html (excerpt):

主要。html(节选):

<input type="text" ng-model="descriptionUncommitted"/>

<button ng-click="commitChanges()">Save</button>

The problem with that is it's not declarative! In fact, it's nothing like AngularJS anywhere else. It's almost as though we need an ng-model-uncommitted="description" where they could make all the changes they want but they only get committed when we trigger with another declaration. Is there such a thing in a plugin somewhere or is AngularJS itself adding it?

问题是它不是声明性的!事实上,它和其他任何地方的英语都不一样。这就好像我们需要一个ng模型-uncommitted="description",在那里他们可以做出他们想要的所有更改,但只有当我们触发另一个声明时才会提交。插件里有这样的东西吗?或者是AngularJS自己添加的?

Edit: It seems that an example of a different way of doing it might be in order.

编辑:似乎有一个不同方法的例子可以说明这一点。

main.js:

main.js:

$scope.filename = "panorama.jpg";
$scope.description = "A panorama of the mountains.";

$scope.persist = function () { // Some function to hit a back end service. };

main.html:

main.html:

<form>
  <input type="text" ng-model-uncommitted="filename"/>
  <input type="text" ng-model-uncommitted="description"/>

  <button ng-commit ng-click="persist()">Save</button>
  <button ng-discard>Cancel</button>
</form>

I stuck a form tag around it because I don't know how you would group the items so it was clear it was all part of the same "transaction" (for lack of a better word). But there would need to be some way that this could all happen automatically and the cloned copies of the model variables are used for initial values, used for input and updated automatically, validated, etc. and then finally discarded or copied to the same value that initially was used to create them if the user decides to commit.

我在它周围贴了一个表单标签,因为我不知道如何对条目进行分组,所以很明显这是同一个“事务”的一部分(因为没有更好的词)。但需要某种方式,这都能自动发生和克隆副本模型的变量是用于初始值,用于输入和自动更新、验证等最后丢弃或复制到相同的值,最初被用来创建它们如果用户决定提交。

Isn't something like this easier than code in the controller to do that work over and over again for 20 modals in a big website? Or am I nuts?

像这样的东西难道不比控制器里的代码更简单吗?还是我疯了吗?

6 个解决方案

#1


23  

Basically, in angular if something is not declarative, you make a directive.

基本上,在角度上,如果有些东西不是陈述性的,你就做一个指令。

 .directive('shadow', function() {
  return {
    scope: {
      target: '=shadow'            
    },
    link: function(scope, el, att) {
      scope[att.shadow] = angular.copy(scope.target);

      scope.commit = function() {
        scope.target = scope[att.shadow];
      };
    }
  };

Then:

然后:

  <div shadow="data">
    <input ng-model="data">
    <button ng-click="commit()">save</button>
  </div>

So data inside the shadow directive will be a copy of the original data. And it will be copied back to the original when the button is clicked.

因此,阴影指令中的数据将是原始数据的副本。当按钮被点击时,它会被复制回原来的样子。

And here is working example: jsbin

这里有一个工作示例:jsbin。

I've not tested it beyond this example, so it may not work in other cases, but I think it gives an idea of the possibilites.

我还没有对它进行测试,因此它在其他情况下可能不适用,但我认为它提供了一种可能性的概念。

Edit:

编辑:

Another example with an object instead of a string, and several fields in the form (an additional angular.copy is required here): jsbin

另一个例子是一个物体而不是一根线,以及形式中的几个字段(一个额外的角)。这里需要拷贝):jsbin

Edit2, angular versions 1.2.x

1.2.x Edit2,角版本

As per this change, the input inside the directive is not accessing the isolated scope anymore. One alternative is creating a non-isolated child scope (scope:true), to hold the copy of the data and accessing the parent scope for saving it.

根据此更改,指令内的输入不再访问隔离范围。另一种方法是创建一个非隔离子范围(作用域:true),以保存数据的副本并访问父作用域以保存它。

So for later versions of angular, this is the same approach as before slightly modified to do the trick:

对于以后的角度,这和之前的方法是一样的稍微修改一下就可以了:

.directive('shadow', function() {
  return {
    scope: true,
    link: function(scope, el, att) {
      scope[att.shadow] = angular.copy(scope[att.shadow]);

      scope.commit = function() {
        scope.$parent[att.shadow] = angular.copy(scope[att.shadow]);
      };
    }
  };
});

Example: jsbin

例如:jsbin

Note that the problem with using $parent, is that it may break if eventually there is another scope in the middle.

注意,使用$parent的问题是,如果最终中间有另一个范围,它可能会崩溃。

#2


21  

As of Angular 1.3 there is ngModelOptions directive that allows to achive the same behaviour natively.

在角度1.3中,有ngModelOptions指令,允许在本质上实现相同的行为。

<form name="userForm">
    <input type="text" ng-model="user.name" ng-model-options="{ updateOn: 'submit' }" name="userName">
    <button type="submit">save</button>
    <button type="button"  ng-click="userForm.userName.$rollbackViewValue();">cancel</button>
</form>

JSFiddle: http://jsfiddle.net/8btk5/104/

JSFiddle:http://jsfiddle.net/8btk5/104/

#3


11  

Facing the same problem and going though this thread I came up with lazy-model directive that works exactly like ng-model but saves changes only when form was submitted.

面对同样的问题并通过这个线程,我提出了延迟模型指令,该指令与ng-model完全相同,但只在提交表单时保存更改。

Usage:

用法:

<input type="text" lazy-model="user.name">

Please note to wrap it into <form> tag otherwise lazy model will not know when to push changes to original model.

请注意将其封装到

标签中,否则惰性模型将不知道何时将更改推送到原始模型中。

Full working demo: http://jsfiddle.net/8btk5/3/

完整的工作演示:http://jsfiddle.net/8btk5/3/

lazyModel directive code:
(better use actual version on github)

lazyModel指令码(最好在github上使用实际版本)

app.directive('lazyModel', function($parse, $compile) {
  return {
    restrict: 'A',  
    require: '^form',
    scope: true,
    compile: function compile(elem, attr) {
        // getter and setter for original model
        var ngModelGet = $parse(attr.lazyModel);
        var ngModelSet = ngModelGet.assign;  
        // set ng-model to buffer in isolate scope
        elem.attr('ng-model', 'buffer');
        // remove lazy-model attribute to exclude recursion
        elem.removeAttr("lazy-model");
        return function postLink(scope, elem, attr) {
          // initialize buffer value as copy of original model 
          scope.buffer = ngModelGet(scope.$parent);
          // compile element with ng-model directive poining to buffer value   
          $compile(elem)(scope);
          // bind form submit to write back final value from buffer
          var form = elem.parent();
          while(form[0].tagName !== 'FORM') {
            form = form.parent();
          }
          form.bind('submit', function() {
            scope.$apply(function() {
                ngModelSet(scope.$parent, scope.buffer);
            });
         });
         form.bind('reset', function(e) {
            e.preventDefault();
            scope.$apply(function() {
                scope.buffer = ngModelGet(scope.$parent);
            });
         });
        };  
     }
  };
});

Actual source code on GitHub

GitHub上的实际源代码

#4


6  

You seem to be over-thinking this. There isn't a plug-in because the process is pretty simple. If you want a pristine copy of the model, make one and keep it in the controller. If a user cancels, reset the model to your copy and use the FormController.$setPristine() method to make the form pristine again.

你似乎想得太多了。因为过程非常简单,所以没有插件。如果您想要模型的原始副本,制作一个并将其保存在控制器中。如果用户取消了,请将该模型重置为您的副本,并使用FormController.$ set()方法使表单再次保持原始状态。

//Controller:

myService.findOne({$route.current.params['id']}, function(results) {
    $scope.myModel = results;
    var backup = results;
}

//cancel
$scope.cancel = function() {
    $scope.myModel = backup;
    $scope.myForm.$setPristine();
}

Then in your view:

然后在你的观点:

<form name="myForm">

You need to name the form to create the $scope.myForm controller.

您需要为创建$范围的表单命名。myForm控制器。

#5


4  

Another way is to copy the model before editing it, and on cancel, restore the original. Angular Controller Code:

另一种方法是在编辑模型之前复制模型,然后在cancel时恢复原始模型。角控制器代码:

//on edit, make a copy of the original model and store it on scope
function edit(model){
  //put model on scope for the cancel method to access
  $scope.modelBeingEdited = model;
  //copy from model -> scope.originalModel
  angular.copy(model,$scope.originalModel);  
}

function cancelEdit(){
  //copy from scope.original back to your model 
  angular.copy($scope.originalModel, $scope.modelBeingEdited)  
}
So on open of the modal dialog, call the edit function and pass a pointer to the model you want to edit. Then make sure your modal dialog is bound to $scope.editingModel. On cancel, call the cancel function, and it will copy the original values back.

Hope that helps someone!

希望能帮助一些人!

#6


0  

Here's my try at keeping it simple, making it declarative and not dependent on form tags or other stuff.

这是我的尝试,保持它简单,使它声明性,不依赖于表单标签或其他东西。

A simple directive:

一个简单的指令:

.directive("myDirective", function(){
return {
  scope: {
    item: "=myDirective"
  },
  link: function($scope){
    $scope.stateEnum = {
      view: 0, 
      edit: 1
    };

    $scope.state = $scope.stateEnum.view;

    $scope.edit = function(){
      $scope.tmp1 = $scope.item.text;
      $scope.tmp2 = $scope.item.description;
      $scope.state = $scope.stateEnum.edit;
    };

    $scope.save = function(){
      $scope.item.text = $scope.tmp1;
      $scope.item.description = $scope.tmp2;
      $scope.state = $scope.stateEnum.view;
    };

    $scope.cancel = function(){
      $scope.state = $scope.stateEnum.view;
    };
  },
  templateUrl: "viewTemplate.html"
};
})

viewTemplate.html:

viewTemplate.html:

<div>
  <span ng-show="state == stateEnum.view" ng-click="edit()">{{item.text}}, {{item.description}}</span>
  <div ng-show="state == stateEnum.edit"><input ng-model="tmp1" type="text"/> <input ng-model="tmp2" type="text"/><a href="javascript:void(0)" ng-click="save()">save</a> <a href="javascript:void(0)" ng-click="cancel()">cancel</a></div>
</div>

Then set the context (item):

然后设置上下文(项目):

<div ng-repeat="item in myItems">
  <div my-directive="item"></div>
</div>

See it in action: http://plnkr.co/edit/VqoKQoIyhtYnge2hzrFk?p=preview

查看它的实际操作:http://plnkr.co/edit/vqokqoiyhtynge2hzrfk?

#1


23  

Basically, in angular if something is not declarative, you make a directive.

基本上,在角度上,如果有些东西不是陈述性的,你就做一个指令。

 .directive('shadow', function() {
  return {
    scope: {
      target: '=shadow'            
    },
    link: function(scope, el, att) {
      scope[att.shadow] = angular.copy(scope.target);

      scope.commit = function() {
        scope.target = scope[att.shadow];
      };
    }
  };

Then:

然后:

  <div shadow="data">
    <input ng-model="data">
    <button ng-click="commit()">save</button>
  </div>

So data inside the shadow directive will be a copy of the original data. And it will be copied back to the original when the button is clicked.

因此,阴影指令中的数据将是原始数据的副本。当按钮被点击时,它会被复制回原来的样子。

And here is working example: jsbin

这里有一个工作示例:jsbin。

I've not tested it beyond this example, so it may not work in other cases, but I think it gives an idea of the possibilites.

我还没有对它进行测试,因此它在其他情况下可能不适用,但我认为它提供了一种可能性的概念。

Edit:

编辑:

Another example with an object instead of a string, and several fields in the form (an additional angular.copy is required here): jsbin

另一个例子是一个物体而不是一根线,以及形式中的几个字段(一个额外的角)。这里需要拷贝):jsbin

Edit2, angular versions 1.2.x

1.2.x Edit2,角版本

As per this change, the input inside the directive is not accessing the isolated scope anymore. One alternative is creating a non-isolated child scope (scope:true), to hold the copy of the data and accessing the parent scope for saving it.

根据此更改,指令内的输入不再访问隔离范围。另一种方法是创建一个非隔离子范围(作用域:true),以保存数据的副本并访问父作用域以保存它。

So for later versions of angular, this is the same approach as before slightly modified to do the trick:

对于以后的角度,这和之前的方法是一样的稍微修改一下就可以了:

.directive('shadow', function() {
  return {
    scope: true,
    link: function(scope, el, att) {
      scope[att.shadow] = angular.copy(scope[att.shadow]);

      scope.commit = function() {
        scope.$parent[att.shadow] = angular.copy(scope[att.shadow]);
      };
    }
  };
});

Example: jsbin

例如:jsbin

Note that the problem with using $parent, is that it may break if eventually there is another scope in the middle.

注意,使用$parent的问题是,如果最终中间有另一个范围,它可能会崩溃。

#2


21  

As of Angular 1.3 there is ngModelOptions directive that allows to achive the same behaviour natively.

在角度1.3中,有ngModelOptions指令,允许在本质上实现相同的行为。

<form name="userForm">
    <input type="text" ng-model="user.name" ng-model-options="{ updateOn: 'submit' }" name="userName">
    <button type="submit">save</button>
    <button type="button"  ng-click="userForm.userName.$rollbackViewValue();">cancel</button>
</form>

JSFiddle: http://jsfiddle.net/8btk5/104/

JSFiddle:http://jsfiddle.net/8btk5/104/

#3


11  

Facing the same problem and going though this thread I came up with lazy-model directive that works exactly like ng-model but saves changes only when form was submitted.

面对同样的问题并通过这个线程,我提出了延迟模型指令,该指令与ng-model完全相同,但只在提交表单时保存更改。

Usage:

用法:

<input type="text" lazy-model="user.name">

Please note to wrap it into <form> tag otherwise lazy model will not know when to push changes to original model.

请注意将其封装到

标签中,否则惰性模型将不知道何时将更改推送到原始模型中。

Full working demo: http://jsfiddle.net/8btk5/3/

完整的工作演示:http://jsfiddle.net/8btk5/3/

lazyModel directive code:
(better use actual version on github)

lazyModel指令码(最好在github上使用实际版本)

app.directive('lazyModel', function($parse, $compile) {
  return {
    restrict: 'A',  
    require: '^form',
    scope: true,
    compile: function compile(elem, attr) {
        // getter and setter for original model
        var ngModelGet = $parse(attr.lazyModel);
        var ngModelSet = ngModelGet.assign;  
        // set ng-model to buffer in isolate scope
        elem.attr('ng-model', 'buffer');
        // remove lazy-model attribute to exclude recursion
        elem.removeAttr("lazy-model");
        return function postLink(scope, elem, attr) {
          // initialize buffer value as copy of original model 
          scope.buffer = ngModelGet(scope.$parent);
          // compile element with ng-model directive poining to buffer value   
          $compile(elem)(scope);
          // bind form submit to write back final value from buffer
          var form = elem.parent();
          while(form[0].tagName !== 'FORM') {
            form = form.parent();
          }
          form.bind('submit', function() {
            scope.$apply(function() {
                ngModelSet(scope.$parent, scope.buffer);
            });
         });
         form.bind('reset', function(e) {
            e.preventDefault();
            scope.$apply(function() {
                scope.buffer = ngModelGet(scope.$parent);
            });
         });
        };  
     }
  };
});

Actual source code on GitHub

GitHub上的实际源代码

#4


6  

You seem to be over-thinking this. There isn't a plug-in because the process is pretty simple. If you want a pristine copy of the model, make one and keep it in the controller. If a user cancels, reset the model to your copy and use the FormController.$setPristine() method to make the form pristine again.

你似乎想得太多了。因为过程非常简单,所以没有插件。如果您想要模型的原始副本,制作一个并将其保存在控制器中。如果用户取消了,请将该模型重置为您的副本,并使用FormController.$ set()方法使表单再次保持原始状态。

//Controller:

myService.findOne({$route.current.params['id']}, function(results) {
    $scope.myModel = results;
    var backup = results;
}

//cancel
$scope.cancel = function() {
    $scope.myModel = backup;
    $scope.myForm.$setPristine();
}

Then in your view:

然后在你的观点:

<form name="myForm">

You need to name the form to create the $scope.myForm controller.

您需要为创建$范围的表单命名。myForm控制器。

#5


4  

Another way is to copy the model before editing it, and on cancel, restore the original. Angular Controller Code:

另一种方法是在编辑模型之前复制模型,然后在cancel时恢复原始模型。角控制器代码:

//on edit, make a copy of the original model and store it on scope
function edit(model){
  //put model on scope for the cancel method to access
  $scope.modelBeingEdited = model;
  //copy from model -> scope.originalModel
  angular.copy(model,$scope.originalModel);  
}

function cancelEdit(){
  //copy from scope.original back to your model 
  angular.copy($scope.originalModel, $scope.modelBeingEdited)  
}
So on open of the modal dialog, call the edit function and pass a pointer to the model you want to edit. Then make sure your modal dialog is bound to $scope.editingModel. On cancel, call the cancel function, and it will copy the original values back.

Hope that helps someone!

希望能帮助一些人!

#6


0  

Here's my try at keeping it simple, making it declarative and not dependent on form tags or other stuff.

这是我的尝试,保持它简单,使它声明性,不依赖于表单标签或其他东西。

A simple directive:

一个简单的指令:

.directive("myDirective", function(){
return {
  scope: {
    item: "=myDirective"
  },
  link: function($scope){
    $scope.stateEnum = {
      view: 0, 
      edit: 1
    };

    $scope.state = $scope.stateEnum.view;

    $scope.edit = function(){
      $scope.tmp1 = $scope.item.text;
      $scope.tmp2 = $scope.item.description;
      $scope.state = $scope.stateEnum.edit;
    };

    $scope.save = function(){
      $scope.item.text = $scope.tmp1;
      $scope.item.description = $scope.tmp2;
      $scope.state = $scope.stateEnum.view;
    };

    $scope.cancel = function(){
      $scope.state = $scope.stateEnum.view;
    };
  },
  templateUrl: "viewTemplate.html"
};
})

viewTemplate.html:

viewTemplate.html:

<div>
  <span ng-show="state == stateEnum.view" ng-click="edit()">{{item.text}}, {{item.description}}</span>
  <div ng-show="state == stateEnum.edit"><input ng-model="tmp1" type="text"/> <input ng-model="tmp2" type="text"/><a href="javascript:void(0)" ng-click="save()">save</a> <a href="javascript:void(0)" ng-click="cancel()">cancel</a></div>
</div>

Then set the context (item):

然后设置上下文(项目):

<div ng-repeat="item in myItems">
  <div my-directive="item"></div>
</div>

See it in action: http://plnkr.co/edit/VqoKQoIyhtYnge2hzrFk?p=preview

查看它的实际操作:http://plnkr.co/edit/vqokqoiyhtynge2hzrfk?