-->

How to use dom-repeat with objects instead of arra

2020-01-24 12:03发布

问题:

Iterating over an array myarray=[1, 2, 3] works like this:

<template is="dom-repeat" items="[[myarray]]">
    <span>[[item]]</span>
</template>

How can I iterate over an object myobject = {a:1, b:2, c:3}?

回答1:

Here is a complete implementation:

<test-element obj='{"a": 1, "b": 2, "c": 3}'></test-element>

<dom-module id="test-element">
    <template>

        <template is="dom-repeat" items="{{_toArray(obj)}}">
            name: <span>{{item.name}}</span>
            <br> value: <span>{{item.value}}</span>
            <br>
            <hr>
        </template>

    </template>
    <script>
    Polymer({

        properties: {
            obj: Object
        },

        _toArray: function(obj) {
            return Object.keys(obj).map(function(key) {
                return {
                    name: key,
                    value: obj[key]
                };
            });
        }

    });
    </script>

</dom-module>


回答2:

I faced the same problem, but my use-case is a bit more demanding: I need two-way deep binding through the repeat. Plus I cannot afford rewriting the whole tree on each change.

Since I did not find a solution and the polymer team seems to take it slowly on this issue, I made something for the time being. It's written in ES2015, but translating that to vanilla ES5 should be straightforward. Runs in Chrome anyway as is. Or throw it at bable. This page details how. The gist for the purpose of this posting:

vulcanize element.html --inline-script --inline-css | \
    crisper -h element.v.html -j element.js;
babel element.js -o element.js

So here we go:

<link rel="import" href="../../bower_components/polymer/polymer.html">

<dom-module id="my-objarray">
    <script>
(function() {
    'use strict';

    class Objarray {
        beforeRegister() {
            this.is = 'my-objarray';
            this.properties = {
                array:{
                    notify:true,
                    type:Array,
                    value:function() {return new Array();}
                },
                object:{
                    notify:true,
                    type:Object
                }
            };
            this.observers = ['_onArray(array.*)', '_onObject(object.*)'];
        }
        _onObject(change) {
            if(this._setting) return;
            if(change.path == "object") this._rewriteArray();
            else this._writeElement(change);
        }

        _rewriteArray() {
            this.splice("array", 0, this.array.length);
            for(let i in this.object) {
                this.push("array", {key:i, value:this.object[i]});
            }
        }

        _writeElement(change) {
            const path = change.path.match(/^object\.([^\.]+)(.*)$/);
            const key = path[1];
            const objectPath = "object." + key + (path[2] || "");
            const id = this._getId(key);
            const arrayPath = "array." + id + ".value" + (path[2] || "");
            this.set(arrayPath, this.get(objectPath));
        }

        _getId(key) {
            const collection = Polymer.Collection.get(this.array);
            for(const element of this.array) {
                if((element && element.key) === key) {
                    return collection.getKey(element);
                }
            }
        }

        _onArray(change) {
            let path = change.path.match(/^array\.(#\d+)\.([^\.]+)(\.|$)/);
            if(!path) return;
            let id = path[1], field = path[2];
            if(field == "key") throw new Error("my-objarray: Must not change key!");
            if(field != "value") throw new Error("my-objarray: Only change inside value!");
            this._setting = true;
            this.set(this._getPath(change, id), change.value);
            delete this._setting;
        }

        _getPath(change, id) {
            let collection = Polymer.Collection.get(change.base);
            let index = change.base.indexOf(collection.getItem(id));
            let key = change.base[index].key;
            return change.path.replace("array." + id + ".value", "object." + key);
        }

    }

    Polymer(Objarray);
})();
    </script>
</dom-module>

Usage:

<dom-module id="my-objarray-test">
    <template strip-whitespace>
        <my-objarray object="{{items}}" array="{{array}}"></my-objarray>
        <template is="dom-repeat" items="{{array}}">
            <div>
                <label>{{item.key}}:</label>
                <input type="number" value="{{item.value.data::input}}">
            </div>
        </template>
    </template>
    <script>
(function() {
    'use strict';

    class ObjarrayTest {
        beforeRegister() {
            this.is = 'my-repeat-test';
            this.properties = {
                items:{
                    notify:true,
                    type:Object,
                    value:function() {return new Object();}
                }
            };
            this.observers = ['_onItems(items.*)'];
        }

        ready() {
            console.log("my-repeat-test.ready");
            this.items = {a:{data:1}, b:{data:2}};
        }

        _onItems(change) {console.log("test._onItems", change.path);}

    }

    Polymer(ObjarrayTest);
})();
    </script>
</dom-module>

Hope that helps somebody. Presumable polymer now gets the feature like tomorrow :-)



回答3:

I've been using Object.keys(obj).map(function(prop){return {id:prop, val:obj[prop]}})



回答4:

Revisiting this to account for issues others have mentioned. This is compatible with all browsers and uses hasOwnProperty.

<template is="dom-repeat" items="[[_toArray(obj)]]">
  key: [[item.key]] val: [[item.val]]
</template>

...

_toArray: function(obj, deep) {
  var array = [];
  for (var key in obj) {
    if (deep || obj.hasOwnProperty(key)) {
      array.push({
        key: key,
        val: obj[key]
      });
    }
  }
  return array;
}


回答5:

You need to turn this object into a meaningful array to be able to iterate over it with the dom-repeat.

I have created a myObj property with the initial value. I have then created a property called myObjAsArray which is an empty array. In the ready callback function which is called when the local dom is ready, I am iterating over all of the properties of myObj and adding them to myObjAsArray (see here for how to iterate through an objects properties). You can then iterate over this array with dom-repeat.

<link rel="import" href="bower_components/polymer/polymer.html">

<dom-module id="test-element">
    <style>
    </style>
    <template>
        <template is="dom-repeat" items="{{myObjAsArray}}">
            name: <span>{{item.name}}</span>
            value: <span>{{item.value}}</span>
        </template>
    </template>
</dom-module>

<script>
    Polymer({
        is: "test-element",
        properties: {
            myObj: {
                type: Object,
                value: function () {
                    return {
                        a: 1,
                        b: 2,
                        c: 3
                    };
                }
            },
            myObjAsArray: {
                type: Array,
                value: function () {
                    return [];
                }
            }
        },
        attached: function () {
            var propArray = [];
            for (var prop in this.myObj) {
                if (this.myObj.hasOwnProperty(prop)) {
                    propArray.push({name: prop, value: this.myObj[prop]});
                }
            }

            this.myObjAsArray = propArray;
        }
    });
</script>


回答6:

Object.keys() doesn't seem to work in IE. So modified the implementation to use _.map instead.

<test-element obj='{"a": 1, "b": 2, "c": 3}'></test-element>

<dom-module id="test-element">
  <template>

    <template is="dom-repeat" items="{{getKeyValue(obj)}}">
      key: <span>{{item.key}}</span>
      <br> value: <span>{{item.value}}</span>
      <br>
      <hr>
    </template>

  </template>
  <script>
    Polymer({

      properties: {
        obj: Object
      },

      getKeyValue: function(obj) {
        return _.map(obj, function(value, key) {
          return {
            key: key,
            value: value
          };
        });
      }

    });

  </script>

</dom-module>

https://jsfiddle.net/avidlearner/36jnb16d/