snuggsi

2022.1.20-4 • Public • Published

snuggsi ツ - Easy Custom Elements in ~1kiloByte

NPM monthly downloads Travis CI build Brotli size npm version dependency status license Pull requests welcome!

All you need is a browser and basic understanding of HTML, CSS, & Javascript classes to be productive!

Performance is the art of avoiding work - #FreeJewelry 💍 💎

Navigation

Why ?

  1. You prefer to be D.R.Y. and build reusable web components on a gradual learning curve.

  2. Because You (probably) don't need a Javascript Framework.

  3. You prefer convention over configuration.

  4. Web Components are ready for production & Custom Elements v1 has support for every modern 🍃 greenfield browser.

Easy Installation

Made with 💖 Vanilla JS™ No need to learn Node.js, React, Webpack, Babel, or Gulp. (You can if ya want to use snuggsiツ with those tools. But you don't need to!)

#UseThePlatform

snuggsiツ works in a plain 'ol HTML file! Simply place the following <script> within your webpage:

<script nomodule src=//unpkg.com/snuggsi></script>

Et Voila (that's it!)

Browser Support

snuggsiツ provides a prolyfill for the following native web platform features:

Support Edge* Chrome* Firefox* Safari 9+ Android* iOS Safari*
Templates
Custom Elements
Slot Replacement

*Indicates the current version of the browser

Quick Tour

snuggsiツ encourages convention over configuration using familiar techniques that are native to all browsers.

Gone are the sleepless nights where your code suffers from <div>itus, or need to install packages on a terminal just to write HTML. People who are more comfortable with HTML should be able to start marking up their ideas immediately! You shouldn't have to know CSS or Javascript! (But it definitely helps if you need styling and functionality).

snuggsiツ believes in using Progressive Enhancement. Not just with your code, but also with your Developer eXperience (DX). We will start from the beginning with a simple Custom Element and gradually enhance functionality step-by-step.

<custom-elements>

When picking a name for your custom element there are a few naming conventions you must be aware of. We can simply use hello-world for now.

HTML Declaration

HTML has always been a declarative language. Why not just use it to declare Custom Elements? If you know how to write HTML you can start using snuggsiツ. Sometimes you need to sandbox a section of your page for styling. Other times you need a custom container of complex functionality. Either way you usually start with a plain ole' HTML element Declaration :

A Brief History Lesson

Not all HTML tags are created equal!

A "valid HTML Element" has always allowed non-standard tag names (as long as you remember to provide a closing tag). In the bad old days of the web, HTML5 elements were once "non-standard" to HTML 4.0 :

<a></a> <!-- valid (hyperlink) -->
<b></b> <!-- valid (HTML4.01)-->
<c><!-- invalid (no closing tag) -->
<c></c> <!-- valid (HTMLUnknownElement) -->
<p></p> <!-- valid (with closing tag)  -->
<p><!-- valid (with optional closing tag)  -->
<img />  <!-- valid (x-HTML self closing tag)  -->
<hr>     <!-- valid (no content tag)  -->

👍 Rule of thumb: Close all non-standard HTML Element tags!


As you learned earlier there are a few conventions to adhere to be considered a "valid Custom Element" you will need an alpha-numeric character followed by a hyphen in the tag name (at minimum) :

<foo></foo> <!-- invalid (CUSTOM element but valid HTML element) -->
<foo-bar>   <!-- invalid (no closing tag) -->
<a-></a->   <!-- valid (you only need a character and a hyphen) -->
<foo-bar></foo-bar> <!-- valid -->

👍 Rule of thumb: Use kebab case (with hyphens) for tag names.


We now know enough to be dangerous and make your own Custom Element tag :

<hello-world></hello-world>

Et Voila ツ (No really … That's it!)


At this point your custom element can be styled using CSS just like any other element.

<style>
hello-world {
  background: #bada55
}
</style>

<hello-world>
  Hello
</hello-world>

See A JavaScript-free custom element implementation And Building a <tool-tip> component for more (sans JavaScript) custom elements CSS fun!


Live {token} Declarations

The {token} is simply a well named dynamic variable you will Describe later. {token}s are placeholders which watch for changes to your custom element's class property of the same name. Since they are "placeholders" and not live code, Front-end designers are no longer blocked by needing to install a Javascript framework just to write CSS!

<foo-bar> This is a token 👉 {baz} and {bat} is another! </foo-bar>

👍 Rule of thumb: If the {token} name is not in a thesaurus then I probably shouldn't use it.


A "live token" is a declarative way to bind data values to your Custom Element. A nice convention to a real historical P.I.T.A. of keeping values updated. Live {token}s are also " automagically" updated each time the element re-renders.

Let's add a {token} to <hello-world> :

<hello-world>

  Hello {planet}

</hello-world>

👍 Rule of thumb: A {token} is not "live" until there is a class description for its functionality.


Lastly, We can visually enhance your <hello-world> Custom Element by making it "block level" with CSS display: block. This way your element plays nicely in your layout :

<hello-world>

  Hello {planet}

  <style>
    hello-world { display: block }
  </style>

</hello-world>

We have finished your Custom Element Declaration using HTML, & CSS!🌟 Now on to your Definition.


Element Definition

Every Custom Element MUST be Defined within the CustomElementsRegistry. This is simple with snuggsiツ

Let's define your element using the Element interface :

// <hello-world> … </hello-world>

Element `hello-world`

👍 Rule of thumb: Use backticks around tag names (``).

This syntax is not JSX. It's actually called tagged template literals and is native to the platform. Custom elements use the native Element interface definition strategy for two reasons :

  1. To prevent you from worrying about browser inconsistencies as the technology matures.
  2. Prevent global namespace polution. (Element has been native to the web platform for decades!)

Classic Javascript syntax may also be used. However this should be the job of a transpiler not the developer. Transpilers take care of normalizing Modern JavaScript to a specific retrograde.

Element ('hello-world') // classic javascript definition

class Description

Great so far!🎉 Although your Element behaves like any other HTMLElement, we should add some functionality custom to your needs.

Next we need to pass a class description to the function returned by your Element definition.

// <hello-world> … </hello-world>

Element `hello-world`

( class HelloWorld extends HTMLElement {  } )

👍 Rule of thumb: MUST define a class which extends HTMLElement

Let's shorten your description up a bit by using an anonymous class expression to describe the class. This convention is preferred as using an explicit class declaration name can potentially polute the global namespace:

// <hello-world> … </hello-world>

Element `hello-world`

( class extends HTMLElement {  } )

👍 Rule of thumb: Use enclosing parenthesis around (class …) definitions.


Live {token} Definitions

Since we previously declared a {planet} token within your <hello-world> element we need to also define a class property of the same name to replace the {planet} token with a value.

Class properties may look like typical Javascript Functions. However they are treated as properties. (called without parenthesis). class properties are described by using the getter and setter annotations before the name.

Let's add a property to your class definition and give {planet} some life :

// <hello-world> … {planet} … </hello-world>

Element `hello-world`

(class extends HTMLElement {

  get planet () // used for {planet} token
    // "✨ automagic" token binding
    { return 'world 🌎' }
})

👍 Rule of thumb: class properties are functions begining with the keywords get & set. 👍 Rule of thumb: {tokens} will use the class property value of the same name by default.

⚠️ The Live {token} value is updated after each re-render but it beyond the scope of this simple example.

Since your hello-world Custom Element is an HTMLElement at it's core, we can access your property directly from the DOM!

// <hello-world> … </hello-world>

document.querySelector
  ('hello-world').planet // world 🌎

👍 Rule of thumb: Do not use parenthesis () when calling getters & setters.


Global event Listeners

event handlers can be any method function which can be placed on any child elements and also onto the custom element itself (i.e.onclick=eatBacon). However, You will not have to explicitly set the handler in HTML when you follow native naming conventions. This is the magic behind snuggsiツ Global event Listeners. They register themselves onto the custom element and "listen" for you! As a convenience, your new custom element uses Event Delegation to capture all it's children's event bubbles of the same name.

// <hello-world>
//   `onclick` is "automagically" attached
//
//   <a onclick=onsneeze> ACHOO! </a>
// </hello-world>

Element `hello-world`

(class extends HTMLElement {

  // native event handler names
  // are "✨automagically" attached to `hello-world`
  onclick (event) {

    // prevent event from bubbling
    // Custom Element will re-render
    // after event unless prevented
    event.preventDefault ()

    event.target // element which triggered event

    this // is ALWAYS bound to the Custom Element container 🎉
  }

  onsneeze (event) {
    /* must be declared in HTML
      <button onclick=onsneeze>
    */
  }
})

👍 Rule of thumb: Use native GlobalEventHandlers names if you don't want to explicitly set listeners in HTML.

See full list of Global Event Handlers on MDN

Lastly, all event handlers (and global event listeners) are passed a native event object.

P.S. YES the event handler will autobind this to the current custom element instance! 🎉


Hello World! (simple)

Play <hello-world> Demo

…or just copy & 🍝pasta into a new HTML file and have at it!

<!-- 👇 Declaration --------------------------->

<hello-world>

  Hello {planet}

  <button onclick=speak>Greet</button>
</hello-world>


<script src=//unpkg.com/snuggsi></script>
<script>

// 👇 Definition -------------------------------

Element `hello-world`

// 👇 Description ------------------------------

(class extends HTMLElement {

  get planet () // property token
    // "✨ automagic" token binding
    { return 'world 🌎' }

  onclick (e) { // event handler
    // "✨ automagic" event registration
    alert(`You clicked on ${e.target.tagName}`)
  }
  
  speak(e) { // bound event handler
    e.preventDefault()
    alert('One small step...')
  }
})

</script>

Hello Kitty! (advanced)

Play <hello-kitty> Demo

…or just copy & 🍝pasta into a new HTML file and have at it!

<hello-kitty icon=😻 >

  <header>{greeting}</header>

  <figure>
    <figcaption>
      <button onclick=meow>Hello new kitty!</button>
    </figcaption>

    <img alt='Random kitty cat' src={url} onclick=pet >
  </figure>

  <style>
    hello-kitty *
      { margin: 1em; text-align: center }
  </style>

</hello-kitty>

<script src=//unpkg.com/snuggsi></script>
<script>

Element `hello-kitty`

(class extends HTMLElement {

// CONSTRUCTOR ---------------------------------------

  initialize ()
    // see `meow` event handler
    { this.url = 'https://placekitten.com/400/400?image=' }


// PROPERTIES ----------------------------------------

  set icon // on element
    // default to html attribute
    ( value = this.getAttribute `icon` )
      // set html attribute to new value
      { this.setAttribute (`icon`, value) }

  get icon () // from element
    { return this.getAttribute `icon` }

  get greeting () // "✨ automagic" token binding
    { return `<hello-kitty> Carousel ${ this.icon }` }


// METHODS -------------------------------------------

  random () {
    return Math.round
      ( Math.random `` * 16 )
  }


// EVENT HANDLERS ------------------------------------

  onclick (e) {
    // "✨ automagic" global event handler registration
    alert (`You clicked on ${e.target.tagName} ${ this.icon }`)
  }

  pet ()
    { alert `Puuuuuurrrrrrrrrrrr!!!` }

  meow (e) { // custom handler
    e.preventDefault ``

    this.querySelector `img`
      .setAttribute (`src`, this.url + this.random () )

    // element will "✨ automagically" re-render !!!
  }
})

</script>

Template

The <template> is used to define custom element content for use within multiple elements.

Useful when we need to:

  1. Separate a custom element definition into a Web Component
  2. Bind a context to the template using An Array or POJO (Plain Ol' Javascript Object)
  3. Append rendered template to the document:
    • If context is an object bind a single <template>
    • If context is a collection (i.e. an Array) bind a sequential <template> DocumentFragment per item

<template> With Object Context

<template name=developer>
  <!-- `{nickname}` will bind to `context` property `nickname` -->
  <h1>{nickname}</h1>
</template>

<script src=//unpkg.com/snuggsi></script>
<script>

const
  template = Template `developer`
, context  = { nickname: 'That Beast' }

template.bind (context)

</script>

Rendered Result

<template name="developer">
<!-- invisible
  <h1>{name}</h1>
 -->
</template>

<h1>That Beast</h1><!-- template is used as head for tail insertion -->

<template> With Array of Objects Context

Each <template name> will be mapped over each context item within the array. When the array items are objects each property will map to a respective {token} of the same name.

Note: The # symbol is used to retrieve the collection's current index of iteration.

<ul>
  <template name=item>
    <li>Hello {name}! Your index # is {#}
  </template>
</ul>

<script src=//unpkg.com/snuggsi></script>
<script>

// when context is a collection
const
  template = Template `item`
, context  = [ {name: 'DevPunk'}, {name: 'Snuggsi'} ]

// internal template render for each item in context
template.bind (context)

</script>

Rendered Result

<ul>
  <template name="item">
  <!-- invisible
    <li>Hello {name}! Your index # is {#}
  -->
  </template>

  <li>Hello DevPunk! Your number index # is 0</li>
  <li>Hello Snuggsi! Your number index # is 1</li>
</ul>

<template> With Scalar Array Context

Each <template name> will be mapped over each context item within the array. When the array items are scalar (i.e. strings, numbers, booleans) each item will map to a {self} helper token.

Note: The # symbol is used to retrieve the collection's current index of iteration.

<dl>
  <template name=recipe>
    <dt> Step {#}.
    <dd> {self}.

  </template>
</dl>

<script src=//unpkg.com/snuggsi></script>
<script>

// when context is a collection of scalar variables (i.e. Strings)
const
  template = Template `recipe`
, context  = [
    "Preheat oven"
  , "Place pre-made cake in oven"
  , "Don't burn the cake"
  , "Nom Nom"
  ]

// internal template render for each item in context
template.bind (context)

</script>

Rendered Result

<dl>
  <template name="recipe"></template>

  <dt> Step 1.</dt>
  <dd> Preheat oven.</dd>

  <dt> Step 2.</dt>
  <dd> Place pre-made cake in oven.</dd>

  <dt> Step 3.</dt>
  <dd> Don't burn the cake!</dd>

  <dt> Step 4.</dt>
  <dd> Nom Nom!</dd>

</dl>

Build Process

snuggsiツ is able to use modern compression algorithms to create bundles as small as ~1500 OCTETS (or one 1500byte Ethernet packet frame)

Read More

CalVer (Calendar Versioning)

A chronological CalVer strategy is used instead of SemVer. Read More

Contributors

Contributing while using Visual Studio Code is simple! Read More

Dependents (0)

Package Sidebar

Install

npm i snuggsi

Weekly Downloads

654

Version

2022.1.20-4

License

MIT

Unpacked Size

76.1 kB

Total Files

39

Last publish

Collaborators

  • snuggsi
  • brandondees