reactive-function
Advanced tools
Comparing version 0.5.0 to 0.6.0
89
index.js
var ReactiveProperty = require("reactive-property"); | ||
var Graph = require("graph-data-structure"); | ||
// Messages for exceptions thrown. | ||
var errors = { | ||
notASetter: Error("You cannot set the value of a reactive function directly.") | ||
}; | ||
// The singleton data dependency graph. | ||
// Nodes are reactive properties and reactive functions. | ||
// Edges represent dependencies of reactive functions. | ||
// Nodes are reactive properties. | ||
// Edges are dependencies between reactive function inputs and outputs. | ||
var graph = Graph(); | ||
// This object accumulates nodes that have changed since the last digest. | ||
// Keys are node ids, values are truthy (the object acts like a Set). | ||
var changedNodes = {}; | ||
// A map for properties based on their assigned id. | ||
// A map for looking up properties based on their assigned id. | ||
// Keys are property ids, values are reactive properties. | ||
var properties = {}; | ||
// Assigns ids to properties for use as nodes in the graph. | ||
// This object accumulates properties that have changed since the last digest. | ||
// Keys are property ids, values are truthy (the object acts like a Set). | ||
var changed = {}; | ||
// Assigns an id to a reactive property so it can be a node in the graph. | ||
// Also stores a reference to the property by id in `properties`. | ||
// If the given property already has an id, does nothing. | ||
var assignId = (function(){ | ||
@@ -33,2 +30,7 @@ var counter = 1; | ||
// The reactive function constructor. | ||
// Accepts an options object with | ||
// * inputs - An array of reactive properties. | ||
// * callback - A function with arguments corresponding to values of inputs. | ||
// * output - A reactive property (optional). | ||
function ReactiveFunction(options){ | ||
@@ -38,10 +40,4 @@ | ||
var callback = options.callback; | ||
var output = options.output; | ||
var output = options.output || function (){}; | ||
// TODO check for correct type for inputs | ||
// TODO check for correct type for callback | ||
// The returned object. | ||
var reactiveFunction = {}; | ||
// This gets invoked during a digest, after inputs have been evaluated. | ||
@@ -74,30 +70,32 @@ output.evaluate = function (){ | ||
// Add change listeners to each input property. | ||
var listeners = inputs | ||
.map(function (property){ | ||
return property.on(function (){ | ||
changedNodes[property.id] = true; | ||
queueDigest(); | ||
}); | ||
// These mark the properties as changed and queue the next digest. | ||
var listeners = inputs.map(function (property){ | ||
return property.on(function (){ | ||
changed[property.id] = true; | ||
queueDigest(); | ||
}); | ||
}); | ||
// This function must be called to explicitly destroy a reactive function. | ||
// Garbage collection is not enough, as we have added listeners and edges. | ||
reactiveFunction.destroy = function (){ | ||
// Return an object that can destroy the listeners and edges set up. | ||
return { | ||
// Remove change listeners from inputs. | ||
listeners.forEach(function (listener, i){ | ||
inputs[i].off(listener); | ||
}); | ||
// This function must be called to explicitly destroy a reactive function. | ||
// Garbage collection is not enough, as we have added listeners and edges. | ||
destroy: function (){ | ||
// Remove the edges that were added to the dependency graph. | ||
inputs.forEach(function (input){ | ||
graph.removeEdge(input.id, output.id); | ||
}); | ||
// Remove change listeners from inputs. | ||
listeners.forEach(function (listener, i){ | ||
inputs[i].off(listener); | ||
}); | ||
// Remove the reference to the 'evaluate' function. | ||
delete output.evaluate; | ||
// Remove the edges that were added to the dependency graph. | ||
inputs.forEach(function (input){ | ||
graph.removeEdge(input.id, output.id); | ||
}); | ||
// Remove the reference to the 'evaluate' function. | ||
delete output.evaluate; | ||
} | ||
}; | ||
return reactiveFunction; | ||
} | ||
@@ -109,3 +107,3 @@ | ||
graph | ||
.topologicalSort(Object.keys(changedNodes), false) | ||
.topologicalSort(Object.keys(changed), false) | ||
.map(function (id){ | ||
@@ -118,3 +116,3 @@ return properties[id]; | ||
changedNodes = {}; | ||
changed = {}; | ||
}; | ||
@@ -126,6 +124,3 @@ | ||
// Returns a function that, when invoked, schedules the given function | ||
// to execute on the next tick of the JavaScript event loop. | ||
// Multiple sequential executions of the returned function within the same tick of | ||
// the event loop will collapse into a single invocation of the original function | ||
// on the next tick. | ||
// to execute once on the next tick of the JavaScript event loop. | ||
// Similar to http://underscorejs.org/#debounce | ||
@@ -132,0 +127,0 @@ function debounce(fn){ |
{ | ||
"name": "reactive-function", | ||
"version": "0.5.0", | ||
"version": "0.6.0", | ||
"description": "A library for managing data flows and changing state.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -7,2 +7,4 @@ # reactive-function [![Build Status](https://travis-ci.org/curran/reactive-function.svg?branch=master)](https://travis-ci.org/curran/reactive-function) | ||
Using this library, you can declaratively construct complex data dependency graphs. The propagation of changes through these graphs is managed for you using a variant of the [topological sort algorithm](https://en.wikipedia.org/wiki/Topological_sorting). This allows you to write simpler code in which each reactive function need only be concerned with its inputs and outputs. You can be confident that the functions will be executed in the correct order when their input values change, and that functions whose input values have not changed will not be executed unnecessarily. | ||
# Usage | ||
@@ -78,2 +80,3 @@ | ||
* [ReactiveJS](https://github.com/mattbaker/Reactive.js) | ||
* [vogievetsky/DVL](https://github.com/vogievetsky/DVL) | ||
* [EmberJS Computed Properties](https://guides.emberjs.com/v2.0.0/object-model/computed-properties/) | ||
@@ -83,1 +86,3 @@ * [AngularJS Digest](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest) | ||
* [Model.js](https://github.com/curran/model) | ||
Thanks to Mike Bostock and Vadim Ogievetsky for suggesting to have the ability to digest within a single tick of the event loop (the main difference between this project and the original [model-js](https://github.com/curran/model)). This idea has inspired the construction of this library, which I hope will provide a solid foundation for complex interactive visualization systems. |
25
test.js
@@ -125,2 +125,9 @@ // Unit tests for reactive-function. | ||
it("Should handle tricky case.", function () { | ||
// This is the case where model-js failed (https://github.com/curran/model), | ||
// which cropped up as flashes of inconstistent states in Chiasm visualizations. | ||
// For example, it happens when you change the X column in this example | ||
// "Magic Heat Map" http://bl.ocks.org/curran/a54fc3a6578efcdc19f4 | ||
// This flaw in model-js is the main inspiration for making this library and using topological sort. | ||
// | ||
// a | ||
@@ -133,2 +140,3 @@ // / \ | ||
// e | ||
// | ||
var a = ReactiveProperty(5); | ||
@@ -397,6 +405,21 @@ | ||
rf.destroy(); | ||
assert.equal(typeof c.evaluate, "undefined"); | ||
}); | ||
it("Should work without any output specified.", function (){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var sideEffect = 0; | ||
var rf = ReactiveFunction({ | ||
inputs: [a, b], | ||
callback: function (a, b){ | ||
sideEffect++; | ||
} | ||
}); | ||
ReactiveFunction.digest(); | ||
assert(sideEffect); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
19015
438
86