4

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?

nicholaswmin
  • 21,686
  • 15
  • 91
  • 167
  • In short, no. Unfortunately, distributed content can only be selected once according to shadow dom specs. – zerodevx Sep 29 '15 at 05:37
  • Thanks for your info! – Dong Nguyen Sep 29 '15 at 07:09
  • 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 `` internally – nicholaswmin Dec 14 '16 at 13:59
  • 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](http://stackoverflow.com/q/35357924/4623467) and [this](http://stackoverflow.com/q/35343213/4623467) question for more details. – alesc Dec 14 '16 at 18:48
  • @alesc I have no problem with using ` – nicholaswmin Dec 14 '16 at 20:39
  • I have create a prototype that properly uses the `Templatizer`, which means that outside CSS styling works and data-binding works. – alesc Dec 14 '16 at 20:51
  • @alesc and it's kick-ass, I'm going through it right now - million thanks. Are you still supporting your answer above that there's no way to piggyback on the `dom-repeat` even after the link I've posted? – nicholaswmin Dec 14 '16 at 20:52
  • 1
    I actually haven't looked into it, since I was busy finishing my answer. Will look into it tomorrow or the day after. – alesc Dec 14 '16 at 20:59
  • @NicholasKyriakides: that solution feels a bit hack-ish to me, because you access the internal state of `dom-repeat`, which can break on any future Polymer release. However, the problem with outside styling in `shadow` mode can easily be solved by putting the `dom-repeat` into light DOM (currently it's withing the element's shadow DOM). A fixed version of that solution can be found [here](http://jsbin.com/rowapapori/1/edit?html,css,output). The fix is literally a one-liner. – alesc Dec 17 '16 at 17:09

2 Answers2

2

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.

alesc
  • 2,776
  • 3
  • 27
  • 45
0

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>
Srik
  • 2,341
  • 2
  • 21
  • 35
  • 1
    Thanks, but what I want to achieve is allowing an user to customize a list of items with light dom instead of using a fixed item component. It's kind of a dynamic template by light dom. – Dong Nguyen Sep 29 '15 at 03:00