Source: TemplateBinder.js

/**
 * @author Greenwald
 * @license PDL
 * @module TemplateBinder
 */
import Binder from './Binder.js';

export default 
/**
 * @class TemplateBinder
 * @memberof module:TemplateBinder
 * @description Enables the use of {@link HTMLTemplateElement} elements as data-templates.
 * @example
 * ```html
 * <html>
 *  <body>
 *    <template bind="model.items">
 *      <h1 bind-textcontent="title"></h1>
 *      <p bind-textcontent="content"></p>
 *    </template>
 *  </body>
 * </html>
 * ```
 * 
 * ```javascript
 * var model = { items: [
 *  { title: 'Item 1', content: 'Item 1 content' },
 *  { title: 'Item 2', content: 'Item 2 content' },
 *  { title: 'Item 3', content: 'Item 3 content' },
 * ] };
 * var binder = new Binder(new TemplateBinder());
 * binder.bind(document.body, model);
 * ```
 */
class TemplateBinder extends Binder.Extension {
  /**
   * @property RECURSE
   * @memberof module:TemplateBinder.TemplateBinder
   * @description The attribute for defining recursive data-templates.
   * @type {string}
   */
  static get RECURSE() { return 'recurse'; }
  static #instances = Symbol('template-instances');
  static #instance = Symbol('template-instance');
  handleElement(binder, element, route) {
    if (element[TemplateBinder.#instance]) { return true; }
    if (!(element instanceof HTMLTemplateElement)) { return false; }
    route = element.hasAttribute(Binder.ATTRIBUTE) ? route.select(element.getAttribute(Binder.ATTRIBUTE)?.split('.')) : route;
    const recurse = element.hasAttribute(Binder.ATTRIBUTE) && element.hasAttribute(TemplateBinder.RECURSE);
    const items = route.result == null ? []
              : Array.isArray(route.result) ? route.result.map((v,i) => route.clone().append(i.toString(), v))
              : [route];
    const instances = element[TemplateBinder.#instances] ??= [];
    while (instances.length > items.length) {
      var instance = instances.pop();
      for (var e of instance) {
        e[TemplateBinder.#instance] = false;
        if (e instanceof HTMLTemplateElement) { 
          binder.bind(e, null); 
        }
        e.remove();
      }
    }
    var insert = instances.at(-1)?.at(-1)?.nextSibling ?? element.nextSibling;
    while (instances.length < items.length) {
      var instance = [...element.content.cloneNode(true).childNodes];
      instances.push(instance);
      for (var e of instance) {
        e[TemplateBinder.#instance] = true;
        element.parentNode.insertBefore(e, insert);
      }
      if (recurse) { 
        const recursion = element.cloneNode(true);
        instance.push(recursion);
        element.parentNode.insertBefore(recursion, insert);
      }
    }
    for (var i = 0; i < items.length; i++) { 
      for (var e of instances[i]) {
        e[TemplateBinder.#instance] = false;
        binder.bind(e, items[i]);
        e[TemplateBinder.#instance] = true;
      }
    }
    return true;
  }
}