UNPKG

relu-core

Version:
462 lines (354 loc) 9.74 kB
# rp (ReactiveProperty) rp = value containers + computed values + automatic dependency tracking * works well with objects and arrays * including common computations ## Basics ``` javascript var rp = require("rp"); // Creating RPs var x = rp.variable(1); // read/writable var y = rp.const(2); // only readable // Reading values x() === 1 y() === 2 y.get() === 2 // y() is a shortcut for y.get() // Computed RPs var xy = rp.computed(function() { return x() + y(); // Dependencies }); xy() === 3 // Writing values x.set(5); x() === 5 // Computed RPs are updated xy() === 6 ``` ## Reading operations Shortcuts for common computations. They all return a RP so they are chainable. ``` javascript var x = rp.variable(Math.PI); var y = rp.variable(123); x.computed(function(x) { return x * x + 2 }); // => 11.869... x.inversed() // => false x.asBool() // => true x.asNumber() // => 3.14... x.asString() // => "3.14..." x.rounded() // => 3 x.floored() // => 3 x.ceiled() // => 4 x.plus(y) // => 126.14... y.minus(x) // => 119.86... y.dividedBy(x) // => 39.15... x.multipliedBy(y) // => 386.41... x.equals(y) // false x.comparedTo(y) // -1 x.comparedTo(y, function(a,b) {...}) // -1 rp.variable("test123TEST").split(y.asString()) // => ["test", "TEST"] rp.variable("[1,2]").parsed() // => [1,2] rp.variable([1,2]).stringified() // => "[1,2]" ``` ``` javascript // Array operations var z = rp.variable([1,2,3,4]); var a = rp.variable(1); z.map(function(item) { return item * (a() + 1); }) // => [2, 4, 6, 8] z.filter(function(item) { return item % 2 === a(); }) // => [1, 3] z.reduce(function(a, b) { return a + b; }) // => 10 z.slice(a, 2) // => [2, 3] z.indexOf(a) // => 0 z.size() // => 4 ``` ## Writing operations ``` javascript var x = rp.variable(1); var y = rp.variable(2); var z = rp.variable([1,2,3]); x.increment(); // x = x + 1 x.decrement(); // x = x - 1 x.add(3); // x = x + 3; x.subtract(6); // x = x - 6; x.multiply(10); // x = x * 10; x.divide(y()); // x = x / y; z.push(y()); z.unshift(y()); z.shift(); // => 2 z.pop(); // => 2 z.splice(1, 2, "test"); z(2).remove(); ``` ## Helpers ``` javascript var z = rp.variable([1,2,3]); z.forEach(function(item, idx) { console.log("at " + idx + " is " + item()); }); z.log("z"); // logs all changes with the name "z" z.readonly(); // readonly version of anything z.fixed(); // cannot be updated, but elements and properties can be changed z.fixedArray(); // cannot be updated, neither can elements be added or removed // But values can be changed z.isConst() === false rp.const([1,2,3]).map(function(i) { return i * i; }).isConst() === true // Returns true is the value is constant ``` ## Objects and Arrays ``` javascript var o = rp.variable({a: {b: [{c: 1}, {d: 2}]}}); // Getting RP of properties and elements var a = o("a"); var b = o("a", "b"); var c = a("b", 0, "c"); var d = o("a", "b", 1, "d"); var p = o("p", "p", "p"); c() === 1 d() === 2 // not existing properties are undefined p() === undefined // RP move with array modifications b.shift(); // d is now o("a", "b", 0, "d") d() === 2 // removed elements become undefined c() === undefined // you get the same instance for a property or element o("a", "b", 0, "d") === d ``` ## Memory management ``` javascript // Every RP is only valid until the next tick var x = rp.variable(1); x() // fine process.nextTick(function() { x() // :( }); // To make them longer valid put them into a scope var x = rp.variable(2).scope(); // into the global scope // or into a private scope var s = rp.scope(function() { var x = rp.variable(4).scope(); }); // continue to run code in this scope s.run(function() { var y = rp.variable(6).scope(); // nest scopes rp.scope(function() { var zz = rp.variable(8); var z = zz.plus(2).multipliedBy(7); // scope computed RPs, // dependencies will stay too z.scope(); }); }); s.scope(function() { var a = rp.const(1).plus(1).scope(); }); // Scope[ x, y, Scope[ z ], Scope[ a ] ] // unref the scope anytime you want // nested scopes are also removed s.unref(); // Computed forms a scope too // but it's not part of the parent scope // It's handled by the normal RP lifetime var x = rp.computed(function() { var y = rp.variable().scope(); }); // x is valid until the next tick, so is y ``` ## Handled get Read the value, but handle changes by some function. ``` javascript rp.prototype.getHandled(changedHandler(value)) rp.prototype.getHandled( updatedHandler(value), addedHandler(idx, item), removedHandler(idx, item) ) ``` A handled get is active as long as the current scope is active. Example: exchange the HTML code of a HTMLElement: ``` javascript var x = rp.variable("Text"); var tag = rp.variable("div"); var html = x.computed(function(x) { return "<h1>" + x + "</h1>"; }); var element = rp.computed(function() { var element = document.createElement(tag()); element.innerHTML = html.getHandled(function(html) { // Changes of html are handled here element.innerHTML = html; }); return element; }); var el1 = element(); x.set("New text"); element() === el1; tag.set("pre"); element() !== el1; ``` ## Atomic ``` javascript var x = rp.variable("test"); var counter = 0; var y = x.computed(function(x) { return x + (++counter); }); y() === "test1" // atomic delay updates to the end of the block // can boost performance for many changes rp.atomic(function() { x.set("a"); x.set("b"); x.set("c"); }); y() === "c2" ``` ## Delegated ``` javascript var a = rp.variable(1); var b = rp.variable(2); var which = rp.variable(false); // while computed RPs provide only read access var y = which.computed(function(which) { return which ? a() : b() }); // delegated RPs can provide read/write access // to a dynamically choosen RP var ref = which.computed(function(which) { return which ? a : b }); var x = rp.delegated(ref); // or: var x = ref.delegated(); // or: var x = rp.delegated(function() { return which() ? a : b; }); x() === 2 x.set(4); b() === 4 which.set(true); x() === 1 s.set(9); a() === 9 ``` ## Two-way computions ``` javascript var x = rp.variable(1); var y = x.computed( function(x) { return x * 2; }, function(y) { return y / 2; } ); y() === 2 y.set(5); x() === 2.5 x.set(2); y() === 4 ``` ### Two-way operations ``` javascript var x = rp.variable(123); var str1 = x.asIntString(); var str2 = x.asFloatString(); str1() === "123" str1.set("456"); x() === 456 x.set(789); str1() === "789" str2() === "789" str2.set("1.23"); x() === 1.23 ``` ### Attributes Every RP can have attributes. One can use them to exchange user data. Each attribute is a `rp.variable`. ``` javascript var x = rp.variable({ a: 1 }); function validate() { var a = x("a"); a.attr("valid").set(a() % 2 === 0); } validate(); var aValid = x("a").attr("valid"); aValid() === false; x("a").set(2); aValid() === true; x("a").hasAttr("valid") === true; x.hasAttr("valid") === false; ``` ## Internals All works with 5 events: * `changed`: The value changed * `updated`: The primitive value changed or the reference changed * `added`: An item is added to the array * `removed`: An item is removed from the array * `nested`: A event occured on nested stuff ``` javascript var a = rp.variable(/test/); var b = rp.variable([1,2]); var c = rp.variable({a: 1}); var d = rp.variable({a: {b: [1]}}); a.set(123); // a updated(123, /test/) // a changed() a.set(123); // no events b.push(3); // b added(2/*idx*/, 3/*value*/) // b changed() b.set([]); // b updated([], [1,2,3]) // b changed c("a").set(2); // c nested("updated", ["a"], 2, 1); // c changed c.set({a: 2}); // c updated({a: 2}, {a: 2}); // c changed d("a", "b").push(2); // c nested("added", ["a", "b"], 2); // d changed d("a", "b", 1).set(3); // c nested("updated", ["a", "b", 1], 3); // d changed ``` The nested event is not used internally, can be used to capture all updates on objects and arrays. Computed values listen on "changed", "updated", "added" and "removed" and update their values. By design they could do this very clever: ``` javascript var x = rp.variable([1,2,3,4]); var y = x.filter(function(item) { return item % 2 === 0 }).map(function(item) { return item * item }); // y() is now [4, 16] x.push(5); // no events for y x.push(6) // y added(2, 36) ``` But it's work on progress for some function. (They currently update the whole array, where a bunch of "added" or "removed" events would do it better.) ``` javascript // You can listen on the events x.onceChanged(function() {}); x.onChanged(function() {}); x.onUpdated(function() {}); x.onAdded(function() {}); x.onRemoved(function() {}); x.onceDisposed(function() {}); x.removeOnceChangedListener(fn); x.removeChangedListener(fn); // ... x.removeDisposedListener(fn); ``` Internally each RP uses reference counting to know when to dispose itself. At creation they get one reference that is removed at nextTick. Disposing means to remove event listener from RPs it depend on. With `x.ref()` and `x.unref()` you can add/remove references, but there is no need to do this if you don't want to write your own RP. Better use `x.scope()` and let the scope manage your RPs. ## TODO # Test status [![Build Status](https://travis-ci.org/sokra/rp.png)](https://travis-ci.org/sokra/rp) # License MIT