Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using custom element content as item template

I'm writing reusable components for our internal framework that abstract away some monkey code. Most of the scenario's are implemented with slots and work great. However, some scenario's require rendering templates inside for loops, and unfortunately slots aren't supported there.

I came up with the following (working) code:

<template>
  <div class="form-group">
    <label for.bind="titleSafe" class="control-label">{title}</label>
    <select id.bind="titleSafe" value.bind="value" class="form-control">
      <option repeat.for="item of itemsSource" >
        <template replaceable part="item-template" containerless>${item}</template>
      </option>
    </select>
  </div>
</template>

This code has, IMO, multiple issues that make it a bad candidate for including it in a framework:

  • It doesn't support default templates like slots does, so when you have only 1 replacable part the syntax is needlessly verbose
  • Having to use 2 different templating systems (slots + replace-part) in my project seems really counter intuitive and will certainly create confusion/bugs in my dev team
  • When you use template parts in the example I provided above, you need to know I declared 'item' as iterator in my for loop in order to construct your template correctly

Therefore I went looking for alternatives. After some research I came up with something like this:

<template>
  <div class="form-group">
    <label for.bind="titleSafe" class="control-label">{title}</label>
    <select id.bind="titleSafe" value.bind="value" class="form-control">
      <option repeat.for="item of itemsSource" >
        <!-- I want to insert my custom element here -->
      </option>
    </select>
  </div>
  <slot></slot>
</template>

The above is my select-item custom element. Then I would also create another custom element for the templating of the repeatable item, like select-item-template, I would then use the two together like this:

<select-item title="myTitle" items-source="myItems">
  <select-item-template><span>${myItemsProperty}</span></select-item-template>
</select-item>

The strength of this approach would be that you can create complex 'root' custom elements with one default slot. In this slot, you could then define multiple custom 'child' elements that the root element can search for when it's initialized (I know you can do this with the @child and @children decorators, so that part is covered). I'm a bit lost on how I would have to use these custom child element's content in my root custom element though.. How would I take my span element in the above example and prepare it's content to be rendered in the repeater? And would it be possible to take the repeated item set it as the template's datasource so I don't have to specify itemin my templates? I hope I didn't make this too verbose, but I wanted to explain what my functional requirement is. If you have any resource that can point me in the right direction I would be very grateful!

like image 868
Arne Deruwe Avatar asked Feb 05 '23 17:02

Arne Deruwe


1 Answers

Use the processContent attribute to transform the element content into a part replacement. The component will still use replace-part internally but consumers of the component won't be exposed to this implementation detail.

https://gist.run?id=2686e551dc3b93c494fa9cc8a2aace09

picker.html

<template>
  <label repeat.for="item of itemsSource" style="display: block">
    <input type="radio" value.bind="item" checked.bind="value">
    <template replaceable part="item-template">${item}</template>
  </label>
</template>

picker.js

import {bindable, processContent} from 'aurelia-templating';
import {bindingMode} from 'aurelia-binding';
import {FEATURE} from 'aurelia-pal';

@processContent(makePartReplacementFromContent)
export class Picker {
  @bindable itemsSource = null;
  @bindable({ defaultBindingMode: bindingMode.twoWay }) value = null;
}


function makePartReplacementFromContent(viewCompiler, viewResources, element, behaviorInstruction) {
  const content = element.firstElementChild;
  if (content) {
    // create the <template>
    const template = document.createElement('template');

    // support browsers that do not have a real <template> element implementation (IE)
    FEATURE.ensureHTMLTemplateElement(template);

    // indicate the part this <template> replaces.
    template.setAttribute('replace-part', 'item-template');

    // replace the element's content with the <template>
    element.insertBefore(template, content);
    element.removeChild(content);
    template.content.appendChild(content);

    return true;
  }
}

usage

<template>
  <require from="picker"></require>

  <h1>Default Item Template</h1>

  <picker items-source.bind="colors" value.bind="color"></picker>

  <h1>Custom Item Template</h1>

  <picker items-source.bind="colors" value.bind="color">
    <em css="color: ${item}">
      ${item}
    </em>
  </picker>
</template>
like image 100
Jeremy Danyow Avatar answered Feb 07 '23 13:02

Jeremy Danyow