Can I change element tag name with Polymer data binding?

Refresh

5 weeks ago

Views

40 time

1

I have this application which contains many types of Polymer elements that can be added to a main Polymer app element. This main element manages instances of these elements and shows them in a UI.

eg.

  • item1.html
  • item2.html
  • item3.html
  • my-app.html

As I add new types of items (eg. item4.html), I need to make several changes to the main UI to handle creating, managing, and showing them. Each type is unique enough that I do not want to merge them into a single item type.

What I'd like to do is have each Polymer element 'register' itself into my-app by calling a function which can add a new object to an array.

To do this, my-app will have a property called itemMap which is an array of objects. One property in this object is the type of item.

itemMap: [
  {
    type: 'item-1',
    instances: []
  }, {
    type: 'item-2',
    instances: []
  }
  ...
]

This implementation works in code. When adding a new instance, I can add a new object to the instances array for that type. However, I do not know how to show the items in the UI. As each type is a different Polymer element, I cannot use a simple dom-repeat template. At the same time, I do not want to hardcode each type in the main UI to improve modularity.

Right now I have:

<iron-list id="item-1-list" items="[[item1_array]]" as="item" grid>
  <template>
    <div class="item">
      <item-1 properties=[[item]]></item-1>
    </div>
  </template>
</iron-list>

<iron-list id="item-2-list" items="[[item2_array]]" as="item" grid>
  <template>
    <div class="item">
      <item-2 properties=[[item]]></item-2>
    </div>
  </template>
</iron-list>

What I want to do is something like the snippet below, which would work for any type I create.

<template is="dom-repeat" items="{{itemMap}}" as="itemType" id="item-grid">
  <iron-list id="[[itemType.type]]-list" as="item" grid items="[[itemType.instances]]">
    <template>
      <div class="item">
        <[[itemType.type]] properties=[[item]]></[[itemType.type]]>
      </div>
    </template>
  </iron-list>
</template>

However, this does not work.

Is this possible, or something equivalent, or am I going down the wrong path completely?

1 answers

0

Polymer data binding does not work for tag names. What you might do to implement this kind of behavior is to create another custom element that accepts the item type as a property and dynamically creates an element of that type:

<template is="dom-repeat" items="{{itemMap}}" as="itemType" id="item-grid">
  <iron-list id="[[itemType.type]]-list" as="item" grid items="[[itemType.instances]]">
    <template>
      <div class="item">
        <x-item-selector type=[[itemType.type]] properties=[[item]]></x-item-selector>
      </div>
    </template>
  </iron-list>
</template>

There could be several ways to implement the x-item-selector element: declarative and imperative:

  • Declarative: Create a set of <dom-if>s--one per type

    If there are only a few element types and you can list them all, you could create a template for the x-item-selector like below:

    <template>
        <template is="dom-if" if="[[_isEqual(type, 'item-1')]]" restamp>
            <item-1 properties="[[properties]]"></item-1>
        </template>
        ...
    </template>
    
  • Imperative: Observe the type property and update the child element manually

    If you are going to support many types of elements, you might want to avoid a large set of <dom-if>s, and update the children of the x-item-selector element imperatively whenever the type property changes. As a downside, the mapping for the properties property you'll also have to establish manually.

    _onTypeChanged(newType, oldType) {
        if (newType !== oldType) {
            for (let child of this.children) {
                this.removeChild(child);
            }
            const newChild = document.createElement(newType);
            newChild.properties = this.properties;
            // also need to add some code to update newChild.properties
            // when this.properties change 
            this.appendChild(newChild);
        }
    }