diff --git a/packages/blaze/builtins.js b/packages/blaze/builtins.js index 9217d7f7ea..fda8c437ef 100644 --- a/packages/blaze/builtins.js +++ b/packages/blaze/builtins.js @@ -49,7 +49,7 @@ Blaze.Each = function (argFunc, contentFunc, elseFunc) { var eachView = Blaze.View('each', function () { var subviews = this.initialSubviews; this.initialSubviews = null; - if (this.isCreatedForExpansion) { + if (this._isCreatedForExpansion) { this.expandedValueDep = new Deps.Dependency; this.expandedValueDep.depend(); } diff --git a/packages/blaze/template.js b/packages/blaze/template.js index f3c6ee0799..b6091b3d2f 100644 --- a/packages/blaze/template.js +++ b/packages/blaze/template.js @@ -42,7 +42,10 @@ Template.prototype.constructView = function (contentFunc, elseFunc) { elseFunc ? new Template('(elseBlock)', elseFunc) : null); if (self.__eventMaps || typeof self.events === 'object') { - view.onMaterialized(function () { + view.onViewRendered(function () { + if (view.renderCount !== 1) + return; + if (! self.__eventMaps.length && typeof self.events === "object") { // Provide limited back-compat support for `.events = {...}` // syntax. Pass `template.events` to the original `.events(...)` @@ -86,8 +89,22 @@ Template.prototype.constructView = function (contentFunc, elseFunc) { } if (self.rendered) { - view.onRendered(function () { - self.rendered.call(view.templateInstance()); + var callRendered = function () { + Deps.afterFlush(function () { + if (! view.isDestroyed) { + Blaze._withCurrentView(view, function () { + self.rendered.call(view.templateInstance()); + }); + } + }); + }; + view.onViewRendered(function onViewRendered() { + if (view.isDestroyed) + return; + if (! view.domrange.isAttached) + view.domrange.onAttached(callRendered); + else + callRendered(); }); } diff --git a/packages/blaze/view.js b/packages/blaze/view.js index f454cb95e4..fe8e43eb73 100644 --- a/packages/blaze/view.js +++ b/packages/blaze/view.js @@ -47,7 +47,6 @@ Blaze.View = function (name, render) { this._callbacks = { created: null, - materialized: null, rendered: null, destroyed: null }; @@ -56,9 +55,9 @@ Blaze.View = function (name, render) { // and also may help Chrome optimize the code by keeping // the View object from changing shape too much. this.isCreated = false; - this.isCreatedForExpansion = false; + this._isCreatedForExpansion = false; this.isDestroyed = false; - this.isInRender = false; + this._isInRender = false; this.parentView = null; this.domrange = null; @@ -71,11 +70,7 @@ Blaze.View.prototype.onViewCreated = function (cb) { this._callbacks.created = this._callbacks.created || []; this._callbacks.created.push(cb); }; -Blaze.View.prototype.onMaterialized = function (cb) { - this._callbacks.materialized = this._callbacks.materialized || []; - this._callbacks.materialized.push(cb); -}; -Blaze.View.prototype.onRendered = function (cb) { +Blaze.View.prototype.onViewRendered = function (cb) { this._callbacks.rendered = this._callbacks.rendered || []; this._callbacks.rendered.push(cb); }; @@ -102,7 +97,7 @@ Blaze.View.prototype.onViewDestroyed = function (cb) { /// of the View (as in Blaze.With) should be started from an onViewCreated /// callback. Autoruns that update the DOM should be started /// from either onViewCreated (guarded against the absence of -/// view.domrange), onMaterialized, or onRendered. +/// view.domrange), or onViewRendered. Blaze.View.prototype.autorun = function (f, _inViewScope) { var self = this; @@ -130,7 +125,7 @@ Blaze.View.prototype.autorun = function (f, _inViewScope) { if (! self.isCreated) { throw new Error("View#autorun must be called from the created callback at the earliest"); } - if (this.isInRender) { + if (this._isInRender) { throw new Error("Can't call View#autorun from inside render(); try calling it from the created or rendered callback"); } if (Deps.active) { @@ -164,7 +159,7 @@ Blaze._createView = function (view, parentView, forExpansion) { view.parentView = (parentView || null); view.isCreated = true; if (forExpansion) - view.isCreatedForExpansion = true; + view._isCreatedForExpansion = true; Blaze._fireCallbacks(view, 'created'); }; @@ -173,32 +168,18 @@ Blaze._materializeView = function (view, parentView) { Blaze._createView(view, parentView); var domrange; - - var needsRenderedCallback = false; - var scheduleRenderedCallback = function () { - if (needsRenderedCallback && ! view.isDestroyed && - view._callbacks.rendered && view._callbacks.rendered.length) { - Deps.afterFlush(function callRendered() { - if (needsRenderedCallback && ! view.isDestroyed) { - needsRenderedCallback = false; - Blaze._fireCallbacks(view, 'rendered'); - } - }); - } - }; - var lastHtmljs; // We don't expect to be called in a Computation, but just in case, // wrap in Deps.nonreactive. Deps.nonreactive(function () { view.autorun(function doRender(c) { // `view.autorun` sets the current view. + view.renderCount++; + view._isInRender = true; // Any dependencies that should invalidate this Computation come // from this line: - view.renderCount++; - view.isInRender = true; var htmljs = view._render(); - view.isInRender = false; + view._isInRender = false; Deps.nonreactive(function doMaterialize() { var materializer = new Blaze._DOMMaterializer({parentView: view}); @@ -211,10 +192,7 @@ Blaze._materializeView = function (view, parentView) { } else { domrange.setMembers(rangesAndNodes); } - Blaze._fireCallbacks(view, 'materialized'); - needsRenderedCallback = true; - if (! c.firstRun) - scheduleRenderedCallback(); + Blaze._fireCallbacks(view, 'rendered'); } }); lastHtmljs = htmljs; @@ -235,8 +213,6 @@ Blaze._materializeView = function (view, parentView) { element, function teardown() { Blaze._destroyView(view, true /* _skipNodes */); }); - - scheduleRenderedCallback(); }); // tear down the teardown hook @@ -261,11 +237,11 @@ Blaze._materializeView = function (view, parentView) { Blaze._expandView = function (view, parentView) { Blaze._createView(view, parentView, true /*forExpansion*/); - view.isInRender = true; + view._isInRender = true; var htmljs = Blaze._withCurrentView(view, function () { return view._render(); }); - view.isInRender = false; + view._isInRender = false; var result = Blaze._expand(htmljs, view); @@ -315,7 +291,7 @@ Blaze._HTMLJSExpander.def({ // (i.e. we are in its render() method). var currentViewIfRendering = function () { var view = Blaze.currentView; - return (view && view.isInRender) ? view : null; + return (view && view._isInRender) ? view : null; }; Blaze._expand = function (htmljs, parentView) { diff --git a/packages/blaze/view_tests.js b/packages/blaze/view_tests.js index 4e6901220b..c44236c678 100644 --- a/packages/blaze/view_tests.js +++ b/packages/blaze/view_tests.js @@ -10,21 +10,29 @@ if (Meteor.isClient) { }); v.onViewCreated(function () { - buf += 'c'; + buf += 'c' + v.renderCount; + }); + v.onViewRendered(function () { + buf += 'r' + v.renderCount; }); v.onViewDestroyed(function () { - buf += 'd'; + buf += 'd' + v.renderCount; }); test.equal(buf, ''); var div = document.createElement("DIV"); Blaze.insert(Blaze.render(v), div); - test.equal(buf, 'c'); + test.equal(buf, 'c0r1'); test.equal(canonicalizeHtml(div.innerHTML), "foo"); + R.set("bar"); + Deps.flush(); + test.equal(buf, 'c0r1r2'); + test.equal(canonicalizeHtml(div.innerHTML), "bar"); + Blaze.remove(v); - test.equal(buf, 'cd'); + test.equal(buf, 'c0r1r2d2'); test.equal(canonicalizeHtml(div.innerHTML), ""); }); diff --git a/packages/ui/render_tests.js b/packages/ui/render_tests.js index f3c335adef..41416d6c27 100644 --- a/packages/ui/render_tests.js +++ b/packages/ui/render_tests.js @@ -457,58 +457,71 @@ Tinytest.add("ui - render - reactive attributes", function (test) { })(); }); -Tinytest.add("ui - render - views", function (test) { +Tinytest.add("ui - render - templates and views", function (test) { (function () { var counter = 1; var buf = []; - var makeView = function () { - var view = Blaze.View('myView', function () { + var myTemplate = Blaze.Template( + 'myTemplate', + function () { return [String(this.number), (this.number < 3 ? makeView() : HR())]; }); - var number = counter++; + myTemplate.constructView = function (number) { + var view = Template.prototype.constructView.call(this); view.number = number; + return view; + }; - view.onViewCreated(function () { - var parent = Blaze.getParentView(view, 'myView'); - if (parent) { - buf.push('parent of ' + view.number + ' is ' + - parent.number); - } + myTemplate.created = function () { + test.isFalse(Deps.active); + var view = this.view; + var parent = Blaze.getParentView(view, 'myTemplate'); + if (parent) { + buf.push('parent of ' + view.number + ' is ' + + parent.number); + } - buf.push('created ' + Blaze.getCurrentData()); + buf.push('created ' + Blaze.getCurrentData()); + }; + + myTemplate.rendered = function () { + test.isFalse(Deps.active); + var nodeDescr = function (node) { + if (node.nodeType === 8) // comment + return ''; + if (node.nodeType === 3) // text + return node.nodeValue; + + return node.nodeName; + }; + + var view = this.view; + var start = view.domrange.firstNode(); + var end = view.domrange.lastNode(); + // skip marker nodes + while (start !== end && ! nodeDescr(start)) + start = start.nextSibling; + while (end !== start && ! nodeDescr(end)) + end = end.previousSibling; + + buf.push('dom-' + Blaze.getCurrentData() + + ' is ' + nodeDescr(start) +'..' + + nodeDescr(end)); + }; + + myTemplate.destroyed = function () { + test.isFalse(Deps.active); + buf.push('destroyed ' + Blaze.getCurrentData()); + }; + + var makeView = function () { + var number = counter++; + return Blaze.With(number, function () { + return myTemplate.constructView(number); }); - - view.onRendered(function () { - var nodeDescr = function (node) { - if (node.nodeType === 8) // comment - return ''; - if (node.nodeType === 3) // text - return node.nodeValue; - - return node.nodeName; - }; - - var start = this.domrange.firstNode(); - var end = this.domrange.lastNode(); - // skip marker nodes - while (start !== end && ! nodeDescr(start)) - start = start.nextSibling; - while (end !== start && ! nodeDescr(end)) - end = end.previousSibling; - - buf.push('dom-' + Blaze.getCurrentData() + - ' is ' + nodeDescr(start) +'..' + - nodeDescr(end)); - }); - - view.onViewDestroyed(function () { - buf.push('destroyed ' + Blaze.getCurrentData()); - }); - - return Blaze.With(number, function () { return view; }); }; var div = document.createElement("DIV");