Есть ли шаблон для работы с "Отмена" в модальных диалогах AngularJS?

Примечание: речь идет не о показе модального диалога с AngularJS, в этой теме есть много вопросов и ответов!

Этот вопрос касается того, как реагировать как на ОК, так и на Отмена в рамках модального диалога на странице. Скажем, у вас есть область с одной переменной в ней:

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

Если я предоставляю вам модальный диалог на странице и использую ng-model = "description" в этом диалоговом окне, все сделанные вами изменения фактически выполняются в реальном времени для самого описания при вводе. Это плохо, потому что тогда как вы отменяете этот диалог?

Вот этот вопрос, который говорит, чтобы делать то, что я объясняю ниже. Принятый для него ответ - это то же самое "решение", которое я придумал: AngularJS: привязанный к данным модальный - сохранение изменений только тогда, когда "Сохранить" щелкнуть или забыть изменения, если "Отменить" щелчок

Я вижу, как это сделать, если щелкнуть по кнопке, чтобы вызвать модальный, возвращается функция обратно в спину и создает временную копию соответствующих данных для модального, а затем всплывает модальный. Затем "ОК" (или "Сохранить" или что-то еще) может скопировать временные значения в фактические значения модели.

main.js(выдержка):

$scope.descriptionUncommitted = $scope.description;

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

main.html(выдержка):

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

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

Проблема с этим не декларативная! Фактически, это ничто иное как AngularJS нигде. Это почти так, как будто нам нужно ng-model-uncommitted = "description", где они могли бы делать все изменения, которые они хотят, но они только фиксируются, когда мы запускаем с другим объявлением. Есть ли что-то в плагине где-то или есть сам AngularJS?

Изменить: Кажется, что пример другого способа сделать это может быть в порядке.

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:

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

Я привязал к нему тег формы, потому что я не знаю, как бы вы группировали элементы, поэтому было ясно, что все это часть одной и той же "транзакции" (из-за отсутствия лучшего слова). Но должно быть каким-то образом, что это может произойти автоматически, и клонированные копии переменных модели используются для начальных значений, используются для ввода и обновления автоматически, проверены и т.д., А затем окончательно отбрасываются или копируются в одно и то же значение, которое первоначально был использован для их создания, если пользователь решил совершить.

Разве это не так проще, чем код в контроллере, чтобы делать эту работу снова и снова для 20 модалов на большом веб-сайте? Или я орехи?

Ответ 1

В принципе, в angular, если что-то не является декларативным, вы создаете директиву .

 .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];
      };
    }
  };

Тогда:

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

Итак data внутри директивы shadow будет копировать оригинала data. И при нажатии кнопки она будет скопирована на оригинал.

И вот рабочий пример: jsbin

Я не тестировал его за пределами этого примера, поэтому он может не работать в других случаях, но я думаю, что он дает представление о возможностях.

Edit:

Другой пример с объектом вместо строки и несколькими полями в форме (здесь требуется дополнительный angular.copy): jsbin

Edit2, angular версии 1.2.x

В соответствии с этим изменить, input внутри директивы больше не обращается к изолированной области. Одной из альтернатив является создание неизолированного дочернего объекта (scope:true) для хранения копии данных и доступа к родительской области для ее сохранения.

Итак, для более поздних версий angular это тот же подход, что и до того, как он слегка изменился, чтобы сделать трюк:

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

Пример: jsbin

Обратите внимание, что проблема с использованием $parent заключается в том, что она может сломаться, если в конце есть еще одна область в середине.

Ответ 2

В соответствии с Angular 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/

Ответ 3

Опираясь на ту же проблему и, несмотря на этот поток, я придумал директиву lazy-model, которая работает точно так же, как ng-model, но сохраняет изменения только , когда форма была отправлена ​​.

Использование:

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

Обратите внимание, чтобы обернуть его в тег <form>, иначе ленивая модель не будет знать, когда нужно вносить изменения в оригинальную модель.

Полная рабочая демонстрация: http://jsfiddle.net/8btk5/3/

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

Фактический исходный код на GitHub

Ответ 4

Ты, кажется, слишком задумываешься об этом. Существует не плагин, потому что процесс довольно прост. Если вы хотите получить оригинальную копию модели, сделайте ее и сохраните в контроллере. Если пользователь отменяет, reset модель для вашей копии и используйте метод FormController. $SetPristine(), чтобы снова создать форму.

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

Тогда, на ваш взгляд:

<form name="myForm">

Вам нужно указать форму для создания контроллера $scope.myForm.

Ответ 5

Другой способ - скопировать модель перед ее редактированием и отменить, восстановить оригинал. Angular Код контроллера:

//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)  
}

Ответ 6

Здесь я стараюсь держать его простым, делая его декларативным и не зависящим от тегов формы или других вещей.

Простая директива:

.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:

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

Затем установите контекст (элемент):

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

Смотрите в действии: http://plnkr.co/edit/VqoKQoIyhtYnge2hzrFk?p=preview