130 lines
13 KiB
JavaScript
130 lines
13 KiB
JavaScript
|
(function () {
|
||
|
|
||
|
/* Imports */
|
||
|
var Meteor = Package.meteor.Meteor;
|
||
|
var Tracker = Package.tracker.Tracker;
|
||
|
var Deps = Package.tracker.Deps;
|
||
|
|
||
|
/* Package-scope variables */
|
||
|
var ReactiveVar;
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// packages/reactive-var/reactive-var.js //
|
||
|
// //
|
||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
/* // 1
|
||
|
* ## [new] ReactiveVar(initialValue, [equalsFunc]) // 2
|
||
|
* // 3
|
||
|
* A ReactiveVar holds a single value that can be get and set, // 4
|
||
|
* such that calling `set` will invalidate any Computations that // 5
|
||
|
* called `get`, according to the usual contract for reactive // 6
|
||
|
* data sources. // 7
|
||
|
* // 8
|
||
|
* A ReactiveVar is much like a Session variable -- compare `foo.get()` // 9
|
||
|
* to `Session.get("foo")` -- but it doesn't have a global name and isn't // 10
|
||
|
* automatically migrated across hot code pushes. Also, while Session // 11
|
||
|
* variables can only hold JSON or EJSON, ReactiveVars can hold any value. // 12
|
||
|
* // 13
|
||
|
* An important property of ReactiveVars, which is sometimes the reason // 14
|
||
|
* to use one, is that setting the value to the same value as before has // 15
|
||
|
* no effect, meaning ReactiveVars can be used to absorb extra // 16
|
||
|
* invalidations that wouldn't serve a purpose. However, by default, // 17
|
||
|
* ReactiveVars are extremely conservative about what changes they // 18
|
||
|
* absorb. Calling `set` with an object argument will *always* trigger // 19
|
||
|
* invalidations, because even if the new value is `===` the old value, // 20
|
||
|
* the object may have been mutated. You can change the default behavior // 21
|
||
|
* by passing a function of two arguments, `oldValue` and `newValue`, // 22
|
||
|
* to the constructor as `equalsFunc`. // 23
|
||
|
* // 24
|
||
|
* This class is extremely basic right now, but the idea is to evolve // 25
|
||
|
* it into the ReactiveVar of Geoff's Lickable Forms proposal. // 26
|
||
|
*/ // 27
|
||
|
// 28
|
||
|
/** // 29
|
||
|
* @class // 30
|
||
|
* @instanceName reactiveVar // 31
|
||
|
* @summary Constructor for a ReactiveVar, which represents a single reactive variable. // 32
|
||
|
* @locus Client // 33
|
||
|
* @param {Any} initialValue The initial value to set. `equalsFunc` is ignored when setting the initial value. // 34
|
||
|
* @param {Function} [equalsFunc] Optional. A function of two arguments, called on the old value and the new value whenever the ReactiveVar is set. If it returns true, no set is performed. If omitted, the default `equalsFunc` returns true if its arguments are `===` and are of type number, boolean, string, undefined, or null.
|
||
|
*/ // 36
|
||
|
ReactiveVar = function (initialValue, equalsFunc) { // 37
|
||
|
if (! (this instanceof ReactiveVar)) // 38
|
||
|
// called without `new` // 39
|
||
|
return new ReactiveVar(initialValue, equalsFunc); // 40
|
||
|
// 41
|
||
|
this.curValue = initialValue; // 42
|
||
|
this.equalsFunc = equalsFunc; // 43
|
||
|
this.dep = new Tracker.Dependency; // 44
|
||
|
}; // 45
|
||
|
// 46
|
||
|
ReactiveVar._isEqual = function (oldValue, newValue) { // 47
|
||
|
var a = oldValue, b = newValue; // 48
|
||
|
// Two values are "equal" here if they are `===` and are // 49
|
||
|
// number, boolean, string, undefined, or null. // 50
|
||
|
if (a !== b) // 51
|
||
|
return false; // 52
|
||
|
else // 53
|
||
|
return ((!a) || (typeof a === 'number') || (typeof a === 'boolean') || // 54
|
||
|
(typeof a === 'string')); // 55
|
||
|
}; // 56
|
||
|
// 57
|
||
|
/** // 58
|
||
|
* @summary Returns the current value of the ReactiveVar, establishing a reactive dependency. // 59
|
||
|
* @locus Client // 60
|
||
|
*/ // 61
|
||
|
ReactiveVar.prototype.get = function () { // 62
|
||
|
if (Tracker.active) // 63
|
||
|
this.dep.depend(); // 64
|
||
|
// 65
|
||
|
return this.curValue; // 66
|
||
|
}; // 67
|
||
|
// 68
|
||
|
/** // 69
|
||
|
* @summary Sets the current value of the ReactiveVar, invalidating the Computations that called `get` if `newValue` is different from the old value.
|
||
|
* @locus Client // 71
|
||
|
* @param {Any} newValue // 72
|
||
|
*/ // 73
|
||
|
ReactiveVar.prototype.set = function (newValue) { // 74
|
||
|
var oldValue = this.curValue; // 75
|
||
|
// 76
|
||
|
if ((this.equalsFunc || ReactiveVar._isEqual)(oldValue, newValue)) // 77
|
||
|
// value is same as last time // 78
|
||
|
return; // 79
|
||
|
// 80
|
||
|
this.curValue = newValue; // 81
|
||
|
this.dep.changed(); // 82
|
||
|
}; // 83
|
||
|
// 84
|
||
|
ReactiveVar.prototype.toString = function () { // 85
|
||
|
return 'ReactiveVar{' + this.get() + '}'; // 86
|
||
|
}; // 87
|
||
|
// 88
|
||
|
ReactiveVar.prototype._numListeners = function() { // 89
|
||
|
// Tests want to know. // 90
|
||
|
// Accesses a private field of Tracker.Dependency. // 91
|
||
|
var count = 0; // 92
|
||
|
for (var id in this.dep._dependentsById) // 93
|
||
|
count++; // 94
|
||
|
return count; // 95
|
||
|
}; // 96
|
||
|
// 97
|
||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
}).call(this);
|
||
|
|
||
|
|
||
|
/* Exports */
|
||
|
if (typeof Package === 'undefined') Package = {};
|
||
|
Package['reactive-var'] = {
|
||
|
ReactiveVar: ReactiveVar
|
||
|
};
|
||
|
|
||
|
})();
|
||
|
|
||
|
//# sourceMappingURL=reactive-var.js.map
|