Skip to content

Commit

Permalink
refactor(frontend): use custom element for add-another component
Browse files Browse the repository at this point in the history
  • Loading branch information
paulrobertlloyd committed Nov 4, 2023
1 parent 27cca75 commit 914c8fd
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 39 deletions.
7 changes: 1 addition & 6 deletions helpers/frontend/views/frontend-form.njk
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@
{{ input({
errorMessage: { text: "Add another input error" } if errors,
name: "add-another-input[0]",
label: "Add another input",
field: {
attributes: {
"data-add-another-target": "field"
}
}
label: "Add another input"
}) | indent(2) }}
{% endcall %}

Expand Down
44 changes: 27 additions & 17 deletions packages/frontend/components/add-another/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { Controller } from "@hotwired/stimulus";
import { wrapElement } from "../../lib/utils/wrap-element.js";

const focusableSelector = `button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]`;

export const AddAnotherController = class extends Controller {
static targets = ["addButton", "deleteButton", "field", "list", "item"];
export const AddAnotherComponent = class extends HTMLElement {
constructor() {
super();

initialize() {
this.addButtonTemplate = this.querySelector("#add-button");
this.deleteButtonTemplate = this.querySelector("#delete-button");
this.fields = this.querySelectorAll(".field");
this.list = this.querySelector(".add-another__list");
}

connectedCallback() {
this.setupItems();
this.updateItems();
this.createAddButton();
Expand All @@ -19,7 +25,7 @@ export const AddAnotherController = class extends Controller {
add(event) {
event.preventDefault();
const $newItem = this.createItem();
this.listTarget.append($newItem);
this.list.append($newItem);
this.updateItems();
$newItem.querySelector(focusableSelector).focus();
}
Expand All @@ -40,7 +46,7 @@ export const AddAnotherController = class extends Controller {
* @returns {HTMLLegendElement} - Group legend
*/
getHeading() {
return this.element.querySelector("legend");
return this.querySelector("legend");
}

/**
Expand All @@ -57,9 +63,12 @@ export const AddAnotherController = class extends Controller {
* Create add button
*/
createAddButton() {
const $addButton = this.addButtonTarget.content.cloneNode(true);
let $addButton = this.addButtonTemplate.content.cloneNode(true);

this.append($addButton);

this.element.append($addButton);
$addButton = this.querySelector(".add-another__add");
$addButton.addEventListener("click", (event) => this.add(event));
}

/**
Expand All @@ -68,7 +77,7 @@ export const AddAnotherController = class extends Controller {
* @returns {HTMLButtonElement} - Delete button
*/
getDeleteButton(element) {
return element.querySelector(".button--warning");
return element.querySelector(".add-another__delete");
}

/**
Expand All @@ -77,7 +86,7 @@ export const AddAnotherController = class extends Controller {
*/
createDeleteButton(element) {
const $deleteButton =
this.deleteButtonTarget.content.firstElementChild.cloneNode(true);
this.deleteButtonTemplate.content.firstElementChild.cloneNode(true);

element.append($deleteButton);
}
Expand All @@ -89,18 +98,18 @@ export const AddAnotherController = class extends Controller {
updateDeleteButton(element) {
const $deleteButton = this.getDeleteButton(element);
$deleteButton.setAttribute("aria-labelledby", `delete-title ${element.id}`);
$deleteButton.addEventListener("click", (event) => this.delete(event));
}

/**
* Take existing form fields and warp in list item
*/
setupItems() {
for (const field of this.fieldTargets.values()) {
for (const $field of this.fields) {
const $item = document.createElement("li");
$item.classList.add("add-another__list-item");
$item.dataset.addAnotherTarget = "item";

wrapElement(field, $item);
wrapElement($field, $item);
}
}

Expand All @@ -109,7 +118,8 @@ export const AddAnotherController = class extends Controller {
* @returns {HTMLLIElement} - List item containing form field(s)
*/
createItem() {
const $item = this.itemTargets.at(0).cloneNode(true);
const $items = this.querySelectorAll(".add-another__list-item");
const $item = $items[0].cloneNode(true);
const uid = Date.now().toString();

const $fields = $item.querySelectorAll("input, select, textarea");
Expand All @@ -125,7 +135,7 @@ export const AddAnotherController = class extends Controller {
$label.setAttribute("for", forAttribute.replace("-0", `-${uid}`));
}

$item.id = `${this.element.id}-${uid}`;
$item.id = `${this.id}-${uid}`;

return $item;
}
Expand All @@ -137,10 +147,10 @@ export const AddAnotherController = class extends Controller {
* - Add remove buttons (or remove if only one item remaining in list)
*/
updateItems() {
const $items = this.itemTargets;
const $items = this.querySelectorAll(".add-another__list-item");

for (const [index, $item] of $items.entries()) {
$item.id = $item.id || `${this.element.id}-${index}`;
$item.id = $item.id || `${this.id}-${index}`;
$item.setAttribute("aria-label", `Item ${index + 1}`);

// If no delete button, add one (if more than 1 item in list)
Expand Down
3 changes: 2 additions & 1 deletion packages/frontend/components/add-another/styles.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[data-controller="add-another"] {
add-another {
--counter-size: 1.66667em;
display: block;
}

.add-another__list {
Expand Down
20 changes: 7 additions & 13 deletions packages/frontend/components/add-another/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
{%- set hasFieldset = true if opts.fieldset else false %}
{# Capture the HTML so we can optionally nest it within a fieldset #}
{%- set innerHtml %}
<ol class="add-another__list" role="list" data-add-another-target="list">
<ol class="add-another__list" role="list">
{{ caller() if caller }}
</ol>
{% endset %}
{% call field({
element: "add-another",
attributes: {
id: id,
"data-controller": "add-another"
id: id
},
errorMessage: opts.errorMessage
}) %}
Expand All @@ -28,23 +28,17 @@
{% else %}
{{ innerHtml | trim | safe }}
{% endif %}
<template data-add-another-target="addButton">
<template id="add-button">
{{ button({
classes: "add-another__add button--secondary",
text: __("addAnother.add", opts.name),
attributes: {
"data-action": "add-another#add"
}
text: __("addAnother.add", opts.name)
}) | indent(4) }}
</template>
<template data-add-another-target="deleteButton">
<template id="delete-button">
{{ button({
classes: "add-another__delete button--warning",
icon: "delete",
iconText: __("addAnother.delete"),
attributes: {
"data-action": "add-another#delete"
}
iconText: __("addAnother.delete")
}) | indent(4) }}
</template>
{% endcall %}
4 changes: 2 additions & 2 deletions packages/frontend/scripts/app.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* global Stimulus */
import { Application } from "@hotwired/stimulus";
import { AddAnotherController } from "../components/add-another/index.js";
import { AddAnotherComponent } from "../components/add-another/index.js";
import { CheckboxesController } from "../components/checkboxes/index.js";
import { ErrorSummaryController } from "../components/error-summary/index.js";
import { GeoInputController } from "../components/geo-input/index.js";
Expand All @@ -11,7 +11,7 @@ import { TagInputController } from "../components/tag-input/index.js";
import { TextareaController } from "../components/textarea/index.js";

window.Stimulus = Application.start();
Stimulus.register("add-another", AddAnotherController);
customElements.define("add-another", AddAnotherComponent);
Stimulus.register("checkboxes", CheckboxesController);
Stimulus.register("error-summary", ErrorSummaryController);
Stimulus.register("geo-input", GeoInputController);
Expand Down

0 comments on commit 914c8fd

Please sign in to comment.