Skip to content

Latest commit

 

History

History
283 lines (204 loc) · 7.52 KB

File metadata and controls

283 lines (204 loc) · 7.52 KB

Dominator

Better ergonomics for working with the DOM and TypeScript.

See documentation if you want to get right to the API. For a high-level overview of the library, keep reading!

Examples

Let's face it, working with the DOM API kind of sucks (especially with TypeScript). This library aims to make it a little less painful.

The following examples compare the use of DOM APIs with this library.

Creating Elements

Instead of creating an Element with the DOM APIs:

const div = document.createElement("div");
div.id = "example";
div.ariaLabel = "Hello!";

You can use the createElement function:

import { createElement } from "@laserware/dominator";

const div = createElement("div", { id: "example", ariaLabel: "Hello!" });

Finding Elements

Finding a single element or multiple elements in the DOM isn't that bad.

Assuming you have this HTML:

<div role="grid" aria-colcount="6">
  <div role="rowgroup">
    <div role="row">
      <div role="columnheader" aria-colindex="1">First name</div>
      <div role="columnheader" aria-colindex="2">Last name</div>
      <div role="columnheader" aria-colindex="5">City</div>
      <div role="columnheader" aria-colindex="6">Zip</div>
    </div>
  </div>
  <div role="rowgroup">
    <div role="row">
      <div role="gridcell" aria-colindex="1">Debra</div>
      <div role="gridcell" aria-colindex="2">Burks</div>
      <div role="gridcell" aria-colindex="5">New York</div>
      <div role="gridcell" aria-colindex="6">14127</div>
    </div>
  </div></div>

Here's a couple of ways you could find element(s) using the DOM APIs:

// Single element:
const firstHeaderRow = document.querySelector(`[role="row"] [role="columnheader"]:first-child`);

// Multiple elements in document:
const allGridCells = document.querySelectorAll(`[role="gridcell"]`);

// Finding all children in parent element:
const grid = document.querySelector("grid");
const gridRows = grid.querySelectorAll(`[role="row"]`);

But there are some issues. For one, document.querySelectorAll returns a NodeList, which are annoying to work with.

You can use findElement and findAllElements instead:

import { findElement, findAllElements } from "@laserware/dominator";

const firstHeaderColumn = findElement(
  `[role="row"] [role="columnheader"]:first-child`,
);

// Returns the grid cell elements as an array:
const allGridCells = findAllElements<"div">(`[role="gridcell"]`);

// You can use string selectors for the target and parent to get children:
const gridRows = findAllElements(`[role="row"]`, "grid");

Setting Attributes

Setting attributes with the DOM APIs has some drawbacks:

  1. You need to call setAttribute for each attribute you want to set.
  2. You have to stringify each value before setting it.
const div = document.createElement("div");

div.setAttribute("role", "gridcell");

// Need to convert boolean to a string:
div.setAttribute("aria-disabled", "true");

// And do the same for numbers:
div.setAttribute("aria-colindex", "1");

You can use setAttribute or setAttributes instead. Both functions return the element:

import { 
  createElement,
  setAttribute,
  setAttributes, 
} from "@laserware/dominator";

let div = createElement("div");

// Set one attribute:
div = setAttribute(div, "role", "gridcell");

// Set multiple attributes:
div = setAttributes(div, {
  // You can just use a boolean, no need to stringify:
  "aria-disabled": true,
  // Same goes for numbers:
  "aria-colindex": 1,
});

Removing Attributes

Removing attributes using the DOM APIs requires that you remove each attribute individually.

Assuming you have this HTML:

<div id="example" role="gridcell" aria-disabled="true">Example</div>

Instead of using the element.removeAttribute API:

const div = document.getElementById("example");

div.removeAttribute("role");
div.removeAttribute("aria-disabled");

You can use removeAttribute or removeAttributes. Both functions return the element:

import {
  findElement,
  removeAttribute,
  removeAttributes,
} from "@laserware/dominator";

let div = findElement("#example")!;

div = removeAttribute(div, "role");

div = removeAttributes(div, ["aria-disabled", "aria-colindex"]);

Checking for Attributes

If you want to check for the existence of attributes using the DOM API, you'd use element.hasAttribute.

If you want to check if a value matches, you're stuck with the annoyances of getAttribute. So you're back to dealing with strings.

Assuming you have this HTML:

<div id="example" role="gridcell" aria-disabled="true" aria-colindex="1">Example</div>

Here's how you would check for attributes with the DOM APIs:

const div = document.getElementById("example");

const hasRole = div.hasAttribute("role");

const index = 1;
const isCol = div.getAttribute("aria-colindex") === index.toString();

You can use hasAttribute, hasAllAttributes, and hasSomeAttributes instead:

import { 
  findElement,
  hasAttribute,
  hasAllAttributes,
  hasSomeAttributes,
} from "@laserware/dominator";

const div = findElement("#example");

const hasRole = hasAttribute(div, "role");

// Check if any of the attributes are present:
const someArePresent = hasSomeAttributes(div, ["aria-colindex"]);

// Check if any of the attributes names and values match:
const someMatchValues = hasSomeAttributes(div, { "aria-colindex": 1 });

// Check if all of the attributes match, you can use `null` to
// check for the _existence_ of an attribute only:
const allMatch = hasAllAttributes(div, { 
  "aria-colindex": 1, 
  "aria-disabled": null,
});

Selecting Elements with Attributes

Building a CSS selector to find something based on attributes requires a lot of manual labor.

Assuming you have this HTML:

<div role="grid" aria-colcount="6">
  <div role="rowgroup">
    <div role="row">
      <div role="columnheader" aria-colindex="1">First name</div>
      <div role="columnheader" aria-colindex="2">Last name</div>
      <div role="columnheader" aria-colindex="5">City</div>
      <div role="columnheader" aria-colindex="6">Zip</div>
    </div>
  </div>
  <div role="rowgroup">
    <div role="row">
      <div role="gridcell" aria-colindex="1">Debra</div>
      <div role="gridcell" aria-colindex="2">Burks</div>
      <div role="gridcell" aria-colindex="5">New York</div>
      <div role="gridcell" aria-colindex="6">14127</div>
    </div>
  </div></div>

You'll need to write the selectors yourself:

const firstHeaderColumn = findElement(`[role="row"] [role="columnheader"]`);

const secondGridCell = findElement(`[role="gridcell"][aria-colindex="2"]`);

You can use selectAttribute and selectAttributes instead:

import { 
  findElement,
  selectAttribute,
  selectAttributes,
} from "@laserware/dominator";

const firstHeaderSelector = [
  selectAttribute("role", "row"),
  selectAttribute("role", "columnheader"),
].join(" ");

const firstHeaderColumn = findElement(firstHeaderSelector);

const secondGridCellSelector = selectAttributes({
  role: "gridcell",
  // Note that we're using a number:
  "aria-colindex": 2,
});

const secondGridCell = findElement(secondGridCellSelector);

You can do more than work with attributes. You can also set, get, remove, and select dataset entries, CSS variables, and styles.