How to re-render hyperHTML to the same element after content change

2.1k Views Asked by At

I am trying to support the same type of thing as React.Children

My code looks like

const elem = document.getElementById("profile")
const render = hyperHTML.bind(elem);
const name = elem.textContent
render`<b>Hi ${name}</b>`

So the API looks like

<div id="profile">alax</div>  <div id="profile"><b>Hi alax</b></div>

and I am using MutationObserver to rerender on content change

But if the content is changed. hyperHTML says its rending to the right element.. but the element keeps its innerHtml(No update)

I can see the <!--_hyper: -2001947635;--> is removed then the content is set but setting up the render & hyperHTML.bind again does nothing

Any thoughts would be great! Thx


Update

The fix to the above problem is to call hyperHTML.bind`` then your normal render using hyperHTML will work

Context - I am using hyperHTML to create a custom element library(hyper-element)

My use case: I work in a mix-tech project (some people use jQuery)

Side note, on the why. I want to support something like partial templates

Example of a partial template:

 <user-list data="[{name:'ann',url:''},{name:'bob',url:''}]">
     <div><a href="{#url}">{#name}</a></div>
 </user-list>

Output:

 <user-list data="[{name:'ann',url:''},{name:'bob',url:''}]">
     <div><a href="">ann</a></div>
     <div><a href="">bob</a></div>
 </user-list>

This is one use of setting custom content in an element you control

At the moment I have the setting of the content by 3-party working/re-rending

https://jsfiddle.net/k25e6ufv/16/


My problem is now: it is rending another custom element and getting the pass content to child element

It looks like hyperHTML is setting the child element's content in front to the element and creating the element without setting the content

Scroll down to bottom of source to see implementation!

https://jsfiddle.net/k25e6ufv/14/

Rending crazy-cats:

  Html`
  xxx: ${this.wrapedContent} zzzz
  `

Current output:

wrapedContent: ppp time:11:35:48 ~ crazy-cats: **Party 11:35:48** xxx: zzzz

<crazy-cats>Party 11:37:21 xxx: <!--_hyper: -362006176;--> zzzz </crazy-cats>

Desired output:

wrapedContent: ppp time:11:35:48 ~ crazy-cats: xxx: **Party 11:35:48** zzzz

<crazy-cats> xxx: Party 11:37:21 zzzz </crazy-cats>
1

There are 1 best solutions below

4
On

I will try to answer as best as I can, but I'll start saying that when asking for help, it'd be much easier/better to show the simplest use case you are trying to solve.

There is a lot of "surrounding" code in your fiddles so that I'll try to answer only to hyperHTML related bits.


hyper-element ?

I am not sure what's the goal of the library but hyperHTML exposes hyper.Component, and there's also an official HyperHTMLElement class to extend, which does most of the things you manually implement in your examples.

I'll keep answering your questions but please consider trying, at least, the official alternative and maybe push some change there if needed.


partial templates

hyperHTML pattern and strength is the Template Literal standard. Accordingly, to generate TL from the DOM would require either parsing of the content or code evaluation. Both solutions aren't the way to go.

Custom Elements require JavaScript to work, and without JS your partial template is useless and also potentially confusing for the user/consumer.

You don't want to define what to do with the data in the layout, you want to define a Custom Element behavior within the class that defines it.

That means: get rid of old-style in-DOM output, and simply use the Custom Element class to define its content. You maintain the related class only instead of maintaining a layout that has no knowledge about how the CE should represent that data.

TL;DR the following is a bad hyperHTML pattern:

<user-list data="[{name:'ann',url:''},{name:'bob',url:''}]">
  <div><a href="{#url}">{#name}</a></div>
</user-list>

all you want to do is to write this:

<user-list data="[{name:'ann',url:''},{name:'bob',url:''}]"></user-list>

but be careful, the data attribute in hyperHTML is special only if passed through the template literal. If you want to pass JSON to the component, call the attribute differently.

// hyperHTML data is special, no need to use JSON
render`<c-e data=${{as: 'it is'}}></c-e>`

Above snippet is different from having JSON as data attribute text so your example should use data-json name, and the class should remember to JSON.parse(this.dataset.json) in its constructor (or have an attribute observer that does that for you)


hyperHTML owns elements

When you write:

it looks like hyperHTML is setting the child element's content in front to the element and creating the element without setting the content

you are assuming you should care at all what hyperHTML does: you shouldn't.

The only thing you should understand is that hyperHTML owns the node it handles. If you trash those nodes via different libraries or manually, you are doing something wrong.

hyperHTML(document.body)`<p>hello ${'world'}</p>`;
// obtrusive libraries ... later on ...
document.body.textContent = 'bye bye';
// hyperHTML still owns the body content
hyperHTML(document.body)`<p>hello ${'world'}</p>`;

Above snippet is perfectly fine and totally wrong at the same time. You don't update the body content manually, you don't interfere with its content via jQuery or other libraries, and you should never trash the content at all.

Once you chose hyperHTML to handle a bound context, that's it, you've made your choice.

This is true for pretty much every library on this world. If you use Angular to create something and you mess it all via jQuery, that breaks. If you write backbone templates and you mess later on with their content manually, that breaks.

If you bind an element to hyperHTML and you mess it up with other libraries, that breaks.

The only thing that won't break are wires, meaning the moment you create a wire, you can append it directly and that's actually a DOM node so it will be there, and it will be handled by hyperHTML.

Yet you should use hyperHTML to handle those changes, never jQuery or JS itself.


The output is right

When you say that the output should not contain the comment you are assuming you should care what output is produced via hyperHTML: you shouldn't!

hyperHTML uses comments as delimiters and these are absolutely fine for both performance, being unaffected by repaint and reflows, and for partial changes like the following one:

hyperHTML(document.body)`<p>${'a'} b ${'c'}</p>`

Both a and c will have a comment as anchor node to be able to update their content with anything later on.

hyperHTML(document.body)`<p>${[list, of, nodes]} b ${otherThing}</p>`

You change interpolations? All good, hyperHTML knows what to replace and where.


force-own the content

If you use a different template literal to re-populate a bound node you are trashing the cache and creating new content.

At that point you are better off with innerHTML because all the features of hyperHTML will be gone.

To start with, if your content can change so much, use an array.

hyper(document.body)`${['text']}`;
// you can clean up the text through empty array
hyper(document.body)`${[]}`;
// re-populate it with new content
hyper(document.body)`${['a', 'b', 'c']}`;

Above example is still better than changing template because all the optimizations for the content will be already there.

However, if you want to be sure the node the initial one created via hyperHTML, assuming no third parts script mutate/trash that node, you can use a wire.

const body = hyper()`<p>my ${'content'}</p>`;
document.body.textContent = '';
document.body.appendChild(body);

It's a bit extreme but at least faster.


As Summary

It looks like you are trying to sneak in hyperHTML into an application that trashes layout all the time through different third parts libraries.

Unless you create a closed Shadow DOM reference and you drop partial template through layout, you'll always have issues with libraries based on side effects with DOM content, libraries that mutates elements they don't own.

In hyperHTML the ownership concept is key, like in React you cannot change at runtime the defined JSX for the component, you should never try to change at runtime the defined template literal for hyperHTML.

Now, as much as I'd like to solve all your issues, I feel like it's right to ask you: are you sure hyperHTML is really the solution for your current app? It looks like surrounding side-effects caused by third parts libraries would constantly break your expectations if you don't use closed mode Shadow DOM and hyperHTML only to update your DOM.