UNPKG

@ideal-postcodes/jsutil

Version:

Browser Address Autocomplete for api.ideal-postcodes.co.uk

332 lines (331 loc) 10.7 kB
import { isInputElem, update, change, isSelect, isInput, hasValue, optionsHasText, } from "./input"; import { toElem, contains } from "./dom"; import { updateCountry, toCountry } from "./country"; import { isGbrAddress } from "./types"; import { isString } from "./string"; import { cssEscape } from "./util"; export const numberOfLines = (targets) => { const { line_2, line_3 } = targets; if (!line_2) return 1; if (!line_3) return 2; return 3; }; export const join = (list) => list .filter((e) => { if (isString(e)) return !!e.trim(); return !!e; }) .join(", "); export const charArrayLength = (arr) => arr.join(" ").trim().length; const truncate = (line, maxLength) => { if (line.length <= maxLength) return [line, ""]; const words = line.split(" "); let truncated = ""; let remaining = ""; for (let i = 0; i < words.length; i++) { const word = words[i]; if (truncated.length + word.length > maxLength) { remaining = words.slice(i).join(" "); break; } truncated += `${word} `; } return [truncated.trim(), remaining.trim()]; }; const prependLine = (remaining, nextLine) => { if (nextLine.length === 0) return remaining; return `${remaining}, ${nextLine}`; }; export const toMaxLengthLines = (l, options) => { const { lineCount, maxLineOne, maxLineTwo, maxLineThree } = options; const result = ["", "", ""]; const lines = [...l]; if (maxLineOne) { const [newLineOne, remaining] = truncate(lines[0], maxLineOne); result[0] = newLineOne; if (remaining) lines[1] = prependLine(remaining, lines[1]); if (lineCount === 1) return result; } else { result[0] = lines[0]; if (lineCount === 1) return [join(lines), "", ""]; } if (maxLineTwo) { const [newLineTwo, remaining] = truncate(lines[1], maxLineTwo); result[1] = newLineTwo; if (remaining) lines[2] = prependLine(remaining, lines[2]); if (lineCount === 2) return result; } else { result[1] = lines[1]; if (lineCount === 2) return [result[0], join(lines.slice(1)), ""]; } if (maxLineThree) { const [newLineThree, remaining] = truncate(lines[2], maxLineThree); result[2] = newLineThree; if (remaining) lines[3] = prependLine(remaining, lines[3]); } else { result[2] = lines[2]; } return result; }; export const toAddressLines = (lineCount, address, options) => { const { line_1, line_2 } = address; const line_3 = "line_3" in address ? address.line_3 : ""; if (options.maxLineOne || options.maxLineTwo || options.maxLineThree) return toMaxLengthLines([line_1, line_2, line_3], { lineCount, ...options, }); if (lineCount === 3) return [line_1, line_2, line_3]; if (lineCount === 2) return [line_1, join([line_2, line_3]), ""]; return [join([line_1, line_2, line_3]), "", ""]; }; export const extract = (a, attr) => { const result = a[attr]; if (typeof result === "number") return result.toString(); if (result === undefined) return ""; return result; }; export const getFields = (o) => ({ ...searchFields(o.outputFields || {}, o.config.scope), ...searchNames(o.names || {}, o.config.scope), ...searchLabels(o.labels || {}, o.config.scope), }); export const searchFields = (outputFields, scope) => { const fields = {}; let key; for (key in outputFields) { const value = outputFields[key]; if (value === undefined) continue; const field = toElem(value, scope); if (isInputElem(field)) fields[key] = field; } return fields; }; export const searchNames = (names, scope) => { const result = {}; let key; for (key in names) { if (!names.hasOwnProperty(key)) continue; const name = names[key]; const named = toElem(`[name="${name}"]`, scope); if (named) { result[key] = named; continue; } const ariaNamed = toElem(`[aria-name="${name}"]`, scope); if (ariaNamed) result[key] = ariaNamed; } return result; }; export const searchLabels = (labels, scope) => { const result = {}; if (labels === undefined) return labels; let key; for (key in labels) { if (!labels.hasOwnProperty(key)) continue; const name = labels[key]; if (!name) continue; const first = contains(scope, "label", name); const label = toElem(first, scope); if (!label) continue; const forEl = label.getAttribute("for"); if (forEl) { const byId = scope.querySelector(`#${cssEscape(forEl)}`); if (byId) { result[key] = byId; continue; } } const inner = label.querySelector("input"); if (inner) result[key] = inner; } return result; }; const skipFields = ["country", "country_iso_2", "country_iso"]; export const mutateAddress = (address, config) => { if (isGbrAddress(address)) { if (config.removeOrganisation) removeOrganisation(address); } const [line_1, line_2, line_3] = toAddressLines(config.lines || 3, address, config); address.line_1 = line_1; address.line_2 = line_2; if (isGbrAddress(address)) address.line_3 = line_3; return address; }; export const populateAddress = (options) => { const { config } = options; const fields = getFields(options); if (config.lines === undefined) config.lines = numberOfLines(fields); const address = mutateAddress({ ...options.address }, config); const { scope, populateCounty } = config; const skip = [...skipFields]; if (isGbrAddress(address)) { if (config.removeOrganisation) removeOrganisation(address); if (populateCounty === false) skip.push("county"); } updateCountry(toElem(fields.country || null, scope), address); const iso2Elem = toElem(fields.country_iso_2 || null, scope); if (isSelect(iso2Elem)) { if (hasValue(iso2Elem, address.country_iso_2)) { change({ e: iso2Elem, value: address.country_iso_2 }); } else { let text = optionsHasText(iso2Elem, address.country_iso_2); if (text.length > 0) { change({ e: iso2Elem, value: text[0].value || "" }); } else { text = optionsHasText(iso2Elem, toCountry(address)); if (text.length > 0) { change({ e: iso2Elem, value: text[0].value || "" }); } } } } if (isInput(iso2Elem)) { update(iso2Elem, address.country_iso_2 || ""); } const iso3Elem = toElem(fields.country_iso || null, scope); if (isSelect(iso3Elem)) { if (hasValue(iso3Elem, address.country_iso)) { change({ e: iso3Elem, value: address.country_iso }); } else { let text = optionsHasText(iso3Elem, address.country_iso); if (text.length > 0) { change({ e: iso3Elem, value: text[0].value || "" }); } else { text = optionsHasText(iso3Elem, toCountry(address)); if (text.length > 0) { change({ e: iso3Elem, value: text[0].value || "" }); } } } } if (isInput(iso3Elem)) update(iso3Elem, address.country_iso || ""); const countyIso = toElem(getCountyIsoSelector(fields), scope); const countyIsoValue = getCountyIso(address); const countyValue = getCounty(address); if (isSelect(countyIso)) { if (hasValue(countyIso, countyIsoValue)) { change({ e: countyIso, value: countyIsoValue }); } else if (hasValue(countyIso, countyValue || "")) { change({ e: countyIso, value: countyValue || "" }); } else { let text = optionsHasText(countyIso, countyValue); if (text.length > 0) { change({ e: countyIso, value: text[0].value || "" }); } else { text = optionsHasText(countyIso, countyIsoValue); if (text) change({ e: countyIso, value: text[0].value || "" }); } } } if (isInput(countyIso)) { update(countyIso, countyIsoValue); } let e; for (e in fields) { if (skip.includes(e)) continue; if (e.startsWith("native.")) { updateNative(e, fields, address, scope); continue; } if (address[e] === undefined) continue; if (fields.hasOwnProperty(e)) { const value = fields[e]; if (!value) continue; update(toElem(value, scope), extract(address, e)); } } }; const updateNative = (nativeAttr, fields, address, scope) => { const e = nativeAttr.replace("native.", ""); const native = address.native; if (native === undefined) return; const nativeValue = native[e]; if (nativeValue === undefined) return; if (fields.hasOwnProperty(nativeAttr)) { const selector = fields[nativeAttr]; if (!selector) return; update(toElem(selector, scope), extract(native, e)); } }; export const removeOrganisation = (address) => { if (address.organisation_name.length === 0) return address; if (address.line_2.length === 0 && address.line_3.length === 0) return address; if (address.line_1 === address.organisation_name) { address.line_1 = address.line_2; address.line_2 = address.line_3; address.line_3 = ""; } return address; }; const isUsaOutputFields = (a) => a.hasOwnProperty("state_abbreviation"); export const getCountyIsoSelector = (a) => { if (isUsaOutputFields(a)) return a.state_abbreviation || null; return a.county_code || null; }; export const getCountySelector = (a) => { if (isUsaOutputFields(a)) return a.state || null; return a.county || null; }; export const getCountyIso = (a) => { if (isGbrAddress(a)) return a.county_code; return a.state_abbreviation; }; export const getCounty = (a) => { if (isGbrAddress(a)) return a.county; return a.state; };