@ribajs/shopify
Version:
Shopify extension for Riba.js
226 lines (201 loc) • 6 kB
text/typescript
import { Component, ScopeBase } from "@ribajs/core";
import {
ShopifyCartLineItem,
ShopifyCartObject,
ShopifyCustomerAddress,
ShopifyShippingRates,
ShopifyShippingRatesNormalized,
} from "../../interfaces/index.js";
import { ShopifyCartService } from "../../services/index.js";
import { getInputValue, hasChildNodesTrim } from "@ribajs/utils/src/dom.js";
export interface Scope extends ScopeBase {
cart: ShopifyCartObject | null;
shippingAddress: ShopifyCustomerAddress | null;
estimateShippingRate: boolean;
shippingRates: ShopifyShippingRatesNormalized;
removeItem: ShopifyCartComponent["removeItem"];
increaseItem: ShopifyCartComponent["increaseItem"];
decreaseItem: ShopifyCartComponent["decreaseItem"];
getItem: ShopifyCartComponent["getItem"];
onItemQuantityChanged: ShopifyCartComponent["onItemQuantityChanged"];
pending: boolean;
}
export class ShopifyCartComponent extends Component {
public static tagName = "shopify-cart";
static get observedAttributes(): string[] {
return ["shipping-address", "estimate-shipping-rate"];
}
protected requiredAttributes(): string[] {
return [];
}
public scope: Scope = this.getScopeDefaults();
protected getScopeDefaults(): Scope {
return {
cart: ShopifyCartService.cart,
shippingAddress: null,
estimateShippingRate: false,
shippingRates: [],
removeItem: this.removeItem,
increaseItem: this.increaseItem,
decreaseItem: this.decreaseItem,
getItem: this.getItem,
onItemQuantityChanged: this.onItemQuantityChanged,
pending: false,
};
}
protected set cart(cart: ShopifyCartObject | null) {
// TODO check if cart values are changed
this.scope.cart = cart;
if (this.scope.shippingAddress && this.scope.estimateShippingRate) {
ShopifyCartService.getShippingRates(this.scope.shippingAddress, true, {
triggerOnChange: false,
triggerOnComplete: false,
triggerOnStart: false,
}).then(
(
shippingRates: ShopifyShippingRates | ShopifyShippingRatesNormalized,
) => {
this.scope.shippingRates =
shippingRates as ShopifyShippingRatesNormalized;
},
);
}
}
protected get cart(): ShopifyCartObject | null {
return this.scope.cart || null;
}
constructor() {
super();
}
protected connectedCallback() {
super.connectedCallback();
this.init(ShopifyCartComponent.observedAttributes);
}
public removeItem(lineItem: ShopifyCartLineItem) {
ShopifyCartService.change(lineItem.variant_id, 0)
.then((cart: ShopifyCartObject) => {
// this.cart = cart;
return cart;
})
.catch((error) => {
console.error(error);
});
}
public increaseItem(lineItem: ShopifyCartLineItem) {
lineItem.quantity++;
ShopifyCartService.change(lineItem.variant_id, lineItem.quantity)
.then((cart: ShopifyCartObject) => {
// this.cart = cart;
return cart;
})
.catch((error) => {
console.error(error);
});
}
public decreaseItem(lineItem: ShopifyCartLineItem) {
lineItem.quantity--;
if (lineItem.quantity < 0) {
lineItem.quantity = 0;
}
ShopifyCartService.change(lineItem.variant_id, lineItem.quantity)
.then((cart: ShopifyCartObject) => {
return cart;
})
.catch((error) => {
console.error(error);
});
}
/**
* Can be used for a changeable quantity input field
* @example
* ```html
* <input type="number" rv-on-change="onItemQuantityChanged" rv-value="quantity" />
* ```
*/
public onItemQuantityChanged(
lineItem: ShopifyCartLineItem,
event: Event,
scope: Scope,
htmlEl: HTMLInputElement,
) {
if (!htmlEl) {
console.warn("Input element not found");
return;
}
const newValue = Number(getInputValue(htmlEl));
if (newValue === lineItem.quantity) {
return;
}
lineItem.quantity = newValue;
ShopifyCartService.change(lineItem.variant_id, lineItem.quantity)
.then((cart: ShopifyCartObject) => {
this.debug("ShopifyCartService changed", cart);
return cart;
})
.catch((error) => {
console.error(error);
});
}
/**
* Get line item by id
* @param id
*/
public getItem(id: number | string): ShopifyCartLineItem | null {
id = Number(id);
const item =
this.scope.cart?.items.find((item) => Number(item.id) === id) || null;
this.debug("getItem", id, item, this.scope.cart?.items);
return item;
}
protected onCartRequestStart() {
this.scope.pending = true;
}
protected onCartRequestComplete(cart: ShopifyCartObject) {
if (cart) {
this.cart = cart;
}
this.scope.pending = false;
}
protected async beforeBind() {
await super.beforeBind();
ShopifyCartService.shopifyCartEventDispatcher.on(
"ShopifyCart:request:start",
this.onCartRequestStart,
this,
);
ShopifyCartService.shopifyCartEventDispatcher.on(
"ShopifyCart:request:complete",
this.onCartRequestComplete,
this,
);
}
protected disconnectedCallback() {
super.disconnectedCallback();
ShopifyCartService.shopifyCartEventDispatcher.off(
"ShopifyCart:request:start",
this.onCartRequestStart,
this,
);
ShopifyCartService.shopifyCartEventDispatcher.off(
"ShopifyCart:request:complete",
this.onCartRequestComplete,
this,
);
}
protected async afterBind() {
this.debug("afterBind", this.scope);
if (!this.cart) {
this.cart = await ShopifyCartService.get();
}
await super.afterBind();
}
protected async template() {
// Only set the component template if there no childs already
if (hasChildNodesTrim(this)) {
return null;
} else {
const { default: template } = await import("./cart.component.html?raw");
return template;
}
}
}