diff --git a/_packs/controllers/cart_contact_controller.js b/_packs/controllers/cart_contact_controller.js index 8eae1fd..2ee4c9f 100644 --- a/_packs/controllers/cart_contact_controller.js +++ b/_packs/controllers/cart_contact_controller.js @@ -4,21 +4,29 @@ export default class extends CartBaseController { static targets = ["form", "username"]; connect() { + this.focusout_event = this._focusout_event.bind(this); + if (!this.hasUsernameTarget) return; if (!this.hasFormTarget) return; - this.formTarget.addEventListener("focusout", (event) => { - if (!this.formTarget.checkValidity()) { - this.formTarget.classList.add("was-validated"); - return; - } + this.formTarget.addEventListener("focusout", this.focusout_event); + } - this.formTarget.classList.remove("was-validated"); + disconnect() { + this.formTarget.removeEventListener("focusout", this.focusout_event); + } - const username = this.usernameTarget.value.trim(); - if (username.length === 0) return; + _focusout_event(event) { + if (!this.formTarget.checkValidity()) { + this.formTarget.classList.add("was-validated"); + return; + } - this.email = username; - }); + this.formTarget.classList.remove("was-validated"); + + const username = this.usernameTarget.value.trim(); + if (username.length === 0) return; + + this.email = username; } } diff --git a/_packs/controllers/cart_controller.js b/_packs/controllers/cart_controller.js index b83a150..e23115e 100644 --- a/_packs/controllers/cart_controller.js +++ b/_packs/controllers/cart_controller.js @@ -22,53 +22,61 @@ export default class extends CartBaseController { connect() { if (!this.hasQuantityTarget) return; + this.change_event = this._change_event.bind(this); + /* * When the quantity selector changes, we update the order to have * that amount of items. * * TODO: Go back to previous amount if there's not enough. */ - this.quantityTarget.addEventListener("change", async (event) => { - const quantity = event.target.value; + this.quantityTarget.addEventListener("change", this.change_event); + } - if (quantity < 1) return; + disconnect() { + this.quantityTarget.removeEventListener("change", this.change_event); + } - const orderToken = await this.tokenGetOrCreate(); - const product = this.product; + async _change_event(event) { + const quantity = event.target.value; - if (!product) return; + if (quantity < 1) return; - event.target.disabled = true; + const orderToken = await this.tokenGetOrCreate(); + const product = this.product; - const response = await this.spree.cart.setQuantity( - { orderToken }, - { - line_item_id: product.line_item.id, - quantity, - include: "line_items", - } - ); + if (!product) return; - event.target.disabled = false; - event.target.focus(); + event.target.disabled = true; - // If we're failing here it could be due to a missing order, so we - // ask the user to decide what they want to do about it - if (response.isFail()) { - this.handleFailure(response); - return; + const response = await this.spree.cart.setQuantity( + { orderToken }, + { + line_item_id: product.line_item.id, + quantity, + include: "line_items", } + ); - this.cart = response; - this.subtotalUpdate(); - this.counterUpdate(); - await this.itemStore(); + event.target.disabled = false; + event.target.focus(); - if (!this.hasSubtotalTarget) return; + // If we're failing here it could be due to a missing order, so we + // ask the user to decide what they want to do about it + if (response.isFail()) { + this.handleFailure(response); + return; + } - this.subtotalTarget.innerText = - product.line_item.attributes.discounted_amount; - }); + this.cart = response; + this.subtotalUpdate(); + this.counterUpdate(); + await this.itemStore(); + + if (!this.hasSubtotalTarget) return; + + this.subtotalTarget.innerText = + product.line_item.attributes.discounted_amount; } subtotalUpdate() { diff --git a/_packs/controllers/cart_counter_controller.js b/_packs/controllers/cart_counter_controller.js index 0daf064..94d30d3 100644 --- a/_packs/controllers/cart_counter_controller.js +++ b/_packs/controllers/cart_counter_controller.js @@ -9,20 +9,31 @@ export default class extends CartBaseController { return; } - window.addEventListener( - "cart:counter", - (event) => (this.counter = event.detail.item_count) - ); - window.addEventListener("storage", (event) => { - if (event.key == "cart:counter") this.counter = event.newValue; - }); + this.cart_count_event = this._cart_count_event.bind(this); + this.storage_event = this._storage_event.bind(this); + + window.addEventListener("cart:counter", this.cart_count_event); + window.addEventListener("storage", this.storage_event); if (!this.cart) return; this.counter = this.cart.data.attributes.item_count; } + disconnect() { + window.removeEventListener("cart:counter", this.cart_count_event); + window.removeEventListener("storage", this.storage_event); + } + set counter(quantity) { this.counterTarget.innerText = quantity; } + + _cart_count_event(event) { + this.counter = event.detail.item_count; + } + + _storage_event(event) { + if (event.key == "cart:counter") this.counter = event.newValue; + } } diff --git a/_packs/controllers/cart_coupon_controller.js b/_packs/controllers/cart_coupon_controller.js index e51f63a..dcf12f6 100644 --- a/_packs/controllers/cart_coupon_controller.js +++ b/_packs/controllers/cart_coupon_controller.js @@ -7,10 +7,18 @@ export default class extends CartBaseController { static targets = ["couponCodeInvalid", "preDiscount", "total"]; connect() { - this.couponCode.addEventListener("input", (event) => { - this.couponCode.parentElement.classList.remove("was-validated"); - this.couponCode.setCustomValidity(""); - }); + this.input_event = this._input_event.bind(this); + + this.couponCode.addEventListener("input", this.input_event); + } + + disconnect() { + this.couponCode.removeEventListener("input", this.input_event); + } + + _input_event(event) { + this.couponCode.parentElement.classList.remove("was-validated"); + this.couponCode.setCustomValidity(""); } get couponCode() { diff --git a/_packs/controllers/cart_payment_methods_controller.js b/_packs/controllers/cart_payment_methods_controller.js index a294639..c9ed0e9 100644 --- a/_packs/controllers/cart_payment_methods_controller.js +++ b/_packs/controllers/cart_payment_methods_controller.js @@ -10,6 +10,8 @@ export default class extends CartBaseController { const orderToken = this.token; const response = await this.spree.checkout.paymentMethods({ orderToken }); + this.change_event = this._change_event.bind(this); + if (response.isFail()) { this.handleFailure(response); return; @@ -31,10 +33,22 @@ export default class extends CartBaseController { if (!this.hasSubmitTarget) return; this.formTarget.elements.forEach((p) => - p.addEventListener("change", (e) => (this.submitTarget.disabled = false)) + p.addEventListener("change", this.change_event) ); } + disconnect() { + if (!this.hasSubmitTarget) return; + + this.formTarget.elements.forEach((p) => + p.removeEventListener("change", this.change_event) + ); + } + + _change_event(event) { + this.submitTarget.disabled = false; + } + async pay(event) { event.preventDefault(); event.stopPropagation(); diff --git a/_packs/controllers/cart_shipping_controller.js b/_packs/controllers/cart_shipping_controller.js index 403b211..593d7ee 100644 --- a/_packs/controllers/cart_shipping_controller.js +++ b/_packs/controllers/cart_shipping_controller.js @@ -4,9 +4,17 @@ export default class extends CartBaseController { static targets = ["methods", "rates", "form"]; connect() { - this.formTarget.addEventListener("formdata", (event) => - this.processShippingAddress(event.formData) - ); + this.formdata_event = this._formdata_event.bind(this); + + this.formTarget.addEventListener("formdata", this.formdata_event); + } + + disconnect() { + this.formTarget.removeEventListener("formdata", this.formdata_event); + } + + _formdata_event(event) { + this.processShippingAddress(event.formData); } async rates(event) { diff --git a/_packs/controllers/country_controller.js b/_packs/controllers/country_controller.js index 3b89fff..f1adccb 100644 --- a/_packs/controllers/country_controller.js +++ b/_packs/controllers/country_controller.js @@ -23,42 +23,18 @@ export default class extends CartBaseController { this.listTarget.appendChild(option); }); - const site = window.site; + this.input_event = this._input_event.bind(this); + this.invalid_event = this._invalid_event.bind(this); + this.change_event = this._change_event.bind(this); // Only allow names on this list this.nameTarget.pattern = countries.map((x) => x.attributes.name).join("|"); - this.nameTarget.addEventListener("input", (event) => - this.nameTarget.setCustomValidity("") - ); - this.nameTarget.addEventListener("invalid", (event) => - this.nameTarget.setCustomValidity(site.i18n.countries.validation) - ); + this.nameTarget.addEventListener("input", this.input_event); + this.nameTarget.addEventListener("invalid", this.invalid_event); // When the input changes we update the actual value and also the // state list via an Event - this.nameTarget.addEventListener("change", (event) => { - const value = this.nameTarget.value.trim(); - - if (value === "") return; - - const options = Array.from(this.nameTarget.list.options); - const option = options.find((x) => x.value == value); - - // TODO: If no option is found, mark the field as invalid - if (!option) return; - - this.idTarget.value = option.dataset.id; - this.isoTarget.value = option.dataset.iso; - - this.idTarget.dispatchEvent(new Event("change")); - this.isoTarget.dispatchEvent(new Event("change")); - - this.dispatchChangedEvent(option.dataset); - - // XXX: Prevent mixing data - delete this.nameTarget.dataset.selectedState; - delete this.nameTarget.dataset.selectedZipcode; - }); + this.nameTarget.addEventListener("change", this.change_event); // The input is disabled at this point this.nameTarget.disabled = false; @@ -67,6 +43,45 @@ export default class extends CartBaseController { this.nameTarget.dispatchEvent(new CustomEvent("change")); } + disconnect() { + this.nameTarget.removeEventListener("input", this.input_event); + this.nameTarget.removeEventListener("invalid", this.invalid_event); + this.nameTarget.removeEventListener("change", this.change_event); + } + + _input_event(event) { + this.nameTarget.setCustomValidity(""); + } + + _invalid_event(event) { + const site = window.site; + this.nameTarget.setCustomValidity(site.i18n.countries.validation); + } + + _change_event(event) { + const value = this.nameTarget.value.trim(); + + if (value === "") return; + + const options = Array.from(this.nameTarget.list.options); + const option = options.find((x) => x.value == value); + + // TODO: If no option is found, mark the field as invalid + if (!option) return; + + this.idTarget.value = option.dataset.id; + this.isoTarget.value = option.dataset.iso; + + this.idTarget.dispatchEvent(new Event("change")); + this.isoTarget.dispatchEvent(new Event("change")); + + this.dispatchChangedEvent(option.dataset); + + // XXX: Prevent mixing data + delete this.nameTarget.dataset.selectedState; + delete this.nameTarget.dataset.selectedZipcode; + } + /* * Sends a `cart:country:update` event so other controllers can * subscribe to changes. diff --git a/_packs/controllers/floating_alert_controller.js b/_packs/controllers/floating_alert_controller.js index c00ee8c..71dd9aa 100644 --- a/_packs/controllers/floating_alert_controller.js +++ b/_packs/controllers/floating_alert_controller.js @@ -8,18 +8,26 @@ export default class extends Controller { static targets = ["content"]; connect() { - window.addEventListener("toast", (event) => { - this.contentTarget.innerText = event.detail.content; - this.set(true); + this.toast_event = this._toast_event.bind(this); - if (this.interval) { - clearTimeout(this.interval); - } - this.interval = setTimeout(() => { - this.set(false); - this.interval = null; - }, 3000); - }); + window.addEventListener("toast", this.toast_event); + } + + disconnect() { + window.removeEventListener("toast", this.toast_event); + } + + _toast_event(event) { + this.contentTarget.innerText = event.detail.content; + this.set(true); + + if (this.interval) { + clearTimeout(this.interval); + } + this.interval = setTimeout(() => { + this.set(false); + this.interval = null; + }, 3000); } set(show) { diff --git a/_packs/controllers/menu_controller.js b/_packs/controllers/menu_controller.js index c05585d..2082fe5 100644 --- a/_packs/controllers/menu_controller.js +++ b/_packs/controllers/menu_controller.js @@ -4,9 +4,17 @@ export default class extends Controller { static targets = ["item"]; connect() { - window.addEventListener("scroll:section", (event) => - this.update(event.detail.id) - ); + this.scroll_section_event = this._scroll_section_event.bind(this); + + window.addEventListener("scroll:section", this.scroll_section_event); + } + + disconnect() { + window.removeEventListener("scroll:section", this.scroll_section_event); + } + + _scroll_section_event(event) { + this.update(event.detail.id); } get items() { diff --git a/_packs/controllers/order_controller.js b/_packs/controllers/order_controller.js index 7bedf71..5091a00 100644 --- a/_packs/controllers/order_controller.js +++ b/_packs/controllers/order_controller.js @@ -15,28 +15,40 @@ export default class extends CartBaseController { this.render({ products, site }); this.subtotalUpdate(); this.itemCountUpdate(); - this.subscribe(); + + this.storage_event = this._storage_event.bind(this); + this.cart_subtotal_update_event = + this._cart_subtotal_update_event.bind(this); + + window.addEventListener("storage", this.storage_event); + window.addEventListener( + "cart:subtotal:update", + this.cart_subtotal_update_event + ); } - /* - * Subscribe to change on the storage to update the cart. - */ - subscribe() { - window.addEventListener("storage", async (event) => { - if (!event.key?.startsWith("cart:item:")) return; + disconnect() { + window.removeEventListener("storage", this.storage_event); + window.removeEventListener( + "cart:subtotal:update", + this.cart_subtotal_update_event + ); + } - const products = this.products; - const site = window.site; + async _storage_event(event) { + if (!event.key?.startsWith("cart:item:")) return; - this.render({ products, site }); - this.subtotalUpdate(); - this.itemCountUpdate(); - }); + const products = this.products; + const site = window.site; - window.addEventListener("cart:subtotal:update", (event) => { - this.itemCountUpdate(); - this.subtotalUpdate(); - }); + this.render({ products, site }); + this.subtotalUpdate(); + this.itemCountUpdate(); + } + + _cart_subtotal_update_event(event) { + this.itemCountUpdate(); + this.subtotalUpdate(); } /* diff --git a/_packs/controllers/postal_code_controller.js b/_packs/controllers/postal_code_controller.js index 4e26707..1b639d0 100644 --- a/_packs/controllers/postal_code_controller.js +++ b/_packs/controllers/postal_code_controller.js @@ -175,24 +175,38 @@ export default class extends Controller { }; connect() { - window.addEventListener("cart:country:update", (event) => { - if (this.data.get("group") !== event.detail.group) return; + this.cart_country_update_event = this._cart_country_update_event.bind(this); - const zipcodeRequired = event.detail.data.zipcodeRequired == "true"; + window.addEventListener( + "cart:country:update", + this.cart_country_update_event + ); + } - this.codeTarget.value = ""; - this.codeTarget.disabled = !zipcodeRequired; - this.codeTarget.required = zipcodeRequired; + disconnect() { + window.removeEventListener( + "cart:country:update", + this.cart_country_update_event + ); + } - if (!zipcodeRequired) return; + _cart_country_update_event(event) { + if (this.data.get("group") !== event.detail.group) return; - this.codeTarget.pattern = - this.postal_codes[event.detail.iso.toLowerCase()] || ".*"; + const zipcodeRequired = event.detail.data.zipcodeRequired == "true"; - if (event.detail.selectedZipcode) { - this.codeTarget.value = event.detail.selectedZipcode; - this.codeTarget.dispatchEvent(new Event("change")); - } - }); + this.codeTarget.value = ""; + this.codeTarget.disabled = !zipcodeRequired; + this.codeTarget.required = zipcodeRequired; + + if (!zipcodeRequired) return; + + this.codeTarget.pattern = + this.postal_codes[event.detail.iso.toLowerCase()] || ".*"; + + if (event.detail.selectedZipcode) { + this.codeTarget.value = event.detail.selectedZipcode; + this.codeTarget.dispatchEvent(new Event("change")); + } } } diff --git a/_packs/controllers/state_controller.js b/_packs/controllers/state_controller.js index 25bb5f6..cdfa11a 100644 --- a/_packs/controllers/state_controller.js +++ b/_packs/controllers/state_controller.js @@ -10,57 +10,75 @@ export default class extends CartBaseController { static targets = ["id", "list", "name"]; connect() { - window.addEventListener("cart:country:update", async (event) => { - if (this.data.get("group") !== event.detail.group) return; + this.cart_country_update_event = this._cart_country_update_event.bind(this); + this.change_event = this._change_event.bind(this); - this.idTarget.value = ""; - this.nameTarget.value = ""; - this.listTarget.innerHTML = ""; - - const statesRequired = event.detail.data.statesRequired == "true"; - - this.nameTarget.disabled = !statesRequired; - this.nameTarget.required = statesRequired; - - if (!statesRequired) return; - - const states = await this.states(event.detail.iso); - const site = window.site; - - states.forEach((state) => { - let option = document.createElement("option"); - option.value = state.attributes.name; - option.dataset.id = state.id; - - this.listTarget.appendChild(option); - }); - - this.nameTarget.pattern = states.map((x) => x.attributes.name).join("|"); - this.nameTarget.addEventListener("input", (event) => - this.nameTarget.setCustomValidity("") - ); - this.nameTarget.addEventListener("invalid", (event) => - this.nameTarget.setCustomValidity(site.i18n.states.validation) - ); - - if (event.detail.selectedState) { - this.nameTarget.value = event.detail.selectedState; - this.nameTarget.dispatchEvent(new Event("change")); - } - }); + window.addEventListener( + "cart:country:update", + this.cart_country_update_event + ); // When the input changes we update the actual value and also the // state list via an Event - this.nameTarget.addEventListener("change", (event) => { - const options = Array.from(this.listTarget.options); - const option = options.find((x) => x.value == this.nameTarget.value); + this.nameTarget.addEventListener("change", this.change_event); + } - // TODO: If no option is found, mark the field as invalid - if (!option) return; + disconnect() { + window.removeEventListener( + "cart:country:update", + this.cart_country_update_event + ); + this.nameTarget.removeEventListener("change", this.change_event); + } - this.idTarget.value = option.dataset.id; - this.idTarget.dispatchEvent(new Event("change")); + async _cart_country_update_event(event) { + if (this.data.get("group") !== event.detail.group) return; + + this.idTarget.value = ""; + this.nameTarget.value = ""; + this.listTarget.innerHTML = ""; + + const statesRequired = event.detail.data.statesRequired == "true"; + + this.nameTarget.disabled = !statesRequired; + this.nameTarget.required = statesRequired; + + if (!statesRequired) return; + + const states = await this.states(event.detail.iso); + const site = window.site; + + states.forEach((state) => { + let option = document.createElement("option"); + option.value = state.attributes.name; + option.dataset.id = state.id; + + this.listTarget.appendChild(option); }); + + this.nameTarget.pattern = states.map((x) => x.attributes.name).join("|"); + this.nameTarget.addEventListener("input", (event) => + this.nameTarget.setCustomValidity("") + ); + this.nameTarget.addEventListener("invalid", (event) => + this.nameTarget.setCustomValidity(site.i18n.states.validation) + ); + + if (event.detail.selectedState) { + this.nameTarget.value = event.detail.selectedState; + this.nameTarget.dispatchEvent(new Event("change")); + } + } + + _change_event(event) { + const options = Array.from(this.listTarget.options); + const option = options.find((x) => x.value == this.nameTarget.value); + + // TODO: If no option is found, mark the field as invalid + if (!option) return; + + this.idTarget.value = option.dataset.id; + this.idTarget.dispatchEvent(new Event("change")); } /*