Polymer and Polymerfire: how to loop through the data from an observer?

1k views Asked by At

I have a simple <firebase-query> tag, and I'd like to manipulate some of the data before having it displayed through a <dom-repeat>. For example, I need to turn some fields into links, and also parse some dates.

So, I need to get the data once it's ready, loop through each item, and change some of the values.

To do that, I have an observer on the data to detect when it's ready. However, I can't figure out how to loop through the data from that JavaScript function. For some reason, for(var i in items) doesn't work, although the items do exist.

Here is the component:

<dom-module id="cool-stuff">

  <template>

    <firebase-query id="query" path="/items" data="{{items}}"></firebase-query>

    <template is="dom-repeat" items="{{items}}" as="item">
      [[item.name]]<br />
      [[item.date]]<br />
    </template>

  </template>

  <script>
    Polymer({
      is: 'ix-table',
      properties: {
        items: {type: Object, observer: "_itemsChanged"},
      }
      itemsChanged: function(data) {
        // how do I loop through the data received from firebase-query?
        console.log(data);
      }
    });
  </script>

</dom-module>

Ideally, all I'd want to do in the observer function is something like:

for(var i in data) {
  obj = data[i];
  obj.name = '<a href="/item/"+obj.key>'+ojb.name+'</a>';
}

But I can't seem to be able to loop through the data.

Inside the observer function, console.log(data) returns some weird stuff like this:

Array[o]
0: Object (which contains a proper item)
1: Object (same)
2: Object (same)

Update:

Here is a screenshot of what console.log(data) returns (from inside the observer):

enter image description here

The array seems to be populated with all the objects, but it shows as Array[0]. So it won't let me loop through them.

Update 2:

Thanks to arfost here is the solution:

<script>
  Polymer({
    is: 'ix-table',
    properties: {
      items: {type: Object},

    }
    observers: [
      '_itemsChanged(items.splices)'
    ],
    _itemsChanged: function(changeRecord) {
      if (changeRecord) {
        changeRecord.indexSplices.forEach(function(s) {
          for (var i=0; i<s.addedCount; i++) {
            var index = s.index + i;
            var item = s.object[index];
            console.log('Item ' + item.name + ' added at index ' + index);
            // do whatever needed with the item here:
            this.items[index].name = "New name";
          }
        }, this);
      }
    },
  });
</script>
3

There are 3 answers

4
Arfost On BEST ANSWER

I thinks I have run into the same issue as you.

It come from the way firebase query is getting the array, the way polymer obersvers works, and is hidden by the fact that the javascript console is reference based when it show the objects.

In fact what really happen here, is that firebase query is creating an empty array, which trigger your polymer observer.

So your function is called as soon as the array is created, but still empty and you can't iterate through, since it's empty. You then log it, where the primitives sub-properties are correctly displayed (array[0])

Then firebase begin to populate the array with the datas. The arrays reference stay the same, so polymer won't fire the observer again, and in the console, when it try to display the array it display the array referenced in the log, which now contains the datas.

I recommend that you use a array mutation observer in place of your simple one as follow

`properties: {
    items: {type: Object},
  },
,
observers: [
       '_itemsChanged(items.splices)'
],`

It will fire every time an object is added to your array, and you would be able to do the work you need :)

I had the link for the documentation on array mutation observer :) polymer array mutation observer

I hope this will solve your issue, have a good day.

1
tony19 On

<firebase-query> results

Note that <firebase-query> results in an array of objects. Let's say your database contained the following items under /notes/<USER_ID>/:

enter image description here

Your <firebase-query> would look similar to this:

<firebase-query
    id="query"
    app-name="notes"
    path="/notes/[[user.uid]]"
    data="{{notes}}">
</firebase-query>

(where user is bound to <firebase-auth>.user).

Assuming the user is logged in, <firebase-query> would then populate its data property (i.e., bound to notes) with the following array:

enter image description here

Note how each object contains a $key property, which corresponds to the item's key seen in the Firebase console's Database view.

You could then iterate notes directly with <dom-repeat>:

<template is="dom-repeat" items="[[notes]]">
  <li>
    <div>key: [[item.$key]]</div>
    <div>body: [[item.body]]</div>
    <div>title: [[item.title]]</div>
  </li>
</template>

Binding to HTML strings

You should be aware that the string data bindingsĀ are rendered literally in this case, so attempting to set name to obj.name = '<a href="...">' would render the literal string instead of an anchor. Instead, you should declare the tags in your template, and bind the key and name properties inside those tags. So, your observer could be replaced with this:

<template is="dom-repeat" items="{{items}}" as="item">
  <a href$="/item/[[item.key]]">[[item.name]]</a><br />
  [[item.date]]<br />
</template>

Iterating an array

The following note is only relevant if you prefer to mutate the data before displaying it...

When iterating an array, you should avoid for..in because it doesn't guarantee order of iteration, and because it may iterate over enumerable properties you might not necessarily care about. Instead, you could use for..of (assuming ES6 is available to your app):

for (let note of notes) {
  note.title += ' ...';
}

or Array.prototype.forEach():

notes.forEach(function(note) {
  note.title += ' ...';
});
3
Ryan Tyler On

i don't think i can think of a scenario where you'd need to mutate the data by looping through the array rather than just using computed bindings. like this:

<template is="dom-repeat" items="{{items}}" as="item">
  <child-el date="{{_computeDate(item.date)}}"></child-el><br />
  <child-el attr1="{{_someOtherConversion(item.prop1)}}"></child-el><br />
  <child-el attr2="{{_iPromiseAnyConversionCanBeDoneLikeThis(item.prop2)}}"></child-el><br />
</template>
<script>
  _computeDate: function(item) {
    //do date converstion
}