使用“重复”值修改数组并使用模板进行渲染

时间:2015-07-01 05:02:06

标签: javascript polymer polymer-1.0

我看到的一个小'数组输入'聚合物元素的行为正在让我发挥最大作用。我创建了一个接受多个值的输入。在内部,值存储在“Array”属性中,并带有标记。这似乎按预期工作。

一旦添加了一个值(使用回车),它就会在数组框中装箱,然后重点关注新值的输入。我希望能够单击已添加的值并在输入中修改它们。现在,它拼接数组中的值,并将其值添加到输入中。

然而 - 当这样做时,单击(并巧合拼接)将使重复值'空白'。获取getValue()函数将返回一个完美的数组。单击“消隐”输出值将在输入中生成正确的值。

这是一个仍然表现出行为的元素的“淡化”版本。

<style>
  :host {
    display: block;
  }
</style>

<template>
  <array-items id="values">
    <template is="dom-repeat" items="{{items}}">
      <array-item style='padding:5px;margin-right:5px;background-color:lightblue;'>{{item}}</array-item>
    </template>
    <input id="input">
  </array-items>
</template>

Polymer({ 
  is: 'app-arraybox',

  properties: {
    // Items are the 'entries' inside the array-box
    items: {
      type: Object,
      reflectToAttribute: true,
      value: []
    },

  },

  listeners: {
    'input.keydown': '_inputKeys',
    'input.blur': '_addValue',
    'values.click': '_itemClick',
  },

  _inputKeys: function(event) {
    var backspace = 8;
    var enter = 13;
    // If enter, then add a new value
    if (event.keyCode == enter)
        this._addValue();
    // if backspace (and input is empty), then edit the last value
    else if (event.keyCode == backspace) {
        if (this.$.input.value.length < 1) {
          event.preventDefault();
          this._editValue();
        }
      }
  },

  // Handle clicking on an existing item
  _itemClick: function(event) {
    // Return if not clicking on an array-item -- this is bound
    // to the container since items don't exist in the DOM yet
    if (event.target.tagName != 'ARRAY-ITEM') return;
    var child = event.target;
    var input = this.$.input;
    // Figure out index of clicked item by iterating through previousSiblings and checking them
    var i = 0; while((child = child.previousSibling) != null) { 
      // only count ARRAY-ITEMS
      if (child.tagName == 'ARRAY-ITEM') i++; 
    } 
    // Before continuing, if there's a value in the input already, add it to the arraybox
    if (input.value.length > 0) this._addValue();
    // Splice the value from 'this.items' and add the value to the input for editing
    this.$.input.value = this.splice('items', i, 1);
    // And finally, focus the input
    this.$.input.focus();
  },

  _addValue: function() {
    // Need the input, and value of the input
    var input = this.$.input;
    var value = input.value;
    // Value must be at least a single character
    if (value.length > 0) {
      // Push the value to the array
      this.push('items', value);
      // Clear the text input
      input.value = '';
    } else {
      // No 'meaningful' value was input
    }
  },

  _editValue: function() {
    var input = this.$.input;
    if (this.items.length == 0)
      return;
      // Set the input to the last value in the array (pop also removes it from the array)
    input.value = this.pop('items');
  },

  _updateIcon: function() {
    // Check if icon is set and set <iron-icon> attribute if necessary
    if (this.icon.length > 0)
      this.$.icon.setAttribute('icon', this.icon);
    // Else, remove the attribute on the <iron-icon>
    else this.$.icon.removeAttribute('icon');
  },

  // Public 'getValue' for API/JavaScript interaction
  getValue: function() {
    // Return items array
    return this.items;
  },

});

请告诉我,我做的事情很愚蠢。 您可以在此处查看演示:app-arraybox demo

1 个答案:

答案 0 :(得分:0)

Polymer依赖于object-identity来优化dom-repeat呈现,以应对不断变化的数据。当您的数据是具体的(简单的数字或字符串)时,数据没有标识(一个'foo'与任何其他'foo'相同)。如果您存储基于对象的数据({text: 'foo'}),则每个对象都是唯一的。

此外,您通常不应该假设dom-node索引对应于数组索引,因为您可能希望将来对视图进行排序/过滤。

最后,Polymer提供了许多可用于处理您没有利用的集合和重复(以及各种其他任务)的功能。例如,您的itemClick可以折叠为以下内容:

// Handle clicking on an existing item
_itemClick: function(event) {
  // get the item reference from the event
  var item = event.model.item;
  // remove item from array
  this.arrayDelete('items', item);
  // return item text to edit mode
  this.value = item.text;
  this.$.input.focus();
},

这是一个实时版本的版本(仅限Chrome,为方便起见):

<!doctype html>
<head>
  
  <meta name="description" content="color-thief with Polymer">
  <meta charset="utf-8">

  <base href="http://polygit.org/components/">

  <script src="webcomponentsjs/webcomponents-lite.min.js"></script>
  <link href="polymer/polymer.html" rel="import">
  <link rel="import" href="iron-icon/iron-icon.html">
  <link rel="import" href="iron-icons/iron-icons.html">

  <style>
