Skip to content

Instantly share code, notes, and snippets.

@curran
Last active August 29, 2015 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save curran/05780c9eb997b86eab76 to your computer and use it in GitHub Desktop.
Save curran/05780c9eb997b86eab76 to your computer and use it in GitHub Desktop.
ModelJS firstName lastName

This is a simple example that uses Model.js to update a "lastName" property based on properties "firstName" and "lastName" using forms.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ModelJS Example</title>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="model.js"></script>
<style>
body {
margin: 220px;
}
</style>
</head>
<body>
<form>
First name: <input type="text" id="firstNameInput"><br>
Last name: <input type="text" id="lastNameInput"><br>
Full name: <span id="fullNameSpan"></span>
</form>
<script>
var model = Model({
firstName: "",
lastName: ""
});
d3.select("#firstNameInput").on("input", function (e){
model.firstName = this.value;
});
d3.select("#lastNameInput").on("input", function (e){
model.lastName = this.value;
});
model.when(["firstName", "lastName"], function (firstName, lastName){
model.fullName = firstName + " " + lastName;
});
model.when("fullName", function (fullName){
d3.select("#fullNameSpan").text(fullName);
console.log("Full name updated.");
});
</script>
</body>
</html>
// ModelJS v0.2.1
//
// https://github.com/curran/model
//
// Last updated by Curran Kelleher March 2015
//
// Includes contributions from
//
// * github.com/mathiasrw
// * github.com/bollwyvl
// * github.com/adle29
//
// The module is defined inside an immediately invoked function
// so it does not pullute the global namespace.
(function(){
// The constructor function, accepting default values.
function Model(defaults){
// The returned public API object.
var model = {},
// The internal stored values for tracked properties. { property -> value }
values = {},
// The callback functions for each tracked property. { property -> [callback] }
listeners = {},
// The set of tracked properties. { property -> true }
trackedProperties = {};
// The functional reactive "when" operator.
//
// * `properties` An array of property names (can also be a single property string).
// * `callback` A callback function that is called:
// * with property values as arguments, ordered corresponding to the properties array,
// * only if all specified properties have values,
// * once for initialization,
// * whenever one or more specified properties change,
// * on the next tick of the JavaScript event loop after properties change,
// * only once as a result of one or more synchronous changes to dependency properties.
function when(properties, callback, thisArg){
// Make sure the default `this` becomes
// the object you called `.on` on.
thisArg = thisArg || this;
// Handle either an array or a single string.
properties = (properties instanceof Array) ? properties : [properties];
// This function will trigger the callback to be invoked.
var listener = debounce(function (){
var args = properties.map(function(property){
return values[property];
});
if(allAreDefined(args)){
callback.apply(thisArg, args);
}
});
// Trigger the callback once for initialization.
listener();
// Trigger the callback whenever specified properties change.
properties.forEach(function(property){
on(property, listener);
});
// Return this function so it can be removed later with `model.cancel(listener)`.
return listener;
}
// Returns a debounced version of the given function.
// See http://underscorejs.org/#debounce
function debounce(callback){
var queued = false;
return function () {
if(!queued){
queued = true;
setTimeout(function () {
queued = false;
callback();
}, 0);
}
};
}
// Returns true if all elements of the given array are defined, false otherwise.
function allAreDefined(arr){
return !arr.some(function (d) {
return typeof d === 'undefined' || d === null;
});
}
// Adds a change listener for a given property with Backbone-like behavior.
// Similar to http://backbonejs.org/#Events-on
function on(property, callback, thisArg){
thisArg = thisArg || this;
getListeners(property).push(callback);
track(property, thisArg);
}
// Gets or creates the array of listener functions for a given property.
function getListeners(property){
return listeners[property] || (listeners[property] = []);
}
// Tracks a property if it is not already tracked.
function track(property, thisArg){
if(!(property in trackedProperties)){
trackedProperties[property] = true;
values[property] = model[property];
Object.defineProperty(model, property, {
get: function () { return values[property]; },
set: function(newValue) {
var oldValue = values[property];
values[property] = newValue;
getListeners(property).forEach(function(callback){
callback.call(thisArg, newValue, oldValue);
});
}
});
}
}
// Cancels a listener returned by a call to `model.when(...)`.
function cancel(listener){
for(var property in listeners){
off(property, listener);
}
}
// Removes a change listener added using `on`.
function off(property, callback){
listeners[property] = listeners[property].filter(function (listener) {
return listener !== callback;
});
}
// Sets all of the given values on the model.
// `newValues` is an object { property -> value }.
function set(newValues){
for(var property in newValues){
model[property] = newValues[property];
}
}
// Transfer defaults passed into the constructor to the model.
set(defaults);
// Public API.
model.when = when;
model.cancel = cancel;
model.on = on;
model.off = off;
model.set = set;
return model;
}
// Model.None is A representation for an optional Model property that is not specified.
// Model property values of null or undefined are not propagated through
// to when() listeners. If you want the when() listener to be invoked, but
// some of the properties may or may not be defined, you can use Model.None.
// This way, the when() listener is invoked even when the value is Model.None.
// This allows the "when" approach to support optional properties.
//
// For example usage, see this scatter plot example with optional size and color fields:
// http://bl.ocks.org/curran/9e04ccfebeb84bcdc76c
//
// Inspired by Scala's Option type.
// See http://alvinalexander.com/scala/using-scala-option-some-none-idiom-function-java-null
Model.None = "__NONE__";
// Support AMD (RequireJS), CommonJS (Node), and browser globals.
// Inspired by https://github.com/umdjs/umd
if (typeof define === "function" && define.amd) {
define([], function () { return Model; });
} else if (typeof exports === "object") {
module.exports = Model;
} else {
this.Model = Model;
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment