Creating a smart, dirty flag

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

by Camille Laborde

HTML

<script src="http://knockoutjs.com/downloads/knockout-2.2.0.js"></script>
<ul data-bind="foreach: items">
    <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>
</ul>
<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

//not used in this example.  one time flag, that drops its subscriptions after the first change.
ko.oneTimeDirtyFlag = function (root) {
    var _initialized;

    //one-time dirty flag that gives up its dependencies on first change
    var result = ko.computed(function () {
        if (!_initialized) {
            //just for subscriptions
            ko.toJS(root);

            //next time return true and avoid ko.toJS
            _initialized = true;

            //on initialization this flag is not dirty
            return false;
        }

        //on subsequent changes, flag is now dirty
        return true;
    });

    return result;
};

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

    result.isDirty = ko.computed(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 = function (items) {
    this.items = ko.observableArray([
    new Item(1, "one"),
    new Item(2, "two"),
    new Item(3, "three")]);

    this.save = function () {
        alert("Sending changes to server: " + ko.toJSON(this.dirtyItems));
    };

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

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

ko.applyBindings(new ViewModel());