4
votes

In my component I would like to repeat a list of item with template provided by the light dom of the component. For example:

<template is="dom-repeat" items="{{items}}">
    <content select="#itemTemplate"></content>
</template>

However, it seems that Polymer only inserts the light dom element #itemTemplate exactly one time instead of multiple times. Is there other way to repeat a light dom element?

2
In short, no. Unfortunately, distributed content can only be selected once according to shadow dom specs. - zerodevx
Thanks for your info! - Dong Nguyen
There's other ways to achieve this - see iron-list which makes use of the Templatizer. I'd really like to know how we can implement an iron-list-like element that makes use of the <dom-repeat> internally - nicholaswmin
If Templatizer is used, then you cannot use it with dom-repeat, but have to stamp the template yourself. Also, the light DOM children will need to be wrapped in a template tag, just like with iron-list. I have done some progress on this topic, but haven't really managed to finish it. See this and this question for more details. - alesc
I actually haven't looked into it, since I was busy finishing my answer. Will look into it tomorrow or the day after. - alesc

2 Answers

2
votes

I have created a simple prototype, that lets you specify the number of repetitions of the light DOM template.

Because the content is in the light DOM, you can style it from the outside as you would usually do. And data binding inside the template also works, since I have implemented the _forwardParentProp, and _forwardParentPath methods from the Templatizer.

Be aware, that I have not implemented the instance specific properties, which would allow per row specific variables, such as index and item. This can, of course, be done, but would need a bit more work.

See the prototype in action: JSBin.

OK, let's go into details:

The usage of the test-element along with data-binding to both input elements is fairly straightforward:

<template is="dom-bind">
    Number of repeats: <input type="text" value="{{repeats::input}}" /> <br />
    Custom message: <input type="text" value="{{customMessage::input}}" />

    <test-element repeats="{{repeats}}">
        <template>
            <h1>Title!</h1>

            <p>
                Custom message: <em>[[customMessage]]</em>
            </p>
        </template>
    </test-element>
</template>

Notice the dom-bind, which is needed to create a data-binding scope.

As for the test-element, the whole source code looks like this:

<dom-module id="test-element">
    <template>
      <style>
        :host {
          display: block;
        }
      </style>

          <content></content>
    </template>

    <script>
        Polymer({

            is: 'test-element',

            behaviors: [
                Polymer.Templatizer,
            ],

            properties: {
                repeats: {
                    type: Number,
                    value: 3,
                    notify: true,
                },
            },

            observers: [
                '_repeatsChanged(repeats)',
            ],

            _repeatsChanged: function(repeats) {
                // First time only: initialize template
                if (this.template === undefined) {
                    this.template = Polymer.dom(this).querySelector('template');
                    this.templatize(this.template);
                }

                // Remove previously stamped children
                while (Polymer.dom(this).firstChild) {
                    Polymer.dom(this).removeChild(Polymer.dom(this).firstChild);
                }

                // Stamp new ones
                this.stamped = new Array(repeats);

                var inst;
                for (var i = 0; i < repeats; i++) {
                    inst = this.stamp(null);
                    this.stamped[i] = inst.root.querySelector('*');
                    Polymer.dom(this).appendChild(inst.root);
                }
            },

            // Copied from iron-list
            _forwardParentProp: function(prop, value) {
                if (this.stamped) {
                    this.stamped.forEach(function(item) {
                        item._templateInstance[prop] = value;
                    }, this);
                }
            },

            // Copied from iron-list
            _forwardParentPath: function(path, value) {
                if (this.stamped) {
                    this.stamped.forEach(function(item) {
                        item._templateInstance.notifyPath(path, value, true);
                    }, this);
                }
            },

        });
    </script>
</dom-module>

There is only one property, repeats, which specifies the number of stamped instances. Default value is 3. To accomodate changes of said property's value, a observer has been created. This is also the place where the stamping takes place:

_repeatsChanged: function(repeats) {
    // First time only: initialize template
    if (this.template === undefined) {
        this.template = Polymer.dom(this).querySelector('template');
        this.templatize(this.template);
    }

    // Remove previously stamped children
    while (Polymer.dom(this).firstChild) {
        Polymer.dom(this).removeChild(Polymer.dom(this).firstChild);
    }

    // Stamp new ones
    this.stamped = new Array(repeats);

    var inst;
    for (var i = 0; i < repeats; i++) {
        inst = this.stamp(null);
        this.stamped[i] = inst.root.querySelector('*');
        Polymer.dom(this).appendChild(inst.root);
    }
},
  • Firstly (and only once), the template is read from the light DOM and the templatize method is called. This method initializes the Templatize behavior.
  • Secondly, all previously stamped children are removed (so that the
    elements don't just build up infinitely).
  • Thirdly, new children are stamped, according to the current value of repeats. All stamped instances are saved to this.stamped, which is needed for the data-binding from the outside to work.

Last but not least, the Templatizer behavior is implemented via two methods (and two are left unimplemented):

// Copied from iron-list
_forwardParentProp: function(prop, value) {
        if (this.stamped) {
                this.stamped.forEach(function(item) {
                        item._templateInstance[prop] = value;
                }, this);
        }
},

// Copied from iron-list
_forwardParentPath: function(path, value) {
        if (this.stamped) {
                this.stamped.forEach(function(item) {
                        item._templateInstance.notifyPath(path, value, true);
                }, this);
        }
},

Both methods are taken from the iron-list. They iterate through the stamped children and propagate property changes and path notifications.

0
votes

You can include your content in a separate element and use it.

<template is="dom-repeat" items={{items}}">
  <child-element item=[[item]]></child-element>
</template>