From bae1a59af66f689e106e1600cc1f4ecc417892db Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 12 Oct 2012 00:50:35 -0700 Subject: [PATCH 1/3] Publicize Meteor._autorun as Meteor.autorun (with docs and tests). --- History.md | 3 ++ docs/client/api.html | 25 ++++++++++ docs/client/api.js | 12 +++++ docs/client/concepts.html | 3 +- docs/client/docs.js | 3 +- .../template-demo/client/template-demo.js | 2 +- packages/accounts-password/email_tests.js | 2 +- packages/accounts-password/passwords_tests.js | 6 +-- packages/deps/deps-utils.js | 4 +- packages/deps/deps_tests.js | 46 +++++++++++++++++++ packages/deps/package.js | 6 +++ packages/session/session_tests.js | 6 +-- packages/spark/spark.js | 2 +- 13 files changed, 107 insertions(+), 13 deletions(-) create mode 100644 packages/deps/deps_tests.js diff --git a/History.md b/History.md index 3c86fddf58..3add885bb3 100644 --- a/History.md +++ b/History.md @@ -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. diff --git a/docs/client/api.html b/docs/client/api.html index 958378749d..97f48e02ae 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1934,7 +1934,32 @@ flush keeps flushing until everything is totally settled. The DOM elements are cleaned up by logic that is triggered by context invalidations. +{{> 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!"); + }); + +Like all reactive sources, the rerun occurs at the time of the next +[`Meteor.flush`](#meteor_flush).

Meteor.http

diff --git a/docs/client/api.js b/docs/client/api.js index 80f04a4631..7016857fca 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -615,6 +615,18 @@ Template.api.flush = { descr: ["Ensure that any reactive updates have finished. Allow auto-updating DOM element to be cleaned up if they are offscreen."] }; +Template.api.autorun = { + id: "meteor_autorun", + name: "Meteor.autorun(func)", + locus: "Client", + descr: ["Runs a function immediately, and reruns 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."} + ] +}; + // writeFence // invalidationCrossbar diff --git a/docs/client/concepts.html b/docs/client/concepts.html index ca1a4a4640..51c69c9d4d 100644 --- a/docs/client/concepts.html +++ b/docs/client/concepts.html @@ -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: diff --git a/docs/client/docs.js b/docs/client/docs.js index f7b096b7e4..c97df192e2 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -223,7 +223,8 @@ var toc = [ {instance: "context", name: "invalidate"} ], {name: "Meteor.deps.Context.current", id: "current"}, - "Meteor.flush" + "Meteor.flush", + "Meteor.autorun" // ], // "Environment Variables", [ diff --git a/examples/other/template-demo/client/template-demo.js b/examples/other/template-demo/client/template-demo.js index 92a490cafa..7959a9ca2c 100644 --- a/examples/other/template-demo/client/template-demo.js +++ b/examples/other/template-demo/client/template-demo.js @@ -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; }); diff --git a/packages/accounts-password/email_tests.js b/packages/accounts-password/email_tests.js index fc7da6b80e..f3ff35701e 100644 --- a/packages/accounts-password/email_tests.js +++ b/packages/accounts-password/email_tests.js @@ -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(); diff --git a/packages/accounts-password/passwords_tests.js b/packages/accounts-password/passwords_tests.js index 99177c2474..4f8e11e19c 100644 --- a/packages/accounts-password/passwords_tests.js +++ b/packages/accounts-password/passwords_tests.js @@ -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(); diff --git a/packages/deps/deps-utils.js b/packages/deps/deps-utils.js index 569d2deadd..cf39e39d6a 100644 --- a/packages/deps/deps-utils.js +++ b/packages/deps/deps-utils.js @@ -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 = { diff --git a/packages/deps/deps_tests.js b/packages/deps/deps_tests.js new file mode 100644 index 0000000000..63e4e718ef --- /dev/null +++ b/packages/deps/deps_tests.js @@ -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); +}); diff --git a/packages/deps/package.js b/packages/deps/package.js index f21ff22073..a38a57f69e 100644 --- a/packages/deps/package.js +++ b/packages/deps/package.js @@ -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'); +}); diff --git a/packages/session/session_tests.js b/packages/session/session_tests.js index 0a750b4086..d148914a1f 100644 --- a/packages/session/session_tests.js +++ b/packages/session/session_tests.js @@ -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); }); diff --git a/packages/spark/spark.js b/packages/spark/spark.js index ee4d0b69be..861bf417c1 100644 --- a/packages/spark/spark.js +++ b/packages/spark/spark.js @@ -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, From c7b5f51420017e66e1f0e72ff29aa1e51a57bb37 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Fri, 12 Oct 2012 14:55:32 -0700 Subject: [PATCH 2/3] Re-order autorun and flush. --- docs/client/api.html | 54 ++++++++++++++++++++++---------------------- docs/client/api.js | 14 ++++++------ docs/client/docs.js | 4 ++-- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 97f48e02ae..0e72dff8bf 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1902,6 +1902,33 @@ 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!"); + }); + +Like all reactive sources, the rerun occurs at the time of the next +[`Meteor.flush`](#meteor_flush). + {{> api_box flush }} @@ -1934,33 +1961,6 @@ flush keeps flushing until everything is totally settled. The DOM elements are cleaned up by logic that is triggered by context invalidations. -{{> 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!"); - }); - -Like all reactive sources, the rerun occurs at the time of the next -[`Meteor.flush`](#meteor_flush). -

Meteor.http

diff --git a/docs/client/api.js b/docs/client/api.js index 7016857fca..804d72ae5e 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -608,13 +608,6 @@ Template.api.current = { descr: ["The current [`invalidation context`](#context), or `null` if not being called from inside [`run`](#run)."] }; -Template.api.flush = { - id: "meteor_flush", - name: "Meteor.flush()", - locus: "Client", - descr: ["Ensure that any reactive updates have finished. Allow auto-updating DOM element to be cleaned up if they are offscreen."] -}; - Template.api.autorun = { id: "meteor_autorun", name: "Meteor.autorun(func)", @@ -627,6 +620,13 @@ Template.api.autorun = { ] }; +Template.api.flush = { + id: "meteor_flush", + name: "Meteor.flush()", + locus: "Client", + descr: ["Ensure that any reactive updates have finished. Allow auto-updating DOM element to be cleaned up if they are offscreen."] +}; + // writeFence // invalidationCrossbar diff --git a/docs/client/docs.js b/docs/client/docs.js index c97df192e2..68bf32d2ad 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -223,8 +223,8 @@ var toc = [ {instance: "context", name: "invalidate"} ], {name: "Meteor.deps.Context.current", id: "current"}, - "Meteor.flush", - "Meteor.autorun" + "Meteor.autorun", + "Meteor.flush" // ], // "Environment Variables", [ From ada7d811124319e4aa89f84e7228055ae779b456 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Fri, 12 Oct 2012 15:20:27 -0700 Subject: [PATCH 3/3] Minor tweaks to docs. --- docs/client/api.html | 3 ++- docs/client/api.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 0e72dff8bf..202b0e7cb6 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1926,7 +1926,8 @@ the first time it does so, using the `stop` handle to prevent further runs: alert("Oh no!"); }); -Like all reactive sources, the rerun occurs at the time of the next +The function is invoked immediately and — like all reactive +sources — the rerun occurs at the time of the next [`Meteor.flush`](#meteor_flush). diff --git a/docs/client/api.js b/docs/client/api.js index 804d72ae5e..06bd2daf93 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -612,7 +612,7 @@ Template.api.autorun = { id: "meteor_autorun", name: "Meteor.autorun(func)", locus: "Client", - descr: ["Runs a function immediately, and reruns it whenever its dependencies change. Returns a handle that provides a `stop` method, which will prevent further reruns."], + 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",