Let Me Tink About It
Let Me Tink About It

Reputation: 16102

Polymer 1.x: Databinding Arrays

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:

  1. Open this jsBin.
  2. Observe the pre-selected states are Colorado and South Dakota.
  3. Notice Colarado and South Dakota are listed in the upper left.
  4. Click Texas.
  5. Observe the state of Texas is selected in the chart.
  6. ❌ Notice the state of Texas is not included in the list of selected states in the upper left. i.e., <div>[[selected]]</div>
  7. 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 call this.set('selected', selected);, Polymer sees that the identity of selected 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.

http://jsbin.com/yosajuwime/1/edit?html,console,output
// 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>

Upvotes: 0

Views: 472

Answers (3)

Let Me Tink About It
Let Me Tink About It

Reputation: 16102

Databinding arrays can be tricky.

So here is a complete implementation example.

http://jsbin.com/xonanucela/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="paper-button/paper-button.html" rel="import">
</head>
<body>

<x-element></x-element>

<dom-module id="x-element">
<template>

  <br><br>
  <paper-button on-tap="_addNew">Click To Add</paper-button>
  <p>
    <strong>Items</strong>:
    <template is="dom-repeat" items="{{items}}">
      <span>[[item]] </span>
    </template>
  </p>

</template>
<script>
  Polymer({
    is: 'x-element',
    properties: {
      items: {
        type: Array,
        value: function() {
          return ['foo'];
        }
      }
    },
    _addNew: function() {
      var a = this.items; // Clones array
      a.push('bar'); // Updates "value"
      console.log('a', a);
      this.set('items', a.slice()); // Updates "identity"
      console.log('items', this.items);
    },

  });
</script>
</dom-module>
</body>

Upvotes: 0

Alon
Alon

Reputation: 2929

If you want to bind array you have to use the dom-repeat.

<template is="dom-repeat" items="[[selected]]">[[item]] </template>

http://jsbin.com/soxedeqemo/edit?html,console,output

Upvotes: 1

Let Me Tink About It
Let Me Tink About It

Reputation: 16102

The formatting of dom-repeat doesn't support proper use of comma delimiters. So I used a computed property string instead.

http://jsbin.com/ruduwavako/1/edit?html,console,output
<div>[[selectedString]]</div>
...
selectedString: {
  type: String,
  computed: '_computeSelectedString(selected.length)',
},
...
_computeSelectedString: function(selectedInfo) {
  return this.selected.sort().join(', ');
},

Upvotes: 0

Related Questions