chharvey / extrajs-dom

Javascript extensions to DOM.
https://www.npmjs.com/package/extrajs-dom/
MIT License
0 stars 0 forks source link

idea: populating lists #6

Closed chharvey closed 6 years ago

chharvey commented 7 years ago

Definition

/**
 * @summary Populate this element with list items containing data.
 * @description This method appends items to the end of this list.
 * The items are the result of rendering the given data.
 * In order to determine how the data is rendered, this `<ul>` element must have
 * a `<template>` child, which in turn has a single child that is an `<li>`.
 * This element may contain multiple `<template>` children, but this method uses only the first one.
 * This element may also already have any number of `<li>` children; they are not affected.
 * @param   {Array} data any array of things
 * @param   {function(DocumentFragment,*)} [generator=null] modifies the template’s contents with the given data
 * @throws  {ReferenceError} if this `<ul>` does not contain a `<template>`,
 *                           or if that `<template>` does not contain exactly 1 `<li>`.
 * @returns {HTMLUListElement} `this`
 */
function populate(data, generator = null) {
  if (!this.querySelector('template')) {
    throw new ReferenceError('This list does not have a <template> descendant.')
  }
  let documentfragment = this.querySelector('template').content
  if (documentfragment.childNodes.length !== 1 || !documentfragment.childNodes[0].matches('li')) {
    throw new ReferenceError ('The <template> must contain exactly 1 <li> element.')
  }
  this.append(...data.map(function (d) {
    let frag = documentfragment.cloneNode(true)
    if (generator) generator.call(null, frag, d)
    return (generator) ? frag : (d===null||d===undefined) ? d : d.toString()
  }))
  return this
}

Usage

<ul class="list">
  <template>
    <li class="list-item">
      <a class="list-link" href="{{ url }}" itemprop="significantLink">{{ text }}</a>
    </li>
  </template>
</ul>
let data = [
  { "text": "Career Connections", "url": "#0" },
  { "text": "Getting Licensed &amp; Certified", "url": "#0" },
  { "text": "Career resources", "url": "#0" },
  { "text": "Code of Ethics", "url": "#0" }
]
populate.call(document.querySelector('ul.list'), data, function (frag, d) {
  // frag is the cloned `template.content`;
  // d is an item in the data array
  frag.querySelector('.list-link').href        = d.url
  frag.querySelector('.list-link').textContent = d.text
})
chharvey commented 6 years ago

Updated with new xjs.HTMLTemplateElement methods (v4+):

function populate(data, generator = (f,d) => {}) {
  let template = this.querySelector('template')
  if (template===null) {
    throw new ReferenceError('This list does not have a <template> descendant.')
  }
  if (template.content.children.length !== 1 || !template.content.children[0].matches('li')) {
    throw new ReferenceError ('The <template> must contain exactly 1 <li> element.')
  }
  this.append(...data.map((datum) =>
    new xjs.HTMLTemplateElement(template)
      .setRenderer(generator)
      .render(datum)
  ))
  return this
}

usage remains same as above

Add this function as instance methods of xjs.HTMLOListElement and xjs.HTMLUListElement

chharvey commented 6 years ago

minor optimizations:

    // `this` is an xjs.HTMLElement
    let component = new xjs.HTMLTemplateElement(template).setRenderer(generator)
    return this.append(
      new xjs.DocumentFragment(jsdom.JSDOM.fragment(''))
        .append(...data.map((datum) => component.render(datum)))
    )
  1. create the component once, instead of for each datum. then render it for each datum
  2. append all the <li> items to a new document fragment first, and then append that document fragment to the list; saves a lot of document rerendering
chharvey commented 6 years ago

extrajs-dom v4.1.0