Polymer 1.x: Observers

人盡茶涼 提交于 2019-12-04 22:00:44

Your problem is here:

if (index === -1) {
    selected.push(string);
    selected.sort();
} else {
    selected.splice(index, 1);
}
this.set('selected', selected);

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 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. (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 via slice() :

if (index === -1) {
    selected.push(string);
    selected.sort();
    this.set('selected', selected.slice());

2) In the case where you are simply splicing, use the splice helper function :

} else {
    this.splice('selected', index, 1);
}

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 observing selected.* which will fire for selected, selected.length, and selected.splices. Observing selected.length instead of selected.* might help.

UPDATE

There were three other major problems with your example:

  1. data is bound to to the google-chart (i.e. data="[[data]]") so the chart will redraw itself when data changes and we can remove _drawChart completely.
  2. _computeData(items.*, selected.*) is too aggressive, as selected.* will fire for changes in 'selected.length', 'selected.splices', and selected. Instead use _computeData(items, selected.length).
  3. google-chart itself appears to be buggy. In particular, it's own drawChart 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 multiplying chart-select events to fire on your application). I would appreciate it if you would file a bug on google-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.

<!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() {
        
        // monkey-patching google-chart
        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',
          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: 'blue'
            },
            options: {
              type: Object,
              computed: '_computeOptions(color)',
            },
            selected: {
              type: Array,
              value: function() {
                return [];
              }
            },
            data: {
              type: Array,
              computed: '_computeData(items, 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) {
            console.log('_onGoogleChartSelect: ', e.detail)
            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);
            }
            // Next step should be '_computeData' per observers
            console.log('_select:', this.selected);
          },
          // After 'items' populates or 'selected' changes, compute 'data'
          _computeData: function(items, selectedInfo) {
            console.log('_computeData');
            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>

HTH


Random extra stuff:

var _this = this;
setTimeout(function() {
    _this._drawChart();
}.bind(_this), 100)

You need to either capture the value of this (_this) or use bind, but it doesn't make sense to do both.

setTimeout(function() {
    this._drawChart();
}.bind(this), 100)

... is enough.

Here is an example of implementation of the accepted solution.

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>
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!