Creating a smart, dirty flag

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

by Silvestrs Kante

HTML

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

<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>
    </li>
</script>
<button data-bind="enable: isDirty, click: save">Save</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 _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);
}

var viewModel = {
    items_open: ko.observableArray([
        new Item(1, "one"),
        new Item(2, "two"),
        new Item(3, "three")
        ]),
    items_closed: ko.observableArray([
        new Item(4, "four"),
        new Item(5, "five"),
        new Item(6, "six")
        ]),
    save: function() {
        alert("Sending changes to server: " + ko.toJSON(this.dirtyItems));  
    }
};

viewModel.dirtyItems = ko.dependentObservable(function() {
    var result = [],
        open = this.items_open(),
        closed = this.items_closed();
    
    for (var i = 0, j = open.length; i < j; i++) {
        if (open[i].dirtyFlag.isDirty()) {
           result.push(open[i]);   
        }
    }
    
    for (var i = 0, j = closed.length; i < j; i++) {
        if (closed[i].dirtyFlag.isDirty()) {
           result.push(closed[i]);   
        }
    }
    
    return result
}, viewModel);

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

ko.applyBindings(viewModel);