Как отменить/вернуть изменения в наблюдаемую модель (или заменить модель в массиве нетронутой копией)
У меня есть viewModel с наблюдаемым массивом объектов с наблюдаемыми переменными.
В моем шаблоне отображаются данные с кнопкой редактирования, которая скрывает элементы отображения и показывает входные элементы со связанными значениями. Вы можете начать редактирование данных, а затем у вас есть возможность отменить. Я бы хотел, чтобы это отменить, чтобы вернуться к неизменной версии объекта.
Я попытался клонировать объект, выполнив что-то вроде этого:
viewModel.tempContact = jQuery.extend({}, contact);
или
viewModel.tempContact = jQuery.extend(true, {}, contact);
но viewModel.tempContact изменяется, как только контакт делает.
Есть ли что-нибудь, что встроено в KnockoutJS, чтобы справиться с такой ситуацией, или мне лучше всего просто создать новый контакт с точно такими же деталями и заменить измененный контакт новым контактом на отмену?
Любые советы приветствуются. Спасибо!
Ответы
Ответ 1
Есть несколько способов справиться с чем-то подобным. Вы можете создать новый объект с теми же значениями, что и текущий, и отбросить его при отмене. Вы можете добавить дополнительные наблюдаемые элементы для привязки к полям редактирования и сохранить их при принятии или взглянуть на этот post за идею о инкапсуляции этой функции в многоразовый тип (это мой предпочтительный метод).
Ответ 2
Я столкнулся с этим сообщением, пытаясь решить подобную проблему, и решил, что отправлю свой подход и решение для следующего парня.
Я пошел с вашей мыслью - клонировать объект и повторно заполнять старыми данными на "undo":
1) Скопируйте объект данных в новую переменную страницы ( "_initData" )
2) Создать Observable из исходного объекта сервера
3) при перезагрузке "отменить", наблюдаемой с неизменными данными ( "_initData" )
Упрощенный JS: var _viewModel; var _initData = {};
$(function () {
//on initial load
$.post("/loadMeUp", {}, function (data) {
$.extend(_initData , data);
_viewModel = ko.mapping.fromJS(data);
});
//to rollback changes
$("#undo").live("click", function (){
var data = {};
$.extend(data, _initData );
ko.mapping.fromJS(data, {}, _viewModel);
});
//when updating whole object from server
$("#updateFromServer).live("click", function(){
$.post("/loadMeUp", {}, function (data) {
$.extend(_initData , data);
ko.mapping.fromJS(data, {}, _viewModel);
});
});
//to just load a single item within the observable (for instance, nested objects)
$("#updateSpecificItemFromServer).live("click", function(){
$.post("/loadMeUpSpecificItem", {}, function (data) {
$.extend(_initData.SpecificItem, data);
ko.mapping.fromJS(data, {}, _viewModel.SpecificItem);
});
});
//updating subItems from both lists
$(".removeSpecificItem").live("click", function(){
//object id = "element_" + id
var id = this.id.split("_")[1];
$.post("/deleteSpecificItem", { itemID: id }, function(data){
//Table of items with the row elements id = "tr_" + id
$("#tr_" + id).remove();
$.each(_viewModel.SpecificItem.Members, function(index, value){
if(value.ID == id)
_viewModel.SpecificItem.Members.splice(index, 1);
});
$.each(_initData.SpecificItem.Members, function(index, value){
if(value.ID == id)
_initData.SpecificItem.Members.splice(index, 1);
});
});
});
});
У меня был объект, который был достаточно сложным, и я не хотел добавлять обработчики для каждого отдельного свойства.
Некоторые изменения внесены в мой объект в реальном времени, эти изменения изменяют как наблюдаемые, так и "_initData".
Когда я возвращаю данные с сервера, я обновляю свой объект "_initData", чтобы попытаться синхронизировать его с сервером.
Ответ 3
Очень старый вопрос, но я просто сделал что-то очень похожее и нашел очень простой, быстрый и эффективный способ сделать это с помощью плагина сопоставления.
Фон; Я редактирую список объектов KO, связанных с помощью foreach
. Каждый объект установлен в режим редактирования с помощью простого наблюдаемого, который указывает на просмотр ярлыков или входов.
Функции предназначены для использования в привязке click
для каждого элемента foreach
.
Затем отредактируйте/сохраните/отмените просто:
this.edit = function(model, e)
{
model.__undo = ko.mapping.toJS(model);
model._IsEditing(true);
};
this.cancel = function(model, e)
{
// Assumes you have variable _mapping in scope that contains any
// advanced mapping rules (this is optional)
ko.mapping.fromJS(model.__undo, _mapping, model);
model._IsEditing(false);
};
this.save = function(model, e)
{
$.ajax({
url: YOUR_SAVE_URL,
dataType: 'json',
type: 'POST',
data: ko.mapping.toJSON(model),
success:
function(data, status, jqxhr)
{
model._IsEditing(false);
}
});
};
Это очень полезно при редактировании списков простых объектов, хотя в большинстве случаев я обнаруживаю, что имею список, содержащий легкие объекты, а затем загружаю полную детальную модель для фактического редактирования, поэтому эта проблема не возникает.
Вы можете добавить методы saveUndo
/restoreUndo
к модели, если вам не нравится прикреплять свойство __undo
таким же образом, но лично я считаю, что этот способ более ясен, а также намного меньше кода и можно использовать на любой модели, даже без явного объявления.
Ответ 4
Для этого вы можете использовать KO-UndoManager. Здесь пример кода для регистрации вашей модели просмотра:
viewModel.undoMgr = ko.undoManager(viewModel, {
levels: 12,
undoLabel: "Undo (#COUNT#)",
redoLabel: "Redo"
});
Затем вы можете добавить кнопки отмены/повтора в свой html следующим образом:
<div class="row center-block">
<button class="btn btn-primary" data-bind="
click: undoMgr.undoCommand.execute,
text: undoMgr.undoCommand.name,
css: { disabled: !undoMgr.undoCommand.enabled() }">UNDO</button>
<button class="btn btn-primary" data-bind="
click: undoMgr.redoCommand.execute,
text: undoMgr.redoCommand.name,
css: { disabled: !undoMgr.redoCommand.enabled() }">REDO</button>
</div>
И здесь a Plunkr, показывающий его в действии. Чтобы отменить все изменения, вам нужно будет вызвать вызов undoMgr.undoCommand.execute
в javascript до тех пор, пока все изменения не будут отменены.
Ответ 5
Мне нужно что-то подобное, и я не мог использовать защищенные наблюдаемые, поскольку мне нужно было вычислить обновление временных значений. Поэтому я написал это расширение для нокаута:
Это расширение создает подчёркнутую версию каждого наблюдаемого, т.е. self.Comments() → self._Comments()
ko.Underscore = function (data) {
var obj = data;
var result = {};
// Underscore Property Check
var _isOwnProperty = function (isUnderscore, prop) {
return (isUnderscore == null || prop.startsWith('_') == isUnderscore) && typeof obj[prop] == 'function' && obj.hasOwnProperty(prop) && ko.isObservable(obj[prop]) && !ko.isComputed(obj[prop])
}
// Creation of Underscore Properties
result.init = function () {
for (var prop in obj) {
if (_isOwnProperty(null, prop)) {
var val = obj[prop]();
var temp = '_' + prop;
if (obj[prop].isObservableArray)
obj[temp] = ko.observableArray(val);
else
obj[temp] = ko.observable(val);
}
}
};
// Cancel
result.Cancel = function () {
for (var prop in obj) {
if (_isOwnProperty(false, prop)) {
var val = obj[prop]();
var p = '_' + prop;
obj[p](val);
}
}
}
// Confirm
result.Confirm = function () {
for (var prop in obj) {
if (_isOwnProperty(true, prop)) {
var val = obj[prop]();
var p = prop.replace('_', '');
obj[p](val);
}
}
}
// Observables
result.Properties = function () {
var obs = [];
for (var prop in obj) {
if (typeof obj[prop] == 'function' && obj.hasOwnProperty(prop) && ko.isObservable(obj[prop]) && !ko.isComputed(obj[prop])) {
var val = obj[prop]();
obs.push({ 'Name': prop, 'Value': val });
}
}
return obs;
}
if (obj != null)
result.init();
return result;
}
Это расширение позволит вам записывать дубликаты каждого из ваших наблюдаемых и игнорировать ваши вычисления. Он работает следующим образом:
var BF_BCS = function (data) {
var self = this;
self.Score = ko.observable(null);
self.Comments = ko.observable('');
self.Underscore = ko.Underscore(self);
self.new = function () {
self._Score(null);
self._Comments('');
self.Confirm();
}
self.Cancel = function () {
self.Pause();
self.Underscore.Cancel();
self.Resume();
}
self.Confirm = function () {
self.Pause();
self.Underscore.Confirm();
self.Resume();
}
self.Pause = function () {
}
self.Resume = function () {
}
self.setData = function (data) {
self.Pause();
self._Score(data.Score);
self._Comments(data.Comments);
self.Confirm();
self.Resume();
}
if (data != null)
self.setData(data);
else
self.new();
};
Итак, вы можете видеть, есть ли у вас кнопки на html:
<div class="panel-footer bf-panel-footer">
<div class="bf-panel-footer-50" data-bind="click: Cancel.bind($data)">
Cancel
</div>
<div class="bf-panel-footer-50" data-bind="click: Confirm.bind($data)">
Save
</div>
</div>
Отмена отменяет и возвращает ваши наблюдаемые обратно к тем, какими они были, а также сохраняет обновленные реальные значения с временными значениями в одной строке