mirror of
https://github.com/Polymer/polymer.git
synced 2025-02-25 18:55:30 -06:00
Add debounced multiple computed property support.
This commit is contained in:
59
PRIMER.md
59
PRIMER.md
@@ -1103,7 +1103,7 @@ Values will be serialized according to type: Arrays/Objects will be `JSON.string
|
||||
<a name="computed-properties"></a>
|
||||
## Computed properties
|
||||
|
||||
Polymer supports virtual properties whose values are calculated from other properties. Computed properties can be defined by providing an object-valued `computed` property on the prototype that maps property names to computing functions. The name of the function to compute the value is provided as a string with dependent properties as arguments in parenthesis. Only one dependency is supported at this time.
|
||||
Polymer supports virtual properties whose values are calculated from other properties. Computed properties can be defined by providing an object-valued `computed` property on the prototype that maps property names to computing functions. The name of the function to compute the value is provided as a string with dependent properties as arguments in parenthesis. The function will be called once (asynchronously) for any change to the dependent properties.
|
||||
|
||||
```html
|
||||
<dom-module id="x-custom">
|
||||
@@ -1118,13 +1118,13 @@ Polymer supports virtual properties whose values are calculated from other prope
|
||||
is: 'x-custom',
|
||||
|
||||
computed: {
|
||||
// when `user` changes `computeFullName` is called and the
|
||||
// value it returns is stored as `fullName`
|
||||
fullName: 'computeFullName(user)',
|
||||
// when `first` or `last` changes `computeFullName` is called once
|
||||
// (asynchronously) and the value it returns is stored as `fullName`
|
||||
fullName: 'computeFullName(first, last)',
|
||||
},
|
||||
|
||||
computeFullName: function(user) {
|
||||
return user.firstName + ' ' + user.lastName;
|
||||
computeFullName: function(first, last) {
|
||||
return first + ' ' + last;
|
||||
}
|
||||
|
||||
...
|
||||
@@ -1133,6 +1133,8 @@ Polymer supports virtual properties whose values are calculated from other prope
|
||||
</script>
|
||||
```
|
||||
|
||||
Note: Only direct properties of the element (as opposed to sub-properties of an object) can be used as dependencies at this time.
|
||||
|
||||
<a name="read-only"></a>
|
||||
## Read-only properties
|
||||
|
||||
@@ -1304,50 +1306,11 @@ Current limitations that are on the backlog for evaluation/improvement are liste
|
||||
* Support for compound property binding
|
||||
* See below
|
||||
|
||||
## Compound property effects
|
||||
## Compound observation
|
||||
|
||||
Polymer 0.8 currently has no built-in support for compound observation or compound binding expressions. This problem space is on the backlog to be tackled in the near future. This section will discuss lower-level tools that are available in 0.8 that can be used instead.
|
||||
Polymer 0.8 does not currently support observer functions called once for changes to a set of dependent properties, outside of computed properties. If the work of computing the property is expensive, or if the side-effects of the binding are expensive, then you may want to ensure side-effects only occur once for any number of changes to them during a turn by manually introducing asynchronicity. The `computed` property feature uses `debounce` under the hood to achieve the same effect.
|
||||
|
||||
Assume an element has a boolean property that should be set when either of two conditions are true: e.g. when `<my-parent>.isManager == true` OR `<my-parent>.mode == 2`, you want to set `<my-child>.disabled = true`.
|
||||
|
||||
The most naive way to achieve this in 0.8 is with separate change handlers for the dependent properties that set a `shouldDisable` property bound to the `my-child`.
|
||||
|
||||
Example:
|
||||
|
||||
```html
|
||||
<dom-module id="x-parent">
|
||||
<template>
|
||||
<x-child disabled="{{shouldDisable}}"></my-child>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
Polymer({
|
||||
|
||||
is: 'x-parent',
|
||||
|
||||
bind: {
|
||||
isManager: 'computeShouldDisable',
|
||||
mode: 'computeShouldDisable',
|
||||
},
|
||||
|
||||
// Warning: Called once for every change to dependent properties!
|
||||
computeShouldDisable: function() {
|
||||
this.shouldDisable = this.isManager || (this.mode == 2);
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
Due to the synchronous nature of bindings in 0.8, code such as the following will result in `<my-child>.disabled` being set twice (and any side-effects of that property changing to potentially occur twice):
|
||||
|
||||
```js
|
||||
myParent.isManager = false;
|
||||
myParent.mode = 5;
|
||||
```
|
||||
|
||||
If the work of computing the property is expensive, or if the side-effects of the binding are expensive, then you may want to ensure side-effects only occur once for any number of changes to them during a turn by manually introducing asynchronicity. The `debounce` API on the Polymer Base prototype can be used to achieve this. The `debounce` API takes a signal name (String), callback, and optional wait time, and only calls the callback once for any number `debounce` calls with the same `signalName` started within the wait period.
|
||||
The `debounce` API on the Polymer Base prototype can be used to achieve this. The `debounce` API takes a signal name (String), callback, and optional wait time, and only calls the callback once for any number `debounce` calls with the same `signalName` started within the wait period.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -16,10 +16,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
var method = expression.slice(0, index);
|
||||
var args = expression.slice(index + 1, -1).replace(/ /g, '').split(',');
|
||||
//console.log('%c on [%s] compute [%s] via [%s]', 'color: green', args[0], name, method);
|
||||
this.addPropertyEffect(model, args[0], 'compute', {
|
||||
property: name,
|
||||
method: method
|
||||
});
|
||||
var methodArgs = 'this._data.' + args.join(', this._data.');
|
||||
var methodString = 'this.debounce(\'_' + method + '\', function() {\n' +
|
||||
'\t\tthis.' + name + ' = this.' + method + '(' + methodArgs + ');\n' +
|
||||
'\t});';
|
||||
for (var i=0; i<args.length; i++) {
|
||||
this.addPropertyEffect(model, args[i], 'compute', methodString);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO(sjmiles): case shenanigans
|
||||
@@ -99,8 +102,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
},
|
||||
|
||||
compute: function(model, source, effect) {
|
||||
return 'this.' + effect.property
|
||||
+ ' = this.' + effect.method + '(this._data.' + source + ');';
|
||||
return effect;
|
||||
},
|
||||
|
||||
reflect: function(model, source) {
|
||||
|
||||
@@ -117,7 +117,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
// var group = '\'' + this.is + ':' + property + '\'';
|
||||
// effects.unshift('console.group(' + group + ');');
|
||||
// effects.push('console.groupEnd(' + group + ');');
|
||||
effects = effects.join('\n\t\t');
|
||||
effects = '\t' + effects.join('\n\t');
|
||||
// construct effector
|
||||
var effector = '_' + property + 'Effector';
|
||||
model[effector] = new Function('old', effects);
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
type: Number,
|
||||
notify: true
|
||||
},
|
||||
computedFromMultipleValues: {
|
||||
type: Number,
|
||||
notify: true
|
||||
},
|
||||
camelNotifyingValue: {
|
||||
type: Number,
|
||||
notify: true
|
||||
@@ -25,13 +29,15 @@
|
||||
},
|
||||
computed: {
|
||||
computedvalue: 'computeValue(value)',
|
||||
computednotifyingvalue: 'computeNotifyingValue(notifyingvalue)'
|
||||
computednotifyingvalue: 'computeNotifyingValue(notifyingvalue)',
|
||||
computedFromMultipleValues: 'computeFromMultipleValues(sum1, sum2, divide)'
|
||||
},
|
||||
bind: {
|
||||
value: 'valueChanged',
|
||||
computedvalue: 'computedvalueChanged',
|
||||
notifyingvalue: 'notifyingvalueChanged',
|
||||
readonlyvalue: 'readonlyvalueChanged'
|
||||
readonlyvalue: 'readonlyvalueChanged',
|
||||
computedFromMultipleValues: 'computedFromMultipleValuesChanged'
|
||||
},
|
||||
valueChanged: function() {},
|
||||
computeValue: function(val) {
|
||||
@@ -42,7 +48,11 @@
|
||||
readonlyvalueChanged: function() {},
|
||||
computeNotifyingValue: function(val) {
|
||||
return val + 2;
|
||||
}
|
||||
},
|
||||
computeFromMultipleValues: function(sum1, sum2, divide) {
|
||||
return (sum1 + sum2) / divide;
|
||||
},
|
||||
computedFromMultipleValuesChanged: function() {}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -58,10 +58,13 @@ suite('single-element binding effects', function() {
|
||||
assert.equal(called, true, 'Change handler not called');
|
||||
});
|
||||
|
||||
test('computed value updates', function() {
|
||||
test('computed value updates', function(done) {
|
||||
el.value = 44;
|
||||
assert.equal(el.computedvalue, 45, 'Computed value not correct');
|
||||
assert.equal(el.$.boundChild.computedvalue, 45, 'Computed value not propagated to bound child');
|
||||
setTimeout(function() {
|
||||
assert.equal(el.computedvalue, 45, 'Computed value not correct');
|
||||
assert.equal(el.$.boundChild.computedvalue, 45, 'Computed value not propagated to bound child');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('notification sent', function() {
|
||||
@@ -79,23 +82,49 @@ suite('single-element binding effects', function() {
|
||||
assert.equal(notified, 2, 'Notification events not sent');
|
||||
});
|
||||
|
||||
test('computed change handler called', function() {
|
||||
test('computed change handler called', function(done) {
|
||||
var called = false;
|
||||
el.computedvalueChanged = function() {
|
||||
called = true;
|
||||
};
|
||||
el.value = 46;
|
||||
assert.equal(called, true, 'Change handler not called');
|
||||
setTimeout(function() {
|
||||
assert.equal(called, true, 'Change handler not called');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('computed notification sent', function() {
|
||||
test('computed notification sent', function(done) {
|
||||
var notified = false;
|
||||
el.addEventListener('computednotifyingvalue-changed', function(e) {
|
||||
assert.equal(e.detail.value, 49);
|
||||
notified = true;
|
||||
});
|
||||
el.notifyingvalue = 47;
|
||||
assert.equal(notified, true, 'Notification event not sent');
|
||||
setTimeout(function() {
|
||||
assert.equal(notified, true, 'Notification event not sent');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('computed property with multiple dependencies', function(done) {
|
||||
var called = false;
|
||||
el.computedFromMultipleValuesChanged = function() {
|
||||
called = true;
|
||||
};
|
||||
var notified = false;
|
||||
el.addEventListener('computed-from-multiple-values-changed', function(e) {
|
||||
notified = true;
|
||||
});
|
||||
el.sum1 = 10;
|
||||
el.sum2 = 20;
|
||||
el.divide = 2;
|
||||
setTimeout(function() {
|
||||
assert.equal(el.computedFromMultipleValues, 15, 'Computed value wrong');
|
||||
assert.equal(notified, true, 'Notification event not sent');
|
||||
assert.equal(called, true, 'Change handler not called');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('no read-only change handler called with assignment', function() {
|
||||
@@ -172,19 +201,25 @@ suite('2-way binding effects between elements', function() {
|
||||
assert.equal(called, false, 'changed handler for property bound to non-notifying property called and should not have been');
|
||||
});
|
||||
|
||||
test('binding to non-notifying computed property', function() {
|
||||
test('binding to non-notifying computed property', function(done) {
|
||||
el.boundcomputedvalue = 42;
|
||||
el.$.basic1.value = 43;
|
||||
assert.equal(el.boundcomputedvalue, 42, 'binding to non-notifying computed property updated and should not have been');
|
||||
setTimeout(function() {
|
||||
assert.equal(el.boundcomputedvalue, 42, 'binding to non-notifying computed property updated and should not have been');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('changed handler for property bound to non-notifying computed property', function() {
|
||||
test('changed handler for property bound to non-notifying computed property', function(done) {
|
||||
var called = false;
|
||||
el.boundcomputedvalueChanged = function() {
|
||||
called = true;
|
||||
};
|
||||
el.$.basic1.value = 44;
|
||||
assert.equal(called, false, 'changed handler for property bound to non-notifying computed property called and should not have been');
|
||||
setTimeout(function() {
|
||||
assert.equal(called, false, 'changed handler for property bound to non-notifying computed property called and should not have been');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('binding to notifying property', function() {
|
||||
@@ -206,18 +241,24 @@ suite('2-way binding effects between elements', function() {
|
||||
assert.equal(called, true, 'changed handler for property bound to notifying property not called');
|
||||
});
|
||||
|
||||
test('binding to notifying computed property', function() {
|
||||
test('binding to notifying computed property', function(done) {
|
||||
el.$.basic1.notifyingvalue = 43;
|
||||
assert.equal(el.boundcomputednotifyingvalue, 45, 'binding to notifying computed property not updated');
|
||||
setTimeout(function() {
|
||||
assert.equal(el.boundcomputednotifyingvalue, 45, 'binding to notifying computed property not updated');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('changed handler for property bound to notifying computed property', function() {
|
||||
test('changed handler for property bound to notifying computed property', function(done) {
|
||||
var called = false;
|
||||
el.boundcomputednotifyingvalueChanged = function() {
|
||||
called = true;
|
||||
};
|
||||
el.$.basic1.notifyingvalue = 45;
|
||||
assert.equal(called, true, 'changed handler for property bound to non-notifying computed property not called');
|
||||
setTimeout(function() {
|
||||
assert.equal(called, true, 'changed handler for property bound to non-notifying computed property not called');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('no change for binding into read-only property', function() {
|
||||
@@ -274,19 +315,25 @@ suite('1-way binding effects between elements', function() {
|
||||
assert.equal(called, false, 'changed handler for property one-way-bound to non-notifying property called and should not have been');
|
||||
});
|
||||
|
||||
test('one-way binding to non-notifying computed property', function() {
|
||||
test('one-way binding to non-notifying computed property', function(done) {
|
||||
el.boundcomputedvalue = 42;
|
||||
el.$.basic2.value = 43;
|
||||
assert.equal(el.boundcomputedvalue, 42, 'binding to non-notifying computed property updated and should not have been');
|
||||
setTimeout(function() {
|
||||
assert.equal(el.boundcomputedvalue, 42, 'binding to non-notifying computed property updated and should not have been');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('changed handler for property one-way-bound to non-notifying computed property', function() {
|
||||
test('changed handler for property one-way-bound to non-notifying computed property', function(done) {
|
||||
var called = false;
|
||||
el.boundcomputedvalueChanged = function() {
|
||||
called = true;
|
||||
};
|
||||
el.$.basic2.value = 44;
|
||||
assert.equal(called, false, 'changed handler for property bound to non-notifying computed property called and should not have been');
|
||||
setTimeout(function() {
|
||||
assert.equal(called, false, 'changed handler for property bound to non-notifying computed property called and should not have been');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('one-way binding to notifying property', function() {
|
||||
@@ -305,19 +352,25 @@ suite('1-way binding effects between elements', function() {
|
||||
assert.equal(called, false, 'changed handler for property bound to notifying property called and should not have been');
|
||||
});
|
||||
|
||||
test('one-way binding to notifying computed property', function() {
|
||||
test('one-way binding to notifying computed property', function(done) {
|
||||
el.boundcomputednotifyingvalue = 42;
|
||||
el.$.basic2.notifyingvalue = 43;
|
||||
assert.equal(el.boundcomputednotifyingvalue, 42, 'binding to notifying computed property updated and should not have been');
|
||||
setTimeout(function() {
|
||||
assert.equal(el.boundcomputednotifyingvalue, 42, 'binding to notifying computed property updated and should not have been');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('changed handler for property one-way-bound to notifying computed property', function() {
|
||||
test('changed handler for property one-way-bound to notifying computed property', function(done) {
|
||||
var called = false;
|
||||
el.boundcomputednotifyingvalueChanged = function() {
|
||||
called = true;
|
||||
};
|
||||
el.$.basic2.notifyingvalue = 45;
|
||||
assert.equal(called, false, 'changed handler for property bound to non-notifying computed property called and should not have been');
|
||||
setTimeout(function() {
|
||||
assert.equal(called, false, 'changed handler for property bound to non-notifying computed property called and should not have been');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user