UNPKG

@stimulus-library/mixins

Version:

A library of useful controllers for Stimulus

181 lines (180 loc) 6.39 kB
import { useMixin } from "./use_mixin"; import { getOtherRadiosInGroup, isElementCheckable, isHTMLInputElement, isHTMLSelectElement } from "@stimulus-library/utilities"; import { useEventListener } from "./use_event_listener"; const CACHE_ATTR_NAME = "data-detect-dirty-load-value"; export function useDirtyInputTracking(controller, element) { const setup = () => { cacheLoadValues(element); checkDirty(element); useEventListener(controller, element, ["input", "change"], () => checkDirty(element), { debounce: 10 }); }; const teardown = () => { }; useMixin(controller, setup, teardown); return { restore: () => restoreElementFromLoadValue(element), teardown, }; } export function useDirtyFormTracking(controller, form) { const teardowns = []; const restores = []; const setup = () => { form.querySelectorAll("input, select, textarea").forEach((element) => { const functions = useDirtyInputTracking(controller, element); teardowns.push(functions.teardown); restores.push(functions.restore); }); useEventListener(controller, form, "input-dirtied", () => { form.setAttribute("data-dirty", "true"); form.dispatchEvent(new CustomEvent("form-dirtied", { bubbles: true, cancelable: true, detail: { target: form, }, })); }); useEventListener(controller, form, "input-cleaned", () => { if (form.querySelectorAll("[data-dirty=\"true\"]").length === 0) { form.removeAttribute("data-dirty"); form.dispatchEvent(new CustomEvent("form-cleaned", { bubbles: true, cancelable: true, detail: { target: form, }, })); } }); }; const teardown = () => { teardowns.forEach((teardown) => teardown()); }; const restore = () => { restores.forEach((restore) => restore()); }; useMixin(controller, setup, teardown); return { restore, teardown, }; } function checkDirty(element) { var _a; if (isHTMLInputElement(element) && element.type == "radio") { getOtherRadiosInGroup(element).forEach((radio) => radio.removeAttribute("data-dirty")); } if (isElementDirty(element)) { element.setAttribute("data-dirty", "true"); (_a = element.form) === null || _a === void 0 ? void 0 : _a.setAttribute("data-dirty", "true"); element.dispatchEvent(new CustomEvent("input-dirtied", { bubbles: true, cancelable: true, detail: { target: element, }, })); } else { element.removeAttribute("data-dirty"); element.dispatchEvent(new CustomEvent("input-cleaned", { bubbles: true, cancelable: true, detail: { target: element, }, })); } } export function getElementValue(element) { if (isElementCheckable(element)) { return element.checked; } else if (isHTMLSelectElement(element) && element.multiple) { return JSON.stringify(getMultiSelectValues(element)); } else { return element.value; } } export function getMultiSelectLoadValues(element) { let options = Array.from(element.options); options = options.filter(option => option.defaultSelected); return options.map(option => option.value); } export function getMultiSelectValues(element) { let options = Array.from(element.selectedOptions); return options.map(option => option.value); } export function getElementLoadValue(element) { const value = element.getAttribute(CACHE_ATTR_NAME); if (isElementCheckable(element)) { return value == null ? element.defaultChecked : value == "true"; } else if (isHTMLSelectElement(element) && value == null) { const options = Array.from(element.options); if (element.multiple) { return JSON.stringify(getMultiSelectLoadValues(element)); } options.forEach((option) => { if (option.defaultSelected) { return option.value; } }); } else if (value !== null) { return value; } return value; } export function elementHasCachedLoadValue(element) { return element.hasAttribute(CACHE_ATTR_NAME); } export function isElementDirty(element) { return getElementValue(element) !== getElementLoadValue(element); } export function restoreElementFromLoadValue(element) { const cacheValue = element.getAttribute(CACHE_ATTR_NAME); if (isElementCheckable(element)) { element.setAttribute(CACHE_ATTR_NAME, element.checked.toString()); element.checked = cacheValue == null ? element.defaultChecked : cacheValue == "true"; } else if (isHTMLSelectElement(element)) { if (cacheValue == null) { const options = Array.from(element.options); options.forEach((option) => { if (option.defaultSelected) { option.selected = true; } }); } else { element.multiple ? restoreMultiSelect(element, cacheValue) : element.value = cacheValue; } } else { element.value = cacheValue == null ? element.defaultValue : cacheValue; } checkDirty(element); } export function restoreMultiSelect(element, cacheValue) { let selectedOptions = JSON.parse(cacheValue); Array.from(element.options).forEach((option) => option.selected = selectedOptions.includes(option.value)); } export function cacheLoadValues(element) { if (isElementCheckable(element)) { if (!elementHasCachedLoadValue(element)) { element.setAttribute(CACHE_ATTR_NAME, element.checked.toString()); } } else if (isHTMLSelectElement(element) && element.multiple) { element.setAttribute(CACHE_ATTR_NAME, JSON.stringify(getMultiSelectLoadValues(element))); } else { element.setAttribute(CACHE_ATTR_NAME, element.value.toString()); } } export function isDirty(element) { return element.hasAttribute("data-dirty"); }