201 lines
5.7 KiB
JavaScript
201 lines
5.7 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import AddressOption from "../components/address-option.js";
|
|
import PaymentStateSubscriberMixin from "../mixins/PaymentStateSubscriberMixin.js";
|
|
import RichSelect from "../components/rich-select.js";
|
|
import paymentRequest from "../paymentRequest.js";
|
|
|
|
/**
|
|
* <address-picker></address-picker>
|
|
* Container around add/edit links and <rich-select> with
|
|
* <address-option> listening to savedAddresses & tempAddresses.
|
|
*/
|
|
|
|
export default class AddressPicker extends PaymentStateSubscriberMixin(HTMLElement) {
|
|
static get observedAttributes() {
|
|
return ["address-fields"];
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
this.dropdown = new RichSelect();
|
|
this.dropdown.addEventListener("change", this);
|
|
this.addLink = document.createElement("a");
|
|
this.addLink.className = "add-link";
|
|
this.addLink.href = "javascript:void(0)";
|
|
this.addLink.textContent = this.dataset.addLinkLabel;
|
|
this.addLink.addEventListener("click", this);
|
|
this.editLink = document.createElement("a");
|
|
this.editLink.className = "edit-link";
|
|
this.editLink.href = "javascript:void(0)";
|
|
this.editLink.textContent = this.dataset.editLinkLabel;
|
|
this.editLink.addEventListener("click", this);
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.appendChild(this.dropdown);
|
|
this.appendChild(this.addLink);
|
|
this.append(" ");
|
|
this.appendChild(this.editLink);
|
|
super.connectedCallback();
|
|
}
|
|
|
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
if (oldValue !== newValue) {
|
|
this.render(this.requestStore.getState());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* De-dupe and filter addresses for the given set of fields that will be visible
|
|
*
|
|
* @param {object} addresses
|
|
* @param {array?} fieldNames - optional list of field names that be used when
|
|
* de-duping and excluding entries
|
|
* @returns {object} filtered copy of given addresses
|
|
*/
|
|
filterAddresses(addresses, fieldNames = [
|
|
"address-level1",
|
|
"address-level2",
|
|
"country",
|
|
"name",
|
|
"postal-code",
|
|
"street-address",
|
|
]) {
|
|
let uniques = new Set();
|
|
let result = {};
|
|
for (let [guid, address] of Object.entries(addresses)) {
|
|
let addressCopy = {};
|
|
let isMatch = false;
|
|
// exclude addresses that are missing all of the requested fields
|
|
for (let name of fieldNames) {
|
|
if (address[name]) {
|
|
isMatch = true;
|
|
addressCopy[name] = address[name];
|
|
}
|
|
}
|
|
if (isMatch) {
|
|
let key = JSON.stringify(addressCopy);
|
|
// exclude duplicated addresses
|
|
if (!uniques.has(key)) {
|
|
uniques.add(key);
|
|
result[guid] = address;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
render(state) {
|
|
let addresses = paymentRequest.getAddresses(state);
|
|
let desiredOptions = [];
|
|
let fieldNames;
|
|
if (this.hasAttribute("address-fields")) {
|
|
let names = this.getAttribute("address-fields").split(/\s+/);
|
|
if (names.length) {
|
|
fieldNames = names;
|
|
}
|
|
}
|
|
let filteredAddresses = this.filterAddresses(addresses, fieldNames);
|
|
|
|
for (let [guid, address] of Object.entries(filteredAddresses)) {
|
|
let optionEl = this.dropdown.getOptionByValue(guid);
|
|
if (!optionEl) {
|
|
optionEl = new AddressOption();
|
|
optionEl.value = guid;
|
|
}
|
|
|
|
for (let key of AddressOption.recordAttributes) {
|
|
let val = address[key];
|
|
if (val) {
|
|
optionEl.setAttribute(key, val);
|
|
} else {
|
|
optionEl.removeAttribute(key);
|
|
}
|
|
}
|
|
|
|
desiredOptions.push(optionEl);
|
|
}
|
|
|
|
let el = null;
|
|
while ((el = this.dropdown.popupBox.querySelector(":scope > address-option"))) {
|
|
el.remove();
|
|
}
|
|
for (let option of desiredOptions) {
|
|
this.dropdown.popupBox.appendChild(option);
|
|
}
|
|
|
|
// Update selectedness after the options are updated
|
|
let selectedAddressGUID = state[this.selectedStateKey];
|
|
let optionWithGUID = this.dropdown.getOptionByValue(selectedAddressGUID);
|
|
this.dropdown.selectedOption = optionWithGUID;
|
|
|
|
if (selectedAddressGUID && !optionWithGUID) {
|
|
throw new Error(`${this.selectedStateKey} option ${selectedAddressGUID}` +
|
|
`does not exist in options`);
|
|
}
|
|
}
|
|
|
|
get selectedStateKey() {
|
|
return this.getAttribute("selected-state-key");
|
|
}
|
|
|
|
handleEvent(event) {
|
|
switch (event.type) {
|
|
case "change": {
|
|
this.onChange(event);
|
|
break;
|
|
}
|
|
case "click": {
|
|
this.onClick(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
onChange(event) {
|
|
let select = event.target;
|
|
let selectedKey = this.selectedStateKey;
|
|
if (selectedKey) {
|
|
this.requestStore.setState({
|
|
[selectedKey]: select.selectedOption && select.selectedOption.guid,
|
|
});
|
|
}
|
|
}
|
|
|
|
onClick({target}) {
|
|
let nextState = {
|
|
page: {
|
|
id: "address-page",
|
|
},
|
|
"address-page": {
|
|
addressFields: this.getAttribute("address-fields"),
|
|
selectedStateKey: this.selectedStateKey,
|
|
},
|
|
};
|
|
|
|
switch (target) {
|
|
case this.addLink: {
|
|
nextState["address-page"].guid = null;
|
|
nextState["address-page"].title = this.dataset.addAddressTitle;
|
|
break;
|
|
}
|
|
case this.editLink: {
|
|
let state = this.requestStore.getState();
|
|
let selectedAddressGUID = state[this.selectedStateKey];
|
|
nextState["address-page"].guid = selectedAddressGUID;
|
|
nextState["address-page"].title = this.dataset.editAddressTitle;
|
|
break;
|
|
}
|
|
default: {
|
|
throw new Error("Unexpected onClick");
|
|
}
|
|
}
|
|
|
|
this.requestStore.setState(nextState);
|
|
}
|
|
}
|
|
|
|
customElements.define("address-picker", AddressPicker);
|