1// forms.js 2 3// HELPERS 4const resolveElement = (elOrSelector, root = document) => { 5 if (!elOrSelector) return null; 6 if (typeof elOrSelector === "string") { 7 return root.querySelector(elOrSelector); 8 } 9 return elOrSelector?.nodeType === 1 ? elOrSelector : null; 10}; 11 12const resolveForm = (formOrSelector) => { 13 const el = resolveElement(formOrSelector); 14 return el instanceof HTMLFormElement ? el : null; 15}; 16 17const addFormDataEntries = (formEl) => { 18 const fd = new FormData(formEl); 19 const obj = {}; 20 21 // Collect values; duplicate keys become arrays 22 for (const [key, value] of fd.entries()) { 23 if (key in obj) { 24 if (!Array.isArray(obj[key])) obj[key] = [obj[key]]; 25 obj[key].push(value); 26 } else { 27 obj[key] = value; 28 } 29 } 30 31 return obj; 32}; 33 34const normaliseCheckboxes = (formEl, obj) => { 35 const checkboxEls = [...formEl.querySelectorAll('input[type="checkbox"][name]')]; 36 if (checkboxEls.length === 0) return obj; 37 38 const counts = new Map(); 39 checkboxEls.forEach((cb) => counts.set(cb.name, (counts.get(cb.name) || 0) + 1)); 40 41 counts.forEach((count, name) => { 42 if (count > 1) { 43 // Group: ensure array shape, even if none/one selected 44 if (!(name in obj)) obj[name] = []; 45 else if (!Array.isArray(obj[name])) obj[name] = [obj[name]]; 46 return; 47 } 48 49 // Single checkbox: boolean 50 const input = formEl.querySelector( 51 `input[type="checkbox"][name="${CSS.escape(name)}"]` 52 ); 53 obj[name] = !!input?.checked; 54 }); 55 56 return obj; 57}; 58 59const normaliseSelectMultiple = (formEl, obj) => { 60 formEl.querySelectorAll("select[multiple][name]").forEach((sel) => { 61 const values = [...sel.options].filter((o) => o.selected).map((o) => o.value); 62 obj[sel.name] = values; 63 }); 64 65 return obj; 66}; 67 68// EXPORTS 69// formToObject: selector or <form> -> plain JS object 70export const formToObject = (formOrSelector) => { 71 const formEl = resolveForm(formOrSelector); 72 if (!formEl) return null; 73 74 const obj = addFormDataEntries(formEl); 75 76 normaliseCheckboxes(formEl, obj); 77 normaliseSelectMultiple(formEl, obj); 78 79 return obj; 80}; 81 82export const getCheckedValue = (name, root = document) => { 83 if (!name) return null; 84 return root.querySelector(`input[name="${CSS.escape(name)}"]:checked`)?.value ?? null; 85}; 86 87export const checkRadioByValue = (name, value, root = document) => { 88 if (!name) return false; 89 90 const radio = root.querySelector( 91 `input[type="radio"][name="${CSS.escape(name)}"][value="${CSS.escape(value)}"]` 92 ); 93 94 if (!radio) return false; 95 96 if (!radio.checked) { 97 radio.checked = true; 98 radio.dispatchEvent(new Event('change', { bubbles: true })); 99 } 100 101 return true; 102}; 103 104export const addAndSetHiddenInput = (containerOrSelector, name, value) => { 105 const containerEl = 106 typeof containerOrSelector === "string" 107 ? document.querySelector(containerOrSelector) 108 : containerOrSelector; 109 110 if (!containerEl) return false; 111 112 let inputEl = containerEl.querySelector( 113 `input[type="hidden"][name="${CSS.escape(name)}"]` 114 ); 115 116 if (!inputEl) { 117 inputEl = document.createElement("input"); 118 inputEl.type = "hidden"; 119 inputEl.name = name; 120 containerEl.appendChild(inputEl); 121 } 122 123 inputEl.value = value; 124 return true; 125};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.