javascript 如何取消/恢复对可观察模型的更改(或用未修改的副本替换数组中的模型)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5874860/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to cancel/revert changes to an observable model (or replace model in array with untouched copy)
提问by Damien Gabrielson
I have a viewModel with an observableArray of objects with observable variables.
我有一个带有可观察变量的 observableArray 对象的 viewModel。
My template shows the data with an edit button that hides the display elements and shows input elements with the values bound. You can start editing the data and then you have the option to cancel. I would like this cancel to revert to the unchanged version of the object.
我的模板显示带有编辑按钮的数据,该按钮隐藏显示元素并显示具有绑定值的输入元素。您可以开始编辑数据,然后您可以选择取消。我希望这个取消恢复到对象的未更改版本。
I have tried clone the object by doing something like this:
我尝试通过执行以下操作来克隆对象:
viewModel.tempContact = jQuery.extend({}, contact);
or
或者
viewModel.tempContact = jQuery.extend(true, {}, contact);
but viewModel.tempContact gets modified as soon as contact does.
但是 viewModel.tempContact 会在接触后立即修改。
Is there anything built into KnockoutJS to handle this kind of situation or am I best off to just create a new contact with exactly the same details and replace the modified contact with the new contact on cancel?
KnockoutJS 中是否有任何内置功能可以处理这种情况,还是我最好只创建一个具有完全相同详细信息的新联系人,并在取消时将修改后的联系人替换为新联系人?
Any advice is greatly appreciated. Thanks!
任何意见是极大的赞赏。谢谢!
采纳答案by RP Niemeyer
There are a few ways to handle something like this. You can construct a new object with the same values as your current one and throw it away on a cancel. You could add additional observables to bind to the edit fields and persist them on the accept or take a look at this postfor an idea on encapsulating this functionality into a reusable type (this is my preferred method).
有几种方法可以处理这样的事情。您可以构造一个与当前对象具有相同值的新对象,然后在取消时将其丢弃。您可以添加额外的 observable 以绑定到编辑字段并将它们持久化到接受或查看这篇文章以了解将此功能封装到可重用类型中的想法(这是我的首选方法)。
回答by THammons
I ran across this post while looking to solve a similar problem and figured I would post my approach and solution for the next guy.
我在寻找解决类似问题的同时遇到了这篇文章,并认为我会为下一个人发布我的方法和解决方案。
I went with your line of thinking - clone the object and repopulate with old data on "undo":
我和你的想法一样 - 克隆对象并在“撤消”上重新填充旧数据:
1) Copy the data object into a new page variable ("_initData") 2) Create Observable from original server object 3) on "undo" reload observable with unaltered data ("_initData")
1) 将数据对象复制到新的页面变量 ("_initData") 2) 从原始服务器对象创建 Observable 3) 在“撤消”时重新加载具有未更改数据的 observable ("_initData")
Simplified JS: var _viewModel; var _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);
});
});
});
});
I had an object that was complicated enough that I didn't want to add handlers for each individual property.
我有一个足够复杂的对象,我不想为每个单独的属性添加处理程序。
Some changes are made to my object in real time, those changes edit both the observable and the "_initData".
实时对我的对象进行了一些更改,这些更改同时编辑了 observable 和“_initData”。
When I get data back from the server I update my "_initData" object to attempt to keep it in sync with the server.
当我从服务器取回数据时,我会更新我的“_initData”对象以尝试使其与服务器保持同步。
回答by Whelkaholism
Very old question, but I just did something very similar and found a very simple, quick, and effective way to do this using the mapping plugin.
很老的问题,但我只是做了一些非常相似的事情,并找到了一种使用映射插件的非常简单、快速和有效的方法。
Background; I am editing a list of KO objects bound using a foreach
. Each object is set to be in edit mode using a simple observable, which tells the view to display labels or inputs.
背景; 我正在编辑使用foreach
. 每个对象都使用一个简单的 observable 设置为处于编辑模式,它告诉视图显示标签或输入。
The functions are designed to be used in the click
binding for each foreach
item.
这些函数旨在click
用于每个foreach
项目的绑定。
Then, the edit / save / cancel is simply:
然后,编辑/保存/取消很简单:
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);
}
});
};
This is very useful when editing lists of simple objects, although in most cases I find myself having a list containing lightweight objects, then loading a full detail model for the actual editing, so this problem does not arise.
这在编辑简单对象列表时非常有用,尽管在大多数情况下我发现自己有一个包含轻量级对象的列表,然后加载完整的细节模型以进行实际编辑,因此不会出现此问题。
You could add saveUndo
/ restoreUndo
methods to the model if you don't like tacking the __undo
property on like that, but personally I think this way is clearer as well as being a lot less code and usable on any model, even one without an explicit declaration.
如果您不喜欢这样添加属性,您可以向模型添加saveUndo
/restoreUndo
方法__undo
,但我个人认为这种方式更清晰,代码更少,可用于任何模型,即使是没有明确声明的模型。
回答by tyler_mitchell
I needed something similar, and I couldn't use the protected observables, as I needed the computed to update on the temporary values. So I wrote this knockout extension:
我需要类似的东西,我不能使用受保护的 observables,因为我需要计算来更新临时值。所以我写了这个淘汰赛扩展:
This extension creates an underscore version of each observable ie self.Comments() -> self._Comments()
这个扩展创建了每个 observable 的下划线版本,即 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;
}
This extension will save you writing duplicates of each of your observables and ignores your computed. It works like this:
此扩展程序将节省您编写每个 observables 的副本并忽略您的计算。它是这样工作的:
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();
};
So as you can see if you have buttons on html:
因此,您可以看到 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>
Cancel will undo and revert your observables back to what they were, as were save will update the real values with the temp values in one line
Cancel 将撤消并将您的 observables 恢复到原来的状态,因为 save 将使用一行中的临时值更新实际值
回答by Ziad
You might consider using KO-UndoManagerfor this. Here's a sample code to register your viewmodel:
您可以考虑为此使用KO-UndoManager。以下是注册视图模型的示例代码:
viewModel.undoMgr = ko.undoManager(viewModel, {
levels: 12,
undoLabel: "Undo (#COUNT#)",
redoLabel: "Redo"
});
You can then add undo/redo buttons in your html as follows:
然后,您可以在 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>
And here's a Plunkr showing it in action. To undo all changes you'll need to loop call undoMgr.undoCommand.execute
in javascript until all the changes are undone.
而这里的一个Plunkr显示在行动了。要撤消所有更改,您需要undoMgr.undoCommand.execute
在 javascript 中循环调用,直到撤消所有更改。