Skip to content

Instantly share code, notes, and snippets.

@kig
Last active December 22, 2015 01:49
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kig/6399084 to your computer and use it in GitHub Desktop.
Save kig/6399084 to your computer and use it in GitHub Desktop.
Super-powerful two-way DOM model binding.
<html>
<body>
<h1 contenteditable id="title"></h1>
<input id="edit">
<script src="model.js"></script>
<script>
var m = new Model();
m.sync('greeting', document.getElementById('edit'), 'value');
m.sync('greeting', document.getElementById('title'), 'innerText');
m.to('greeting', document, 'title');
m.on('greeting', function(v,k,who){ console.log('<'+(who && (who.tagName || who))+'> ' + v); });
m.set('greeting', 'Hello, World!');
</script>
</body>
</html>
var Model = function(values) {
this.values = values || {};
this.listeners = {};
this.changeListeners = [];
};
Model.prototype = {
on: function(k, callback) {
var l = this.listeners[k] = this.listeners[k] || [];
l.push(callback);
},
off: function(k, callback) {
if (!callback) {
delete this.listeners[k];
} else {
var l = (this.listeners[k] || []);
var i = l.indexOf(callback);
if (i > -1) {
l.splice(i, 1);
}
}
},
onChange: function(callback) {
this.changeListeners.push(callback);
},
offChange: function(callback) {
var i = this.changeListeners.indexOf(callback);
if (i > -1) {
this.changeListeners.splice(i, 1);
}
},
set: function(k, v, who) {
if (typeof k === 'object') {
for (var n in k) {
if (!/^_/.test(n)) {
this.set(n, k[n], v);
}
}
} else {
this.values[k] = v;
(this.listeners[k]||[]).forEach(function(f) { f.call(this,v,k,who); });
this.changeListeners.forEach(function(f) { f.call(this,k,v,who); });
}
},
get: function(k) {
return this.values[k];
},
to: function(key, elem, elemKey) {
this.on(key, function(v,k,who) {
if (who !== elem) {
elem[elemKey] = v;
}
});
},
from: function(key, src, srcKey) {
src.on(srcKey, function(v,k,who) {
if (this.get(key) !== v) {
this.set(key, v, who);
}
});
},
sync: function(key, elem, elemKey, predicate, eventNames) {
var self = this;
(eventNames || Model.eventNames).forEach(function(n) {
elem.addEventListener(n, function() {
if (self.get(key) !== elem[elemKey] && (!predicate || predicate(elem))) {
self.set(key, elem[elemKey], elem);
}
}, false);
});
this.to(key, elem, elemKey);
}
};
Model.eventNames = ["selectstart", "search", "reset", "paste", "beforepaste", "copy", "beforecopy", "cut", "beforecut", "submit", "select", "scroll", "mousewheel", "mouseup", "mouseover", "mouseout", "mousemove", "mousedown", "load", "keyup", "keypress", "keydown", "invalid", "input", "focus", "error", "drop", "dragstart", "dragover", "dragleave", "dragenter", "dragend", "drag", "dblclick", "contextmenu", "click", "change", "blur", "abort"];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment