npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

html-element-property-mixins

v0.11.0

Published

A collection of mixins extending HTMLElement with properties.

Downloads

294

Readme

# html-element-property-mixins

Installation

$ npm install html-element-property-mixins

Introduction

html-element-property-mixins is a collection of mixins extending HTMLElement with properties, powering custom elements.

  1. ObservedProperties enables observed properties (just like built-in observedAttributes).
  2. DOMProperties enables attribute to property synchonisation.
  3. ReflectedProperties enables property to attribute synchonisation.
  4. Properties combines all three above.

Furthermore, we created a bunch of addons:

  1. PropertiesChangedCallback Debounces / batches property changes for efficient DOM-rendering.
  2. PropertyChangedHandler enables change handlers methods for property changes.
  3. PropertiesChangedHandler enables change handlers methods for multiple property changes.

Mixins

ObservedProperties

import { ObservedProperties } from 'html-element-property-mixins';

Observing

By default, Custom Elements can observe attribute value changes whitelisted in the observedAttributes Array. ObservedProperties offers a similar solution for DOM properties using observedProperties. When a property has changed, propertyChangedCallback is called, passing the property name, the old value and the new value.'

class DemoElement extends ObservedProperties(HTMLElement) {

  static get observedProperties() {
    return ['firstName', 'lastName', 'age']
  }

  propertyChangedCallback(propName, oldValue, newValue) {
    console.info(`${propName} changed from ${oldValue} to ${newValue}`);
  }
  
}

If you like you can add your own getter / setter pairs:

static get observedProperties() {
  return ['initials']
}

get initials() {
  return this._initials;
}

set initials(val) {
  this._initials = val.toUpperCase();
}

constructor() {
  this.initials = 'a.b.c.';
}

propertyChangedCallback(propName, oldValue, newValue) {
  console.info(`${propName} changed to ${newValue}`); //initials changed to A.B.C;
}

Accessors don't require a getter / setter pair. Keep in mind though that by default, private property values are assigned using the following pattern: #${propName}.

static get observedProperties() {
  return ['firstName']
}

get firstName() {
  return this['#firstName'].toLowerCase()
}

DOMProperties

import { DOMProperties } from 'html-element-property-mixins';

Some native properties (e.g. input value) can be set using a DOM attribute. This mixin adds exactly this behavior: attribute to property sync:

class DemoElement extends DOMProperties(HTMLElement) {

  static get DOMProperties() {
    return ['firstname', 'lastname']
  }

}
<demo-element id="demo" firstname="Adewale" lastname="King"></demo-element>
<script>
  console.info(demo.firstname, demo.lastname); // Adewale, King
</script>

By default, attributes are lowercased property names (e.g. 'myPropName' becomes 'mypropname'). You can configure custom attribute mappings using 'propertyAttributeNames':

static get DOMProperties() {
  return ['myBestFriend']
}

static get propertyAttributeNames() {
  return {
    myBestFriend: 'my-best-friend',
  }
}
<demo-element id="demo" my-best-friend="Hellen"></demo-element>

Attribute Converters

Attribute values are always strings. If you wish to set attributes based on properties taht have a specific type, you can confifure converters using propertyFromAttributeConverters:

static get DOMProperties() {
  return ['married', 'friends']
}

static get propertyFromAttributeConverters() {
  return {
    married: function(value) {
      if(value === '') return true;
      return false;
    },
    friends: function(value) {
      if(!value) return null;
      return JSON.parse(value);
    }
  }
}
<demo-element id="demo" married friends='["Gabriella","Anik","Linda"]'></demo-element>
<script>
  console.info(demo.married, demo.friends); //true, ['Gabriella','Anik','Linda'];
</script>

html-element-property-mixins come with a set of attribute converters for boolean, string, number and object types:

  import { StringConverter, NumberConverter, BooleanConverter, ObjectConverter } from 'html-element-property-mixins/utils/attribute-converters';

  static get propertyFromAttributeConverters() {
    return {
      firstName: StringConverter.fromAttribute,
      age: NumberConverter.fromAttribute,
      married: BooleanConverter.fromAttribute,
      friends: ObjectConverter.fromAttribute,
    }
  }

ReflectedProperties

import { ReflectedProperties, ObservedProperties } from 'html-element-property-mixins';

This enables property to attribute sync. Using the 'reflectedProperties' object, one can map properties (keys) to attributes (values). The ObservedProperties mixin is required.

class DemoElement extends ReflectedProperties(ObservedProperties(HTMLElement)) {

  static get observedProperties() {
    return ['firstname', 'lastname', 'age']
  }

  static get reflectedProperties() {
    return ['firstname', 'lastname', 'age']
  }

  constructor() {
    this.firstname = 'Amira';
    this.firstname = 'Arif';
    this.age = 24;
  }

}

By default, attributes are lowercased property names (e.g. 'myPropName' becomes 'mypropname'). You can configure custom attribute mappings using 'propertyAttributeNames':

static get reflectedProperties() {
  return ['firstName']
}

static get propertyAttributeNames() {
  return {
    firstName: 'first-name',
  }
}
<demo-element first-name="Amira"></demo-element>

Attribute Converters

Attribute values are always strings. If you wish to set attributes based on properties taht have a specific type, you can confifure converters using propertyToAttributeConverters:

static get reflectedProperties() {
  return ['married', 'friends']
}

