【问题标题】:Polymer 1.0 'array-style' path accessors, alternative to bracket notation in expressionsPolymer 1.0 'array-style' 路径访问器,替代表达式中的括号符号
【发布时间】:2015-06-03 21:43:50
【问题描述】:

Polymer 1.0 文档指出:

路径语法不支持数组样式的访问器(例如 用户[0].name)。但是,您可以直接在路径中包含索引 (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 有一个用于绑定的复杂表达式解析器,为了简单和性能,我们已经消除了它。您现在可以使用其他模式来获得类似的结果,但需要您更加明确。

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

【问题讨论】:

    标签: polymer polymer-1.0


    【解决方案1】:

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

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

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

    但是,此元素尚未完成:在其当前状态下,它不执行任何设置或获取其值的工作。如前所述,该值取决于行和字段。让我们在元素的原型中添加一个多属性观察者,当满足这两个要求时,它会观察,并从数据集中填充 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 的数据填充元素。为了处理另一个流程,即用户输入,我们需要一种方法来捕捉用户何时输入数据,然后更新行中的数据。

    首先,我们将&lt;input&gt;元素上的绑定修改为{{value::input}}

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

    Polymer 不会完全自动绑定到原生元素,因为它不会向它们添加自己的抽象。只有{{value}},Polymer 不知道何时更新绑定。 {{value::input}} 告诉 Polymer 它应该更新原生元素的 input 事件的绑定,只要 &lt;input&gt; 元素的 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 对象的任何元素。使用 this.set 可以让 Polymer 轻拍一下我们正在更改 row 内部的一个属性,这允许它随后触发链中所有适当的观察者。

    总而言之,basic-field 元素现在应该看起来像这样(我添加了一些基本样式以使&lt;input&gt; 元素适合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 使用括号符号访问器完成了等效的双向绑定。

    如果你想实时玩这个设置,我有uploaded on Plunker

    【讨论】:

    • 太棒了!很彻底!我看到了你为它付出的所有努力,我很感激。
    【解决方案2】:

    您可以进行计算绑定。 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 指出的观察者更改

    【讨论】:

    • +1!这绝对是目前要走的路。但是,为了避免这种情况,我已经开始 discussion on the Polymer mailing list 询问是否可以添加对括号表示法数组访问器的支持而不会导致任何性能问题。希望 Polymer 开发人员做出回应。 :)
    • 我认为是性能问题导致了这种变化。不幸的是,上述解决方案没有复制 0.5 中括号表示法的行为,因为对输入值的更改不会反映在 obj 中。我应该进一步证明我希望实现 2 路数据绑定。
    • @user241584 我将在答案中添加我对 2 路绑定的丑陋解决方案,但它确实涉及使用太多的迂回观察者和一堆我认为不合适的多余代码。
    • @VartanSimonian 对不起,我现在感觉有点愚蠢,不明白你的解决方案如何更干净。我知道我使用计算只是为了获取初始值,而不是任何与 2 路绑定相关的东西。至于 sjmiles 的回答,我觉得它真的很酷,但是你失去了操纵数组其余部分的能力(除非你把它传递下去,但是你需要问为什么你绑定到数组值)。我不知道user241584需要什么,但我还是想把他所有的原始能力都给他。 (将为观察者更新双重绑定,谢谢)
    • @VartanSimonian 哦,好的,我现在明白了,让我用 ready 函数再次更新代码以设置 2 个默认值。
    猜你喜欢
    • 1970-01-01
    • 2021-03-16
    • 2015-01-28
    • 1970-01-01
    • 1970-01-01
    • 2020-10-29
    • 2014-01-10
    • 1970-01-01
    相关资源
    最近更新 更多