Creating a smart, dirty flag

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

by slace

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" />
        <button data-bind="click: dirtyFlag.reset">Reset</button>
        <button data-bind="click: dirtyFlag.revert">Revert</button>
    </li>
</script>
<button data-bind="enable: isDirty, click: save">Save</button>
<button data-bind="enabled: isDirty, click: reset">Reset all</button>
<hr />
<h3>Just Dirty Items</h3>
<div data-bind="text: ko.toJSON(dirtyItems)"></div>

CSS

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

JavaScript

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

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

    result.reset = function() {
        _initialState(ko.toJSON(root));
        _isInitiallyDirty = false;
    };
    
    result.revert = function () {
        for (var i in initial) {
            root[i](initial[i]);
        }
        
        result.reset();
    };
    
    result.source = function () {
        return JSON.parse(JSON.stringify(initial));
    };

    return result;
};


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

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));  
    },
    reset: function () {
        ko.utils.arrayForEach(this.items(), function (item) { item.dirtyFlag.reset(); });
    }
};

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

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

ko.applyBindings(viewModel);