kyooriouskoala
kyooriouskoala

Reputation: 583

Polymer :: Unable to target elements inside dom-repeat when using iron-ajax to import data

I can target element inside dom-repeat if the data is hard-coded as a property value. However, when using iron-ajax to import the data, this.$$(selector) returns null.

Custom Element

<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../bower_components/iron-collapse/iron-collapse.html"/>
<link rel="import" href="../../bower_components/iron-ajax/iron-ajax.html"/>
<link rel="import" href="../../bower_components/polymer-sortablejs/polymer-sortablejs.html"/>

<dom-module id="sortable-math">

  <template>
    <style>
      :host {
        display: block;
      }

      .math {
        display: block;
        padding: 20px 0;
        border-bottom: 1px solid #cccccc;
      }

      .total {
        border-top: 1px solid #cccccc;
      }
    </style>

    <h1>Math +</h1>

    <div id="empty-div"></div>

    <iron-ajax
        auto
        id="numbersDataRequest"
        url="../data/numbers.json"
        handle-as="json"
        on-response="numbersDataResponse">
    </iron-ajax>

    <div>
      <sortable-js>
        <template is="dom-repeat" items="{{numbersData}}">
          <div id="math-{{index}}" class="math">
            <table>
              <tr>
                <td class="givenNumber" on-click="toggle" toggle-id$="{{index}}">
                  {{item.givenNumber}}
                </td>
              </tr>
              <tr>
                <td class="toAdd">
                  + {{item.toAdd}}
                </td>
              </tr>
              <tr>
                <td id="total-{{index}}" class="total">?</td>
                <td></td>
              </tr>
            </table>

            <iron-collapse id$="collapsible-{{index}}">
              <div>Hint</div>
            </iron-collapse>
          </div>
        </template>
      </sortable-js>

  </div>

  </template>

  <script>
    Polymer({
      is: 'sortable-math',

      properties: {
        numbersData: {
          type: Array
        }
      },

      ready: function() {
        console.log(this.$$('#empty-div'));
      },

      _onSort: function(evt){
        console.log('sorted');
      },

      /*
      //Not working either

      attached: function(){
        this.async(function() {
          this.$$('#empty-div').innerHTML = 'Empty no more.'; //This works.
          console.log(this.$$('#math-1')); //Returns null
        });
      },

      */

      toggle: function (e, detail, sender) {
        var target = Polymer.dom(e).rootTarget.getAttribute('toggle-id'),
            selector = '#collapsible-' + target;
        this.$$(selector).toggle();
        console.log(this.$$('#math-1')); 
      },

      numbersDataResponse: function (data) {
        //console.log('numbersDataResponse');
        this.numbersData = data.detail.response;
        this.$$('#empty-div').innerHTML = 'Empty no more.'; //This works.

        //Attempt to target element created with dom-repeat
        console.log(this.$$('#math-0')); //Returns null
        console.log(this.$$('#math-0 #total-0')); //Returns null
        console.log(this.$$('#math-0 .total')); //Returns null
      }
    });

    var targetEl = function(ref, selector){
      return Polymer.dom(ref).querySelector(selector);
    }



  </script>
</dom-module>

JSON

[
  {
    "givenNumber": "3",
    "toAdd": "2"
  },
  {
    "givenNumber": "?",
    "toAdd": "5"
  },
  {
    "givenNumber": "?",
    "toAdd": "10"
  },
  {
    "givenNumber": "?",
    "toAdd": "7"
  }
]

Additional info

What I am trying to do:

  1. Get content of #math-0 .givenNumber (3)
  2. Get content of #math-0 .toAdd: (2)
  3. Calculate the total (5) and append it to #math-1 .givenNumber
  4. Get content of #math-1 .givenNumber: (5)
  5. Get content of #math-1 .toAdd: (5)
  6. Calculate the total (10) and append it to #math-2 .givenNumber

and so on ...

View without calculation

View after calculation

Thanks!

Upvotes: 0

Views: 569

Answers (2)

marx_tseng
marx_tseng

Reputation: 46

Because the dom-repeat takes time to bind, so the parameters will be set directly after the call will have time difference, it is recommended to wait and then call on it

English is not good, I hope you see understand, thank you

jsbin

Upvotes: 1

tony19
tony19

Reputation: 138336

You're querying for the repeater's items before the repeater has had a chance to stamp them, which results in null. You could either wait until after the next render with Polymer.RenderStatus.afterNextRender() before querying the item:

Polymer.RenderStatus.afterNextRender(this, () => {
  console.log('math-0 (after render)', this.$$('#math-0'));
});

...or you could wait until the end of the current microtask (which includes the template repeater rendering):

this.async(() => {
  console.log('math-0 (after microtask)', this.$$('#math-0'))
});

<head>
  <base href="https://polygit.org/polymer+1.7.0/polymer-sortablejs+SortableJS+:master/Sortable+RubaXa+:master/components/">
  <script src="webcomponentsjs/webcomponents-lite.min.js"></script>
  <link rel="import" href="polymer/polymer.html">
  <link rel="import" href="iron-ajax/iron-ajax.html">
  <link rel="import" href="polymer-sortablejs/polymer-sortablejs.html">
</head>
<body>
  <x-foo></x-foo>

  <dom-module id="x-foo">
    <template>
      <style>
        :host {
          font-family: Roboto, sans-serif;
        }
        .item {
          margin: 20px;
        }
      </style>
      <iron-ajax url="https://jsonplaceholder.typicode.com/posts"
                 on-response="_onResponse"
                 auto></iron-ajax>
      <sortable-js>
        <template is="dom-repeat" items="[[items]]">
          <div class="item" id$="item-{{index}}">{{item.title}}</div>
        </template>
      </sortable-js>
    </template>
    <script>
      HTMLImports.whenReady(function() {
        Polymer({
          is: 'x-foo',
          _onResponse: function(e) {
            this.items = e.detail.response;

            // The template repeater has not rendered its items yet, so #item-1 would
            // not yet exist yet for us to query here.
            console.log('item-1 (before render)', this.$$('#item-1'));

            // We can use Polymer.RenderStatus.afterNextRender to explicitly wait until
            // the next render event before checking for #item-1.
            Polymer.RenderStatus.afterNextRender(this, () => console.log('item-1 (after render)', this.$$('#item-1')));

            // Alternatively, we can wait until the end of the current microtask (which
            // includes rendering the template repeater), before checking for #item-1.
            this.async(() => console.log('item-1 (after microtask)', this.$$('#item-1')));
          }
        });
      });
    </script>
  </dom-module>
</body>

codepen

Upvotes: 0

Related Questions