This is a simple example that uses Model.js to update a "lastName" property based on properties "firstName" and "lastName" using forms.
Last active
August 29, 2015 14:21
-
-
Save curran/05780c9eb997b86eab76 to your computer and use it in GitHub Desktop.
ModelJS firstName lastName
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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