2

I would like to be able to render a remote collection fetched with <core-ajax> as such:

<rendered-collection url="/api/items">
  <custom-element value="{{ _it_ }}"></custom-element>
</rendered-collection>

where <rendered-collection> would look something like:

<link rel="import" href="/core-ajax/core-ajax.html">

<polymer-element name="rendered-collection" attributes="url" noscript>
  <template>
    <core-ajax url="{{ url }}" response="{{ collection }}" auto handleAs="json"></core-ajax>
    <template repeat="{{ _it_ in collection }}">
      <content><!-- cannot be used like that unfortunately --></content>
    </template>
  </template>
</polymer-element>

I realise that this is not how <content> is supposed to work and that I still have to inject the model into it anyway.

I have seen answers advising to retrieve the content's nodes in JS:

<style>
  ::content > * {
    display: none;
  }
</style>
<content id="content"></content>
...
<script>
  Polymer('rendered-collection', {
    attached: function () {
      this.contentNodes = this.$.content.getDistributedNodes();
      // then...how do I inject models from the collection into the nodes?
    }
  });
</script>

What's the best way to go?

Community
  • 1
  • 1
dimdm
  • 1,121
  • 1
  • 9
  • 13

3 Answers3

2

If I understand your use case correctly, you want the children of <rendered-collection> to describe the rendering for each item in the collection. This is exactly what <template> is for. So, if we propose usage of <rendered-collection> like so:

<rendered-collection>
  <template>
    <h2>{{name}}</h2>
  </template>
</rendered-collection>

Then we can render it with a bit of template-fu:

<polymer-element name="rendered-collection">
<template>
  <content></content>
</template>
<script>
  Polymer('rendered-collection', {
    collection: [
      {name: 'alpha'},
      {name: 'beta'},
      {name: 'gamma'}
    ],
    ready: function() {
      this.bindTemplate();
    },
    bindTemplate: function() {
      // user-supplied template
      var t = this.querySelector('template');
      // optional, but supplies fancy expression support
      t.bindingDelegate = new PolymerExpressions();
      // repeat over the entire model
      t.setAttribute('repeat', '{{}}');
      // set the model to our collection
      t.model = this.collection;
    }
  });
</script>
</polymer-element>

http://jsbin.com/kedig/1/edit

Scott Miles
  • 11,025
  • 27
  • 32
  • Aside: the `'rendered-collection'` argument to `Polymer()` isn't necessary if the element definition is in an import. – Scott Miles Jul 19 '14 at 18:35
  • Your solution is a lot cleaner than what I came up with. I noticed that you imported core-ajax in your jsbin but did not use it. How would it work against http://www.myapifilms.com/search?title=Airplane!&format=JSON for example? – dimdm Jul 19 '14 at 19:14
  • That site apparently doesn't allow CORS requests, so you can't use ajax to access that data directly. – Scott Miles Jul 19 '14 at 21:23
  • Right, sorry... I didn't check whether the Access-Control-Allow-Origin header was correctly set. I was wondering if I could avoid listening for the 'core-response' event in JS and bind the response of the directly to this.collection. – dimdm Jul 19 '14 at 21:58
0

The code below seems to work but I would like to have feedback on this solution which feels hacky and could be improved:

<rendered-collection url="/api/items">
  <custom-element value="{{ _it_ }}"></custom-element>
</rendered-collection>

with

<polymer-element name="html-render" attributes="html model">
  <template></template>
  <script>
    Polymer({
      htmlChanged: function() {
        var tmpl = document.createElement("template");
        tmpl.setAttribute("bind", "");
        // WARNING: Potential XSS vulnerability
        tmpl.innerHTML = this.html;
        tmpl.model = { _it_: this.model };
        this.shadowRoot.appendChild(tmpl);
        // Needed ?
        Platform.performMicrotaskCheckpoint();
      }
    });
  </script>
</polymer-element>

<polymer-element name="rendered-collection" attributes="url">
  <template>
    <content id="content"></content>
    <core-ajax url="{{ url }}" response="{{ collection }}" auto handleAs="json"></core-ajax>
    <template repeat="{{ item in collection }}">
      <template repeat="{{ node in contentNodes }}">
        <html-render html="{{ node.outerHTML }}" model="{{ item }}"></html-render>
      </template>
    </template>
  </template>
  <script>
    Polymer({
      attached: function () {
        var nodes = this.$.content.getDistributedNodes();
        this.contentNodes = _.isArray(nodes) ? nodes : nodes.array();
        this.$.content.remove();
      }
    });
  </script>
</polymer-element>
dimdm
  • 1,121
  • 1
  • 9
  • 13
0

Another stab at it using Scott Miles' solution but including <core-ajax>:

<rendered-collection url="/api/items">
  <template>
    <custom-element value="{{ }}"></custom-element>
  </template>
</rendered-collection>

with

<polymer-element name="rendered-collection" attributes="url">
  <template>
    <core-ajax auto
               url="{{ url }}"
               handleAs="json"
               response="{{ collection }}"></core-ajax>
    <content></content>
  </template>
  <script>
    Polymer({
      ready: function() {
        this.clientTemplate = this.querySelector('template');
        this.clientTemplate.bindingDelegate = new PolymerExpressions();
        this.clientTemplate.model = this;
        this.clientTemplate.setAttribute('repeat', '{{ collection }}');
      }
    });
  </script>
</polymer-element>
dimdm
  • 1,121
  • 1
  • 9
  • 13
  • You can remove `collectionChanged` and bind the template like so: `this.clientTemplate.model = this; this.clientTemplate.setAttribute('repeat', '{{collection}}');` – Scott Miles Jul 21 '14 at 18:08
  • Edited... This is really tidy: I love it! Thank you so much for an awesome framework and following up on SO :) – dimdm Jul 21 '14 at 19:15
  • @ScottMiles Since polymer 0.8 is just around the corner now, how would you implement this using and 0.8? – dimdm Apr 27 '15 at 16:22