Get started with web components and Lit

When building plugins, we recommend using lightweight frameworks that are designed to create web components and manipulate the ShadowDOM.

Frameworks such as React and Angular are designed to build full web applications, and can add a lot of code your plugin won't use, which will increase the load time of your forms. As JQuery does not support the ShadowDOM, you may find building plugins with JQuery difficult.

We've chosen Lit as a good example of a lightweight framework that will help you build plugins easily. You do not need to use Lit: any framework that supports web components and ShadowDOM can be used to create plugins.

You can use Lit with JavaScript or with compilers such as Babel or TypeScript. Using compilers allows you to make use of decorators to add reactive properties, declarative event listeners, and queries.

Jump to:

Looking for examples? See our tutorials on some basic Example plugins.

Define a web component in Lit

Lit defines web components as custom HTML elements:

  • Components are defined as a class that extends LitElement.
  • Components can have reactive properties, which automatically trigger a re-rendering of the component when they are changed, such as the message property in the example. Custom fields in your plugin's contract automatically connect with reactive properties of the same name. See Create a custom configuration field.
  • Components can also have encapsulated styles, which control the component's appearance without impacting other elements, such as the highlight style in the example.
  • Each component has a render() function that uses Templates to define how the web component renders on the page.

Note: The render() function is automatically called to update the component in the DOM when any reactive property is changed. For best practice, avoid manipulating the DOM outside of the render() function. See Rendering in the Lit documentation.

import {LitElement, html} from 'lit';
@customElement('my-plugin')
export class MyPlugin extends LitElement {
  @property()
  message = 'Hello World';

  static styles = css`
    .highlight {
      font-weight: 700;
      color: #E8960A;
    }
  `;

  render() {
    return html`
      </div id="pluginDiv">
        </p>This is a template.</p>
        </p>This is my message ${this.message}.</p>
      </div>
	`;
  }
}
import {LitElement, html} from 'lit';
export class MyPlugin extends LitElement {
  static properties = {
    message: {},
  };

  static styles = css`
    .highlight {
      font-weight: 700;
      color: #E8960A;
  }
`;

  constructor() {
    super();
    this.message = 'Hello World';
  }

  render() {
    return html`
      </div id="pluginDiv">
        <p>This is a template.</p>
        <p>This is my message ${this.message}.</p>
      </div>
	`;
  }
}
customElements.define('my-plugin', MyPlugin);

Define the component's HTML with templates

