Ultimately, I want to select individual states from this geochart. But this question is limited to getting the observer labeled _computeData
to fire in response to mutating the array of selected
states.
Reproduce the problem with the following steps:
- Open this jsBin.
- Clear the console.
- Select the state of Texas.
Note the console reads:
You selected: Colorado,South Dakota,Texas
which is expected per this line:
console.log('You selected: ' + this.selected); // Logs properly
However, I expect the console to also read:
selected
per this line:
_computeData: function() {
console.log('selected'); // Does not log properly; function not called?
...
which should be called by the following set of observers.
http://jsbin.com/wuqugigeha/1/edit?html,console,output...
observers: [
'_computeData(items.*, selected.*)',
'_dataChanged(data.*)',
],
...
Question
What's going on here? Why isn't the observer calling the _computeData
method? What can be done to get the method to fire after mutating the selected
array?
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<base href="https://polygit.org/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
<link href="google-chart/google-chart.html" rel="import"> </head>
<body>
<dom-module id="x-element"> <template>
<style>
google-chart {
width: 100%;
}
</style>
<br><br><br><br>
<button on-tap="_show">Show Values</button>
<button on-tap="clearAll">Clear All</button>
<button on-tap="selectAll">Select All</button>
<div>[[selected]]</div>
<google-chart
id="geochart"
type="geo"
options="[[options]]"
data="[[data]]"
xon-google-chart-select="_onGoogleChartSelect">
</google-chart>
</template>
<script>
(function() {
Polymer({
is: 'x-element',
properties: {
items: {
type: Array,
value: function() {
return [ 'Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming', ].sort();
},
},
color: {
type: String, // '#455A64'
value: function() {
return 'blue';
}
},
options: {
type: Object,
notify: true,
reflectToAttribute: true,
computed: '_computeOptions(color)',
},
selected: {
type: Array,
notify: true,
reflectToAttribute: true,
value: function() {
return [];
},
//observer: '_computeData', // Unsuccessfully tried this
},
data: {
type: Array,
notify: true,
reflectToAttribute: true,
//computed: '_computeData(items.*, selected.*)', // Unsuccessfully tried this
},
},
observers: [
'_computeData(items.*, selected.*)',
'_dataChanged(data.*)',
],
// Bind select event listener to chart
ready: function() {
var _this = this;
this.$.geochart.addEventListener('google-chart-select', function(e) {
this._onGoogleChartSelect(e);
}.bind(_this));
},
_computeOptions: function() {
return {
region: 'US',
displayMode: 'regions',
resolution: 'provinces',
legend: 'none',
defaultColor: 'white',
colorAxis: {
colors: ['#E0E0E0', this.color],
minValue: 0,
maxValue: 1,
}
}
},
// On select event, compute 'selected'
_onGoogleChartSelect: function(e) {
var string = e.path[0].textContent.split('Select')[0].trim(), // e.g. 'Ohio'
selected = this.selected, // Array of selected items
index = selected.indexOf(string);
// If 'string' is not in 'selected' array, add it; else delete it
if (index === -1) {
selected.push(string);
selected.sort();
} else {
selected.splice(index, 1);
}
this.set('selected', selected);
console.log('You selected: ' + this.selected); // Logs properly
// Next step should be '_computeData' per observers
},
// After 'items' populates or 'selected' changes, compute 'data'
_computeData: function() {
console.log('selected'); // Does not log properly; function not called?
var data = [],
items = this.items,
selected = this.selected,
i = items.length;
while (i--) {
data.unshift([items[i], selected.indexOf(items[i]) > -1 ? 1 : 0]);
}
data.unshift(['State', 'Select']);
this.set('data', data);
},
// After 'data' changes, redraw chart
// Add delay to avoid 'google not defined' error
_dataChanged: function() {
var _this = this;
setTimeout(function() {
_this._drawChart();
}.bind(_this), 100)
},
// After delay, draw chart
_drawChart: function() {
var data = this.data,
dataTable = this.$.geochart._createDataTable(data);
console.log(dataTable);
this.$.geochart._chartObject.draw(dataTable, this.options);
},
clearAll: function() {
this.set('selected', []);
},
selectAll: function() {
this.set('selected', this.items);
},
_show: function() {
console.log('items: ' + this.items);
console.log('selected: ' + this.selected);
console.log('data: ' + this.data);
},
});
})();
</script>
</dom-module>
<x-element color="red" selected='["Colorado", "South Dakota"]'></x-element>
</body>
</html>
Here is an example of implementation of the accepted solution.
http://jsbin.com/xonanucela/edit?html,console,outputYour problem is here:
Polymer's data-handling methods like
set
allow you to give Polymer specific information about how your data is changing, allowing Polymer to make very fast DOM updates.In this case, you are doing work where Polymer cannot see it (i.e. the array manipulations), and then asking
set
to figure out what happened. However, when you callthis.set('selected', selected);
, Polymer sees that the identity ofselected
hasn't changed (that is, it's the same Array object as before) and it simply stops processing. (Fwiw, this is a common problem, so we are considering a modification that will go ahead and examine the array anyway.)The solution is two-fold:
1) In the case where you are sorting the array, create a fresh array reference to for
set
viaslice()
:2) In the case where you are simply splicing, use the
splice
helper function :Ideally you avoid sorting your array, then you can use
this.push
directly.Note: with these changes
_computeData
is being called, but now it's being called way too many times. Partly this is due to observingselected.*
which will fire forselected
,selected.length
, andselected.splices
. Observingselected.length
instead ofselected.*
might help.UPDATE
There were three other major problems with your example:
data
is bound to to thegoogle-chart
(i.e.data="[[data]]"
) so the chart will redraw itself whendata
changes and we can remove_drawChart
completely._computeData(items.*, selected.*)
is too aggressive, asselected.*
will fire for changes in 'selected.length', 'selected.splices', andselected
. Instead use_computeData(items, selected.length)
.google-chart
itself appears to be buggy. In particular, it's owndrawChart
is not set up to be properly re-entrant. The most obvious problem is that every time the chart draws, it adds an additional selection listener (which causes multiplyingchart-select
events to fire on your application). I would appreciate it if you would file a bug ongoogle-chart
and link back to this SO. :)Here is a modified version where I've monkey patched
google-chart.drawChart
, fixed the other two major problems, and made a variety of smaller repairs.HTH
Random extra stuff:
You need to either capture the value of
this
(_this
) or usebind
, but it doesn't make sense to do both.... is enough.