聚合物1.0'阵列式'路径访问器,表达式中括号表示法的替代

时间:2015-06-03 21:43:51

标签: polymer polymer-1.0

Polymer 1.0文档声明:

  

路径语法不支持数组样式的访问器(例如   用户[0]。名称)。但是,您可以直接在路径中包含索引   (users.0.name)。

如何动态设置路径,并使用Polymer 0.5获得与以下示例相同的行为?这特别是在为Object定义的模型生成表单的上下文中。

<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具有用于绑定的复杂表达式解析器,为简单性和性能而我们已经消除了这些解析器。您现在可以使用其他模式来获得类似的结果,只需要您更明确。

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

2 个答案:

答案 0 :(得分:14)

是的,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>

(在上面的代码段中,您注意到我还添加了一个单独的重复模板来生成列标题,并为行索引添加了一列 - 这对我们的约束机制。)

现在我们已经完成了这项工作,让我们自己创建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.namerow.phone等。
    • value - 在给定路径设置的值
    • base - 基础对象,在本例中是行本身。

但是,此代码仅处理第一个流程 - 使用fieldset中的数据填充元素。要处理其他流程,用户输入,我们需要一种方法来捕获用户输入数据的时间,然后更新行中的数据。

首先,让我们将<input>元素上的绑定修改为{{value::input}}

<input value="{{value::input}}">

Polymer不会完全自动绑定到本机元素,因为它不会向它们添加自己的抽象。只有{{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之外的任何元素。使用this.set使聚合物在肩膀上轻敲我们正在改变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 computed属性,无论何时对其进行任何更改,我们都会生成字段集的JSON预览,并将其显示在数据输入网格下方。

而且,使用编辑器现在非常简单 - 我们只需使用:

即可实例化它
<fieldset-editor fieldset="{{fieldset}}"></fieldset-editor>

你有它!我们使用Polymer 1.0使用括号表示法访问器完成了相当于双向绑定的方法。

如果您想实时使用此设置,我会uploaded on Plunker

答案 1 :(得分:1)

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

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

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

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

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

&#13;
&#13;
<!-- 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>
&#13;
&#13;
&#13;

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