Polymer: simple 'count' option for dom-repeat template

Created on 20 Jan 2016  路  29Comments  路  Source: Polymer/polymer

<dom-module id="my-sweets">
  <template>
    <template is="dom-repeat" count="[[num_sweets]]">
      <my-sweet id="sweet_[[index]]"></my-sweet>
    </template>
    <other-stuff>
  </template>
  <script>
    Polymer({
      is: 'my-sweets',

      properties: {
        num_sweets: {
          type: Number,
          value: 0
        }
      }
    });
  </script>

  <my-sweets num-sweets="10"></my-sweets>

(see https://github.com/Polymer/polymer/issues/3313#issuecomment-175722634 for example output, copied here :

  <my-sweet id="sweet_0"></my-sweet>
  <my-sweet id="sweet_1"></my-sweet>
  <my-sweet id="sweet_2"></my-sweet>
  <my-sweet id="sweet_3"></my-sweet>
  <my-sweet id="sweet_4"></my-sweet>

)

Currently, my option is to proxy through an array property :

   <template is="dom-repeat" items="[[_sweets]]">
...
   properties: {
     ...
     _sweets: {
       type: Array,
       value: []
     },
   },
   ...
   ready: function () {
     this._sweets = new Array(this.num_sweets);
   }

...or something like that. I hope you get the idea.

The latter creates an array with no purpose and seems quite hacky. I tried to make an object on which I could set 'length', but Polymer insists on an Array. I also tried to simply set length on an array, but Polymer actually wants stuff in the array, hence the constructor.