body {
  font-family: sans-serif;
  background-color: #999;
}
  </style>

</head>
<body>

  <app-arraybox icon="menu"></app-arraybox>

  <!--
  [ APP-ARRAYBOX | BY JEREMY SCHROEDER | ... ]
  Multi-value input boxes that hold values as an array.
  -->

  <!-- 'dom-module' is a Polymer element that registers arbitrary content by an `id`-->

  <dom-module id="app-arraybox">

<!-- The style contained is relative to the element itself and will not style
     anything outside. To style the element itself, use the ':host' specifier -->

<style>
  :host {
    display: block;
    cursor: text;
  }

  :host.raised {
    border: 1px solid lightgray;
    border-bottom: 4px solid lightgray;
    border-right: 4px solid lightgray;
    padding-left: 6px;
    border-radius: 3px;
  }

  iron-icon {
    display: none;
  }

  iron-icon[icon] {
    display: inline-block;
    top: 8px;
    margin-right: 12px;
    margin-left: 6px;
  }

  input {
    flex: 1 1;
    height: 28px;
    padding: 6px;
    border: none;
    outline: none;
    padding-left: 12px;
    font-size: 100%;
  }

  array-item {
    flex: none;
    padding: 3px;
    padding-right: 12px;
    padding-left: 12px;
    margin-bottom: 4px;
    margin-right: 6px;
    background-color: whitesmoke;
    top: 7px;
    position: relative;
    height: 21px;
    border-radius: 3px;
    cursor: pointer;
  }

  array-items {
    max-width: 100%;
    flex: none;
    display: flex;
    flex-wrap: wrap;
  }

</style>

<!-- The template contains the markup of the element itself. <content></content> can be used to pass along
      any 'inner text' that the user specifies. There are also a slew of 'helpers' such as template repeating,
      data binding, and event registration that can be taken advantage of here. -->
<template>

  <array-items id="values">

    <iron-icon id="icon" icon$="{{icon}}"></iron-icon>

    <template is="dom-repeat" items="{{items}}">
      <array-item on-click="_itemClick">{{item.text}}</array-item>
    </template>

    <input id="input" value="{{value::input}}" on-blur='_commitValue' on-keydown="_inputKeys">

  </array-items>

</template>

<!-- The script contains is called upon registration. Though it typically includes a call to Polymer(...), it can also
     include other code that can be used to manage the element and its features. -->
<script>

  Polymer({

    // Required -- tells Polymer which element is being registered. If there is a dom-module
    // registered with the same value for `id`, Polymer will look in that dom-module for 
    // templates and style-sheets.
    is: 'app-arraybox',

    // Properties registered here can be accessed via 'this' at any time. 
    properties: {

      // Items are the 'entries' inside the array-box
      items: {
        type: Object,
        value: function() {
          return [];
        }
      },

      // Icon is an iron-icon that can be shown at the beginning of the arraybox (optional)
      icon: {
        type: String,
        reflectToAttribute: true
      }, 

      raised: {
        type: Boolean,
        reflectToAttribute: true,
        value: false,
        observer: '_raise'
      },

      value: {
        value: ''
      }

    },

    // Register listeners -- these will execute functions when the specified event is fired
    listeners: {
      'click': '_focusInput'
    },

    _raise: function() {
      this.toggleClass('raised', this.raised);
    },

    _focusInput: function(event) {
      if (event.target.tagName == 'APP-ARRAYBOX')
        this.$.input.focus();
    },

    // Handle 'keyup' for the input
    _inputKeys: function(event) {
      var backspace = 8;
      var enter = 13;
      // If enter, then add a new value
      if (event.keyCode == enter) {
        this._commitValue();
      } else
      // if backspace, then edit the last value
      if (event.keyCode == backspace) {
        if (this.$.input.value.length <= 0) {
          event.preventDefault();
          this._editValue();
        }
      }
    },

    // Handle clicking on an existing item
    _itemClick: function(event) {
      // get the item reference from the event
      var item = event.model.item;
      // remove item from array
      this.arrayDelete('items', item);
      // return item text to edit mode
      this.value = item.text;
      this.$.input.focus();
    },

    _commitValue: function() {
      // Value must be at least a single character
      if (this.value.length > 0) {
        console.log('Adding ' + this.value);
        // Push the value to the array
        this.addValue(this.value);
        // Clear the text input
        this.value = '';
      } else {
        // No 'meaningful' value was input
      }
    },

    _editValue: function() {
      // Value must none, otherwise it's a regular 'backspace', in which case we just return without
      // doing anything.
      if (this.items.length > 0) {
        // Set the input to the last value in the array (pop also removes it from the array)
        this.value = this.pop('items');
      }
    },

    // Public 'addValue' for API/JavaScript interaction
    addValue: function(value) {
      // Push new item to the items array
      this.push('items', {text: value});
    },

    // Public 'getValue' for API/JavaScript interaction
    getValue: function() {
      // Return items array
      return this.items;
    },

    setValue: function(array) {
      this.items = array;
    }

  });

</script>

  </dom-module>
  
</body>