2

Polymer 1.0 文档指出:

路径语法不支持数组样式的访问器(例如 users[0].name)。但是,您可以直接在路径中包含索引 (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

没有近期计划来支持这一点。Polymer 0.5 有一个用于绑定的复杂表达式解析器,为了简单性和性能,我们已经消除了它。您现在可以使用其他模式来获得类似的结果,但需要您更加明确。

实现两种方式数据绑定的替代模式仍不清楚。

4

2 回答 2

14

是的,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: truerow属性。

然而,这个元素还没有完成:在它的当前状态下,它不做任何设置或获取它的值的工作。如前所述,该值取决于行和字段。让我们在元素的原型中添加一个多属性观察者,它会在满足这两个要求时进行观察,并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才会触发观察者,从而更新引用。相反,意味着我们正在监视, 以及它本身的内容。rowrow.*rowrow
  • 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可能在观察对象之外的元素。fieldsetUsingthis.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 上

于 2015-06-04T23:37:32.507 回答
1

您可以进行计算绑定。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

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

<!-- 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 指出的观察者更改

于 2015-06-03T23:25:30.647 回答