是的,Polymer 1.0 确实不再支持 myObject[key] 绑定表达式。但是,在您的特定用例中,有一些方法可以避开这个问题。
单向数据绑定
当涉及到单向数据绑定时,克服这一限制相当简单。只需使用一个同时接受对象和相关键的计算属性:
<my-element value="[[getValue(obj, key)]]"></my-element>
getValue: function(obj, key) {
return obj[key];
}
双向数据绑定
在双向数据绑定的情况下,仍然可以在 Polymer 1.0 中创建绑定表达式{{obj[key]}} 的功能替代方案。但是,这需要考虑您希望实现绑定的特定用例。
考虑到您问题中的示例,您似乎正在使用某种表格或字段集。出于示例的目的,我将使用稍微不同但非常相似的结构。
假设我们有一个fieldset 对象,并且该对象的结构如下:
{
"fields": [
{ "name": "Name", "prop": "name" },
{ "name": "E-mail", "prop": "email" },
{ "name": "Phone #", "prop": "phone" }
],
"rows": [
{
"name": "John Doe",
"email": "jdoe@example.com",
"phone": "(555) 555-1032"
},
{
"name": "Allison Dougherty",
"email": "polymer.rox.1337@example.com",
"phone": "(555) 555-2983"
},
{
"name": "Mike \"the\" Pike",
"email": "verypunny@example.com",
"phone": "(555) 555-7148"
}
]
}
如果我们想创建代表该对象的某种类似表格的输出,我们可以使用两个嵌套的重复模板:第一个迭代不同的行,第二个迭代不同的字段。使用上面的单向数据绑定替代方案会很简单。
但是,在这种情况下,实现双向数据绑定是非常不同的。它是如何做到的?
为了了解如何提出解决方案,重要的是分解此结构,以帮助我们确定应该实施哪些观察流程。
当您像这样可视化fieldset 对象时,事情就变得简单了:
fieldset
--> rows (0, 1, ...)
--> row
--> fields (name, email, phone)
--> value
当您考虑到元素/应用程序的工作流程时,它会变得更简单。
在这种情况下,我们将构建一个简单的网格编辑器:
+---+------+--------+-------+
| | Name | E-mail | Phone |
+---+------+--------+-------+
| 0 | xxxx | xxxxxx | xxxxx |
+---+------+--------+-------+
| 1 | xxxx | xxxxxx | xxxxx |
+---+------+--------+-------+
| 2 | xxxx | xxxxxx | xxxxx |
+---+------+--------+-------+
数据在此应用中的流动有两种基本方式,由不同的交互触发。
因此,首先让我们创建一个重复模板,该模板将使用一个新的自定义元素,我们称之为basic-field:
<div class="layout horizontal flex">
<div>-</div>
<template is="dom-repeat" items="[[fieldset.fields]]" class="flex">
<div class="flex">[[item.name]]</div>
</template>
</div>
<template is="dom-repeat" items="{{fieldset.rows}}" as="row" index-as="rowIndex">
<div class="layout horizontal flex">
<div>[[rowIndex]]</div>
<template is="dom-repeat" items="[[fieldset.fields]]" class="flex">
<basic-field class="flex" field="[[item]]" row="{{row}}"></basic-field>
</template>
</div>
</template>
(在上面的 sn-p 中,你会注意到我还添加了一个单独的重复模板来生成列标题,并为行索引添加了一个列 - 这对我们的绑定没有影响机制。)
现在我们已经完成了,让我们自己创建 basic-field 元素:
<dom-module>
<template>
<input value="{{value}}">
</template>
</dom-module>
<script>
Polymer({
is: 'basic-field',
properties: {
row: {
type: Object,
notify: true
},
field: {
type: Object
},
value: {
type: String
}
}
});
</script>
我们不需要从元素本身修改元素的字段,因此field 属性没有notify: true。但是,我们将修改行的内容,所以我们在 row 属性上确实有 notify: true。
但是,此元素尚未完成:在其当前状态下,它不执行任何设置或获取其值的工作。如前所述,该值取决于行和字段。让我们在元素的原型中添加一个多属性观察者,当满足这两个要求时,它会观察,并从数据集中填充 value 属性:
observers: [
'_dataChanged(row.*, field)'
],
_dataChanged: function(rowData, field) {
if (rowData && field) {
var value = rowData.base[field.prop];
if (this.value !== value) {
this.value = value;
}
}
}
这个观察者有几个部分使它起作用:
-
row.* - 如果我们刚刚指定了row,那么只有在我们将row 设置为完全不同的行时才会触发观察者,从而更新引用。 row.* 表示我们正在监视 row 的内容,以及 row 本身。
-
rowData.base[field.prop] - 在观察者中使用 obj.* 语法时,这告诉 Polymer 我们正在使用路径观察。因此,不是只返回对象本身,这意味着rowData 将返回一个具有三个属性的对象:
-
path - 更改的路径,在我们的例子中可能是 row.name、row.phone 等。
-
value - 在给定路径设置的值
-
base - 基础对象,在本例中是行本身。
但是,此代码只处理第一个流程 - 使用来自 fieldset 的数据填充元素。为了处理另一个流程,即用户输入,我们需要一种方法来捕捉用户何时输入数据,然后更新行中的数据。
首先,我们将<input>元素上的绑定修改为{{value::input}}:
<input value="{{value::input}}">
Polymer 不会完全自动绑定到原生元素,因为它不会向它们添加自己的抽象。只有{{value}},Polymer 不知道何时更新绑定。 {{value::input}} 告诉 Polymer 它应该更新原生元素的 input 事件的绑定,只要 <input> 元素的 value 属性发生更改,就会触发该事件。
现在让我们为value 属性添加一个观察者:
value: {
type: String,
observer: '_valueChanged'
}
...
_valueChanged: function(value) {
if (this.row && this.field && this.row[this.field] !== value) {
this.set('row.' + this.field.prop, value);
}
}
请注意,我们没有使用this.row[this.field.prop] = value;。如果我们这样做了,Polymer 将不会意识到我们的变化,因为它不会自动进行路径观察(出于前面描述的原因)。仍会进行更改,但不会通知basic-field 之外可能正在观察fieldset 对象的任何元素。使用 this.set 可以让 Polymer 轻拍一下我们正在更改 row 内部的一个属性,这允许它随后触发链中所有适当的观察者。
总而言之,basic-field 元素现在应该看起来像这样(我添加了一些基本样式以使<input> 元素适合basic-field 元素):
<link rel="import" href="components/polymer/polymer.html">
<dom-module id="basic-field">
<style>
input {
width: 100%;
}
</style>
<template>
<input value="{{value::input}}">
</template>
</dom-module>
<script>
Polymer({
is: 'basic-field',
properties: {
row: {
type: Object,
notify: true
},
field: {
type: Object
},
value: {
type: String,
observer: '_valueChanged'
}
},
observers: [
'_dataChanged(row.*, field)'
],
_dataChanged: function(rowData, field) {
if (rowData && field) {
var value = rowData.base[field.prop];
if (this.value !== value) {
this.value = value;
}
}
},
_valueChanged: function(value) {
if (this.row && this.field && this.row[this.field] !== value) {
this.set('row.' + this.field.prop, value);
}
}
});
</script>
让我们继续使用我们之前制作的模板并将它们也放入自定义元素中。我们称之为fieldset-editor:
<link rel="import" href="components/polymer/polymer.html">
<link rel="import" href="components/iron-flex-layout/iron-flex-layout.html">
<link rel="import" href="basic-field.html">
<dom-module id="fieldset-editor">
<style>
div, basic-field {
padding: 4px;
}
</style>
<template>
<div class="layout horizontal flex">
<div>-</div>
<template is="dom-repeat" items="[[fieldset.fields]]" class="flex">
<div class="flex">[[item.name]]</div>
</template>
</div>
<template is="dom-repeat" items="{{fieldset.rows}}" as="row" index-as="rowIndex">
<div class="layout horizontal flex">
<div>[[rowIndex]]</div>
<template is="dom-repeat" items="[[fieldset.fields]]" class="flex">
<basic-field class="flex" field="[[item]]" row="{{row}}"></basic-field>
</template>
</div>
</template>
<pre>[[_previewFieldset(fieldset.*)]]</pre>
</template>
</dom-module>
<script>
Polymer({
is: 'fieldset-editor',
properties: {
fieldset: {
type: Object,
notify: true
}
},
_previewFieldset: function(fieldsetData) {
if (fieldsetData) {
return JSON.stringify(fieldsetData.base, null, 2);
}
return '';
}
});
</script>
您会注意到我们使用与basic-field 内部相同的路径观察语法来观察fieldset 的变化。使用 _previewFieldset 计算属性,我们将在对字段集进行任何更改时生成一个 JSON 预览,并将其显示在数据输入网格下方。
而且,现在使用编辑器非常简单——我们可以通过使用来实例化它:
<fieldset-editor fieldset="{{fieldset}}"></fieldset-editor>
你有它!我们已经使用 Polymer 1.0 使用括号符号访问器完成了等效的双向绑定。
如果你想实时玩这个设置,我有uploaded on Plunker。