reactive-function
Advanced tools
Comparing version 0.3.0 to 0.4.0
21
index.js
@@ -18,14 +18,13 @@ var ReactiveProperty = require("reactive-property"); | ||
// A map for properties based on their assigned id. | ||
// Keys are property ids, values are reactive properties. | ||
var propertiesById = {}; | ||
var properties = {}; | ||
// Assigns ids to properties for use as nodes in the graph. | ||
var assignId = (function(){ | ||
var idCounter = 1; | ||
var counter = 1; | ||
return function (property){ | ||
if(!property.id){ | ||
property.id = idCounter++; | ||
propertiesById[property.id] = property; | ||
property.id = counter++; | ||
properties[property.id] = property; | ||
} | ||
@@ -50,2 +49,3 @@ }; | ||
// Get the values for each of the input reactive properties. | ||
var values = inputs.map(function (input){ | ||
@@ -55,3 +55,6 @@ return input(); | ||
// If all input values are defined, | ||
if(defined(values)){ | ||
// invoke the callback and assign the output value. | ||
output(callback.apply(null, values)); | ||
@@ -94,2 +97,5 @@ } | ||
// Remove the reference to the 'evaluate' function. | ||
delete output.evaluate; | ||
}; | ||
@@ -106,3 +112,3 @@ | ||
.map(function (id){ | ||
return propertiesById[id]; | ||
return properties[id]; | ||
}) | ||
@@ -122,3 +128,4 @@ .forEach(function (property){ | ||
// 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. | ||
// the event loop will collapse into a single invocation of the original function | ||
// on the next tick. | ||
// Similar to http://underscorejs.org/#debounce | ||
@@ -125,0 +132,0 @@ function debounce(fn){ |
{ | ||
"name": "reactive-function", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "A library for managing data flows and changing state.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
158
README.md
@@ -22,3 +22,3 @@ # reactive-function | ||
Let's say you have two reactive properties to represent someone's name. | ||
Suppose you have two reactive properties to represent someone's first and last name. | ||
@@ -30,159 +30,43 @@ ```javascript | ||
You can define a reactive function that depends on those two properties like this. | ||
Suppose you'd like to have another property that represents the full name of the person. | ||
```javascript | ||
var fullName = ReactiveFunction(function (first, last){ | ||
return first + " " + last; | ||
}, firstName, lastName); | ||
var fullName = ReactiveProperty(); | ||
``` | ||
This defines a "reactive function" that will be invoked when its dependencies (`firstName` and `lastName`) are both defined and whenever either one changes. The function will be invoked on the next tick of the JavaScript event loop after it is defined and after any dependencies change. | ||
You could set the full name value like this. | ||
To force a synchronous evaluation of all reactive functions whose dependencies have updated, you can call: | ||
```javascript | ||
ReactiveFunction.digest(); | ||
fullName(firstName() + " " + lastName()); | ||
``` | ||
Now you can access the computed value of the reactive function by invoking it. | ||
However, this sets the value of `fullName` only once, and it does not get updated when `firstName` or `lastName` change. | ||
```javascript | ||
console.log(fullName()); // Prints "Jane Smith" | ||
``` | ||
Here's how you can define a reactive function that updates the full name whenever the first name or last name changes. | ||
Here's some sample code from the tests that demonstrates the features of this library. | ||
```javascript | ||
it("Should depend on any number of reactive properties.", function () { | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(15); | ||
var d = ReactiveFunction(function (a){ | ||
return a * 2; | ||
}, a); | ||
var e = ReactiveFunction(function (a, b, c){ | ||
return a + b + c; | ||
}, a, b, c); | ||
ReactiveFunction.digest(); | ||
assert.equal(d(), 10); | ||
assert.equal(e(), 30); | ||
ReactiveFunction({ | ||
inputs: [firstName, lastName], | ||
output: lastName, | ||
callback: function (first, last){ | ||
return first + " " last; | ||
} | ||
}); | ||
it("Should depend on a reactive function.", function () { | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveFunction(function (a){ | ||
return a * 2; | ||
}, a); | ||
var c = ReactiveFunction(function (b){ | ||
return b / 2; | ||
}, b); | ||
ReactiveFunction.digest(); | ||
assert.equal(c(), 5); | ||
}); | ||
it("Should depend on a reactive property and a reactive function.", function () { | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveFunction(function (a){ | ||
return a * 2; | ||
}, a); | ||
var d = ReactiveFunction(function (b, c){ | ||
return b + c; | ||
}, b, c); | ||
ReactiveFunction.digest(); | ||
assert.equal(d(), 20); | ||
}); | ||
it("Should handle tricky case.", function () { | ||
// a | ||
// / \ | ||
// b | | ||
// | d | ||
// c | | ||
// \ / | ||
// e | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveFunction(function (a){ return a * 2; }, a); | ||
var c = ReactiveFunction(function (b){ return b + 5; }, b); | ||
var d = ReactiveFunction(function (a){ return a * 3; }, a); | ||
var e = ReactiveFunction(function (c, d){ return c + d; }, c, d); | ||
ReactiveFunction.digest(); | ||
assert.equal(e(), ((a() * 2) + 5) + (a() * 3)); | ||
}); | ||
it("Should throw an error if attempting to set the value directly.", function () { | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveFunction(function (a){ | ||
return a * 2; | ||
}, a); | ||
assert.throws(function (){ | ||
b(5); | ||
}); | ||
}); | ||
it("Should clear changed nodes on digest.", function () { | ||
var numInvocations = 0; | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveFunction(function (a){ | ||
numInvocations++; | ||
return a * 2; | ||
}, a); | ||
ReactiveFunction.digest(); | ||
ReactiveFunction.digest(); | ||
assert.equal(numInvocations, 1); | ||
}); | ||
it("Should automatically digest on next tick.", function (done) { | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveFunction(function (a, b){ | ||
return a + b; | ||
}, a, b); | ||
setTimeout(function (){ | ||
assert.equal(c(), 15); | ||
done(); | ||
}, 0); | ||
}); | ||
``` | ||
# Case Study: Width and Height | ||
This defines a "reactive function" that will be invoked when its inputs (`firstName` and `lastName`) are both defined and whenever either one changes. The function will be invoked on the next tick of the JavaScript event loop after it is defined and after any dependencies change. | ||
Suppose you have some reactive properties that change, such as `width` and `height`. | ||
To force a synchronous evaluation of all reactive functions whose dependencies have updated, you can call | ||
```javascript | ||
var my = { | ||
width: ReactiveProperty(), | ||
height: ReactiveProperty() | ||
}; | ||
ReactiveFunction.digest(); | ||
``` | ||
Suppose also that you have a function that resizes an SVG element that depends on those two properties. | ||
Now you can access the computed value of the reactive function by invoking it as a getter. | ||
```javascript | ||
var svg = d3.select("body").append("svg"); | ||
function render(){ | ||
svg | ||
.attr("width", my.width()) | ||
.attr("height", my.height()); | ||
} | ||
console.log(fullName()); // Prints "Jane Smith" | ||
``` | ||
How can you invoke the `render function` when `my.width` and `my.height` change? | ||
For more detailed example code, have a look at the [tests](https://github.com/curran/reactive-function/blob/master/test.js). | ||
@@ -192,1 +76,5 @@ Related work: | ||
* [ReactiveJS](https://github.com/mattbaker/Reactive.js) | ||
* [EmberJS Computed Properties](https://guides.emberjs.com/v2.0.0/object-model/computed-properties/) | ||
* [AngularJS Digest](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest) | ||
* [ZJONSSON/clues](https://github.com/ZJONSSON/clues) | ||
* [Model.js](https://github.com/curran/model) |
64
test.js
@@ -292,2 +292,66 @@ // Unit tests for reactive-function. | ||
}); | ||
it("Should be able to implement unidirectional data binding.", function (){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
ReactiveFunction({ | ||
inputs: [a], | ||
output: b, | ||
callback: function (a){ | ||
return a; | ||
} | ||
}); | ||
ReactiveFunction.digest(); | ||
assert.equal(b(), 5); | ||
}); | ||
it("Should be able to implement bidirectional data binding.", function (){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
function identity(x){ return x; } | ||
ReactiveFunction({ inputs: [a], output: b, callback: identity }); | ||
ReactiveFunction({ inputs: [b], output: a, callback: identity }); | ||
ReactiveFunction.digest(); | ||
// The most recently added edge takes precedence. | ||
assert.equal(b(), 10); | ||
a(50); | ||
ReactiveFunction.digest(); | ||
assert.equal(b(), 50); | ||
b(100); | ||
ReactiveFunction.digest(); | ||
assert.equal(a(), 100); | ||
}); | ||
it("Should remove the 'evaluate' function from the output on destroy.", function (){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(); | ||
var rf = ReactiveFunction({ | ||
inputs: [a, b], | ||
output: c, | ||
callback: function (a, b){ | ||
return a + b; | ||
} | ||
}); | ||
assert.equal(typeof c.evaluate, "function"); | ||
rf.destroy(); | ||
assert.equal(typeof c.evaluate, "undefined"); | ||
}); | ||
}); |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
386
0
16020
78