聚合物1.0“数组样式"路径访问器,可替代表达式中的括号表示法

问题描述:

Polymer 1.0文档指出:

The Polymer 1.0 documentation states:

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

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的以下示例相同的行为?尤其是在为对象定义的模型生成表单的情况下.

How would one get around this in setting the path dynamically, and obtain the same behavior as the following example using Polymer 0.5? This is specifically in the context of generating forms for a model defined by an 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 :

没有近期计划来支持这一点.聚合物0.5具有用于绑定的复杂表达式解析器,为简单起见和性能我们已将其删除.您今天可以使用其他模式来获得类似的结果,只是要求您更加明确.

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.

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

What the alternate pattern would be to achieve two way data binding remains unclear.

是的,的确,Polymer 1.0不再在绑定表达式中支持myObject[key].但是,在您的特定用例中,有一些方法可以避免此问题.

Yes, it is true that Polymer 1.0 no longer supports myObject[key] in binding expressions. However, in your particular use-case, there are ways to sidestep this problem.

在单向数据绑定中克服此限制非常简单.只需使用一个既接受对象又涉及问题键的计算属性:

It is fairly simple to overcome this limitation when it comes to one-way data-binding. Simply use a computed property that accepts both the object and the key in question:

<my-element value="[[getValue(obj, key)]]"></my-element>

getValue: function(obj, key) {
  return obj[key];
}

双向数据绑定

在双向数据绑定的情况下,仍然可以创建聚合物1.0中绑定表达式{{obj[key]}}的功能替代.但是,这将需要考虑您希望实现绑定的特定用例.

Two-way data-binding

In the case of two-way data-binding, it is still possible to create a functional alternative to the binding expression {{obj[key]}} in Polymer 1.0. However, it will require taking into consideration the particular use-case in which you are hoping to implement the binding.

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

Taking into account the example in your question, it seems that you are doing work with some sort of table or fieldset. For the purposes of the example here, I will use a slightly different, yet very similar structure.

让我们假设我们有一个fieldset对象,并且该对象的结构如下:

Let's assume that we have a fieldset object, and that this object is structured as such:

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

如果我们要创建某种表示该对象的类似于表的输出,则可以使用两个嵌套的重复模板:第一个迭代通过不同的行,第二个迭代通过不同的字段.使用上面的单向数据绑定替代方案,这将很简单.

If we want to create some sort of table-like output which represents this object, we can use two nested repeating templates: the first to iterate through the different rows, and the second to iterate through the different fields. This would be simple using the one-way data-binding alternative above.

但是,在这种情况下,实现双向数据绑定非常不同.怎么做?

However, achieving two-way data-binding is very different in this case. How is it do-able?

为了理解如何提出解决方案,重要的是分解这种结构,以帮助我们弄清楚应该执行什么观察流程.

In order to understand how to come up with a solution, it is important to break down this structure in a way that will help us figure out what observation flow we should implement.

像这样可视化fieldset对象时,它变得很简单:

It becomes simple when you visualize the fieldset object like this:

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

并且当您考虑元素/应用程序的工作流程时,甚至变得更简单.

And becomes even simpler when you factor in the workflow of your element/application.

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

In this case, we will be building a simple grid editor:

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

数据通过不同的交互触发在此应用中流动的两种基本方式.

There are two basic ways that data will flow in this app, triggered by different interactions.

  • 预填充字段值:这很容易;我们可以使用前面提到的嵌套模板轻松完成此操作.

  • Pre-populating the field value: This is easy; we can easily accomplish this with the nested templates mentioned earlier.

用户更改字段的值:如果查看应用程序设计,我们可以推断出,为了处理这种交互,无论用作输入控件的元素是什么,它都必须有某种了解的方式:

User changes a field's value: If we look at the application design, we can infer that, in order to handle this interaction, whatever element we use as an input control, it must have some sort of way of knowing:

  • 会影响
  • 它代表的字段
  • The row it will affect
  • The field it represents

因此,首先让我们创建一个重复模板,该模板将使用一个称为basic-field的新自定义元素:

Therefore, first let's create a repeating template which will use a new custom element that we will call 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元素本身:

Now that we've done this, let's create the basic-field element itself:

<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.

We have no need to modify the element's field from within the element itself, so the field property does not have notify: true. However, we will be modifying the contents of the row, so we do have notify: true on the row property.

