23

我有一个带有可观察变量的 observableArray 对象的 viewModel。

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

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

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

或者

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

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

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

任何意见是极大的赞赏。谢谢!

4

5 回答 5

16

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

于 2011-05-03T20:08:13.830 回答
3

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

我采用了您的思路-克隆对象并在“撤消”上使用旧数据重新填充:

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

我有一个足够复杂的对象,我不想为每个单独的属性添加处理程序。

实时对我的对象进行了一些更改,这些更改同时编辑了 observable 和“_initData”。

当我从服务器取回数据时,我会更新我的“_initData”对象以尝试使其与服务器保持同步。

于 2014-10-15T21:49:26.820 回答
2

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

背景; 我正在编辑使用foreach. 每个对象都使用一个简单的 observable 设置为处于编辑模式,它告诉视图显示标签或输入。

这些功能旨在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

于 2015-06-18T11:42:42.280 回答
0

您可以考虑为此使用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展示它的实际应用。要撤消所有更改,您需要undoMgr.undoCommand.execute在 javascript 中循环调用,直到撤消所有更改。

于 2014-12-21T21:16:53.183 回答
0

我需要类似的东西,但我不能使用受保护的 observables,因为我需要计算来更新临时值。所以我写了这个淘汰赛扩展:

此扩展为每个可观察对象创建下划线版本,即 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;
}

此扩展将节省您编写每个 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();
};

因此,您可以查看 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>

取消将撤消并将您的可观察对象恢复到原来的状态,因为保存将使用一行中的临时值更新实际值

于 2017-07-05T10:52:39.043 回答