Есть ли шаблон для работы с "Отмена" в модальных диалогах 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