DEV Community

artydev
artydev

Posted on

Hypertext Literal from Observablehq

htl is an awesome tool inspired by lit-html and HTM.

Look at this code and enjoy :-) MultipleCounters

const {html} = htl

function Counter (start = 0) {
  let value = start;
  let counter =  html`<span>${value}</span>`;
  let button = html`<button onclick=${function () {
    counter.innerText = ++value;
    if (value > 9) {
       this.style.display = "none"
       counter.innerText = "Limit reached"
    }
  }}>INC</button>`
  function view () {
    return html`
      <div style="margin-bottom:10px">
        ${counter}
        ${button}
      </div>`
  }
  return view()
}

const multipleCounters =  
  [...Array(50)]
    .map(() => Math.floor(Math.random() * 10 ))
    .map(Counter)

function App () {
  return html`<div>
    <h1>Muliple statefull counters</h1>
    ${multipleCounters}
  </div>`
}

document.body.append(App())



Enter fullscreen mode Exit fullscreen mode

Top comments (16)

Collapse
 
efpage profile image
Eckehard

to be honest, I do not really understand how html really works. How are the literals handeled here?

This is, what htl exports:

export const html = Object.assign(
  hypertext(
    renderHtml, fragment => {
      if (fragment.firstChild === null)
        return null;
      if (fragment.firstChild === fragment.lastChild)
        return
      fragment.removeChild(fragment.firstChild);
      const span = document.createElement("span");
      span.appendChild(fragment);
      return span;
    }
  ),
  {
    fragment: hypertext(renderHtml, fragment => fragment)
  }
);
Enter fullscreen mode Exit fullscreen mode

Seems I´m missing something.

Collapse
 
artydev profile image
artydev

This code is also a little confusing to me.
I want neverless show you this code about tagged template litterals

TL


function htmlstring(template) {
    var text = typeof template == 'string' ? [template] : [template[0]];
    for (var i = 1, length = arguments.length; i < length; i++) {
      text.push(arguments[i], template[i]);
    }
    return text.join('');
}


let name = "MAX"

let string1 =  htmlstring("<h1>Hello" + name + "</h1>")

let name2 = "BOB"
let string2 = htmlstring`<h1>Hello ${name2} </h1>`

document.write(string1)
document.write(string2)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
efpage profile image
Eckehard • Edited

Sorry, but your code seems to be a bit misleading. Did it work for you? Try this:
Thank you very much for sharing!

I found this awsome code to create template literals from strings. This is the inversion of your function. This would give us some nice options.

Thread Thread
 
artydev profile image
artydev

Try this ClickMe

Thread Thread
 
efpage profile image
Eckehard • Edited

I´m still struggeling to understand the html function, but here ist an example, how it could be. I just did not manage to include _appendBase() into html() directly:

    import { html } from "./htl.js";
    import {_appendBase} from "./DML.js"

    _appendBase(html`<h1>Titel</h1>`)
    const main = _appendBase(html`<h1>Main</h1>`)
    _appendBase(html`<h1>Footer</h1>`)
    _appendBase(html`<button>Press me</button>`).onclick =
          () => main.textContent = "Hello World"
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
artydev profile image
artydev

Why not create a wrapper around HTML and return the appended Base
result...

Thread Thread
 
efpage profile image
Eckehard • Edited

