聚合物1.x:观察员

Polymer 1.x: Observers

最终,我想select个人状态from this geochart。但是这个问题仅限于让标记为 _computeData 的观察者响应改变 selected 状态数组而触发。

通过以下步骤重现问题:

  1. Open this jsBin.
  2. 清除控制台。
  3. Select 德克萨斯州。

注意控制台显示:

You selected: Colorado,South Dakota,Texas

这一行的预期结果:

console.log('You selected: ' + this.selected); // Logs properly

不过,我希望控制台 读取:

selected

每行:

_computeData: function() {
  console.log('selected'); // Does not log properly; function not called?
  ...

应由以下一组观察者调用。

http://jsbin.com/wuqugigeha/1/edit?html,控制台,输出
...
observers: [
  '_computeData(items.*, selected.*)',
  '_dataChanged(data.*)',
],
...

问题

这是怎么回事?为什么观察者不调用 _computeData 方法?在改变 selected 数组后,如何才能触发方法?

http://jsbin.com/wuqugigeha/1/edit?html,控制台,输出
<!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>

你的问题在这里:

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

Polymer 的数据处理方法如 set 允许您向 Polymer 提供有关数据如何变化的特定信息,从而允许 Polymer 进行非常快速的 DOM 更新。

在这种情况下,您正在做 Polymer 看不到的工作(即数组操作),然后要求 set 弄清楚发生了什么。然而,当您调用 this.set('selected', selected); 时,Polymer 发现 selected 的身份没有改变(也就是说,它与以前的 Array 对象相同),它只是停止处理。 (顺便说一句,这是一个常见问题,所以我们正在考虑进行修改,无论如何都要检查数组。)

解决方案有两个:

1) 在对数组进行排序的情况下,通过 slice()set 创建一个新的数组引用:

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

2)如果是简单的拼接,使用splice辅助函数:

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

理想情况下,您可以避免对数组进行排序,然后您可以直接使用 this.push

注意:通过这些更改 _computeData 被调用,但现在被调用的次数太多了。部分原因是观察 selected.* 会触发 selectedselected.lengthselected.splices。观察 selected.length 而不是 selected.* 可能会有所帮助。

更新

您的示例还有其他三个主要问题:

  1. data 绑定到 google-chart(即 data="[[data]]"),因此图表将在 data 更改时自行重绘,我们可以删除 _drawChart完全。
  2. _computeData(items.*, selected.*) 过于激进,因为 selected.* 会因 'selected.length'、'selected.splices' 和 selected 中的更改而触发。而是使用 _computeData(items, selected.length).
  3. google-chart 本身似乎有问题。特别是,它自己的 drawChart 未设置为可重入。最明显的问题是,每次绘制图表时,它都会添加一个额外的选择侦听器(这会导致乘法 chart-select 事件在您的应用程序上触发)。如果您 file a buggoogle-chart 和 link 回到这个 SO,我会 感激 它。 :)

这是一个修改版本,我用猴子修补了 google-chart.drawChart,修复了其他两个主要问题,并进行了各种较小的修复。

<!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


随机额外的东西:

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

您需要或者捕获this的值(_this使用bind, 但两者都做没有意义。

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

……够了。

Here is an example of implementation of the accepted solution.

http://jsbin.com/xonanucela/edit?html,控制台,输出
<!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>