但是,该元素尚未完成:在当前状态下,它不执行任何设置或获取其值的工作.如前所述,该值取决于行和字段.让我们在元素的原型中添加一个多属性观察者,该观察者将监视何时满足这两个要求,并从数据集中填充value属性:

However, this element is not yet complete: In its current state, it doesn't do any of the work of setting or getting its value. As discussed before, the value is dependent on the row and the field. Let's add a multi-property observer to the element's prototype that will watch for when both of these requirements have been met, and populate the value property from the dataset:

observers: [
  '_dataChanged(row.*, field)'
],
_dataChanged: function(rowData, field) {
  if (rowData && field) {
    var value = rowData.base[field.prop];
    if (this.value !== value) {
      this.value = value;
    }
  }
}

此观察者可以通过以下几部分来使其工作:

There are several parts to this observer that make it work:

  • row.*-如果我们刚刚指定了row,则仅当将row设置为完全不同的行从而更新引用时,才会触发观察者.相反,row.*意味着我们正在监视row的内容以及row本身.
  • rowData.base[field.prop]-在观察者中使用obj.*语法时,这告诉Polymer我们正在使用路径观察.因此,这不仅仅是返回对象本身,这还意味着rowData将返回具有三个属性的对象:
    • path-更改的路径,在我们的示例中,可能是row.namerow.phone等.
    • value-在给定路径上设置的值
    • base-基础对象,在这种情况下将是行本身.
    • row.* - If we had just specified row, the observer would only be triggered if we set row to an entirely different row, thus updating the reference. row.* instead means that we are watching over the contents of row, as well as row itself.
    • rowData.base[field.prop] - When using obj.* syntax in an observer, this tells Polymer that we are using path observation. Thus, instead of returning just the object itself, this means that rowData will return an object with three properties:
      • path - The path to the change, in our case this could be row.name, row.phone, etc.
      • value - The value that was set at the given path
      • base - The base object, which in this case would be the row itself.

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

      This code, however, only takes care of the first flow - populating the elements with data from the fieldset. To handle the other flow, user input, we need a way to catch when the user has inputted data, and to then update the data in the row.

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

      First, let's modify the binding on the <input> element to {{value::input}}:

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

      Polymer不会完全自动绑定到本地元素,因为它不会向其添加自己的抽象.仅使用{{value}},Polymer不知道何时更新绑定. {{value::input}}告诉Polymer应该更新本机元素的input事件上的绑定,该事件会在<input>元素的value属性更改时触发.

      Polymer does not fully automate binding to native elements as it doesn't add its own abstractions to them. With just {{value}}, Polymer doesn't know when to update the binding. {{value::input}} tells Polymer that it should update the binding on the native element's input event, which is fired whenever the value attribute of the <input> element is changed.

      现在让我们为value属性添加一个观察者:

      Now let's add an observer for the value property:

      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内部的属性,从而可以触发链中所有合适的观察者.

      Notice that we aren't using this.row[this.field.prop] = value;. If we did, Polymer would not be aware of our change, as it does not do path observation automatically (for the reasons described earlier). The change would still be made, but any element outside of basic-field that may be observing the fieldset object would not be notified. Using this.set gives Polymer a tap on the shoulder that we are changing a property inside of row, which allows it to then trigger all the proper observers down the chain.

      总的来说,basic-field元素现在应该看起来像这样(我已经添加了一些基本样式以使<input>元素适合basic-field元素):

      Altogether, the basic-field element should now look something like this (I've added some basic styling to fit the <input> element to the basic-field element):

      <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:

      Let's also go ahead and take the templates we've made before and throw them into a custom element as well. We'll call it the 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预览,并将其显示在数据输入网格的下方.

      You'll notice we're using the same path observation syntax as we did inside of basic-field to observe changes to fieldset. Using the _previewFieldset computed property, we will generate a JSON preview of the fieldset whenever any change is made to it, and display it below the data entry grid.

      而且,使用编辑器现在非常简单-我们可以使用以下方法实例化它:

      And, using the editor is now very simple - we can instantiate it just by using:

      <fieldset-editor fieldset="{{fieldset}}"></fieldset-editor>
      

      就在那里!我们已经使用Polymer 1.0完成了使用括号符号访问器的双向绑定的等效功能.

      And there you have it! We have accomplished the equivalent of a two-way binding using bracket-notation accessors using Polymer 1.0.

      如果您想实时使用此设置,请已上传在Plunker上.

      If you'd like to play with this setup in real-time, I have it uploaded on Plunker.