Protected Observable
A knockout extension for providing "transactional" properties for observables.
by Areson
HTML
<script src="https://github.com/SteveSanderson/knockout/raw/master/build/output/knockout-latest.debug.js"></script>
<script src="https://raw.github.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.debug.js"></script>
<input type="text" data-bind="value: observe"></input>
<button data-bind="click: observe.reset">Reset</button>
<button data-bind="click: observe.commit">Commit</button>
The value is <span data-bind="text: observe"></span>
<hr/>
<h3>List Example</h3>
<input data-bind="value: listAdd"></input>
<button data-bind="click: addToList">Add to List</button>
<button data-bind="click: observeArray.reset">Reset</button>
<button data-bind="click: observeArray.commit">Commit</button>
<button data-bind="click: emptyArray">Remove All</button>
<table data-bind="foreach: observeArray()">
<tr>
<td data-bind="text: $data"></td>
</tr>
</table>
<hr/>
Complex Observable Example
<label>ID: <input data-bind="value: complexObserve().id"/></label>
<label>Value: <input data-bind="value: complexObserve().value"/></label>
<button data-bind="click: complexObserve.reset">Reset</button>
<button data-bind="click: complexObserve.commit">Commit</button>
<br/>
ID: <span data-bind="text: complexObserve().id"></span>
<br/>Value: <span data-bind="text: complexObserve().value"></span>
JavaScript
ko.extenders.protected = function(target, options) {
var _actual = target;
var _deepObjects = [];
//Setup how we will "copy" the values
//back and forth
var copyValue = function(value) {
if (_actual.removeAll)
return ($.extend(true, [], value));
else if ($.isPlainObject(_actual()))
return $.extend({}, value);
else return (value);
}
//Are we doing "deep" protection?
if(options.deep)
{
//Get the underlying object
var object = _actual();
//Iterate over all observables and protect them
for(var name in object)
{
if(ko.isObservable(object[name]))
{
//Make the observable protected
object[name] = ko.extenders.protected(object[name], options);
//Save the observable to our "protected" array
_deepObjects.push(object[name])
}
}
}
//Hold onto the intermediate value in between commits
var _cachedValue = copyValue(target());
//Setup a dependent variable to control
//access to our observable
var result = ko.computed({
read: function() {
return (_actual());
},
write: function(value) {
_cachedValue = value;
}
});
var _deepCommit = function() {
for(var i in _deepObjects)
_deepObjects[i].commit();
}
var _deepReset = function() {
for(var i in _deepObjects)
_deepObjects[i].reset();
}
//Commit the changes to the observable
result.commit = function() {
if($.isPlainObject(_actual()))
{
if(options.deep && _deepObjects.length > 0)
_deepCommit();
else
_actual(copyValue(_cachedValue));
}
else
_actual(copyValue(_cachedValue));
}
result.reset = function() {
...