Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way/workaround to have the slot principle in hyperHTML without using Shadow DOM?

Tags:

hyperhtml

I like the simplicity of hyperHtml and lit-html that use 'Tagged Template Literals' to only update the 'variable parts' of the template. Simple javascript and no need for virtual DOM code and the recommended immutable state.

I would like to try using custom elements with hyperHtml as simple as possible with support of the <slot/> principle in the templates, but without Shadow DOM. If I understand it right, slots are only possible with Shadow DOM?

Is there a way or workaround to have the <slot/> principle in hyperHTML without using Shadow DOM?

    <my-popup>
      <h1>Title</h1>
      <my-button>Close<my-button>
    </my-popup>

Although there are benefits, some reasons I prefer not to use Shadow DOM:

  • I want to see if I can convert my existing SPA: all required CSS styling lives now in SASS files and is compiled to 1 CSS file. Using global CSS inside Shadow DOM components is not easily possible and I prefer not to unravel the SASS (now)
  • Shadow DOM has some performance cost
  • I don't want the large Shadow DOM polyfill to have slots (webcomponents-lite.js: 84KB - unminified)
like image 942
Jo VdB Avatar asked Feb 11 '18 00:02

Jo VdB


People also ask

Can I use Shadow DOM elements with hyperhtml?

With hyperHTML you can also use Custom Elements right away, optionally including a proper polyfill upfront when needed. Verify the result on Code Pen . There are no special rules to remember with Shadow DOM because behind the scenes hyperHTML uses a template element so if you bind a Shadow DOM element to hyperHTML, everything will just work.

What do I need to know about hyperhtml?

There is nothing new to learn with hyperHTML other than standard JavaScript, HTML, or CSS. Its core features are built on top of template literals , where every interpolation is addressed once, as a unique DOM operation, and updated at lightning-speed when needed.

What is hyperhtmlelement?

The goal of HyperHTMLElement is to be an ideal companion to hyperHTML itself. It removes all the repeated, boring and sometimes problematic setup steps needed to define Custom Elements. This makes their creation a no-brainer. Have a look at the comparisons VS Polymer . Below are some HyperHTMLElement class features:

How do I test hyperhtml components?

The static Component.for (context [, uid]) utility helps solving tedious sub-components initialization. hyperHTML is easy to test in the browser and in NodeJS. If you're testing in NodeJS you will, however, need a JavaScript implementation of the DOM ( basicHTML or jsdom ).


2 Answers

Let me start describing what are slots and what problem these solve.

Just Parked Data

Having slots in your layout is the HTML attempt to let you park some data within the layout, and address it later on through JavaScript.

You don't even need Shadow DOM to use slots, you just need a template with named slots that will put values in place.

    <user-data>
      <img  src="..." slot="avatar">
      <span slot="nick-name">...</span>
      <span slot="full-name">...</span>
    </user-data>

Can you spot the difference between that component and the following JavaScript ?

    const userData = {
      avatar: '...',
      nickName: '...',
      fullName: '...'
    };

In other words, with a function like the following one we can already convert slots into useful data addressed by properties.

    function slotsAsData(parent) {
      const data = {};
      parent.querySelectorAll('[slot]').forEach(el => {
        // convert 'nick-name' into 'nickName' for easy JS access
        // set the *DOM node* as data property value
        data[el.getAttribute('slot').replace(
          /-(\w)/g,
          ($0, $1) => $1.toUpperCase())
        ] = el; // <- this is a DOM node, not a string ;-)
      });
      return data;
    }

Slots as hyperHTML interpolations

Now that we have a way to address slots, all we need is a way to place these inside our layout.

Theoretically, we don't need Custom Elements to make it possible.

    document.querySelectorAll('user-data').forEach(el => {
      // retrieve slots as data
      const data = slotsAsData(el);
      // place data within a more complex template
      hyperHTML.bind(el)`
        <div class="user">
          <div class="avatar">
            ${data.avatar}
          </div>
          ${data.nickName}
          ${data.fullName}
        </div>`;
    });

However, if we'd like to use Shadow DOM to keep styles and node safe from undesired page / 3rd parts pollution, we can do it as shown in this Code Pen example based on Custom Elements.

As you can see, the only needed API is the attachShadow one and there is a super lightweight polyfill for just that that weights 1.6K min-zipped.

Last, but not least, you could use slots inside hyperHTML template literals and let the browser do the transformation, but that would need heavier polyfills and I would not recommend it in production, specially when there are better and lighter alternatives as shown in here.

I hope this answer helped you.

like image 79
Andrea Giammarchi Avatar answered Sep 21 '22 23:09

Andrea Giammarchi


I have a similar approach, i created a base element (from HyperElement) that check the children elements inside a custom element in the constructor, if the element doesn't have a slot attribute im just sending them to default slot

    import hyperHTML from 'hyperhtml/esm';

    class HbsBase extends HyperElement {
        constructor(self) {
            self = super(self);
            self._checkSlots();
        }
        _checkSlots() {
            const slots = this.children;
            this.slots = {
                default: []
            };
            if (slots.length > 0) {
                [...slots].map((slot) => {
                     const to = slot.getAttribute ? slot.getAttribute('slot') : null;
                    if (!to) {
                         this.slots.default.push(slot);
                    } else {
                        this.slots[to] = slot;
                    }
                 })
            }
        }
    }

custom element, im using a custom rollup plugin to load the templates

    import template from './customElement.hyper.html';
    class CustomElement extends HbsBase {
        render() {
            template(this.html, this, hyperHTML);
        }
    }

Then on the template customElement.hyper.html

    <div>
         ${model.slots.body}
    </div>

Using the element

    <custom-element>
       <div slot="body">
         <div class="row">
             <div class="col-sm-6">
                 <label for="" class="">Name</label>
                 <p>
                      <a href="#">${model.firstName} ${model.middleInitial}      ${model.lastName}</a>
                 </p>
             </div>
         </div>
         ...
       </div>
    </custom-element>
like image 34
mrpix Avatar answered Sep 20 '22 23:09

mrpix