如何取消/恢复对可观察模型的更改(或用未更改的副本替换数组中的模型)

发布于 2024-11-04 20:51:59 字数 491 浏览 4 评论 0原文

我有一个 viewModel ,其中包含带有可观察变量的对象的 observableArray 。

我的模板使用编辑按钮显示数据,该按钮隐藏显示元素并显示具有绑定值的输入元素。您可以开始编辑数据,然后可以选择取消。我希望此取消恢复到对象的未更改版本。

我尝试通过执行类似以下操作来克隆对象:

viewModel.tempContact = jQuery.extend({}, contact);

或者

viewModel.tempContact = jQuery.extend(true, {}, contact);

但是 viewModel.tempContact 在联系后立即被修改。

KnockoutJS 中是否内置了任何内容来处理这种情况,或者我最好只创建一个具有完全相同详细信息的新联系人,并在取消时用新联系人替换修改后的联系人?

非常感谢任何建议。谢谢!

I have a viewModel with an observableArray of objects with observable variables.

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.

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?

Any advice is greatly appreciated. Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

风筝有风,海豚有海 2024-11-11 20:51:59

有几种方法可以处理这样的事情。您可以构造一个与当前对象具有相同值的新对象,并在取消时将其丢弃。您可以添加其他可观察量以绑定到编辑字段并将它们保留在接受上或查看此 post 了解将此功能封装到可重用类型中的想法(这是我的首选方法)。

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 post for an idea on encapsulating this functionality into a reusable type (this is my preferred method).

软糖 2024-11-11 20:51:59

我在寻找解决类似问题时遇到了这篇文章,并认为我会为下一个人发布我的方法和解决方案。

我按照你的思路 - 克隆对象并在“撤消”上重新填充旧数据:

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”对象以尝试使其与服务器保持同步。

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

Simplified 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".

When I get data back from the server I update my "_initData" object to attempt to keep it in sync with the server.

花开柳相依 2024-11-11 20:51:59

非常老的问题,但我只是做了一些非常类似的事情,并找到了一种非常简单、快速且有效的方法来使用映射插件来做到这一点。

背景;我正在编辑使用 foreach 绑定的 KO 对象列表。每个对象都使用一个简单的可观察对象设置为编辑模式,该可观察对象告诉视图显示标签或输入。

这些函数设计用于每个 foreach 项的 click 绑定中。

然后,编辑/保存/取消很简单:

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

这在编辑简单对象列表时非常有用,尽管在大多数情况下我发现自己有一个包含轻量级对象的列表,然后加载完整的详细模型以进行实际编辑,所以这个问题不会出现。

如果您不喜欢这样添加 __undo 属性,您可以向模型添加 saveUndo / restoreUndo 方法,但我个人认为这是方式更清晰,代码也少得多,并且可以在任何模型上使用,即使是没有显式声明的模型。

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.

The functions are designed to be used in the click binding for each foreach item.

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.

薄荷梦 2024-11-11 20:51:59

您可以考虑使用 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> 

这里是 Plunkr 展示它的实际应用。要撤消所有更改,您需要在 JavaScript 中循环调用 undoMgr.undoCommand.execute,直到撤消所有更改。

You might consider using KO-UndoManager for this. Here's a sample code to register your viewmodel:

viewModel.undoMgr = ko.undoManager(viewModel, {
  levels: 12,
  undoLabel: "Undo (#COUNT#)",
  redoLabel: "Redo"
});

You can then add undo/redo buttons in your html as follows:

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

幸福还没到 2024-11-11 20:51:59

我需要类似的东西,但我无法使用受保护的可观察量,因为我需要计算来更新临时值。所以我写了这个淘汰扩展:

这个扩展创建了每个可观察的下划线版本,即 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>

“取消”将撤消您的可观察值并将其恢复到原来的状态,就像“保存”将使用一行中的临时值更新实际值

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:

This extension creates an underscore version of each observable ie 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:

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:

<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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文