Creating a smart, dirty flag

http://www.knockmeout.net/2011/05/creating-smart-dirty-flag-in-knockoutjs.html

by jcreamer898

HTML

<script src="http://rniemeyer.github.com/KnockMeOut/Scripts/jquery.tmpl.js"></script>
<script src="http://rniemeyer.github.com/KnockMeOut/Scripts/knockout-latest.debug.js"></script>
<ul data-bind="template: { name: 'itemsTmpl', foreach: items }"></ul>

<script id="itemsTmpl" type="text/html">
    <li data-bind="css: { dirty: dirtyFlag.isDirty }">
        <span data-bind="text: id"></span>
        <input data-bind="value: name" />
        <a href="#" data-bind="click: function() { viewModel.removeItem($data); }">Delete</a>
    </li>
</script>
<button data-bind="click: addItem">Add Item</button>
<button data-bind="enable: isDirty, click: save">Save</button>
<hr />
<h3>Just Dirty Items</h3>
<pre data-bind="text: JSON.stringify(ko.toJS(dirtyItems), null, 2)"></pre>

CSS

li { padding: 2px; margin: 2px; }
input { width: 75px; }
.dirty { border: solid yellow 2px; }

JavaScript

ko.dirtyFlag = function(root, isInitiallyDirty) {
    var result = function() {}
    var _initialState = ko.observable(ko.toJSON(root));
    var _isInitiallyDirty = ko.observable(isInitiallyDirty);

    result.isDirty = ko.dependentObservable(function() {
        return _isInitiallyDirty() || _initialState() !== ko.toJSON(root);
    });

    result.reset = function() {
        _initialState(ko.toJSON(root));
        _isInitiallyDirty(false);
    };

    return result;
};


function Item(id, name) {
    this.id = ko.observable(id);
    this.name = ko.observable(name);
    this.dirtyFlag = new ko.dirtyFlag(this, id === 0);
}

var viewModel = {
    items: ko.observableArray([
        new Item(1, "one"),
        new Item(2, "two"),
        new Item(3, "three")
        ]),
    save: function() {
        alert("Sending changes to server: " + ko.toJSON(this.dirtyItems));  
    },
    addItem: function() {
        viewModel.items.push(new Item(0, "new"));   
    },
    removeItem: function(item) {
        viewModel.items.destroy(item);   
    }
};

viewModel.dirtyItems = ko.dependentObservable(function() {
    return ko.utils.arrayFilter(this.items(), function(item) {
        return item.dirtyFlag.isDirty() || item._destroy;
    });
}, viewModel);

viewModel.isDirty = ko.dependentObservable(function() {
    return this.dirtyItems().length > 0;
}, viewModel);

ko.applyBindings(viewModel);