Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymer Dom-Repeat Binding to Content

In polymer 1.2.3, Is it possible for a dom-repeat to use content as a template and bind values to the elements provided?

Custom Element:

<dom-module id="modify-collection">
  <template>
    <div>
      <template is="dom-repeat" items="[[collection]]">
        <content></content>
      </template>
    </div>
  </template>
</dom-module>

Usage:

<modify-collection collection="{{things}}">
  <list-item resource="[[item]]"></list-item>
</modify-collection>

I've looked at the following resources without help:

Polymer: How to watch for change in <content> properties

Polymer 1.0 template bind ref equivalent

Data-binding between nested polymer elements

https://github.com/grappendorf/grapp-template-ref

https://github.com/Trakkasure/dom-bindref

https://github.com/Polymer/polymer/issues/1852

https://github.com/Polymer/polymer/pull/2196


Update 2016-02-03: From the Polymer Team(PR #2196), better support for this is planned in the future, to help address some of the shortcomings.
like image 552
Zachary Moshansky Avatar asked Jan 06 '16 23:01

Zachary Moshansky


People also ask

Is dom repeated?

dom-repeat accepts an items property, and one instance of the template is stamped for each item into the DOM at the location of the dom-repeat element. The item property will be set on each instance's binding scope, thus templates should bind to sub-properties of item .

How do you use dom in polymer?

The <dom-if> element will stamp a light-dom <template> child when the if property becomes truthy, and the template can use Polymer data-binding and declarative event features when used in the context of a Polymer element's template. When if becomes falsy, the stamped content is hidden but not removed from dom.


2 Answers

FWIW, it looks like the following works just as well as solution 1, and adds notify paths (and automatic binding/changing, etc) to it:

<dom-module id="i-me">
  <template>
    <content></content>
    <template is="dom-repeat" id="repeater" items="[[collection]]"></template>
  </template>
  <script>
    Polymer({
      is: 'i-me',
      properties: {
        collection: {
          type: Array,
          value: [
            {id: 1, name: 'a'},
            {id: 2, name: 'b'}
          ],
          notify: true
        }
      },
      ready() {
        this.$.repeater.templatize(this.querySelector('#templ'));
        Polymer.Bind.prepareModel(this.$.repeater);
        Polymer.Base.prepareModelNotifyPath(this.$.repeater);
      }
    });
  </script>
</dom-module>

Then just use this:

<i-me>
  <template id="templ">
  <p><span>[[item.id]]</span>: <span>[[item.name]]</span></p>
  </template>
</i-me>
like image 33
btelles Avatar answered Oct 01 '22 15:10

btelles


TLDR

Solution 2 while a little more verbose, seems to work best. It also allows you to dynamically choose your template, perhaps based on a model's attributes. (See Solution 2: Advanced Example)


Backstory

A Lot of stuff is happening in this simple example including templatizing, "getting" content between shadow?, shady, and light doms, and of course data-binding.

This question provided some insight, but without Polymer 0.5's injectBoundHtml it wasn't really workable (Using template defined in light dom inside a Polymer element). The problem being that our data-bindings seem to get lost (AFAIK) when we try to copy an element using innerHTML to our template.

So without that we can't create our template on the fly with data-bindings. Thus both solutions wrap the content in a template ahead of time; this caused the html to be inert and allows Polymer to data-bind at the appropriate time (http://www.html5rocks.com/en/tutorials/webcomponents/template/).

If you really want to understand everything, I'd recommended reading the Polymer src for lib/template/dom-repeat.html, lib/template/templatizer.html, lib/annotations/annotations.html (~1500 lines).


Solution 1 -

See btelle's answer below for an improved solution 1.

Note, this approach causes dom-repeat to not render content automatically, so we call render manually.

Element

<dom-module id="modify-collection">
  <template>
    <div>
      <content></content>
      <template id="repeater" is="dom-repeat" items="[[collection]]"></template>
    </div>
  </template>

  <script>
    ...
    ready: function() {
      this.$.repeater.templatize(this.querySelector('#templ'));
    }
    _changeCollection: function(item) {
      this.push('collection', item);
      this.$.repeater.render();
    }
  </script>
</dom-module>

Usage

<modify-collection collection="{{things}}">
  <template id="templ"><list-item resource="[[item]]"></list-item></template>
</modify-collection>


Solution 2

Note, that the usage of this varies from element 1, in that the <template> must have an is="dom-template" attribute.

Helper Element

(Slightly modified from this PR: https://github.com/Polymer/polymer/pull/2196, originally based on https://github.com/grappendorf/grapp-template-ref)

!--
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<!--
  Portions of this code have been adapted from the `grapp-template-ref` element.

  The original copyright notices are below.
-->
<!--
MIT License
Copyright (c) 2014-2015 Dirk Grappendorf, www.grappendorf.net

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
 -->

<!--

 The `dom-ref` element is a custom `HTMLTemplateElement` type extension that
can be used to reference another template by id using the `ref` property.
`dom-bindref` accepts a `bind` property to bind an object to the referenced
template. By default the bound object can be accessed as `item`, this can be
changed using the `as` property.

Example:

```html
<template is="dom-template" id="template-bind"><span>[[item.key]]</span></template>
<template is="dom-ref" ref="template-bind" bind='{"key":"value"}'></dom-ref>
```

-->
<!-- <link rel="import" href="templatizer.html"> -->

<script>

  Polymer({

    is: 'dom-ref',
    extends: 'template',

    /**
     * Fired whenever DOM is added or removed by this template (by
     * default, rendering occurs lazily).  To force immediate rendering, call
     * `render`.
     *
     * @event dom-change
     */

    properties: {

      /**
       * Reference to another template's id.
       */
      ref: {
        type: String,
        observer: '_refChanged'
      },

      /**
       * Object to be bound to referenced template.
       */
      bind: {
        type: Object,
        observer: '_bindChanged'
      },

      /**
       * The name of the variable to add to the binding scope for the
       * element associated with a given template instance.
       */
      as: {
        type: String,
        value: 'item'
      }
    },

    behaviors: [
      Polymer.Templatizer
    ],

    ready: function() {
      this.templatize(this);
    },

    attached: function() {
      return this._stamp();
    },

    detached: function() {
      return this._removeChildren();
    },

    _refChanged: function(newRef, oldRef) {
      if (oldRef) {
        this._removeChildren();
        return this._stamp();
      }
    },

    _bindChanged: function(newBind, oldBind) {
      if (oldBind) {
        this._removeChildren();
        return this._stamp();
      }
    },

    _stamp: function() {
      var root, template, templateRoot, bind = {};
      this._parent = Polymer.dom(this).parentNode;
      root = this._parent;

      while (Polymer.dom(root).parentNode) {
        root = Polymer.dom(root).parentNode;
      }
      template = Polymer.dom(root).querySelector("template#" + this.ref);

      // Check For Light Dom Elements that may be passed to this shadow root (Useful for: `<content></content>`)
      if (!template) {
        template = root.host.querySelector("template#" + this.ref);
      }

      // Check the whole document
      if (!template) {
        template = document.querySelector("template#" + this.ref);
      }
      bind[this.as] = this.bind;

      // templateRoot = template.stamp(bind).root;
      // Use this method until this lands: https://github.com/Polymer/polymer/pull/1889
      templateRoot = (new template.ctor(bind, template)).root;
      this._children = Array.prototype.slice.call(templateRoot.childNodes);
      return Polymer.dom(this._parent).insertBefore(templateRoot, this);
    },

    _removeChildren: function() {
      var child, i, len, ref, results;
      if (this._children) {
        ref = this._children;
        results = [];
        for (i = 0, len = ref.length; i < len; i++) {
          child = ref[i];
          results.push(Polymer.dom(this._parent).removeChild(child));
        }
        return results;
      }
    }

  });

</script>

Element

<dom-module id="modify-collection">
  <template>
    <div>
      <content></content>
      <template is="dom-repeat" items="[[collection]]">
          <template is="dom-ref" bind="[[item]]" ref="templ"></template>
      </template>
    </div>
  </template>
</dom-module>

Usage

<modify-collection collection="{{things}}">
  <template id="templ" is="dom-template"><list-item resource="[[item]]"></list-item></template>
</modify-collection>

Advanced Element

Usage does not change from above.

What we do here is introduce a level of indirection, allowing us to wrap a template passed to our element (in our example, with an overlay).

<dom-module id="modify-collection">
  <template>
    <div>
      <content></content>
      <template id="wrapper" is="dom-template">
        <div class="overlay">
          <template is="dom-ref" bind="[[item]]" ref="templ"></template>
        </div>
      </template>

      <template is="dom-repeat" items="[[collection]]">
        <template is="dom-ref" bind="[[item]]" ref="[[_templateRef(_overlayMode)]]"></template>
      </template>
    </div>
  </template>

  <script>
    ...
    properties: {
      _overlayMode: {
        type: Boolean,
        value: false
      }
    },
    _templateRef: function(overlayMode) {
      return overlayMode ? 'wrapper' : 'templ';
    }
  </script>
</dom-module>
like image 166
Zachary Moshansky Avatar answered Oct 01 '22 16:10

Zachary Moshansky