Objective
I want to databind the selected
property in this element to the DOM (at <div>[[selected]]</div>
).
After selecting a state (like "Texas"), I expect the state's name to appear in the upper left next to the pre-selected states of "Colorado,South Dakota" (inside the <div>[[selected]]</div>
tag). But instead, it does not.
Question
What's going on here? How can I successfully databind the DOM to the selected
property? And have the DOM update automatically with each new state that is selected and de-selected.
Reproduce the Problem
Follow these steps:
- Open this jsBin.
- Observe the pre-selected states are Colorado and South Dakota.
- Notice Colarado and South Dakota are listed in the upper left.
- Click Texas.
- Observe the state of Texas is selected in the chart.
- ❌ Notice the state of Texas is not included in the list of selected states in the upper left. i.e.,
<div>[[selected]]</div>
- Click the button labeled "Show Values" and observe in the console that "Texas" is included in the
selected
array.
Attempted solution using .slice() crashes
This SO answer explains why mutated arrays are not directly observable by the Polymer object.
@ScottMiles writes:...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.
So I attempted to solve the problem by creating a new array using the .slice()
method on the original array as follows.
// Sort new 'selected' array
_selectedChanged: function(selectedInfo) {
var selected = this.selected.slice();
selected.sort();
//this.set('selected', selected); // Uncommenting this line crashes call stack
},
This seems to solve the observability problem but creates a side effect issue and crashes the call stack.
Source
http://jsbin.com/yosajuwime/1/edit?html,console,output<!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]]"
on-google-chart-select="_onGoogleChartSelect">
</google-chart>
</template>
<script>
(function() {
// google-chart monkey patch
var gcp = Object.getPrototypeOf(document.createElement('google-chart'));
gcp.drawChart = function() {
if (this._canDraw) {
if (!this.options) {
this.options = {};
}
if (!this._chartObject) {
var chartClass = this._chartTypes[this.type];
if (chartClass) {
this._chartObject = new chartClass(this.$.chartdiv);
google.visualization.events.addOneTimeListener(this._chartObject,
'ready', function() {
this.fire('google-chart-render');
}.bind(this));
google.visualization.events.addListener(this._chartObject,
'select', function() {
this.selection = this._chartObject.getSelection();
this.fire('google-chart-select', { selection: this.selection });
}.bind(this));
if (this._chartObject.setSelection){
this._chartObject.setSelection(this.selection);
}
}
}
if (this._chartObject) {
this._chartObject.draw(this._dataTable, this.options);
} else {
this.$.chartdiv.innerHTML = 'Undefined chart type';
}
}
};
Polymer({
is: 'x-element',
/** /
* Fired when user selects chart item.
*
* @event us-map-select
* @param {object} detail Alpabetized array of selected state names.
/**/
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,
value: 'blue',
},
options: {
type: Object,
computed: '_computeOptions(color)',
},
selected: {
type: Array,
value: function() {
return [];
}
},
data: {
type: Array,
computed: '_computeData(items, selected.length)'
},
},
observers: [
'_selectedChanged(selected.length)',
],
_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) {
this.push('selected', string);
} else {
this.splice('selected', index, 1);
}
},
// Sort new 'selected' array
_selectedChanged: function(selectedInfo) {
var selected = this.selected.slice();
selected.sort();
//this.set('selected', selected); // Uncommenting this line crashes call stack
},
// After 'items' populates or 'selected' changes, compute 'data'
_computeData: function(items, selectedInfo) {
var data = [],
selected = this.selected,
i = items.length;
while (i--) {
data.unshift([items[i], selected.indexOf(items[i]) > -1 ? 1 : 0]);
}
data.unshift(['State', 'Select']);
return data;
},
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>
set
crashes the call stack because you are effectively saying: "when the selected array changes, change the selected array", which is an infinite loop. Fwiw, your solution to bind to compute a sorted, concatenated string is a good one, since your output is relatively simple. – Scott Miles