If there are other ways to do this (while I'm waiting for dom-repeat to have this options added ;)), please let me know.

databinding dom-repeat enhancement p3 wontfix

Most helpful comment

@davidmaxwaterman , I'll soon finish my v2.0 for https://github.com/MeTaNoV/dom-repeat-n :)

All 29 comments

+1 /sub

-1 You could write your own dom-repeat-er. I don't think there is much in common between the current dom-repeat and what you want. At least for the example you give, dom-repeatseems like overkill b/c there is no real model for the children the templater stamps.

I also agree it's trivial to create your own custom element for this use case.

Indeed there is _no_ real model...that's the point, and to use it we have to fake a model :/

Could it be you guys are too knowledgeable about what is happening under the hood and that is influencing your opinion? It seems very obvious to use this from the user point of view. I'm not sure exactly why you would consider it 'over-kill'; could you explain? It would seem it is actually a much simpler case than using a model as it does now.

IMO, it seems overkill to create another custom element just to do this; however, perhaps that's less work than I imagine - could you give us an example?

Thanks.

The actual implementation of such a custom element depends surely on the features you need. For the example you gave: I would just use a computed property which depends on num_sweets. (T.i. I would not use the ready callback) (I would actually use a range instead of an array of n undefineds too)

That's so easy that you need a good argument to define a custom element for this use case. The usual argument would be that dom-repeat is too slow for this application.

Anyway :cake:

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

<script>
  'use strict';
  Polymer({

    is: 'dom-repeat-simple-count',
    extends: 'template',
    _template: null,

    behaviors: [
      Polymer.Templatizer
    ],

    properties: {

      count: {
        type: Number,
        observer: '_countChanged'
      },

    },

    created() {
      this._instances = [];
    },

    ready() {
      /* Copy from dom-repeat */
      // Template instance props that should be excluded from forwarding
      this._instanceProps = {
        index: true,
      };
      // Templatizing (generating the instance constructor) needs to wait
      // until ready, since won't have its template content handed back to
      // it until then
      if (!this.ctor) {
        this.templatize(this);
      }
    },

    attached() {
      let parent = this._getParentElement();
      for (let instance of this._instances) {
        this._attachInstance(instance, parent);
      }
    },

    detached() {
      for (let instance of this._instances) {
        this._detachInstance(instance);
      }
    },

    _attachInstance(instance, parent=this._getParentElement()) {
      /* We aim for a structure like this:
        <parent>
          <instance>
          <instance>
          <(this) template>
        </parent>
      */
      this._insertBefore(instance, {parent});
    },

    _insertBefore(instance, {parent=this._getParentElement(), before=this}) {
      parent.insertBefore(instance.root, before);
      return instance;
    },

    _detachInstance(instance) {
      for (let child of instance._children) {
        Polymer.dom(instance.root).appendChild(child);
      }
    },

    _getParentElement() {
      let parentNode = Polymer.dom(this).parentNode;
      return Polymer.dom(parentNode);
    },


    // Property observers run before the ready() callback, so we need to 
    // debounce to the end of the microtask. 
    @Pony.d.debounce
    _countChanged(count, oldValue=0) {
      if (count < oldValue) {
        this._detachInstances(count);
      } else {
        this._insertItems(count - oldValue, oldValue);
      }
    },

    _detachInstances(start=0, count=this._instances.length - start) {
      let instances = this._instances.splice(start, count);
      instances.map(inst => this._detachInstance(inst));
    },

    _insertItems(count, startIndex=0) {
      let parent = this._getParentElement();
      let before;
      if (startIndex === this._instances.length) {
        before = this;
      } else {
        let beforeRow = this._instances[startIndex];
        before = beforeRow._children[0];
      }

      let instances = Array.from({length: count})
          .map((value, i) => ({index: i}))
          .map(model => this.stamp(model))
          .map(instance => this._insertBefore(instance, {parent, before}));

      this._instances.splice(startIndex, 0, ...instances);
    },


    // Implements extension point from Templatizer mixin
    // Copied from dom-repeat but I actually don't know if needed
    _showHideChildren: function(hidden) {
      for (let instance of this._instances) {
        instance._showHideChildren(hidden);
      }
    },


  });
</script>

Wow, that's sooo complicated compared to simply adding 'count=5' to a dom-repeat template...I mean, Polymer is supposed to be adding this 'sugar', right? Well, on the other hand, I guess you have to draw a line somewhere - but when I mentioned this in the slack channel, a few others agreed it would be a nice to have feature.

Anyway, I'm more interesting in what you mean by this :

 For the example you gave: I would just use a computed property which depends on num_sweets. (T.i. I would not use the ready callback) (I would actually use a range instead of an array of n undefineds too)

TBH, that sounds pretty much the same as what I'm talking about (I guess a computed property is tidier)...apart from the mention of 'range'...what's that? Is this something in Javascript or the dom-repeat element that I don't know about (yet)?

You could write <template is="dom-repeat" items="[[ _makeSweets(num_sweets) ]]"> and then define the _makeSweets method accordingly.

With range I meant I rather would make an array of numbers [0, 1, 2,..., n-1]. You're using new Array(n) but this gives you an array with surprising behaviors.

I guess I'm confused. What's the actual use case for creating a bunch of identical template instances with no model or data binding?

I have no idea.

@davidmaxwaterman The computed property is imo the simplest way to achieve this: http://jsbin.com/ceforalahu/edit?html,console,output

Since this is quite easy for the user to implement, I think it is fine to leave the decision with the user. Most users will not use a count property of dom-repeat I think.

@TimvdLippe I don't think that does what I want. Doing <span>{{index}}</span> is closer though (since I wanted to be able to add unique IDs to each element. Of course, it isn't any better than my solution since it uses an array for no reason other purpose than its length.

@arthurevans Hi Arthur...I would think use cases would be pretty easy to come up with. Anything where you just want an element that has a configurable number of items inside it, but where there is no data associated with them individually. In one application, for example, I might want 5 of them, but in another, I want 10. I changed it for the example I gave because I considered the original use-case to be 'sensitive'....but perhaps it's no big deal. I essentially have an element that is 'my-mountains' that I want to instantiate a few times, but each time I want it to draw a different number of mountains. A mountain is drawn by 'my-mountain'. There is no data associated with a mountain, since they're always the same, just one after another :

Of course, I think my modified example of having multiple sweets is also valid. In fact, it reminds me of a game :

https://01.org/html5webapps/webapps/counting-beads

I guess it might not quite apply, since they actually move, while my mountains are completely static.

Anyway, it's true that this 'line of thought' was cut short, and not only because of the ugliness of having to use an array for the dom-repeat, so indeed I didn't really get to the point where the use-case was fully examined. I guess I'm trying to say that I might well have ended up having some data too, and so ended up using the regular dom-repeat functionality.

The main reason I posted this was because I asked on the slack channel, and others couldn't see a clean way of doing it and they also seemed to think it would be a nice bit of extra functionality, and suggested I make an issue.

It seems several here disagree....which is fair enough. Either way, I only opened this as a suggestion - I ended up not using Polymer at all (though I might go back to it), but not because of this issue, rather that I need to 'fit in' with Drupal which is problematic, and use of Polymer wasn't really essential (I just find it easier).

If you really don't think it is of any use, I have no problem with you closing it.

If you think about it, you're asking to be able to iterate over a range of numbers, but you say you don't want to create an iterable (e.g array). It just doesn't make much sense. I'd just use dom-repeat, binding items to the result of a function that dynamically generates the ranges you need (e.g. items="[[range(start, end, step)]]").

@miztroh not really...no. I just want the equivalent of for(let i=0; i<count; i++) {bla bla}, instead of var wasteOfSpace = array(count); for(let i=0; i<wasteOfSpace.length; i++) {bla bla};. That's how I look at it anyway.

It makes perfect sense to me and I'm not sure why people can't see it, but I can't be bothered to argue this any more and I'll close it myself.

closing

@davidmaxwaterman even if you closed the issue, just finished a polished version of dom-repeat-n which can be found here: https://github.com/MeTaNoV/dom-repeat-n
Check the documentation and demo to see the options available!

@MeTaNoV that looks perfect, thanks :) I still think this is a good candidate for integration into dom-repeat, but there you go.

Actually, some on slack have explained where the confusion has come, and since it means people aren't objecting to what I am actually suggesting, I'll re-open this :)

The 'output' of the template :

    <template is="dom-repeat" count="5">
      <my-sweet id="sweet_[[index]]"></my-sweet>
    </template>

is simply :

  <my-sweet id="sweet_0"></my-sweet>
  <my-sweet id="sweet_1"></my-sweet>
  <my-sweet id="sweet_2"></my-sweet>
  <my-sweet id="sweet_3"></my-sweet>
  <my-sweet id="sweet_4"></my-sweet>

(count is a noun, as in 'the number of items to stamp, not the verb, as intell me how many there are`).

Hi. The @MeTaNoV's implemetation does not process parentProps.
If you inspect dom-repeat code, you will see the not used '_forwardParentProp' and '_forwardParentPath'.
This methods are used in (Polymer) this.templatize to process attributes and allows template effects and annotations processing later.
There is a great lack of documentation about templates.
I use coffee and process with grunt. So this can be a litle confused

dom-iterate.html

Use 'from', 'to', 'include-last' and 'index-as' to set properties.
'from', 'to' and 'include-last' can be annotations (i.e: {{modelValue}} )

The repo has a demo showing transitional updating.

Yes, my english is lousy

@alejandrocastrounqui thanks for pointing that out, I updated it accordingly! :)

馃憤 for the dom-repeat-n , maybe it could be included in the polymer elements catalogue, to not blow up main polymer!

@jogibear9988 well, it doesn't seem this feature has a big chance to land directly in Polymer core...

_computeDummyArray: function(count) {
  return new Array(count);
}
<template is="dom-repeat" items="{{dots}}">
  <div class="a-dot"></div>
</template>

ready: function() {
  this.dots = new Array(this.n);
},
properties: {
  n: {
    type: Number,
    value: 10
  }
},

@JosefJezek @FranciscoGutierrez those are both examples of what I am trying to avoid...ie making an array just to use its length (the items in the array are ignored).

@davidmaxwaterman , I'll soon finish my v2.0 for https://github.com/MeTaNoV/dom-repeat-n :)

This just came up again on polymer slack. It's such an obvious thing to want, I don't get why it's so controversial. Hey ho.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bmodeprogrammer picture bmodeprogrammer  路  3Comments

dandv picture dandv  路  4Comments

yairopro picture yairopro  路  3Comments

yuihiro picture yuihiro  路  3Comments

nazar-pc picture nazar-pc  路  4Comments