static get propertyToAttributeConverters() {
  return {
    married: function(value) {
      if(value === '') return true;
      return false;
    },
    friends: function(value) {
      if(!value) return null;
      return JSON.parse(value);
    }
  }
}
<demo-element id="demo" married friends='["Gabriella","Anik","Linda"]'></demo-element>
<script>
  console.info(demo.married, demo.friends); //true, ['Gabriella','Anik','Linda'];
</script>

html-element-property-mixins come with a set of attribute converters for boolean, string, number and object types. Attributes are set based on the return value of these functions: when false or undefined, removeAttribute is called. Otherwise, setAttribute is called using the return value.

import { StringConverter, NumberConverter, BooleanConverter, ObjectConverter } from 'html-element-property-mixins/utils/attribute-converters';

static get reflectedProperties() {
  return ['firstName', 'age', 'married', 'friends']
}

static get propertyToAttributeConverters() {
  return {
    firstName: StringConverter.toAttribute,
    age: NumberConverter.toAttribute,
    married: BooleanConverter.toAttribute,
    friends: ObjectConverter.toAttribute,
  }
}

NOTE: ObservedProperties is required for ReflectedProperties.

Properties

import { Properties } from 'html-element-property-mixins';

This wraps all property mixins into a single properties configuration object.

class DemoElement extends Properties(HTMLElement) {

  static get properties() {
    return {
      firstName: {
        observe: true, //add to `observedProperties` array
        DOM: true, //add to `DOMProperties` array
        reflect: true, //add to `reflectedProperties` array
        attributeName: 'first-name', //map to custom attribute name,
        toAttributeConverter: StringConverter.toAttribute, //run when converting to attribute
        fromAttributeConverter: StringConverter.fromAttribute //run when converting from attribute
      }
    }
  }

}

If you use the PropertyChangedHandler addon, you can add 'changedHandler' to your config:

class DemoElement extends PropertyChangedHandler(Properties(HTMLElement)) {

  static get properties() {
    return {
      age: {
        observe: true,
        changedHandler: '_firstNameChanged',
      }
    }
  }

  _firstNameChanged(oldValue, newValue) {
    //custom handler here!
  }

}

Addons

PropertiesChangedCallback

import { ObservedProperties } from 'html-element-property-mixins';
import { PropertiesChangedCallback } from 'html-element-property-mixins/src/addons';

When declaring observed properties using the observedProperties array, property changes are fired each time a a property changes using the propertyChangedCallback. For efficiency reasons (e.g. when rendering DOM), the propertiesChangedCallback (plural!) can be used. This callback is debounced by cancel / requestAnimationFrame on every property change. In the following example, render is invoked only once:

import { PropertiesChangedCallback } from 'html-element-property-mixins/src/addons';
import { ObservedProperties } from 'html-element-property-mixins';

class DemoElement extends PropertiesChangedCallback(ObservedProperties(HTMLElement)) {

  constructor() {
    super();
    this._renderCount = 0;
  }

  static get observedProperties() {
    return ['firstName', 'lastName', 'age'];
  }

  propertiesChangedCallback(propNames, oldValues, newValues) {
    this._renderCount++;
    this.render();
  }

  render() {
    this.innerHTML = `
      Hello, ${this.firstName} ${this.lastName} (${this.age} years).<br>
      Render Count = ${this._renderCount}. 
    `
  }

  constructor() {
    super();
    this.firstName = 'Amina';
    this.lastName = 'Hamzaoui';
    this.age = 24;
  }

}

PropertyChangedHandler

import { ObservedProperties } from 'html-element-property-mixins';
import { PropertyChangedHandler } from 'html-element-property-mixins/src/addons';

Value changes to properties whitelisted in the observedProperties array are always notified using propertyChangedCallback. PropertyChangedHandler provides for custom callbacks for property changes:

class DemoElement extends PropertyChangedHandler(ObservedProperties((HTMLElement)) {
  static get observedProperties() {
    return ['firstName']
  }

  static get propertyChangedHandlers() {
    return {
      firstName: function(newValue, oldValue) {
        console.info('firstName changed!', newValue, oldValue);  
      }
    }
  }
}

Alternatively, callbacks can be passed as string references:

static get propertyChangedHandlers() {
  return { firstName: '_firstNameChanged' }
}

_firstNameChanged(newValue, oldValue) {
  console.info('firstName changed!', newValue, oldValue);
}

Note: PropertyChangedHandler should always be used in conjunction with ObservedProperties.

PropertiesChangedHandler

import { ObservedProperties } from 'html-element-property-mixins';
import { PropertiesChangedHandler } from 'html-element-property-mixins/src/addons';

Its plural companion propertiesChangedHandlers can be used to invoke a function when one of many properties have changed. Key / value pairs are now swapped. A key refers to the handler function, the value holds an array of the observed properties.

class DemoElement extends PropertiesChangedHandler(ObservedProperties((HTMLElement)) {
  static get observedProperties() {
    return ['firstName', 'lastName']
  }

  static get propertiesChangedHandlers() {
    return {
      _nameChanged: ['firstName', 'lastName']
    }
  }

  _nameChanged(propNames, newValues, oldValues) {
    console.info(newValues.firstName, newValues.lastName);
  }

}

Note: PropertiesChangedHandler should always be used in conjunction with ObservedProperties.