Custom JavaScript web components are powerful building blocks revolutionizing web development.They combine HTML, CSS, and JS, offering reusability and modularity.
For eCommerce, they enhance user experience, navigation, and conversions. Being compatible with various frameworks, they integrate seamlessly into existing environments, boosting efficiency and interactivity.
Throughout this blog, we'll explore custom JavaScript web components creation.
Let's begin!
What are Custom JavaScript Web Components?
Custom JavaScript Web Components are based on encapsulation of elements and their functionality. Users can create a custom component in JavaScript with a custom element API and import them in HTML.
Web Components are made with a JavaScript class which extends HTMLElement.
Why do we need HTMLElement?
HTMLElement represents an element in the HTML document tree. HTMLElement is the base type for HTMLDivElement, HTMLSpanElement, HTMLImageElement, and many others. Some elements directly implement this interface, while others implement it via an interface that inherits it.
How to create a Custom Component?
In this section we can take a look on how to create a custom button web component in vanilla JavaScript.
class Button extends HTMLElement {
constructor() {
super();
}
}
window.customElements.define('c-button', Button);
With this definition of custom web component, it can be used in HTML as <c-button>.
Name of the web component can be optional, we are using c- as a component in this example.
Note: Name of the web component requires dash to be able to use them.
Example in HTML
Shadow DOM
Shadow DOM allows us to encapsulate parts of the HTML and isolates the CSS and Javascript code with it, which is rendered separately from the main DOM. This is very useful because it allows the developer to control the functionality of the custom component, keeping its features private, so they can be scripted and styled without the fear of collision with other parts of the document. Shadow Root can be attached in the Constructor of the Class.
class Button extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
}
window.customElements.define('c-button', Button);
- attachShadow mode open is accessible with JavaScript outside of the root.
- attachShadow mode closed denies access to the node(s) of a closed shadow.
root from JavaScript.
- Shadow root and delegates focus allows the focus input field inside a custom.
element. This is good especially for accessibility.
Custom elements have a special callback function that affects the element's behavior.
- connectedCallback: Invoked when the custom element is first connected to the document's DOM.
- disconnectedCallback: Invoked when the custom element is disconnected from the document's DOM.
- adoptedCallback: Invoked when the custom element is moved to a new document.
- attributeChangedCallback: Invoked when one of the custom element's attributes is added, removed, or changed.
It is recommended to call functions in the connectedCallback instead in the Constructor because of the life cycles.
This button is a Class, to make it a reactive component, we can use getters and setters. Attributes can get updated with external script, but those updates will not be visible without observed attributes.
class Button extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
get variation() {
return this.getAttribute('variation');
}
set variation(name) {
if (name !== null) {
this.setAttribute('variation', name);
} else {
this.removeAttribute('variation');
}
}
static get observedAttributes() {
return ['variation'];
}
attributeChangedCallback(attrName, oldValue, newValue) {
if (newValue !== oldValue) {
this[attrName] = newValue;
}
}
}
window.customElements.define('c-button', Button);
Styling Custom Components
Custom components can be styled in the component’s shadow DOM. Since shadow DOM in open mode is allowing JavaScript to manipulate our component, that’s not possible with CSS. Styles can be written inline or imported.
<style>
@import "https://codepen.io/chriscoyier/pen/VqKvZr.css";
</style>
CSS Custom properties can be inherited in the shadow DOM.
External script can be used to define custom properties for a specific element. Properties can be used later in template for inheritance.
// External CSS
c-button {
--display: flex;
--padding: 10px;
--color: white;
--bg-color: black;
--cursor: pointer;
--border: 1px solid black;
}
<style>
button {
display: var(--display);
padding: var(--padding);
color: var(--color);
background-color: var(--bg-color);
cursor: var(--cursor);
border: var(--border);
}
</style>
Pseudo-classes for custom elements:
- :defined - Select any element that is defined with CustomElementRegistry.define()
- :host - Selects the shadow host of the shadow DOM
- :host() - Selects the shadow host of the shadow DOM, but only if the selector
given as the parameter matches the element inside the shadow host.
- :host-context() - Selects the shadow host of the shadow DOM, but only if the selector given as the parameter matches the shadow host’s ancestor(s) in the
place it sits inside the DOM hierarchy.
- :slotted - Selects elements that are parameters inside the function. All <p> elements that are slots in the custom element can be selected with ::slotted(p).
Pseudo-elements for custom components
::part - Represents any element in the shadow tree that has the same part attribute.
Templating custom components
Template elements can be created with document.createElement() method.
let buttonTemplate = document.createElement('template');
buttonTemplate.innerHTML = `
<button><slot></slot></button>
`;
Created template can be appended in the custom element inside connectedCallback().
shadowRoot.appendChild(buttonTemplate.content.cloneNode(true));
<slot> can be used to import content inside. In this case text that can be added inside <c-button> will be inside <button>.
Slots can be identified by their name attribute, and allow you to define placeholders in your template that can be filled with any markup fragment you want when the element is used in the markup.
<button><slot name="title"></slot></button>
<c-button><slot name="title"></slot></c-button>
Options for Salesforce Commerce Cloud and Magento
Salesforce Commerce Cloud's built-in solution simplifies the implementation of web components, supporting the reliability of your platform.
It's a powerful toolset that helps optimize the developer experience and the eCommerce interface. You can find it on the Salesforce official website, inside the Lightning Web Components Developer Guide.
Despite Magento's absence of a native web components library, the availability of a trusted third-party library fills this gap. You can incorporate one of Google's libraries into your Magento project, Lit for example. It will improve your eCommerce interfaces and ability to construct and manage customized and interactive components.
Conclusion
Components have revolutionized web development by promoting reusability, modularity, scalability, and consistency.
Despite the initial setup time and learning curve associated with adopting component-driven development, the long-term benefits far outweigh the challenges. Custom JavaScript components empower developers to build robust and maintainable applications, providing users with seamless and engaging digital experiences.
By leveraging the power of custom JavaScript components, web developers can take their projects to new heights of efficiency and flexibility.
Nikola Turudija
Software Developer