Merge pull request #3358 from kaste/computed-bound-methods

Support dynamic functions for computed annotations.
This commit is contained in:
Daniel Freedman 2016-02-17 14:50:18 -08:00
commit 333f082609
5 changed files with 241 additions and 14 deletions

View File

@ -66,6 +66,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (args) {
fn.apply(this, args);
}
} else if (effect.dynamicFn) {
// dynamic functions can be just like every other property `undefined`
// so we MUST ignore an undefined value here. (That's totally the
// same guard we use within `_marshalArgs` and part of the spec.)
} else {
this._warn(this._logf('_complexObserverEffect', 'observer method `' +
effect.method + '` not defined'));
@ -73,16 +77,21 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_computeEffect: function(source, value, effect) {
var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
if (args) {
var fn = this[effect.method];
if (fn) {
this.__setProperty(effect.name, fn.apply(this, args));
var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
if (args) {
var computedvalue = fn.apply(this, args);
this.__setProperty(effect.name, computedvalue);
}
} else if (effect.dynamicFn) {
// dynamic functions can be just like every other property `undefined`
// so we MUST ignore an undefined value here. (That's totally the
// same guard we use within `_marshalArgs` and part of the spec.)
} else {
this._warn(this._logf('_computeEffect', 'compute method `' +
effect.method + '` not defined'));
}
}
},
_annotatedComputationEffect: function(source, value, effect) {
@ -97,6 +106,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
this._applyEffectValue(effect, computedvalue);
}
} else if (effect.dynamicFn) {
// dynamic functions can be just like every other property `undefined`
// so we MUST ignore an undefined value here. (That's totally the
// same guard we use within `_marshalArgs` and part of the spec.)
} else {
computedHost._warn(computedHost._logf('_annotatedComputationEffect',
'compute method `' + effect.method + '` not defined'));
@ -108,6 +121,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_marshalArgs: function(model, effect, path, value) {
var values = [];
var args = effect.args;
// Actually we should return early as soon as we see an `undefined`,
// but dom-repeat relies on this behavior.
var bailoutEarly = (args.length > 1 || effect.dynamicFn);
for (var i=0, l=args.length; i<l; i++) {
var arg = args[i];
var name = arg.name;
@ -119,7 +135,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
} else {
v = model[name];
}
if (args.length > 1 && v === undefined) {
if (bailoutEarly && v === undefined) {
return;
}
if (arg.wildcard) {

View File

@ -145,8 +145,10 @@ TODO(sjmiles): this module should produce either syntactic metadata
for (var k=0; k<b.parts.length; k++) {
var p = b.parts[k];
if (!p.literal) {
p.signature = this._parseMethod(p.value);
if (!p.signature) {
var signature = this._parseMethod(p.value);
if (signature) {
p.signature = signature;
} else {
p.model = this._modelForPath(p.value);
}
}

View File

@ -79,14 +79,27 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_addComputedEffect: function(name, expression) {
var sig = this._parseMethod(expression);
var dynamicFn = sig.dynamicFn;
for (var i=0, arg; (i<sig.args.length) && (arg=sig.args[i]); i++) {
this._addPropertyEffect(arg.model, 'compute', {
method: sig.method,
args: sig.args,
trigger: arg,
name: name
name: name,
dynamicFn: dynamicFn
});
}
if (dynamicFn) {
this._addPropertyEffect(sig.method, 'compute', {
method: sig.method,
args: sig.args,
trigger: null,
name: name,
dynamicFn: dynamicFn
})
}
},
_addObserverEffect: function(property, observer) {
@ -111,11 +124,22 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
throw new Error("Malformed observer expression '" + observer + "'");
}
var dynamicFn = sig.dynamicFn;
for (var i=0, arg; (i<sig.args.length) && (arg=sig.args[i]); i++) {
this._addPropertyEffect(arg.model, 'complexObserver', {
method: sig.method,
args: sig.args,
trigger: arg
trigger: arg,
dynamicFn: dynamicFn
});
}
if (dynamicFn) {
this._addPropertyEffect(sig.method, 'complexObserver', {
method: sig.method,
args: sig.args,
trigger: null,
dynamicFn: dynamicFn
});
}
},
@ -171,6 +195,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
arg);
}
}
if (sig.dynamicFn) {
// trigger=null is sufficient as long as we don't allow paths to be
// used. If we change our mind, we must first implement this in the
// effects anyway where we basically do a `fn = this[methodName]` at
// the moment.
this.__addAnnotatedComputationEffect(
sig.method, index, note, part, null);
}
}
},
@ -184,7 +216,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
negate: part.negate,
method: part.signature.method,
args: part.signature.args,
trigger: trigger
trigger: trigger,
dynamicFn: part.signature.dynamicFn
});
},
@ -194,6 +227,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var m = expression.match(/([^\s]+?)\(([\s\S]*)\)/);
if (m) {
var sig = { method: m[1], static: true };
// TODO(kaste): Optimize/memoize `getPropertyInfo`.
if (this.getPropertyInfo(sig.method) !== Polymer.nob) {
sig.static = false;
sig.dynamicFn = true;
}
if (m[2].trim()) {
// replace escaped commas with comma entity, split on un-escaped commas
var args = m[2].replace(/\\,/g, '&comma;').split(',');

View File

@ -588,3 +588,73 @@
})();
</script>
</dom-module>
<dom-module id="x-bind-computed-property">
<template>
<div id="check">[[translate('Hello World.')]]</div>
</template>
<script>
(function(){
var TranslateBehavior = {
properties: {
translate: {
type: Function,
computed: '_computeTranslateFn(translator)'
}
}
};
Polymer({
is: 'x-bind-computed-property',
behaviors: [TranslateBehavior],
properties: {
translator: {
type: Function,
value: function() {
return function(message) {
return 'translated: ' + message;
}
}
},
},
_computeTranslateFn: function(translator) {
return function(message) {
return translator(message);
}
},
});
})();
</script>
</dom-module>
<dom-module id="x-bind-computed-property-late-translator">
<template>
<div id="check">[[translate(message)]]</div>
</template>
<script>
Polymer({
is: 'x-bind-computed-property-late-translator',
properties: {
message: {
type: String,
value: 'Hello'
},
translate: {
type: Function,
computed: '_computeTranslateFn(translator)'
},
translator: {
type: Function,
},
},
_computeTranslateFn: function(translator) {
return function(message) {
return translator(message);
}
},
});
</script>
</dom-module>