Templates are JavaScript tagged template literals (also known as template strings) of HTML, parsed using Lit's html function. They're used in the component's render() function to define how your web component renders in the document.

  1. Define the content your web component should render. This can include:
  2. Enclose the content with backticks ` to create a template literal.
  3. Prefix the template string with the Lit function html to create a tagged template literal.
  4. When calling a function in a tagged template literal, there are no enclosing parentheses.

Compose with expressions and sub-templates

Templates can use expressions, such as the ${this.message} expression in the second <p> tag. The expression uses the message property of the component, and will be evaluated by Lit's html function.

Templates can also be composed of sub-templates, allowing you to decompose a larger template into reusable pieces. To use sub-templates:

  1. Create functions that return parts of your template using Lit's html function.
  2. In your render() method, use expressions to call the sub-template functions where required.

For more information, see Rendering in the Lit documentation.

titleTemplate() {
  return html`<header>${this.title}</header>`;
}

render() {
  return html`
    ${this.titleTemplate()}
    </div id="pluginDiv">
      <p>This is a template.</p>
      <p>This is my message ${this.message}.</p>
    </div>
  `;
}

Reference your components in the ShadowDOM

The ShadowDOM allows you to create and add nodes to the DOM without adversely impacting what's already there. ShadowDOMs create a second "hidden" DOM structure that is attached to the regular DOM tree. You can manipulate the nodes in the ShadowDOM the same as you would a normal DOM node, such as appending children or modifying attributes, but none of the code inside a ShadowDOM can affect elements that are in the regular DOM tree.

Form plugins require the use of the ShadowDOM. Your plugins use the ShadowDOM to encapsulate their structure, style, and functionality so that they won't affect or be affected by other form controls or elements on the page. For more information on using the ShadowDOM, see the Mozilla documentation.

Note: JQuery does not support the ShadowDOM. You may find it difficult to create form plugins using JQuery.

 

Web components can be selected and assigned to a property using the @query() decorator in Typescript or the querySelector() function in JavaScript.

For more information on accessing nodes in the ShadowDOM with Lit, see the Lit documentation.

@query('#pluginDiv')
myPlugin!: HTMLElement;
get myPlugin() {
  return this.renderRoot?.querySelector('#pluginDiv') ?? null;
}

Events

Web component event life-cycle

All web components dispatch the same sequence of events during their lifetime, known as the event life-cycle. Standard callback methods allow you to hook into these events.

Callback method Description

constructor()

Called when an element is created, or when an element that already exists in the DOM is upgraded to a custom element.

Use for one-time initialization tasks such as setting default values for properties.

connectedCallback()

Called when an element is added to the DOM.

Use for tasks that should occur when the element is added to the DOM, such as event listeners.

disconnectedCallback()

Called when an element is removed from the DOM.

Use for cleaning up anything that holds a reference to the element, such as event listeners.

attributedChangedCallback()

Called when one of the element's custom attributes is added, removed, or changed.

adoptedCallback()

Called when the element is moved to another document.

For more information on the event life-cycle, see the Mozilla.org Web Components documentation.

Lit reactive events

In addition to the standard web component events, Lit fires a sequence of events when updating an element.

Functions Description

hasChanged()

Schedules an update when a reactive property is changed. Its default behavior is to schedule an update if a strict equality check on the property returns true.

requestUpdate()

Schedules an update when called. Use this to request updates that are not related to property changes.

performUpdate()

Calls several functions in sequence in order to perform the update:

  1. shouldUpdate()
  2. willUpdate()
  3. update()
  4. firstUpdated()
  5. updated()

Most of the functions receive a changedProperties map, which maps the property names to their previous values.

shouldUpdate()

Determines whether an update cycle is required (returns true by default). Use this function to control which properties trigger an update.

willUpdate()

Calculates the new property values needed during the update. Use this function to calculate property values that depend on other properties and are used in the rest of the update process.

update()

Updates the component's DOM by calling the render() function.

Changes made to properties after this function completes will trigger a new update.

render()

Returns renderable HTML, typically using a Template. This function does not receive the changedProperties map, and typically references the component's properties.

Your web component should implement this function.

firstUpdated()

Called after the component has been updated for the first time. Use this function to perform one-time tasks after the component's DOM has been created.

Property changes made during this function will trigger a new update.

updated()

Called whenever the component's update has finished. Use this function to perform tasks that use the component's DOM after an update, such as calculations for an animation.

Property changes made during this function will trigger a new update.

updateComplete()

Returns a promise that resolves when the component's update cycle is complete. Use this function to wait for an update.

Property changes made during this function will trigger a new update.

For more information on Lit's update cycle and functions, see the Lit documentation.

Create custom events

Any DOM node in your web component can create and dispatch events. In Form plugins, custom events are used to tell Nintex Automation Cloud the plugin's value has changed and should be stored, or notify Form rules of other updates, such as a value validation.

See Store a value in an output variable.

Also see Events

  1. Create a custom event, specifying the event type and options.
    • Set the bubbles property to true to allow the event to propagate up the DOM tree.
    • Set the composed property to true to allow the event to propagate from the ShadowDOM to the standard DOM.
    • Add any additional information you want to send in the detail property.
  2. Dispatch the event you created using dispatchEvent().
const event = new CustomEvent('my-custom-event', {
  bubbles: true, 
  composed: true
  detail: "custom event data"
});
this.dispatchEvent(event);

For more information on how Lit handles event listening and dispatching, see the Lit documentation.

Further reading

For simple example plugins built with Lit, see:

For more information on Lit, see: