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)