Merge branch 'support-autorun' into devel

This commit is contained in:
Nick Martin
2012-10-12 15:21:48 -07:00
13 changed files with 109 additions and 14 deletions

View File

@@ -22,6 +22,9 @@
For more information on Meteor Accounts, see http://docs.meteor.com/#accounts
* The new function `Meteor.autorun` allows you run any code in a reactive
context. See http://docs.meteor.com/#meteor_autorun
* Arrays and objects can now be stored in the `Session`; mutating the value you
retrieve with `Session.get` does not affect the value in the session.

View File

@@ -2110,6 +2110,34 @@ might think of it as a dynamically scoped ("special") variable. (That
just means that [`run`](#run) sets it, runs some user-supplied code, and
then restores its previous value.)
{{> api_box autorun }}
`Meteor.autorun` allows you to set up your own reactive context, where you can
perform arbitrary actions when dependencies change. For example, you can monitor
a cursor (which is a reactive data source) and aggregate it into a session
variable:
Meteor.autorun(function() {
var oldest = _.max(Monkeys.find().fetch(), function (monkey) {
return monkey.age;
});
if (oldest)
Session.set("oldest", oldest.name);
});
Or you can wait for a session variable to get a certain value, and do something
the first time it does so, using the `stop` handle to prevent further runs:
Meteor.autorun(function(handle) {
if (!Session.equals("shouldAlert", true)) return;
handle.stop();
alert("Oh no!");
});
The function is invoked immediately and — like all reactive
sources — the rerun occurs at the time of the next
[`Meteor.flush`](#meteor_flush).
{{> api_box flush }}
@@ -2143,8 +2171,6 @@ elements are cleaned up by logic that is triggered by context invalidations.
<h2 id="meteor_http"><span>Meteor.http</span></h2>
`Meteor.http` provides an HTTP API on the client and server. To use

View File

@@ -608,6 +608,18 @@ Template.api.current = {
descr: ["The current [`invalidation context`](#context), or `null` if not being called from inside [`run`](#run)."]
};
Template.api.autorun = {
id: "meteor_autorun",
name: "Meteor.autorun(func)",
locus: "Client",
descr: ["Run a function and rerun it whenever its dependencies change. Returns a handle that provides a `stop` method, which will prevent further reruns."],
args: [
{name: "func",
type: "Function",
descr: "The function to run. It receives one argument: the same handle that `Meteor.autorun` returns."}
]
};
Template.api.flush = {
id: "meteor_flush",
name: "Meteor.flush()",

View File

@@ -199,9 +199,10 @@ application with error-prone logic.
These Meteor functions run your code in a reactive context:
* [Templates](#templates)
* [`Meteor.render`](#meteor_render) and [`Meteor.renderList`](#meteor_renderlist)
* [`Meteor.autosubscribe`](#meteor_autosubscribe)
* [Templates](#templates)
* [`Meteor.autorun`](#meteor_autorun)
And the reactive data sources that can trigger changes are:

View File

@@ -223,6 +223,7 @@ var toc = [
{instance: "context", name: "invalidate"}
],
{name: "Meteor.deps.Context.current", id: "current"},
"Meteor.autorun",
"Meteor.flush"
// ],

View File

@@ -207,7 +207,7 @@ Template.circles.rendered = function () {
if (! self.handle) {
d3.select(self.node).append("rect");
self.handle = Meteor._autorun(function () {
self.handle = Meteor.autorun(function () {
var circle = d3.select(self.node).selectAll("circle")
.data(Circles.find({group: data.group}).fetch(),
function (d) { return d._id; });

View File

@@ -83,7 +83,7 @@
var waitUntilLoggedIn = function (test, expect) {
var unblockNextFunction = expect();
var quiesceCallback = function () {
Meteor._autorun(function (handle) {
Meteor.autorun(function (handle) {
if (!Meteor.userLoaded()) return;
handle.stop();
unblockNextFunction();

View File

@@ -17,7 +17,7 @@ if (Meteor.isClient) (function () {
test.equal(Meteor.user().username, someUsername);
});
return function () {
Meteor._autorun(function(handle) {
Meteor.autorun(function(handle) {
if (!Meteor.userLoaded()) return;
handle.stop();
callWhenLoaded();
@@ -70,7 +70,7 @@ if (Meteor.isClient) (function () {
// Set up a reactive context that only refreshes when Meteor.user() is
// invalidated.
var user;
var handle1 = Meteor._autorun(function () {
var handle1 = Meteor.autorun(function () {
user = Meteor.user();
});
// At the beginning, we're not logged in.
@@ -86,7 +86,7 @@ if (Meteor.isClient) (function () {
handle1.stop();
});
var waitForLoaded = expect(function () {
Meteor._autorun(function(handle2) {
Meteor.autorun(function(handle2) {
if (!Meteor.userLoaded()) return;
handle2.stop();
callWhenLoaded();

View File

@@ -55,14 +55,14 @@
Meteor.deps._ContextSet = _ContextSet;
////////// Meteor._autorun
////////// Meteor.autorun
// Run f(). Record its dependencies. Rerun it whenever the
// dependencies change.
//
// Returns an object with a stop() method. Call stop() to stop the
// rerunning. Also passes this object as an argument to f.
Meteor._autorun = function (f) {
Meteor.autorun = function (f) {
var ctx;
var slain = false;
var handle = {

View File

@@ -0,0 +1,46 @@
Tinytest.add('deps - autorun', function (test) {
var listeners = new Meteor.deps._ContextSet;
var x = 0;
var handle = Meteor.autorun(function (handle) {
listeners.addCurrentContext();
++x;
});
test.equal(x, 1);
Meteor.flush();
test.equal(x, 1);
listeners.invalidateAll();
test.equal(x, 1);
Meteor.flush();
test.equal(x, 2);
listeners.invalidateAll();
test.equal(x, 2);
Meteor.flush();
test.equal(x, 3);
listeners.invalidateAll();
// Prevent the function from running further.
handle.stop();
Meteor.flush();
test.equal(x, 3);
listeners.invalidateAll();
Meteor.flush();
test.equal(x, 3);
Meteor.autorun(function (internalHandle) {
listeners.addCurrentContext();
++x;
if (x == 6)
internalHandle.stop();
});
test.equal(x, 4);
listeners.invalidateAll();
Meteor.flush();
test.equal(x, 5);
listeners.invalidateAll();
// Increment to 6 and stop.
Meteor.flush();
test.equal(x, 6);
listeners.invalidateAll();
Meteor.flush();
// Still 6!
test.equal(x, 6);
});

View File

@@ -11,3 +11,9 @@ Package.on_use(function (api, where) {
api.use('underscore', where);
api.add_files(['deps.js', 'deps-utils.js'], where);
});
Package.on_test(function (api) {
api.use('tinytest');
api.use('deps');
api.add_files('deps_tests.js', 'client');
});

View File

@@ -78,7 +78,7 @@
Tinytest.add('session - context invalidation for get', function (test) {
var xGetExecutions = 0;
Meteor._autorun(function () {
Meteor.autorun(function () {
++xGetExecutions;
Session.get('x');
});
@@ -99,7 +99,7 @@
Tinytest.add('session - context invalidation for equals', function (test) {
var xEqualsExecutions = 0;
Meteor._autorun(function () {
Meteor.autorun(function () {
++xEqualsExecutions;
Session.equals('x', 5);
});
@@ -132,7 +132,7 @@
function (test) {
// Make sure the special casing for equals undefined works.
var yEqualsExecutions = 0;
Meteor._autorun(function () {
Meteor.autorun(function () {
++yEqualsExecutions;
Session.equals('y', undefined);
});

View File

@@ -795,7 +795,7 @@ Spark.isolate = function (htmlFunc) {
var range;
var firstRun = true;
var retHtml;
Meteor._autorun(function (handle) {
Meteor.autorun(function (handle) {
if (firstRun) {
retHtml = renderer.annotate(
htmlFunc(), Spark._ANNOTATION_ISOLATE,