Code Monkey home page Code Monkey logo

wicked-elements's Introduction

wickedElements ๐Ÿง™

Social Media Photo by Jonatan Pie on Unsplash

๐Ÿ“ฃ Community Announcement

Please ask questions in the dedicated discussions repository, to help the community around this project grow โ™ฅ


An all inclusive ~1.3K library to handle any element as if it was a Custom Element.

import {define, defineAsync, get, upgrade, whenDefined} from 'wicked-elements';
const {define, defineAsync, get, upgrade, whenDefined} = require('wicked-elements');
<script src="https://unpkg.com/wicked-elements">
  // as global variable
  wickedElements.{define, get, upgrade, whenDefined};
</script>

All versions changes

Please read VERSIONS.md to know more about historical changes, including the breaking one.

API

Exact same customElements API, with the following differences:

  • wickedElements.get(CSS) returns the component definition, which should be an object literal, or a combination of definitions (i.e. Object.assign({}, BaseDefinition, OverwritesDefinition))
  • wickedElements.define(CSS, definition) accepts any CSS selector, where the more specific it is, the better.

The definition is a literal object with optional helpers/utilities described as such:

wickedElements.define(
  // a unique, specific, CSS selector that will become wicked
  '[data-wicked="my-component"]',
  {
    // a one-off, `constructor` like, initialization,
    // the right place to populate node content, add listeners, or setup components
    init() {
      // the element that is related to this wicked instance will be always
      // reachable through `this.element`, even without an `init()` method
      this.element;
      // always points, in every other method, to the DOM element related
      // to this wicked element/component
    },

    // Custom Elements like callbacks, without the redundant `Callback` suffix
    connected() {},
    disconnected() {},

    // invokes `attributeChanged` if any of these attributes is changed, set, removed
    observedAttributes: ['data-thing', 'value'],
    attributeChanged(name, oldValue, newValue) {},

    // zero, one, or more, listeners, automatically setup per each component
    // the context of each method-listener will be the wicked instance,
    // not the element itself, but you can reach event.currentTarget or this.element
    // at any time within the code
    onClick(event) {},
    onCustomEvent(event) {}
    // if defined camelCase, events will be attached both lowercase
    // and also camelCase, so that element.dispatchEvent(new CustomEvent('customEvent'))
    // or element.dispatchEvent(new CustomEvent('customevent')) will both work.
    // the `event.type` will be the one dispatched, i.e. `click` or `customEvent`
    // or even `customevent`.

    // any property with an `Options` suffix, will be used to add the listener,
    // so that special cases like `{once: true}`, `true` to capture, and others,
    // can be easily addressed through the definition. By default, options is `false`.
    onClickOptions: {once: true}
  }
);

F.A.Q.

Can I use 3rd parts libraries to render content?

Sure thing! Following a lighterhtml integration example, also live in CodePen:

import {render, html, svg} from 'lighterhtml';
const LighterHTML = {
  html() { return render(this.element, html.apply(null, arguments)); },
  svg() { return render(this.element, svg.apply(null, arguments)); }
};

import {define} from 'wicked-elements';
define('.my-component', {
  ...LighterHTML,
  init() { this.render(); },
  render() {
    this.html`<h3>Hello ๐Ÿ‘‹</h3>`;
  }
});
Can I haz hooks too?

You can either check hookedElements for an out-of-the-box solution, or you could use augmentor, which is just perfect for this use case ๐Ÿ˜‰, which is indeed exactly what hookedElements use (it's just automatically integrated).

Test it live on CodePen.

import {augmentor, useState} from 'augmentor';
import {define} from 'wicked-elements';
define('button.counter', {
  init() {
    // augment once any method, and that's it ๐Ÿฆ„
    this.render = augmentor(this.render.bind(this));
    this.render();
  },
  render() {
    const [counter, update] = useState(0);
    const {element} = this;
    element.onclick = () => update(counter + 1);
    element.textContent = `${counter} clicks`;
  }
});
Any basic example to play with?

This is a classic one, the WebComponents.dev click counter, also in in CodePen.

Any other example?

Sure. Here any element with a disabled class will effectively become disabled.

wickedElements.define('.disabled', {
  init() {
    const {element} = this;

    // if the element has its native way to be disabled, return
    if ('disabled' in element)
      return;

    // otherwise define the behavior
    Object.defineProperty(element, 'disabled', {
      get: () => element.hasAttribute('disabled'),
      set: value => {
        if (value) {
          element.style.cssText = this.disabled;
          element.setAttribute('disabled', '');
        }
        else {
          element.style.cssText = '';
          element.removeAttribute('disabled');
        }
      }
    });

    // if the element was live, just trigger/ensure its status
    element.disabled = element.disabled;
  },
  // the style to attach to disabled elements
  disabled: `
    pointer-events: none;
    opacity: 0.5;
  `
});

Once a definition is known, even same DOM nodes can be handled by multiple definitions/behaviors.

As example, here we are addressing all elements that will eventually have a [disabled] attribute.

wickedElements.define('[disabled]', {
  onMouseOver() {
    const {element} = this;
    // as elements can be promoted but never come back,
    // which is the same that happens to Custom Elements definitions,
    // we can check these elements are still disabled, per each mouseover event
    if (element.disabled) {
      element.style.visibility = 'hidden';
    }
  },
  onMouseOut() {
    this.element.style.visibility = 'visible';
  }
});

Each definition/behavior will provide a new instance of such definition (definition as prototype), meaning there are no conflicts between definitions, and each wicked instance deals with what its prototype object had at the time of definition.

Any caveat/hint to consider?

Same as Custom Elements suffer name-clashing, so that you can have only one custom-element definition per page, wicked definitions also could clash if the name is too generic.

It is a good practice to ensure, somehow, your definitions are namespaced, or unique enough, if you're after portability.

wickedElements.define('[data-wicked="my-proj-name-table"]', {
  // unique(ish) definition what will likely not clash with others
});

Using data-wicked="..." is convenient to also be sure a single element would represent the definition and nothing else, as you cannot have multiple values within an element.dataset.wicked, or better, you can serve these components via Server Side Rendering and reflect their special powers via JS once their definition lands on the client, which can be at any given time.

Using a runtime unique class/attribute name also grants behaviors and definitions won't clash, but portability of each wicked behavior could be compromised.

My element doesn't become wicked, what should I do?

There are cases where an element might not become wicked, such as when the element class changes at runtime, and after the definition occurs.

wickedElements.define('.is-wicked', {
  init() {
    this.element.classList.remove('not-wicked-yet');
    console.log(this.element, 'is now wicked ๐ŸŽ‰');
  }
});

const later = document.querySelector('.not-wicked-yet');
later.classList.add('is-wicked');
// ... nothing happens ...

For obvious performance reasons, the MutationObserver doesn't trigger per each possible class change in the DOM, but fear not, like it is for customElements.upgrade(element), you can always upgrade one or more elements via wickedElements.upgrade(element).

wickedElements.upgrade(later);
// console.log ...
// <div class="is-wicked"></div> is now wicked ๐ŸŽ‰

If you'd like to upgrade many elements at once, you can always pass their top-most container, and let the library do the rest.

// upgrade all wicked definitions at once ๐Ÿ‘
wickedElements.upgrade(document.documentElement);

Don't worry though, elements that were already wicked won't be affected by an upgrade, so that each init() is still granted to execute only once per fresh new element, and never again.

wicked-elements's People

Contributors

webreflection avatar greenkeeper[bot] avatar keul avatar oskarrough avatar klipstein avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.