sutty-base-jekyll-theme/_packs/controllers/cart_controller.js
Nulo 3a0c41b736 Limpiar eventos cuando el elemento o controlador que los escucha desaparece
Squashed commit of the following:

commit 482eea28821868f03ace33562e7bd34ab9a4478f
Merge: 5f48528 1c128f2
Author: f <f@sutty.nl>
Date:   Thu Nov 25 18:31:35 2021 -0300

    Merge branch 'master' into limpiar-eventos

commit 5f48528c28b0709bd859a4dc52a830f60bfedc6e
Author: f <f@sutty.nl>
Date:   Thu Nov 25 18:23:23 2021 -0300

    pretty

commit 70d05bc90a6cb64d1c4bfc39f48388af3fbc3c18
Merge: c4f33c0 ff1bc21
Author: Nulo <nulo@sutty.nl>
Date:   Thu Oct 28 16:46:31 2021 -0300

    Merge branch 'master' into limpiar-eventos

commit c4f33c084058002a10fc0ec2137ffe045826cfd2
Author: f <f@sutty.nl>
Date:   Thu Oct 28 14:52:41 2021 -0300

    limpiar eventos
2021-11-25 21:40:44 +00:00

317 lines
7.7 KiB
JavaScript

import { CartBaseController } from "./cart_base_controller";
/*
* Manages the cart and its contents.
*
* We need to create an order in Spree API to get a token that allows to
* make changes to it.
*
* The order contains attributes for the order and other data can be
* included, like line items, variants, payments, promotions, shipments,
* billing and shipping addresses, and the user.
*
* Variants are products added to the cart. To remove an item or change
* its quantity, a line item for the variant must be found. We store
* this information into localStorage so we don't have to make annoying
* queries to JSON:API everytime.
*/
export default class extends CartBaseController {
static targets = ["quantity", "subtotal", "addedQuantity"];
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", this.change_event);
}
disconnect() {
this.quantityTarget.removeEventListener("change", this.change_event);
}
async _change_event(event) {
const quantity = event.target.value;
if (quantity < 1) return;
const orderToken = await this.tokenGetOrCreate();
const product = this.product;
if (!product) return;
event.target.disabled = true;
const response = await this.spree.cart.setQuantity(
{ orderToken },
{
line_item_id: product.line_item.id,
quantity,
include: "line_items",
}
);
event.target.disabled = false;
event.target.focus();
// 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.cart = response;
this.subtotalUpdate();
this.counterUpdate();
await this.itemStore();
if (!this.hasSubtotalTarget) return;
this.subtotalTarget.innerText =
product.line_item.attributes.discounted_amount;
}
subtotalUpdate() {
window.dispatchEvent(new Event("cart:subtotal:update"));
}
/*
* Creates an order and stores the data into localStorage.
*
* @return [String]
*/
async cartCreate() {
const response = await this.spree.cart.create();
// If we fail here it's probably a server error, so we inform the
// user.
if (response.isFail()) {
this.handleFailure(response);
return;
}
this.cart = response;
this.storage.setItem("token", response.success().data.attributes.token);
return this.token;
}
/*
* Gets the order token and creates a cart if it doesn't exist.
*
* @return [String]
*/
async tokenGetOrCreate() {
let token = this.storage.getItem("token");
return token || (await this.cartCreate());
}
/*
* The variant ID is used to identify products
*
* @return [String]
*/
get variantId() {
return this.data.get("variantId");
}
get product() {
const product = JSON.parse(this.storage.getItem(this.storageId));
if (!product) {
console.error(
"El producto es nulo!",
this.storageId,
this.storage.length,
this.cart
);
}
return product;
}
/*
* Obtains the line_item_id by a variant_id by inspecting the cart and
* its included items
*
* @return [Object]
*/
findLineItem() {
const line_item = this.cart.included.find(
(x) =>
x.type === "line_item" &&
x.relationships.variant.data.id == this.variantId
);
return line_item || {};
}
get storageId() {
return `cart:item:${this.variantId}`;
}
/*
* Stores an item for later usage.
*
* @see {./order_controller.js}
*/
itemStore() {
this.storage.setItem(
this.storageId,
JSON.stringify({
variant_id: this.variantId,
line_item: this.findLineItem(),
image: this.data.get("image"),
title: this.data.get("title"),
url: this.data.get("url"),
stock: this.data.get("stock"),
in_stock: this.data.get("inStock"),
extra: this.data.get("extra") ? this.data.get("extra").split("|") : [],
})
);
}
/*
* Adds item to cart. This is meant to be used by an "Add to cart"
* button. If the item already exists in the cart it updates the
* quantity by +1.
*
* The item needs a variant ID to be added.
*/
async add(event, quantity = 1, floating_alert = true) {
const addedQuantity = this.addedQuantity();
if (addedQuantity > 1) quantity = addedQuantity;
const orderToken = await this.tokenGetOrCreate();
const response = await this.spree.cart.addItem(
{ orderToken },
{ variant_id: this.variantId, quantity, include: "line_items" }
);
if (response.isFail()) {
this.handleFailure(response);
return;
}
this.cart = response;
this.itemStore();
this.counterUpdate();
this.fireCajon();
if (floating_alert) {
const site = window.site;
const content = site.cart.added;
window.dispatchEvent(
new CustomEvent("floating:alert", { detail: { content } })
);
}
}
/*
* Remove the element from the cart. It contacts the API and if the
* item is removed, it removes itself from the page and the storage.
*/
async remove() {
const product = this.product;
if (!product) return;
if (!product.line_item) return;
const orderToken = this.token;
const response = await this.spree.cart.removeItem(
{ orderToken },
product.line_item.id,
{ include: "line_items" }
);
if (response.isFail()) {
this.handleFailure(response);
return;
}
this.cart = response;
this.storage.removeItem(this.storageId);
this.element.remove();
this.subtotalUpdate();
this.counterUpdate();
}
/*
* Shows variants
*/
async variants() {
const template = "variants";
const data = {
product: {
variant_id: this.data.get("variantId"),
digital_variant_id: this.data.get("digitalVariantId"),
image: this.data.get("image"),
title: this.data.get("title"),
price: this.data.get("price"),
digital_price: this.data.get("digitalPrice"),
in_stock: this.data.get("inStock"),
extra: this.data.get("extra").split("|"),
},
};
window.dispatchEvent(
new CustomEvent("notification", { detail: { template, data } })
);
}
/*
* Recovers the order if something failed
*/
async recover() {
console.error("Recuperando pedido", this.token);
// Removes the failing token
this.storage.removeItem("token");
// Get a new token and cart
await this.tokenGetOrCreate();
// Stores the previous cart
const cart = this.cart;
if (!cart) return;
// Add previous items and their quantities to the new cart by
// mimicking user's actions
//
// XXX: We don't use forEach because it's not async
for (const variant of cart.data.relationships.variants.data) {
this.data.set("variantId", variant.id);
const product = this.product;
if (!product) continue;
this.data.set("image", product.image);
this.data.set("title", product.title);
this.data.set("extra", product.extra.join("|"));
await this.add(null, product.line_item.attributes.quantity, false);
}
}
/*
* Si le compradore aumenta la cantidad antes de agregar
*/
addedQuantity() {
if (!this.hasAddedQuantityTarget) return 0;
const addedQuantity = parseInt(this.addedQuantityTarget.value);
return isNaN(addedQuantity) ? 0 : addedQuantity;
}
}