The Source Dataset Pattern for Custom Invoker Commands
How to use the 'Source Dataset Pattern' to pass arguments to invoker commands for html custom elements. Using the source property of the CommandEvent.
published
[Invoker Commands] are part of the growing trend of adding interactivity to HTML declaratively and reducing reliance on JavaScript. The Invoker Commands API allows you to assign behaviors to buttons, giving you control over interactive elements when the button is clicked or invoked via a keypress.
Using Invoker Commands with Web Components
Invoker commands are a great way to target actions in an html custom element (aka. web component) in an declarative manner.
Lets create a simple component that captures its content with snapdom and downloads it as a png file.
import { snapdom } from "https://unpkg.com/@zumer/snapdom/dist/snapdom.mjs";
class snapDomComponent extends HTMLElement {
connectedCallback() {
this.addEventListener("command", (event) => {
if (event.command === "--download") {
snapdom.download(this);
}
});
}
}
customElements.define("snap-dom", snapDomComponent);
Now we can Target the Capture and download of the Dom with an invoker command.
<snap-dom id="my-snap">
<div>Demo Content</div>
</snap-dom>
<button commandfor="my-snap" command="--download">download</button>
Arguments for a command
Invoker commands have no built-in way to pass arguments into a command. Something like command="--download('png')" will not work. We could brute force simply add multiple download commands for different kinds of files. Technically inventing our own DSL for our component as described in the invoker commands explainer.
<button commandfor="my-snap" command="--download-as-png">
download as png
</button>
<button commandfor="my-snap" command="--download-as-jpeg">
download as jpeg
</button>
<button commandfor="my-snap" command="--download-as-webp">
download as webp
</button>
The Source Dataset Pattern
But we have a much more elegant solution available to us. We can use one of the hidden super powers of the API: The source property of the CommandEvent that returns the Element that invoked the given command. This allows us to read any attributes from the button. Especially useful for our purposes is the dataset. So instead of writing an ever escalating list of commands, we simply add a data-attribute to our button.
<button commandfor="my-snap" command="--download" data-format="jpeg">
download as jpeg
</button>
Now we adapt our code to read the file format right from the button with event.source.dataset.format.
import { snapdom } from "https://unpkg.com/@zumer/snapdom/dist/snapdom.mjs";
class snapDomComponent extends HTMLElement {
connectedCallback() {
this.addEventListener("command", (event) => {
if (event.command === "--download") {
const format = event.source.dataset.format || "png";
snapdom.download(this, { format });
}
});
}
}
customElements.define("snap-dom", snapDomComponent);
This way we can pass any arbitrary amount of data to our command and access it by simply destructuring the dataset.
const { format = "png", filename = "snap-dom-download" } = event.source.dataset;
Check out the full example on codepen.
Leveraging the source property provides a clean, standards-based bridge between declarative markup and component logic.