是的,Polymer 1.0 确实不再支持myObject[key]
绑定表达式。但是,在您的特定用例中,有一些方法可以回避这个问题。
单向数据绑定
当涉及到单向数据绑定时,克服这一限制相当简单。只需使用接受对象和相关键的计算属性:
<my-element value="[[getValue(obj, key)]]"></my-element>
getValue: function(obj, key) {
return obj[key];
}
双向数据绑定
在双向数据绑定的情况下,仍然可以创建一个功能替代{{obj[key]}}
Polymer 1.0 中的绑定表达式。但是,它需要考虑您希望实现绑定的特定用例。
考虑到您问题中的示例,您似乎正在使用某种表格或字段集。出于示例的目的,我将使用稍微不同但非常相似的结构。
假设我们有一个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>
(在上面的代码片段中,您会注意到我还添加了一个单独的重复模板来生成列标题,并为行索引添加了一个列 - 这对我们的绑定机制没有影响。)
现在我们已经完成了这个,让我们创建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
. 但是,我们将修改该行的内容,因此我们确实拥有notify: true
该row
属性。
然而,这个元素还没有完成:在它的当前状态下,它不做任何设置或获取它的值的工作。如前所述,该值取决于行和字段。让我们在元素的原型中添加一个多属性观察者,它会在满足这两个要求时进行观察,并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 没有完全自动绑定到原生元素,因为它没有向它们添加自己的抽象。使用 just {{value}}
,Polymer 不知道何时更新绑定。{{value::input}}
告诉 Polymer 它应该更新原生元素事件的绑定,只要元素的属性发生更改input
,就会触发该绑定。value
<input>
现在让我们为该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
Usingthis.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 使用括号符号访问器完成了等效的双向绑定。
如果您想实时使用此设置,我已将其上传到 Plunker 上。