Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

reactive-function

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

reactive-function - npm Package Compare versions

Comparing version 0.2.0 to 0.3.0

85

index.js

@@ -19,3 +19,4 @@ var ReactiveProperty = require("reactive-property");

// A lookup table for properties based on their assigned id.
// A map for properties based on their assigned id.
// Keys are property ids, values are reactive properties.
var propertiesById = {};

@@ -34,45 +35,38 @@

//ReactiveFunction(dependencies... , callback)
function ReactiveFunction(){
function ReactiveFunction(options){
// TODO check for wrong number of args
var inputs = options.inputs;
var callback = options.callback;
var output = options.output;
// Parse arguments.
var args = Array.apply(null, arguments);
var dependencies = args.splice(1);
var callback = args[0];
// TODO check for correct type for inputs
// TODO check for correct type for callback
// This stores the output value.
var value;
// The returned reactive function acts as a getter (not a setter).
var reactiveFunction = function (){
if(arguments.length > 0){ throw errors.notASetter; }
return value;
};
var reactiveFunction = {};
// This gets invoked during a digest, after dependencies have been evaluated.
reactiveFunction.evaluate = function (){
value = callback.apply(null, dependencies.map(function (dependency){
return dependency();
}));
// This gets invoked during a digest, after inputs have been evaluated.
output.evaluate = function (){
var values = inputs.map(function (input){
return input();
});
if(defined(values)){
output(callback.apply(null, values));
}
};
// Assign node ids to dependencies and the reactive function.
assignId(reactiveFunction);
dependencies.forEach(assignId);
// Assign node ids to inputs and the reactive function.
assignId(output);
inputs.forEach(assignId);
// Set up edges in the graph from each dependency.
dependencies.forEach(function (dependency){
graph.addEdge(dependency.id, reactiveFunction.id);
// Set up edges in the graph from each input.
inputs.forEach(function (input){
graph.addEdge(input.id, output.id);
});
// Compute which of the dependencies are properties (excludes reactive functions).
var properties = dependencies
.filter(function (dependency){
return dependency.on;
});
// Add change listeners to each dependency that is a property.
var listeners = properties
// Add change listeners to each input property.
var listeners = inputs
.map(function (property){

@@ -89,12 +83,10 @@ return property.on(function (){

// TODO test
// Remove change listeners from dependencies that are properties.
// Remove change listeners from inputs.
listeners.forEach(function (listener, i){
properties[i].off(listener);
inputs[i].off(listener);
});
// TODO test
// Remove the edges that were added to the dependency graph.
dependencies.forEach(function (dependency){
graph.removeEdge(dependency.id, reactiveFunction.id);
inputs.forEach(function (input){
graph.removeEdge(input.id, output.id);
});

@@ -109,2 +101,3 @@

ReactiveFunction.digest = function (){
graph

@@ -115,5 +108,6 @@ .topologicalSort(Object.keys(changedNodes))

})
.forEach(function (reactiveFunction){
reactiveFunction.evaluate();
.forEach(function (property){
property.evaluate();
});
changedNodes = {};

@@ -138,2 +132,9 @@ };

// Returns true if all elements of the given array are defined.
function defined(arr){
return !arr.some(function (d){
return typeof d === "undefined" || d === null;
});
}
module.exports = ReactiveFunction;
{
"name": "reactive-function",
"version": "0.2.0",
"version": "0.3.0",
"description": "A library for managing data flows and changing state.",

@@ -5,0 +5,0 @@ "main": "index.js",

# reactive-function
A library for managing data flows and changing state.
# Usage
If you are using NPM, install this package with:
`npm install reactive-function`
Require it in your code like this:
```javascript
var ReactiveFunction = require("reactive-function");
```
This library is designed to work with [reactive-property](https://github.com/curran/reactive-property), you'll need that too.
```javascript
var ReactiveProperty = require("reactive-property");
```
Let's say you have two reactive properties to represent someone's name.
```javascript
var firstName = ReactiveProperty("Jane");
var lastName = ReactiveProperty("Smith");
```
You can define a reactive function that depends on those two properties like this.
```javascript
var fullName = ReactiveFunction(function (first, last){
return first + " " + last;
}, firstName, lastName);
```
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.
To force a synchronous evaluation of all reactive functions whose dependencies have updated, you can call:
```javascript
ReactiveFunction.digest();
```
Now you can access the computed value of the reactive function by invoking it.
```javascript
console.log(fullName()); // Prints "Jane Smith"
```
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);
});
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
Suppose you have some reactive properties that change, such as `width` and `height`.
```javascript
var my = {
width: ReactiveProperty(),
height: ReactiveProperty()
};
```
Suppose also that you have a function that resizes an SVG element that depends on those two properties.
```javascript
var svg = d3.select("body").append("svg");
function render(){
svg
.attr("width", my.width())
.attr("height", my.height());
}
```
How can you invoke the `render function` when `my.width` and `my.height` change?
Related work:
* [ReactiveJS](https://github.com/mattbaker/Reactive.js)

@@ -13,9 +13,16 @@ // Unit tests for reactive-function.

it("Should depend on two reactive properties.", function () {
var a = ReactiveProperty(5);
var b = ReactiveProperty(10);
var c = ReactiveFunction(function (a, b){
return a + b;
}, a, b);
var c = ReactiveProperty();
ReactiveFunction({
inputs: [a, b],
output: c,
callback: function (a, b){
return a + b;
}
});
ReactiveFunction.digest();

@@ -30,10 +37,21 @@ assert.equal(c(), 15);

var d = ReactiveFunction(function (a){
return a * 2;
}, a);
var d = ReactiveProperty();
var e = ReactiveProperty();
var e = ReactiveFunction(function (a, b, c){
return a + b + c;
}, a, b, c);
ReactiveFunction({
inputs: [a],
output: d,
callback: function (a){
return a * 2;
}
});
ReactiveFunction({
inputs: [a, b, c],
output: e,
callback: function (a, b, c){
return a + b + c;
}
});
ReactiveFunction.digest();

@@ -45,13 +63,25 @@

it("Should depend on a reactive function.", function () {
it("Should depend on a reactive function output.", function () {
var a = ReactiveProperty(5);
var b = ReactiveFunction(function (a){
return a * 2;
}, a);
var b = ReactiveProperty();
var c = ReactiveProperty();
var c = ReactiveFunction(function (b){
return b / 2;
}, b);
ReactiveFunction({
inputs: [a],
output: b,
callback: function (a){
return a * 2;
}
});
ReactiveFunction({
inputs: [b],
output: c,
callback: function (b){
return b / 2;
}
});
ReactiveFunction.digest();

@@ -61,14 +91,26 @@ assert.equal(c(), 5);

it("Should depend on a reactive property and a reactive function.", function () {
it("Should depend on a property and a reactive function output.", function () {
var a = ReactiveProperty(5);
var b = ReactiveProperty(10);
var c = ReactiveFunction(function (a){
return a * 2;
}, a);
var c = ReactiveProperty();
var d = ReactiveProperty();
var d = ReactiveFunction(function (b, c){
return b + c;
}, b, c);
ReactiveFunction({
inputs: [a],
output: c,
callback: function (a){
return a * 2;
}
});
ReactiveFunction({
inputs: [b, c],
output: d,
callback: function (b, c){
return b + c;
}
});
ReactiveFunction.digest();

@@ -88,7 +130,12 @@ assert.equal(d(), 20);

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);
var b = ReactiveProperty();
var c = ReactiveProperty();
var d = ReactiveProperty();
var e = ReactiveProperty();
ReactiveFunction({ inputs: [a], output: b, callback: function (a){ return a * 2; } });
ReactiveFunction({ inputs: [b], output: c, callback: function (b){ return b + 5; } });
ReactiveFunction({ inputs: [a], output: d, callback: function (a){ return a * 3; } });
ReactiveFunction({ inputs: [c, d], output: e, callback: function (c, d){ return c + d; } });
ReactiveFunction.digest();

@@ -99,13 +146,20 @@

it("Should throw an error if attempting to set the value directly.", function () {
var a = ReactiveProperty(5);
// Not sure if this is really necessary.
// It would be a nice safeguard but would clutter the code.
//it("Should throw an error if attempting to set the value directly.", function () {
// var a = ReactiveProperty(5);
// var b = ReactiveProperty();
var b = ReactiveFunction(function (a){
return a * 2;
}, a);
// ReactiveFunction({
// inputs: [a],
// output: b,
// callback: function (a){
// return a * 2;
// }
// });
assert.throws(function (){
b(5);
});
});
// assert.throws(function (){
// b(5);
// });
//});

@@ -115,6 +169,13 @@ it("Should clear changed nodes on digest.", function () {

var a = ReactiveProperty(5);
var b = ReactiveFunction(function (a){
numInvocations++;
return a * 2;
}, a);
var b = ReactiveProperty();
ReactiveFunction({
inputs: [a],
output: b,
callback: function (a){
numInvocations++;
return a * 2;
}
});
ReactiveFunction.digest();

@@ -128,6 +189,11 @@ ReactiveFunction.digest();

var b = ReactiveProperty(10);
var c = ReactiveProperty();
var c = ReactiveFunction(function (a, b){
return a + b;
}, a, b);
ReactiveFunction({
inputs: [a, b],
output: c,
callback: function (a, b){
return a + b;
}
});

@@ -139,2 +205,97 @@ setTimeout(function (){

});
it("Should remove listeners on destroy.", function (done){
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;
}
});
// Wait until after the first auto-digest.
setTimeout(function (){
rf.destroy();
// Without the call to destroy(), this would trigger a digest.
a(20);
// Give time for an auto-digest to occur.
setTimeout(function (){
// Confirm that a digest did not occur.
assert.equal(c(), 15);
done();
}, 0);
}, 0);
});
it("Should remove edges 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;
}
});
ReactiveFunction.digest();
assert.equal(c(), 15);
rf.destroy();
a(20);
ReactiveFunction.digest();
assert.equal(c(), 15);
});
it("Should not invoke if an input is undefined.", function (){
var numInvocations = 0;
var a = ReactiveProperty();
var b = ReactiveProperty(10);
var c = ReactiveProperty();
ReactiveFunction({
inputs: [a, b],
output: c,
callback: function (a, b){
numInvocations++;
return a + b;
}
});
ReactiveFunction.digest();
assert.equal(numInvocations, 0);
});
it("Should not invoke if an input is null.", function (){
var numInvocations = 0;
var a = ReactiveProperty(null);
var b = ReactiveProperty(10);
var c = ReactiveProperty();
ReactiveFunction({
inputs: [a, b],
output: c,
callback: function (a, b){
numInvocations++;
return a + b;
}
});
ReactiveFunction.digest();
assert.equal(numInvocations, 0);
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc