Polymer: Feature: model capturing items on outer levels in dom-repeat

Created on 19 Jun 2015  路  6Comments  路  Source: Polymer/polymer

quote from Handling events in dom-repeat templates

When handling events generated by a dom-repeat template instance, you frequently want to map the element firing the event to the model data that generated that item.
When you add a declarative event handler inside the template, the repeater adds a model property to each event sent to the listener. The model is the scope data used to generate the template instance, so the item data is model.item

This feature seems to support only one (the inner-most) level. i in the following example is not captured. Please click on <input>'s to experiment:

<!DOCTYPE html>
<html>
  <head>
    <script src="bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
    <link rel="import" href="proto-element.html">
  </head>
  <body>
    <proto-element></proto-element>
  </body>
</html>

<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/iron-input/iron-input.html">
<dom-module id="proto-element">
    <template>
        <template is="dom-repeat" items="{{rows}}" as="row" index-as="i">
            <template is="dom-repeat" items="{{row}}" as="column" index-as="j">
                <input is="iron-input" bind-value="[[column]]" on-tap="clicked">
            </template>
        </template>
    </template>
</dom-module>
<script>
    Polymer({
        is: "proto-element",
        properties:{
            rows:{
                type:Array,
                value:function(){ return [[1,2],[3,4]]; }
            }
        },
        clicked:function(e){
            alert(e.model.i+"->"+e.model.j);
        }
    });
</script>
1.x dom-repeat enhancement p3

Most helpful comment

I used event.model.dataHost.dataHost.item.field in an on-click function inside the inner dom-if template to retrieve the item.field in the dom-repeat template. It works!

All 6 comments

I can repro this issue, but I'm not sure if it's a documentation issue, a code issue, or both.

Here's the breakdown: examining the results, it appears that e.model only includes the values that are bound _in that scope_. Since the inner template repeat doesn't bind to {{i}}, it doesn't get attached to the model object.

Binding i in the inner scope fixes this:

http://jsbin.com/tinuda/1/edit?html,output

However, binding i in the outer scope doesn't make it available in e.model in the inner scope.

It seems to me that this is probably incorrect, and that perhaps we should take index-as and item-as as cues that the data is interesting even if it's not bound.

As a workaround, you can assign an ID to the outer dom-repeat, and then use indexForElement, like this:

        var outerIndex = this.$.outerDomRepeat.indexForElement(e.target);

http://jsbin.com/tinuda/5/edit?html,output

@kevinpschaaf, is the current behavior WAI? If so, it seems like the docs should be updated to note that _only paths bound in the current scope are available in e.model._ Please advise.

Right now it is working as intended, although I acknowledge the use case presented that we don't cover.

For performance reasons, only notifications for properties actually used in inner scope are forwarded in, hence the inner instance not having access to outer scope properties that were not referenced in bindings.

The workaround @arthurevans provided works well for two nested dom-repeats, where you can access the outer dom-repeat by id. More generally, you could do something like bind the outer i to the inner dom-repeat, and access that from event.model.dataHost (where dataHost of the model will be that dom-repeat that created it):

        <template is="dom-repeat" items="{{rows}}" as="row" index-as="i">
            <template is="dom-repeat" items="{{row}}" as="column" index-as="j" outer-index="{{i}}>
                <input is="iron-input" bind-value="[[column]]" on-tap="clicked">
            </template>
        </template>
        clicked:function(e){
            alert(e.model.dataHost.outerIndex + "->" + e.model.j);
        }

We'll keep this issue open to consider better API to meet this use case.

@kevinpschaaf I fail to figure out the reason that the following alert() yields the first two undefined,undefined:

<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/iron-input/iron-input.html">
<dom-module id="proto-element">
    <template>
        <table>
        <template is="dom-repeat" items="{{rows}}" as="row" index-as="i">
            <tr>
            <template is="dom-repeat" items="{{row}}" as="column" index-as="j" row-index="[[i]]">
                <td>
                    <template is="dom-if" if="[[yes()]]">
                        <input is="iron-input" bind-value="[[column]]" on-tap="clicked" data-row-index$="[[i]]">
                    </template>
                </td>
            </template>
            </tr>
        </template>
        </table>
    </template>
</dom-module>
<script>
    Polymer({
        is: "proto-element",
        properties:{
            rows:{
                type:Array,
                value:function(){ return [[1,2],[3,4]]; }
            }
        },
        yes:function(){
            return true;
        },
        clicked:function(e){
            alert(e.model.dataHost.rowIndex+","+e.model.j+","+e.target.getAttribute("data-row-index"));
        }
    });
</script>

Perhaps this issue is relative to this issue.

My recent workaround for this missing feature is adding _data-index_ attributes to dom-repeat children. Then the script can find its own position by referencing to these attributes like this:

   <template is="dom-repeat" items="{{rows}}" as="row" index-as="i">
        <template is="dom-repeat" items="{{row}}" as="column" index-as="j">
                    <input is="iron-input" bind-value="[[column]]" on-tap="clicked" data-row-index$="[[i]]" data-column-index$="[[j]]">
        </template>
    </template>

    clicked:function(e){
            console.log(e.target.getAttribute("data-row-index"),e.target.getAttribute("data-column-index"));
    }

I used event.model.dataHost.dataHost.item.field in an on-click function inside the inner dom-if template to retrieve the item.field in the dom-repeat template. It works!

Given the performance characteristics of <dom-repeat>, the workaround as posted by @yingqiaogit is the trade-off we have to make. We have considered exposing explicit API for this use-case, but we are not convinced there are a lot of benefits to that API, other than the current workaround. Therefore I am closing this issue.

Was this page helpful?
0 / 5 - 0 ratings