port "if"; fix chaining; buf.write(func); more get()

This commit is contained in:
David Greenspan
2013-07-24 23:58:19 -07:00
parent ac26dd5a4f
commit 320fc842e8
7 changed files with 165 additions and 33 deletions

View File

@@ -307,7 +307,7 @@ _.extend(Deps, {
},
// two values are equal if `equals(x, y)`, which defaults to `===`
isolate: function (f, equals) {
isolateValue: function (f, equals) {
if (! Deps.active)
return f();

View File

@@ -43,7 +43,7 @@ UI.If = Component.extend({
},
render: function (buf) {
var self = this;
var condition = Deps.isolate(function () {
var condition = Deps.isolateValue(function () {
return !! self.condition();
});
buf(condition ? self.content() : self.elseContent());
@@ -59,7 +59,7 @@ UI.Unless = Component.extend({
},
render: function (buf) {
var self = this;
var condition = Deps.isolate(function () {
var condition = Deps.isolateValue(function () {
return ! self.condition();
});
buf(condition ? self.content() : self.elseContent());

View File

@@ -133,12 +133,18 @@ _extend(UI.Component, {
}
});
callChainedCallback = function (comp, propName) {
callChainedCallback = function (comp, propName, orig) {
// Call `comp.foo`, `comp._super.foo`,
// `comp._super._super.foo`, and so on, but in reverse
// order, and only if `foo` is an "own property" in each
// case. Furthermore, the passed value of `this` should
// remain `comp` for all calls (which is achieved by
// filling in `orig` when recursing).
if (comp._super)
callChainedCallback(comp._super, propName);
callChainedCallback(comp._super, propName, orig || comp);
if (comp.hasOwnProperty(propName))
comp[propName].call(comp);
comp[propName].call(orig || comp);
};
// Make `typeName` a non-empty string starting with an ASCII
@@ -410,7 +416,7 @@ _extend(UI.Component, {
// building on the client, and it can also be used on the
// client or server to generate initial HTML.
render: function (buf) {
buf.write(this.content.extend());
buf.write(this.content);
},
_populate: function (div) {

View File

@@ -23,46 +23,45 @@ UI.HTML = Component.extend({
buf.write(this._stringify(data));
}
});
/*
UI.If = Component.extend({
typeName: 'If',
init: function () {
// here we implement the idea that the one positional arg to
// a component becomes its data by default, but components
// like `#if` don't want it to be the data context
// seen by the content so they can change it.
// the implementation will change (but not the idea)
// if Geoff's proposal for extend and args is implemented.
// It's also possible the right thing to do is
// to have `arg` and `data` be separate.
this.condition = this.data;
this.data = this.parent.data;
// content doesn't see the condition as `data`
this.data = null;
// XXX I guess this means it's kosher to mutate properties
// of a Component during init (but presumably not before
// or after)?
},
render: function (buf) {
var self = this;
var condition = Deps.isolate(function () {
return !! self.condition();
// re-render if and only if condition changes
var condition = Deps.isolateValue(function () {
return !! self.get('condition');
});
buf(condition ? self.content() : self.elseContent());
buf.write(condition ? self.content : self.elseContent);
}
});
UI.Unless = Component.extend({
typeName: 'Unless',
init: function () {
// see comment in `If`
this.condition = this.data;
this.data = this.parent.data;
this.data = null;
},
render: function (buf) {
var self = this;
var condition = Deps.isolate(function () {
return ! self.condition();
// re-render if and only if condition changes
var condition = Deps.isolateValue(function () {
return !! self.get('condition');
});
buf(condition ? self.content() : self.elseContent());
buf.write(condition ? self.elseContent : self.content);
}
});
/*
UI.Counter = Component.extend({
typeName: "Counter",
fields: {

View File

@@ -1,11 +1,38 @@
var UI = UI2;
var findComponentWithProp = function (id, comp) {
while (comp) {
if (id in comp)
return comp;
comp = comp.parent;
}
return null;
};
var getData = function (comp) {
comp = findComponentWithProp('data', comp);
return (comp ?
(typeof comp.data === 'function' ?
comp.data() : comp.data) :
null);
};
_extend(UI.Component, {
get: function (id) {
// this is the (! id) case where `id` is `""` or absent.
// actually it should probably search up the parent tree too.
return (typeof this.data === 'function' ?
this.data() : this.data);
if (! id) {
// `id` is `""` or absent/undefined
return getData(this);
} else {
var comp = findComponentWithProp(id, this);
if (comp) {
// found a method
return (typeof comp[id] === 'function' ?
comp[id]() : comp[id]);
} else {
var data = getData(this);
return data[id];
}
}
},
// convenient syntax
withData: function (data) {

View File

@@ -114,7 +114,8 @@ makeRenderBuffer = function (component, options) {
arg = arg.extend();
handleComponent(arg);
} else if (arg.child) {
} else if ((typeof arg === 'function') || arg.child) {
// `componentFunction`, or
// `{child: componentOrFunction, props: object}`
// In `{child: comp}` with no `props`, it's ok
@@ -130,8 +131,15 @@ makeRenderBuffer = function (component, options) {
// is uninited and we instantiate a copy, `curComp`
// is not that copy, it's the original component used
// as a prototype.
var curComp = arg.child;
var props = arg.props;
var curComp, props;
if (typeof arg === 'function') {
curComp = arg;
props = null;
} else {
curComp = arg.child;
props = arg.props;
}
if (typeof curComp === 'function') {
var compFunc = curComp;
// use `Deps.autorun`, not `component.autorun`,

View File

@@ -150,6 +150,7 @@ Tinytest.add("ui - render", function (test) {
test.equal(which.numListeners(), 0);
})();
(function () {
var R = ReactiveVar(1);
var which = ReactiveVar("H");
@@ -170,7 +171,7 @@ Tinytest.add("ui - render", function (test) {
var c = UI.Component.extend({
render: function (buf) {
var self = this;
// also, choose which one to use reactively
// choose which one to use reactively
buf.write({child: function () {
return which.get() === "H" ? Hello : World;
}, props: {
@@ -203,4 +204,95 @@ Tinytest.add("ui - render", function (test) {
test.equal(name.numListeners(), 0);
})();
(function () {
var which = ReactiveVar("H");
// two factory (uninited) Components
var Hello = UI.Text.withData("hello");
var World = UI.Text.withData("world");
var c = UI.Component.extend({
render: function (buf) {
var self = this;
// choose which one to use reactively
buf.write(function () {
return which.get() === "H" ? Hello : World;
});
}
});
c.build();
test.equal($(c._offscreen).html(), "hello");
which.set("W");
Deps.flush();
test.equal($(c._offscreen).html(), "world");
test.equal(_.keys(c.children).length, 1);
which.set("H");
Deps.flush();
test.equal($(c._offscreen).html(), "hello");
test.equal(_.keys(c.children).length, 1);
c.destroy();
test.equal(which.numListeners(), 0);
})();
});
Tinytest.add("ui - if/unless", function (test) {
var hello = UI.Text.withData("hello");
var world = UI.Text.withData("world");
var R = ReactiveVar('true');
var renderedCounts = [0, 0, 0];
var c = UI.Component.extend({
rendered: function () { renderedCounts[0]++; },
render: function (buf) {
buf.write(
UI.If.extend({
data: function () {
return R.get().charAt(0) === 't';
},
content: hello,
elseContent: world,
rendered: function () {
renderedCounts[1]++;
}
}),
UI.Unless.extend({
data: function () {
return R.get().charAt(0) === 't';
},
content: hello,
elseContent: world,
rendered: function () {
renderedCounts[2]++;
}
}));
}
});
c.build();
test.equal($(c._offscreen).html(), "helloworld");
test.equal(renderedCounts, [1,1,1]);
R.set('false');
Deps.flush();
test.equal($(c._offscreen).html(), "worldhello");
test.equal(renderedCounts, [1,2,2]);
R.set('true');
Deps.flush();
test.equal($(c._offscreen).html(), "helloworld");
test.equal(renderedCounts, [1,3,3]);
R.set('torrid');
Deps.flush();
test.equal($(c._offscreen).html(), "helloworld");
test.equal(renderedCounts, [1,3,3]);
R.set('flagrant');
Deps.flush();
test.equal($(c._offscreen).html(), "worldhello");
test.equal(renderedCounts, [1,4,4]);
R.set('fromage');
Deps.flush();
test.equal($(c._offscreen).html(), "worldhello");
test.equal(renderedCounts, [1,4,4]);
c.destroy();
test.equal(R.numListeners(), 0);
});