Polymer 1.0 'array-style' 路径访问器,替代表达式中的括号符号

Polymer 1.0 'array-style' path accessors, alternative to bracket notation in expressions

Polymer 1.0 文档指出:

The path syntax doesn’t support array-style accessors (such as users[0].name). However, you can include indexes directly in the path (users.0.name).

如何通过动态设置路径来解决这个问题,并获得与以下使用 Polymer 0.5 的示例相同的行为?这特别适用于为对象定义的模型生成表单的上下文。

<template repeat="{{row in fieldset.rows}}">
<div layout horizontal flex>
    <template repeat="{{field in row}}" flex>
        <paper-field field="{{model.fields[field]}}" value="{{obj[field]}}">
        </paper-field>
    </template>
</div>
</template>

编辑:

根据 https://github.com/Polymer/polymer/issues/1504:

No near-term plans to support this. Polymer 0.5 had a complex expression parser used for bindings that we have eliminated for simplicity and performance. There are alternate patterns you can use today to achieve similar results that just require you to be more explicit.

实现双向数据绑定的替代模式是什么仍不清楚。

您可以进行计算绑定。 https://www.polymer-project.org/1.0/docs/migration.html#computed-bindings

<paper-field field="{{_computeArrayValue(model.fields, field)}}" value="{{_computeArrayValue(obj, field}}"></paper-field>

<script>
  Polymer({
    ...
    _computeArrayValue: function(array, index) {
      return array[index];
    },
    ...
  });
</script>

顺便说一句,您还需要将重复更新为 dom-repeat https://www.polymer-project.org/1.0/docs/devguide/templates.html#dom-repeat

编辑:这是我对双向绑定的丑陋解决方案。这个想法是你有一个计算变量获取初始值,然后在观察者更新时更新这个变量。

<!-- Create a Polymer module that takes the index and wraps the paper field-->
<paper-field field="{{fieldArrayValue}}" value="{{objArrayValue}}"></paper-field>

<script>
  Polymer({
    ...
    properties: {
            fields: { //model.fields
                type: Array,
                notify: true
            },
            obj: {
                type: Array,
                notify: true
            },
            arrayIndex: {
                type: Number,
                notify: true
            },
            fieldArrayValue: {
                type: String,
                computed: '_computeInitialValue(fields, number)'
            },
            objArrayValue: {
                type: String,
                computed: '_computeInitialValue(obj, number)'
            }
        },
    _computeInitialValue: function(array, index) {
      return array[index];
    },
    observers: [
            'fieldsChanged(fields.*, arrayIndex)',
            'objChanged(fields.*, arrayIndex)'
    ],
    fieldsChanged: function (valueData, key) {
       this.set('fieldArrayValue', this.fields[this.arrayIndex]);            
    },
    objChanged: function (valueData, key) {
       this.set('objArrayValue', this.obj[this.arrayIndex]);            
    },
    ...
  });
</script>

编辑 2:更新了编辑 1 中的代码以反映 Vartan Simonian 指出的观察者更改

是的,Polymer 1.0 确实不再支持绑定表达式中的 myObject[key]。但是,在您的特定 use-case 中,有一些方法可以避免这个问题。

One-way data-binding

one-way data-binding 克服这个限制是相当简单的。只需使用接受 object 和相关密钥的计算 属性:

<my-element value="[[getValue(obj, key)]]"></my-element>
getValue: function(obj, key) {
  return obj[key];
}

Two-way data-binding

在 two-way data-binding 的情况下,仍然可以在 Polymer 1.0 中创建绑定表达式 {{obj[key]}} 的功能替代。但是,需要考虑您希望在其中实现绑定的特定 use-case。

考虑到您问题中的示例,您似乎正在使用某种 table 或字段集。出于此处示例的目的,我将使用略有不同但非常相似的结构。

假设我们有一个 fieldset object,并且这个 object 的结构如下:

{
  "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"
    }
  ]
}

如果我们想要创建某种 table-like 输出来代表这个 object,我们可以使用两个嵌套的重复模板:第一个迭代不同的行,第二个迭代通过不同的领域。使用上面的 one-way data-binding 替代方法会很简单。

然而,在这种情况下实现 two-way data-binding 是非常不同的。怎么样do-able?

为了理解如何提出解决方案,以一种有助于我们弄清楚应该实施什么样的观察流程的方式分解这个结构很重要。

当您像这样想象 fieldset object 时,事情就变得简单了:

fieldset
    -->  rows (0, 1, ...)
        -->  row
            -->  fields (name, email, phone)
                -->  value

当您考虑 element/application.

工作流程时, 变得更加简单

在这种情况下,我们将构建一个简单的网格编辑器:

+---+------+--------+-------+
|   | Name | E-mail | Phone | 
+---+------+--------+-------+
| 0 | xxxx | xxxxxx | xxxxx |
+---+------+--------+-------+
| 1 | xxxx | xxxxxx | xxxxx |
+---+------+--------+-------+
| 2 | xxxx | xxxxxx | xxxxx |
+---+------+--------+-------+

数据在此应用程序中流动的基本方式有两种,由不同的交互触发。

  • Pre-populating字段值:这个很简单;我们可以使用前面提到的嵌套模板轻松完成此操作。

  • 用户更改字段值:如果我们查看应用程序设计,我们可以推断,为了处理此交互,我们使用的任何元素作为一个输入控件,它必须有某种方式知道:

    • 会影响
    • 它代表的字段

因此,首先让我们创建一个重复模板,它将使用我们将调用的新自定义元素 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>

(在上面的代码片段中,您会注意到我还添加了一个单独的重复模板来生成列 headers,并为行索引添加了一列 - 这对我们的绑定机制没有影响。)

现在我们已经完成了这个,让我们创建 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

但是,此元素尚未完成:在其当前状态下,它不执行任何设置或获取其值的工作。如前所述,该值取决于行和字段。让我们向元素的原型添加一个 multi-property 观察者,它将观察何时满足这两个要求,并从数据集中填充 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 我们正在使用路径观察。因此,不是 return 只是 object 本身,这意味着 rowData 将 return 一个具有三个属性的 object:
    • path - 更改的路径,在我们的例子中,这可能是 row.namerow.phone
    • value - 在给定路径设置的值
    • base - 基数 object,在本例中就是行本身。

然而,这段代码只处理第一个流程——用来自 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 object 的元素都不会收到通知。使用 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.

使用 bracket-notation 访问器完成了相当于 two-way 绑定

如果您想在 real-time 中使用此设置,我有 uploaded on Plunker