开发者

How to create an observable array with undo?

开发者 https://www.devze.com 2023-03-26 10:32 出处:网络
I am trying to add knockout JS to a search 开发者_运维技巧page on our website. Currently you open up a jQuery dialog box, which has a number of checkboxes of criteria that you can select.

I am trying to add knockout JS to a search 开发者_运维技巧page on our website. Currently you open up a jQuery dialog box, which has a number of checkboxes of criteria that you can select.

There are multiple dialogs with multiple types of criteria. When you open the dialog, the checkboxes do not take effect until you hit an "Update" button, if you click cancel or just close the window, the changes you made get reverted and the dialog is set to its former state.

I read this and a few other posts. However this seems to only work with ko.observable, and I cannot seem to get it to work with ko.observableArray.

Has anyone accomplished this or have any ideas?

An example of what I want to do:

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


Here would be one way to approach it:

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

You would bind your checkboxes to yourName.temp and the other part of your UI to just yourName.

Here is a sample: http://jsfiddle.net/rniemeyer/YrfyW/

The slice(0) is one way to get a shallow copy of an array (or even just slice()). Otherwise, you would be performing operations on a reference to the same array.


Given HTML similar to:

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

You could use some Knockout code similar to this, basically saving the undo stack as a JSON string representation of the state after every change. Basically you create a fake dependent observable to subscribe to all the properties in the view, alternatively you could manually iterate and subscribe to each property.

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

I have a sample of N-Level undo with knockout here:

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

You may be able to adapt for your uses.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号