Behaviour has its own model hosted in the custom binding and its api is exposed as what i call a "custom binding context"
after that the , gui will only use the api and the implementation may be changed on the fly
<!DOCTYPE html>
<html>
<head>
<script src="jquery.js"></script>
<script src="knockout-3.3.0.debug.js"></script>
<script>
<!-- custom binding which models dirty items, exposed api $dirtyItems.addItem,$dirtyItems.removeItem , $dirtyItems.getItems . $dirtyItem.isDirty -->
ko.bindingHandlers['dirtyItems'] = {
'init': function (element, valueAccessor, allBindingsAccessor,
viewModel,bindingContext) {
var DirtyItems = function(){
var self = this;
self.items = ko.observableArray();
self.addItem = function(item){
if(! ko.utils.arrayFirst(self.items(),function(x){ return x == item;}))
self.items.push(item);
}
self.removeItem = function(item){
self.items.remove(item);
}
self.getItems = function(){
var reply = [];
ko.utils.arrayForEach(self.items(),function(item){reply.push(item.data);});
return reply;
}
self.isDirty = ko.computed(function(){ return self.items().length > 0 });
}
var props = { };
props["$dirtyItems"] = new DirtyItems();
var innerBindingContext = bindingContext.extend(props);
ko.applyBindingsToDescendants(innerBindingContext, element);
// Also tell KO *not* to bind the descendants itself,
//otherwise they will be bound twice
return { controlsDescendantBindings: true };
}} ;
<!-- custom binding which models a dirty item, exposed api $dirty.reset, $dirty.isDirty -->
ko.bindingHandlers['dirty'] = {
'init': function (element, valueAccessor, allBindingsAccessor,
viewModel,bindingContext) {
var data = valueAccessor();
var DirtyModel = function(data,dirtyItems){
var self = this;
self.originalValue = ko.unwrap(data);
self.data = data;
self.dirtyItems = dirtyItems;
self.reset = function(){
self.data(self.originalValue);
self.dirtyItems.removeItem(self);
}
self.isDirty = ko.computed(function(){
return self.originalValue != ko.unwrap(self.data);
});
self.data.subscribe(function(newVal){
if(self.dirtyItems) {
self.dirtyItems.addItem(self);
}
});
}
var props = { };
props['$dirty'] = new DirtyModel(data, bindingContext['$dirtyItems']);
bindingContext['$dirty'] =props['$dirty'];
var innerBindingContext = bindingContext.extend(props);
ko.applyBindingsToDescendants(innerBindingContext, element);
// Also tell KO *not* to bind the descendants itself,
//otherwise they will be bound twice
return { controlsDescendantBindings: true };
}};
function Item(id, name) {
}
<!-- wow wonderful clean model -->
var ViewModel = function(items) {
var self =this;
self.items = ko.observableArray([
new Item(1, "one"),
new Item(2, "two"),
new Item(3, "three")
]);
self.save = function(items){
alert(''+ko.toJSON(items));
}
}
$(document).ready(function(){
model = new ViewModel();
ko.applyBindings(model);
});
</script>
<style>
li { padding: 2px; margin: 2px; }
input { width: 75px; }
.dirty { border: solid yellow 2px; }
</style>
</head>
<body >
<div data-bind="dirtyItems: {}" >
<ul data-bind="foreach: items">
<li data-bind="dirty: $
data.name,css: { dirty: $dirty.isDirty }">
<span data-bind="text: id"></span>
<input data-bind="value: name" />
<!-- here we go , that what i call "custom binding context" $dirty -->
<button data-bind="click: $dirty.reset">Reset</button>
</li>
</ul>
<button data-bind="enable: $dirtyItems.isDirty, click: function(){ save($dirtyItems.getItems());}">Save</button>
<hr />
<h3>Just Dirty Items</h3>
<div data-bind="text: ko.toJSON($dirtyItems.getItems())"></div>
</div>
</body>
</html>
btw. i would like to know what the knockout "cracks" think about my concept of custom binding context and separating behaviour in its on models ?