Finally, it´s was that complicated. The code is just a bit confusing. I did not find out, for what reason they used 'Object.assign()' as it works also this way:

      export const html = hypertext(renderHtml,
Enter fullscreen mode Exit fullscreen mode

We need to append the object after its rendered and postprocessed, so I changed the last line of hypertext(), which is possibly not the smartest solution, but it works.

    return _appendBase(postprocess(root));
Enter fullscreen mode Exit fullscreen mode

Now you can write this code, which gives the same result four times:

    import { html } from "./htl.js";
    import { h1, make, button, div, span } from "./DML.js"
    let b = "Bob"

    html`<b>======================= This is htl =======================<b>`
    html`<h1>Hello ${b},</h1>`
    const main = html`<h1>Main</h1>`
    html`<h1>This is the Footer</h1>`
    html`<button>Press me</button>`.onclick = () => main.textContent = "how Are you"

    html`<br><b>======================= This is DML =======================<b>`
    h1(`Hello ${b}`) // did not know it´s possible at all :-)
    const main2 = h1("Main")
    h1("This is Footer")
    button("Press me").onclick = () => main2.textContent = "how Are you" 

    html`<br><b>======================= This is vanilla JS =======================<b>`
    let el = document.createElement("span")
    el.innerHTML = `<h1>Hello ${b},</h1>
    <h1 id="main3">Main</h1>
    <h1>This is the Footer</h1> 
    <button onclick = "main3.textContent = 'how Are you'">Press me</button>
    `
    document.body.appendChild(el)

    html`<br><b>======================= This is DML again =======================<b>`
    span(`<h1>Hello ${b},</h1>
    <h1 id="main4">Main</h1>
    <h1>This is the Footer</h1> 
    `)
    button("Press me").onclick = () => main4.textContent = "how Are you" 
Enter fullscreen mode Exit fullscreen mode

As ID´s are handled as global variables, you can also access the reference from within Javascript.
I´m not sure, if there are any differences in the way, '${b}' is evaluated, but things seem to be very similar.

Finally, htl boils down to one function:

function renderHtml(string) {
  const template = document.createElement("template");
  template.innerHTML = string;
  return document.importNode(template.content, true);
}
Enter fullscreen mode Exit fullscreen mode

Any clue, what the advantage of all this hypertextProcessing is?

Thread Thread
 
artydev profile image
artydev • Edited

Couldn't this be a workaround : ClickMe


const _html = htl.html

let base = document.getElementById("root")

function html(s) {
  let node = _html(s)
  node.style.color = "red"
  base.appendChild(node)
  return node
}


let test = html`<div>TEST</div>`

Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
efpage profile image
Eckehard

Calling _html with bracktes renders the template-literal into a string. The result will be the same, but then you do not need _html anymore. See this post

Thread Thread
 
artydev profile image
artydev

Eckehard

Look at this AppendHTL

const {html} = htl

let base = root1

function appendToBase(node) {
  base.appendChild(node)
  return node
}

const pipe = (...functions) => input => functions.reduce((acc, fn) => fn(acc), input);

let f = pipe(html,appendToBase) 

f`<h1>Append to Root1</h1>` 

base = root2

f`<h1>Append to Root2</h1>` 

Enter fullscreen mode Exit fullscreen mode
Collapse
 
efpage profile image
Eckehard • Edited

Ok, it´s sometimes not easy to see, where a literal starts and ends.

The concept is a bit confusing to me, as htl seems to generate text only?!? So, how do you get DOM references here? If htl would create DOM elements immediately, this would be far more useful i suppose (ok, if you like to work in the DOM directly).

Just because I was curious, how it looks...

Collapse
 
artydev profile image
artydev • Edited

html creates actualy DOM elements immediately.
In the following example, title is a DOM element.

HtmlToDOM

const html = htl.html

const title = html`<h1>Title</h1>`

document.body.append(title)
Enter fullscreen mode Exit fullscreen mode

What's really nice with this implementation it is that you can embed html elements inside each others
Page

const html = htl.html

const title = html`<h1>Title</h1>`
const main = html`<h1>Main</h1>`
const footer = html`<h1>Footer</h1>`

function App () {
  const view = html`
    ${title}
    ${main}
    ${footer}`
  return view
}

document.body.append(App())
Enter fullscreen mode Exit fullscreen mode
Collapse
 
efpage profile image
Eckehard • Edited

Depends a bit on your task. You will always have some overhead to create your "page". If html could append the objects directly to the DOM (like DML functions do), your code would only have three lines, not 10.

But what is really smart, html creates DOM elements (not text as i thought). So, this code works:

    const title = html`<h1>Title</h1>`
    const main = html`<h1>Main</h1>`
    const footer = html`<h1>Footer</h1>`
    const button = html`<button>Press me</button>`

    button.onclick = () => main.textContent = "Hello World"

    const view = html`
    ${title}
    ${main}
    ${footer}
    ${button}
    `
    document.body.append(view)
Enter fullscreen mode Exit fullscreen mode

Should not be a big deal to make html append the objects directly...

Thread Thread
 
artydev profile image
artydev

Hy Eckehard look at this

const _html  = htl.html
const h =  htl.html

let base;

function html(strings, ...substitutions) {
  let node = _html(strings, ...substitutions)
  base.appendChild(node)
}

let greet = (name) => html`<h1>Hy ${name}</h1>`

function App () {
  base = root
  html`
    <div style='background:green'>
      <h1>Title</h1>
      ${h`<h1>Ciao</div>`}
    </div>
  `
  html`<h1>Main</h1>`
  html`<h1>Footer</h1>`

  base = root2
  greet("Hal")
}

App()
Enter fullscreen mode Exit fullscreen mode

You can test it here : AppendHTML

Collapse
 
efpage profile image
Eckehard

Awsome!

Are your sure, this is correct?

}}>INC</button>`
Enter fullscreen mode Exit fullscreen mode

Seems something is missing...

Collapse
 
artydev profile image
artydev

Hy Eckehard ,

The Dev To Highlighter is lost :-),
The first parenthesis close the function, the second close '${'

I will use this implementation in the next release of MVU...

Reagards