View File

@ -285,6 +285,107 @@ suite('single-element binding effects', function() {
<script>
suite('computed bindings with dynamic functions', function() {
var el;
setup(function() {
});
teardown(function() {
document.body.removeChild(el);
});
test('annotated computation with dynamic function', function() {
el = document.createElement('x-bind-computed-property');
document.body.appendChild(el);
assert.equal(el.$.check.textContent, 'translated: Hello World.');
el.translator = function(message) {
return 'changed: ' + message;
}
assert.equal(el.$.check.textContent, 'changed: Hello World.');
});
test('annotated computation / late resolved dynamic function', function() {
el = document.createElement('x-bind-computed-property-late-translator');
document.body.appendChild(el);
assert.equal(el.$.check.textContent.trim(), '');
el.translator = function(message) {
return 'translated: ' + message;
};
assert.equal(el.$.check.textContent, 'translated: Hello');
});
test('observer with dynamic function', function() {
Polymer({
is: 'x-observer-with-dynamic-function',
properties: {
translate: {
type: Function,
},
message: {
type: String,
value: 'Hello'
},
},
observers: ['translate(message)'],
})
el = document.createElement('x-observer-with-dynamic-function');
document.body.appendChild(el);
var called = 0;
el.translate = function() {
called += 1;
};
assert.equal(called, 1);
});
test('computed property with dynamic function', function() {
Polymer({
is: 'x-computed-property-with-dynamic-function',
properties: {
computedValue: {
computed: "translate('Hello')"
},
translate: {
type: Function
}
}
});
el = document.createElement('x-computed-property-with-dynamic-function');
document.body.appendChild(el);
assert.equal(el.computedValue, undefined);
var called = 0;
el.translate = function(message) {
called += 1;
return 'translated: ' + message;
};
assert.equal(called, 1);
assert.equal(el.computedValue, 'translated: Hello');
});
});
</script>
<script>
suite('2-way binding effects between elements', function() {
var el;