Как отменить / отменить изменения в наблюдаемой модели (или заменить модель в массиве нетронутой копией) - PullRequest
23 голосов
/ 03 мая 2011

У меня есть viewModel с observableArray объектов с наблюдаемыми переменными.

Мой шаблон показывает данные с кнопкой редактирования, которая скрывает элементы отображения и показывает элементы ввода со связанными значениями. Вы можете начать редактирование данных, а затем у вас есть возможность отменить. Я хотел бы, чтобы эта отмена вернулась к неизмененной версии объекта.

Я попытался клонировать объект, выполнив что-то вроде этого:

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

или

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

но viewModel.tempContact изменяется, как только контакт делает.

Есть ли что-то встроенное в KnockoutJS, чтобы справиться с такой ситуацией, или мне лучше всего просто создать новый контакт с точно такими же данными и заменить измененный контакт новым контактом при отмене?

Любой совет очень ценится. Спасибо!

Ответы [ 5 ]

16 голосов
/ 04 мая 2011

Есть несколько способов справиться с чем-то подобным. Вы можете создать новый объект с теми же значениями, что и текущий, и выбросить его при отмене. Вы можете добавить дополнительные наблюдаемые для привязки к полям редактирования и сохранить их при принятии или взглянуть на эту запись для идеи по инкапсуляции этой функциональности в повторно используемый тип (это мой предпочтительный метод).

3 голосов
/ 16 октября 2014

Я наткнулся на этот пост, пытаясь решить похожую проблему, и решил, что опубликую свой подход и решение для следующего парня.

Я придерживался вашего мнения - клонируем объект и пополняем старые данные на "отменить":

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", чтобы попытаться синхронизировать его с сервером.

2 голосов
/ 18 июня 2015

Очень старый вопрос, но я только что сделал нечто очень похожее и нашел очень простой, быстрый и эффективный способ сделать это с помощью подключаемого модуля отображения.

Фон; Я редактирую список объектов 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 таким образом, но лично я думаю, что этот способ более понятен, а также намного меньше кода и можно использовать с любой моделью, даже без явного объявления.

0 голосов
/ 05 июля 2017

Мне нужно было что-то подобное, и я не мог использовать защищенные наблюдаемые, так как мне нужно было вычислять для обновления временных значений.Итак, я написал это расширение:

Это расширение создает версию подчеркивания для каждой наблюдаемой, т.е. 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>

Отмена отменяет и возвращает ваши наблюдаемые обратно к тому, что они были, как былиsave обновит реальные значения с временными значениями в одна строка

0 голосов
/ 22 декабря 2014

Вы можете использовать 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, пока все изменения не будут отменены.

...