Bug 1427950 - Implement a multi-line PaymentRequest shipping address picker. r?MattN
MozReview-Commit-ID: 1CfKosjulNV
--- a/toolkit/components/payments/res/components/address-option.js
+++ b/toolkit/components/payments/res/components/address-option.js
@@ -31,19 +31,17 @@ class AddressOption extends ObservedProp
"name",
"postal-code",
"street-address",
"tel",
]);
}
connectedCallback() {
- for (let child of this.children) {
- child.remove();
- }
+ this.innerHTML = "";
let fragment = document.createDocumentFragment();
RichOption._createElement(fragment, "name");
RichOption._createElement(fragment, "street-address");
RichOption._createElement(fragment, "email");
RichOption._createElement(fragment, "tel");
this.appendChild(fragment);
--- a/toolkit/components/payments/res/components/rich-option.js
+++ b/toolkit/components/payments/res/components/rich-option.js
@@ -57,28 +57,36 @@ class RichOption extends ObservedPropert
}
}
get selected() {
return this.hasAttribute("selected");
}
set selected(value) {
+ let changed = this.selected != value;
+
+ let select = this.closest("rich-select");
if (value) {
- let oldSelectedOptions = this.parentNode.querySelectorAll("[selected]");
- for (let option of oldSelectedOptions) {
- option.removeAttribute("selected");
+ if (select) {
+ let oldSelectedOptions = select.querySelectorAll(`[selected]:not([guid="${this.guid}"])`);
+ changed = changed || !!oldSelectedOptions.length;
+ for (let option of oldSelectedOptions) {
+ option.removeAttribute("selected");
+ }
}
this.setAttribute("selected", value);
} else {
this.removeAttribute("selected");
}
- let richSelect = this.closest("rich-select");
- if (richSelect && richSelect.render) {
- richSelect.render();
+
+ if (changed && select) {
+ let event = document.createEvent("UIEvent");
+ event.initEvent("change", true, true);
+ select.dispatchEvent(event);
}
return value;
}
static _createElement(fragment, className) {
let element = document.createElement("span");
element.classList.add(className);
fragment.appendChild(element);
--- a/toolkit/components/payments/res/components/rich-select.js
+++ b/toolkit/components/payments/res/components/rich-select.js
@@ -43,16 +43,24 @@ class RichSelect extends ObservedPropert
get popupBox() {
return this.querySelector(":scope > .rich-select-popup-box");
}
get selectedOption() {
return this.popupBox.querySelector(":scope > [selected]");
}
+ get selectedStateKey() {
+ return this.getAttribute("selected-state-key");
+ }
+
+ set selectedStateKey(val) {
+ this.setAttribute("selected-state-key", val);
+ }
+
namedItem(name) {
return this.popupBox.querySelector(`:scope > [name="${CSS.escape(name)}"]`);
}
handleEvent(event) {
switch (event.type) {
case "blur": {
this.onBlur(event);
@@ -139,16 +147,20 @@ class RichSelect extends ObservedPropert
/* eslint-enable max-len */
for (let option of options) {
popupBox.appendChild(option);
}
let selectedChild;
for (let child of popupBox.children) {
if (child.selected) {
+ // Only allow one child to be selected at a time.
+ if (selectedChild) {
+ selectedChild.selected = false;
+ }
selectedChild = child;
}
}
if (!selectedChild && popupBox.children.length) {
selectedChild = popupBox.children[0];
selectedChild.selected = true;
}
--- a/toolkit/components/payments/res/containers/address-picker.js
+++ b/toolkit/components/payments/res/containers/address-picker.js
@@ -11,39 +11,61 @@
* Container around <rich-select> (eventually providing add/edit links) with
* <address-option> listening to savedAddresses.
*/
class AddressPicker extends PaymentStateSubscriberMixin(HTMLElement) {
constructor() {
super();
this.dropdown = document.createElement("rich-select");
+ this.dropdown.picker = this;
+ this.dropdown.selectedStateKey = this.selectedStateKey;
+ this.addEventListener("change", this);
}
connectedCallback() {
this.appendChild(this.dropdown);
super.connectedCallback();
}
+ handleEvent(event) {
+ if (event.type == "change") {
+ let select = event.target;
+ let selectedKey = select.selectedStateKey;
+ if (selectedKey) {
+ let state = {};
+ state[selectedKey] = select.querySelector("[selected]").guid;
+ this.requestStore.setState(state);
+ }
+ }
+ }
+
render(state) {
let {savedAddresses} = state;
let desiredOptions = [];
for (let [guid, address] of Object.entries(savedAddresses)) {
let optionEl = this.dropdown.namedItem(guid);
if (!optionEl) {
optionEl = document.createElement("address-option");
optionEl.name = guid;
optionEl.guid = guid;
}
for (let [key, val] of Object.entries(address)) {
optionEl.setAttribute(key, val);
}
+ if (guid == state[this.selectedStateKey]) {
+ optionEl.setAttribute("selected", "true");
+ }
desiredOptions.push(optionEl);
}
let el = null;
while ((el = this.dropdown.popupBox.querySelector(":scope > address-option"))) {
el.remove();
}
this.dropdown.popupBox.append(...desiredOptions);
}
+
+ get selectedStateKey() {
+ return this.getAttribute("selected-state-key");
+ }
}
customElements.define("address-picker", AddressPicker);
--- a/toolkit/components/payments/res/mixins/PaymentStateSubscriberMixin.js
+++ b/toolkit/components/payments/res/mixins/PaymentStateSubscriberMixin.js
@@ -30,16 +30,17 @@ let requestStore = new PaymentsStore({
paymentOptions: {
requestPayerName: false,
requestPayerEmail: false,
requestPayerPhone: false,
requestShipping: false,
shippingType: "shipping",
},
},
+ selectedShippingAddress: null,
savedAddresses: {},
savedBasicCards: {},
});
/* exported PaymentStateSubscriberMixin */
/**
--- a/toolkit/components/payments/res/paymentRequest.xhtml
+++ b/toolkit/components/payments/res/paymentRequest.xhtml
@@ -31,17 +31,17 @@
<div id="host-name"></div>
<div id="total">
<h2 class="label"></h2>
<currency-amount></currency-amount>
</div>
<div><label>Shipping Address</label></div>
- <address-picker>
+ <address-picker selected-state-key="selectedShippingAddress">
</address-picker>
<div id="controls-container">
<button id="cancel">Cancel</button>
<button id="pay">Pay</button>
</div>
</template>
</head>
--- a/toolkit/components/payments/test/mochitest/test_address_picker.html
+++ b/toolkit/components/payments/test/mochitest/test_address_picker.html
@@ -19,17 +19,18 @@ Test the address-picker component
<script src="rich-option.js"></script>
<script src="address-option.js"></script>
<link rel="stylesheet" type="text/css" href="rich-select.css"/>
<link rel="stylesheet" type="text/css" href="address-option.css"/>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display">
- <address-picker id="picker1"></address-picker>
+ <address-picker id="picker1"
+ selected-state-key="selectedShippingAddress"></address-picker>
</p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<script type="application/javascript">
/** Test the address-picker component **/
@@ -109,16 +110,32 @@ add_task(async function test_update() {
is(options.length, 2, "Check dropdown still has both addresses");
ok(options[0].textContent.includes("MI-edit"), "Check updated first address-level1");
ok(options[0].textContent.includes("Some City-edit"), "Check updated first address-level2");
ok(options[0].textContent.includes("new-edit"), "Check updated first address");
ok(options[1].textContent.includes("P.O. Box 123"), "Check second address is the same");
});
+add_task(async function test_change_selected_address() {
+ let options = picker1.dropdown.popupBox.children;
+ let selectedOption = picker1.dropdown.popupBox.querySelector("[selected]");
+ is(selectedOption, options[0], "Selected option should default to the first option");
+ let {selectedShippingAddress} = picker1.requestStore.getState();
+ is(selectedShippingAddress, selectedOption.guid, "should should have first option selected");
+
+ options[1].selected = true;
+ await asyncElementRendered();
+
+ selectedOption = picker1.dropdown.popupBox.querySelector("[selected]");
+ is(selectedOption, options[1], "Selected option should now be the second option");
+ selectedShippingAddress = picker1.requestStore.getState().selectedShippingAddress;
+ is(selectedShippingAddress, selectedOption.guid, "store should have second option selected");
+});
+
add_task(async function test_delete() {
picker1.requestStore.setState({
savedAddresses: {
// 48bnds6854t was deleted
"68gjdh354j": {
"address-level1": "CA",
"address-level2": "Mountain View",
"country": "US",