How to use dom-repeat with objects instead of arrays in Polymer 1.0?
Asked Answered
M

6

27

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}?

Meridethmeridian answered 11/6, 2015 at 12:58 Comment(0)
B
49

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>
Bilek answered 12/6, 2015 at 1:57 Comment(0)
O
9

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

Offstage answered 3/5, 2016 at 22:5 Comment(0)
M
3

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

Moina answered 11/6, 2015 at 15:15 Comment(2)
Thanks! Do you make a custom element "dom-repeat-map" or something? Because I'm afraid expressions are no longer allowed in tag attributes.Meridethmeridian
That's usually in a computed binding, or processed prior to binding.Moina
C
2

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;
}
Conjuncture answered 5/4, 2017 at 9:35 Comment(0)
E
1

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>
Einhorn answered 11/6, 2015 at 14:46 Comment(2)
Thanks! I am still hoping for a shorter version... Object.keys() could be used instead of your for loop, but that wouldn't make it much shorter.Meridethmeridian
I'm not sure there is much else you can do. You can only use an array in the dom-repeat. I even tried using a function that returned an array but that didn't seem to work.Einhorn
G
0

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/

Greataunt answered 5/2, 2017 at 17:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.