From 18cd889c54218e5efcf97d4f10291b0cd1b2114c Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 8 Jul 2013 11:21:06 -0700 Subject: [PATCH] reactively determined Component class --- examples/unfinished/shark/client/shark.js | 41 +++++++++++++- packages/ui/dom.js | 59 ++++++++++++-------- packages/ui/package.js | 1 + packages/ui/render.js | 65 +++++++++++++++++++++++ packages/ui/tree.js | 6 +++ 5 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 packages/ui/render.js diff --git a/examples/unfinished/shark/client/shark.js b/examples/unfinished/shark/client/shark.js index 8aa1117ded..3de8da2051 100644 --- a/examples/unfinished/shark/client/shark.js +++ b/examples/unfinished/shark/client/shark.js @@ -46,4 +46,43 @@ Template.item({ // $(table).append(frag); // // console.log(TABLE.rows.length); -//}); \ No newline at end of file +//}); + +Span = UIComponent.extend({ + render: function (buf) { + buf("Hello"); + } +}); + +Div = UIComponent.extend({ + render: function (buf) { + buf("
World
"); + } +}); + +Either = UIComponent.extend({ + render: function (buf) { + buf(Div.create(), + { + type: function () { return window[Session.get('which')]; }, + args: { + built: function () { + var self = this; + self.$("*").on('click', function (evt) { + Session.set( + 'which', + Session.get('which') === 'Div' ? 'Span' : 'Div'); + }); + } + } + }, + { type: Span }); + } +}); + +Meteor.startup(function () { + Session.set('which', 'Span'); + + var x = Either.create({isRoot: true}); + x.attach(document.body); +}); diff --git a/packages/ui/dom.js b/packages/ui/dom.js index 74f62e9d23..cf304c5d70 100644 --- a/packages/ui/dom.js +++ b/packages/ui/dom.js @@ -94,33 +94,16 @@ Component({ _populate: function (div) { var self = this; - var strs = []; - var randomString = Random.id(); - var commentUid = 1; - var componentsToAttach = {}; + var buf = makeRenderBuffer(self); + self.render(buf); - self.render(function (/*args*/) { - for (var i = 0; i < arguments.length; i++) { - var arg = arguments[i]; - if (typeof arg === 'string') { - strs.push(arg); - } else if (arg instanceof Component) { - var commentString = randomString + '_' + (commentUid++); - strs.push(''); - self.add(arg); - componentsToAttach[commentString] = arg; - } else { - throw new Error("Expected string or Component"); - } - } - }); - - var html = strs.join(''); + var html = buf.getHtml(); $(div).append(html); var start = div.firstChild; var end = div.lastChild; + var componentsToAttach = buf.componentsToAttach; // walk div and replace comments with Components var wireUpDOM = function (parent) { @@ -182,6 +165,7 @@ Component({ if (c.firstRun) { var div = makeSafeDiv(); + // capture reactivity: var info = self._populate(div); if (! div.firstChild) @@ -191,6 +175,7 @@ Component({ self.start = info.start || div.firstChild; self.end = info.end || div.lastChild; } else { + // capture reactivity: self._rebuild(c.builtChildren); } @@ -593,6 +578,38 @@ Component({ self._computations.push(c); return c; + }, + + replaceChild: function (oldChild, newChild) { + var self = this; + + self._requireBuilt(); + oldChild._requireBuilt(); + if (! oldChild.isAttached) + throw new Error("Child to replace must be attached"); + + var lastNode = oldChild.lastNode(); + var parentNode = lastNode.parentNode; + var nextNode = lastNode.nextSibling; + + oldChild.remove(); + self.insertBefore(newChild, nextNode, parentNode); + }, + + swapChild: function (oldChild, newChild) { + var self = this; + + self._requireBuilt(); + oldChild._requireBuilt(); + if (! oldChild.isAttached) + throw new Error("Child to swap out must be attached"); + + var lastNode = oldChild.lastNode(); + var parentNode = lastNode.parentNode; + var nextNode = lastNode.nextSibling; + + oldChild.detach(); + self.insertBefore(newChild, nextNode, parentNode); } // If Component is ever emptied, it gets an empty comment node. diff --git a/packages/ui/package.js b/packages/ui/package.js index 350cc55de8..abf7df835a 100644 --- a/packages/ui/package.js +++ b/packages/ui/package.js @@ -16,6 +16,7 @@ Package.on_use(function (api) { api.add_files(['base.js', 'lifecycle.js', 'tree.js', + 'render.js', 'dom.js', 'forms.js', 'components.js', diff --git a/packages/ui/render.js b/packages/ui/render.js new file mode 100644 index 0000000000..22eebe2a0c --- /dev/null +++ b/packages/ui/render.js @@ -0,0 +1,65 @@ +var Component = UIComponent; + + +makeRenderBuffer = function (component, options) { + var isPreview = !! options && options.preview; + + var strs = []; + var componentsToAttach = {}; + var randomString = Random.id(); + var commentUid = 1; + + var handle = function (arg) { + if (typeof arg === 'string') { + strs.push(arg); + } else if (arg instanceof Component) { + var commentString = randomString + '_' + (commentUid++); + strs.push(''); + component.add(arg); + componentsToAttach[commentString] = arg; + } else if (arg.type) { + // `{type: componentTypeOrFunction, args: object}` + if (Component.isType(arg.type)) { + handle(arg.type.create(arg.args)); + } else if (typeof arg.type === 'function') { + var curType; + component.autorun(function (c) { + // capture reactivity: + var type = arg.type(); + if (c.firstRun) { + curType = type; + } else if (component.stage !== Component.BUILT || + ! component.hasChild(curChild)) { + c.stop(); + } else if (type !== curType) { + var oldChild = curChild; + curType = type; + Deps.nonreactive(function () { + curChild = curType.create(arg.args); + component.replaceChild(oldChild, curChild); + }); + } + }); + var curChild = curType.create(arg.args); + handle(curChild); + } else { + throw new Error("Expected 'type' to be Component or function"); + } + } else { + throw new Error("Expected string or Component"); + } + }; + + var buf = function (/*args*/) { + for (var i = 0; i < arguments.length; i++) + handle(arguments[i]); + }; + + buf.getHtml = function () { + return strs.join(''); + }; + + buf.componentsToAttach = componentsToAttach; + + return buf; +}; diff --git a/packages/ui/tree.js b/packages/ui/tree.js index 76afa555b6..804a6328be 100644 --- a/packages/ui/tree.js +++ b/packages/ui/tree.js @@ -132,6 +132,12 @@ Component({ self._added(); }, + hasChild: function (comp) { + this._requireNotDestroyed(); + + return this.children[comp.guid] === comp; + }, + extendHooks: { isRoot: function (value) { if (value)