@ideal-postcodes/jsutil
Version:
Browser Address Autocomplete for api.ideal-postcodes.co.uk
332 lines (331 loc) • 10.7 kB
JavaScript
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;
};