Как создать наблюдаемый массив с отменой?

Я пытаюсь добавить нокаут JS на страницу поиска на нашем веб-сайте. В настоящее время вы открываете диалоговое окно jQuery, в котором есть несколько флажков критериев, которые вы можете выбрать.

Существует несколько диалогов с несколькими типами критериев. Когда вы открываете диалог, флажки не вступают в силу, пока вы не нажмете кнопку "Обновить", если вы нажмете "Отменить" или просто закроете окно, сделанные вами изменения будут отменены, а диалог будет установлен в прежнее состояние.

Я прочитал этот и несколько других сообщений. Однако, похоже, это работает только с ko.observable, и я не могу заставить его работать с ko.observableArray.

Кто-нибудь это сделал или имеет какие-то идеи?

Пример того, что я хочу сделать:

Html:

<form>
    <div>
        <div>
            <label><input type="checkbox" data-bind="checked: genders" value="1" />Male</label>
            <label><input type="checkbox" data-bind="checked: genders" value="2" />Female</label>
        </div>
    </div>
    <a id="buttonCancel">Cancel</a>
    <a id="buttonUpdate">Update</a>
</form>
<div data-bind="text: ko.toJSON(viewModel)"></div>

JavaScript:

var viewModel = {
    genders: ko.observableArrayWithUndo([])
};

ko.applyBindings(viewModel);

$('#buttonCancel').click(function(){
   viewModel.genders.resetChange();
});

$('#buttonUpdate').click(function(){
    viewModel.genders.commit();
    return false;
});

Ответ 1

Здесь будет один из способов приблизиться к нему:

//wrapper to an observableArray of primitive types that has commit/reset
ko.observableArrayWithUndo = function(initialArray) {
    var _tempValue = ko.observableArray(initialArray.slice(0)), 
        result = ko.observableArray(initialArray);

    //expose temp value for binding
    result.temp = _tempValue;

    //commit temp value
    result.commit = function() {
        result(_tempValue.slice(0));
    };

    //reset temp value
    result.reset = function() {
        _tempValue(result.slice(0)); 
    };

    return result;
};

Вы привязываете свои флажки к вашемуName.temp, а другую часть вашего пользовательского интерфейса - только к yourName.

Вот пример: http://jsfiddle.net/rniemeyer/YrfyW/

Срез (0) - это один из способов получить мелкую копию массива (или даже просто slice()). В противном случае вы будете выполнять операции над ссылкой на тот же массив.

Ответ 2

Данный HTML-код похож на:

<div>
    <button data-bind="click: function() { undo(); }">Undo</button>
    <input data-bind="value: firstName" />
    <input data-bind="value: lastName" />
    <textarea data-bind="value: text"></textarea>
</div>

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

//current state would probably come from the server, hard coded here for example
var currentState = JSON.stringify({
    firstName: 'Paul',
    lastName: 'Tyng',
    text: 'Text' 
})
   , undoStack = [] //this represents all the previous states of the data in JSON format
    , performingUndo = false //flag indicating in the middle of an undo, to skip pushing to undoStack when resetting properties
    , viewModel = ko.mapping.fromJSON(currentState); //enriching of state with observables


//this creates a dependent observable subscribed to all observables 
//in the view (toJS is just a shorthand to traverse all the properties)
//the dependent observable is then subscribed to for pushing state history
ko.dependentObservable(function() {
    ko.toJS(viewModel); //subscribe to all properties    
}, viewModel).subscribe(function() {
    if(!performingUndo) {
    undoStack.push(currentState);
    currentState = ko.mapping.toJSON(viewModel);
}
});

//pops state history from undoStack, if its the first entry, just retrieve it
    window.undo = function() {
        performingUndo = true;
        if(undoStack.length > 1)
        {
            currentState = undoStack.pop();
            ko.mapping.fromJSON(currentState, {}, viewModel);
        }
        else {
            currentState = undoStack[0];
            ko.mapping.fromJSON(undoStack[0], {}, viewModel);
        }
        performingUndo = false;
};

ko.applyBindings(viewModel);

У меня есть образец N-Level undo с нокаутом здесь:

http://jsfiddle.net/paultyng/TmvCs/22/

Возможно, вы сможете адаптироваться для своих целей.