ez-shp-storefront
Version:
A helper function collection for Shopify storefront.
430 lines (415 loc) • 16.4 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.EzShpStorefront = {}));
})(this, (function (exports) { 'use strict';
/**
* Create an Event Bus object which has the registration and publishing API.
* addEventListener and emitEventListeners functions
* lets the subscriber and publisher to subscribe and publish on events respectively.
*/
class EventBus {
constructor() {
this.eventTopics = {};
}
static getInstance() {
if (!this.instance) {
this.instance = new EventBus();
}
return this.instance;
}
addEventListener(eventName, listener) {
if (!this.eventTopics[eventName] || this.eventTopics[eventName].length < 1) {
this.eventTopics[eventName] = [];
}
this.eventTopics[eventName].push(listener);
return listener;
}
emitEventListeners(eventName, params) {
if (!this.eventTopics[eventName] || this.eventTopics[eventName].length < 1)
return;
this.eventTopics[eventName].forEach(function (listener) {
listener(!!params ? params : {});
});
}
removeListener(eventName, listener) {
if (!this.eventTopics[eventName] || this.eventTopics[eventName].length < 1)
return;
const index = this.eventTopics[eventName].findIndex((f) => f === listener);
// delete listener by event name
delete this.eventTopics[eventName][index];
}
getListener(eventName) {
return this.eventTopics[eventName];
}
}
EventBus.instance = null;
class Fetch {
constructor() {
const coreFetch = window.fetch;
XMLHttpRequest.prototype['realSend'] = XMLHttpRequest.prototype.send;
const updatePaths = ["change", "update", "add"];
window.fetch = async (url, data) => {
if (updatePaths.find((updateUrl) => url.includes(updateUrl))) {
const response = await coreFetch(url, data);
this.getEventBus().emitEventListeners(`Ez_CART_CHANGE`, { url });
return response;
}
return coreFetch(url, data);
};
const newSend = function (vData) {
this.realSend(vData);
const url = this._url;
if (updatePaths.find((updateUrl) => String(url).includes(updateUrl))) {
this.getEventBus().emitEventListeners(`Ez_CART_CHANGE`, { url });
}
};
XMLHttpRequest.prototype.send = newSend;
}
static getInstance() {
if (!this.instance) {
this.instance = new Fetch();
}
return this.instance;
}
setEventBus(eventBus) {
this.eventBus = eventBus;
return this;
}
getEventBus() {
if (!this.eventBus) {
this.eventBus = EventBus.getInstance();
}
return this.eventBus;
}
}
Fetch.instance = null;
class Checkout {
constructor() {
}
static getInstance() {
if (!this.instance) {
this.instance = new Checkout();
}
return this.instance;
}
setEventBus(eventBus) {
this.eventBus = eventBus;
return this;
}
getEventBus() {
if (!this.eventBus) {
this.eventBus = EventBus.getInstance();
}
return this.eventBus;
}
getCheckoutPageError(response) {
if (response.url.includes('account/login')) {
return 'You must be signed in to use the discount code';
}
if (response.url.includes('stock_problems')) {
return "Some items don’t have insufficient stock. Please adjust the quantity.";
}
return '';
}
async getCheckoutPage() {
if (!this.getCheckoutPagePromise) {
this.getCheckoutPagePromise = fetch(window.location.origin + '/checkout', { method: 'get' });
}
const response = (await this.getCheckoutPagePromise).clone();
this.getCheckoutPagePromise = null;
const errorMessage = this.getCheckoutPageError(response);
if (errorMessage) {
const error = { errorMessage };
this.getEventBus().emitEventListeners('Ez_GET_CHECKOUT_PAGE_ERROR', error);
return [null, error];
}
try {
const urlParts = response.url.split('/checkouts/');
if (urlParts.length < 2) {
const error = { errorMessage: 'Cannot get "shopify_checkout_token".' };
this.getEventBus().emitEventListeners('Ez_GET_CHECKOUT_PAGE_ERROR', error);
return [null, error];
}
const shopify_checkout_token = urlParts[1].split('?')[0];
this.shopify_checkout_token = shopify_checkout_token;
const text = await response.text();
let parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
const metaEl = doc.querySelector('meta[name="shopify-checkout-authorization-token"]');
if (!metaEl) {
const error = { errorMessage: 'Cannot get "shopify_checkout_authorization_token".' };
this.getEventBus().emitEventListeners('Ez_GET_CHECKOUT_PAGE_ERROR', error);
return [null, error];
}
const shopify_checkout_authorization_token = metaEl.getAttribute('content');
this.shopify_checkout_authorization_token = shopify_checkout_authorization_token;
const data = { shopify_checkout_token, shopify_checkout_authorization_token };
this.getEventBus().emitEventListeners('Ez_GET_CHECKOUT_PAGE_SUCCESS', data);
return [data, null];
}
catch (e) {
console.log(e);
const error = { errorMessage: e };
return [null, error];
}
}
async putDiscountCode(discount_code) {
const res = await fetch(`/wallets/checkouts/${this.shopify_checkout_token}`, {
method: "PUT",
headers: {
'Accept': '*/*',
'Cache-Control': 'max-age=0',
'x-shopify-checkout-authorization-token': this.shopify_checkout_authorization_token,
'Content-Type': 'application/json'
},
body: JSON.stringify({
checkout: {
discount_code
}
})
});
if (res.status !== 200) {
let error = '';
if (res.status === 429) {
error = 'Too many attempts: Please try again in a few minutes';
}
else {
const { errors } = await res.json();
error = JSON.stringify(errors);
}
this.getEventBus().emitEventListeners('Ez_PUT_DISCOUNT_CODE_ERROR', error);
return [null, error];
}
const data = await res.json();
this.getEventBus().emitEventListeners('Ez_PUT_DISCOUNT_CODE_SUCCESS', data);
return [data, null];
}
}
Checkout.instance = null;
class Dom {
constructor() {
}
static getInstance() {
if (!this.instance) {
this.instance = new Dom();
}
return this.instance;
}
isHidden(el) {
return el.offsetParent === null;
}
}
Dom.instance = null;
const cartFormElementQuery = 'form[action="cart"], form[action="checkout"], form#CartPageForm, form#cart, form.cart';
const checkoutButtonElementQuery = 'main input[type="submit"], main button.checkout, main button[type="submit"], input[type="submit"]#checkout, button[type="submit"]#checkout, button[type="submit"][name="checkout"]';
class Cart {
constructor() {
this.data = { item_count: 0 };
}
static getInstance() {
if (!this.instance) {
this.instance = new Cart();
}
return this.instance;
}
isOnCartPage() {
return window.location.pathname.includes('/cart');
}
getCartItemCount() {
return this.data.item_count;
}
async get() {
this.data = await fetch(window.location.origin + "/cart.js").then((response) => response.json());
return this.data;
}
async clear(redirectUrl) {
try {
await fetch(window.location.origin + "/cart/clear");
}
catch (_a) {
}
if (!redirectUrl) {
return;
}
if (redirectUrl === location.href) {
return location.reload();
}
location.href = redirectUrl;
}
getCartFormElement() {
return document.querySelector(cartFormElementQuery);
}
getCheckoutButtonElement() {
let buttons = [];
const cartFormElementQueryParts = cartFormElementQuery.split(',');
cartFormElementQueryParts.forEach((cartFormElementQueryPart) => {
const checkoutButtonElementQueryParts = checkoutButtonElementQuery.split(',');
checkoutButtonElementQueryParts.forEach((checkoutButtonElementQueryPart) => {
buttons.concat(Array.from(document.querySelectorAll(`${cartFormElementQueryPart} ${checkoutButtonElementQueryPart}`)));
});
});
const dom = Dom.getInstance();
return buttons.find((button) => !dom.isHidden(button));
}
}
Cart.instance = null;
class MoneyFormatter {
constructor() {
}
setMoneyFormat(format) {
this.moneyFormat = format;
return this;
}
getMoneyFormat() {
return this.moneyFormat;
}
static getInstance() {
if (!this.instance) {
this.instance = new MoneyFormatter();
}
return this.instance;
}
formatWithDelimiters(number, precision, thousands, decimal) {
thousands = thousands || ',';
decimal = decimal || '.';
if (isNaN(number) || number === null) {
return '0';
}
number = (number / 100.0).toFixed(precision);
var parts = number.split('.');
var dollarsAmount = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1' + thousands);
var centsAmount = parts[1] ? decimal + parts[1] : '';
return dollarsAmount + centsAmount;
}
get(cents, format) {
if (typeof cents === 'string') {
cents = cents.replace('.', '');
}
var value = '';
var placeholderRegex = /\{\{\s*(\w+)\s*\}\}/;
var formatString = format || this.getMoneyFormat();
formatString = formatString.length < 2 ? this.getMoneyFormat() : formatString;
const matches = formatString.match(placeholderRegex);
const amountStr = matches && matches.length ? matches[1] : 'amount';
switch (amountStr) {
case 'amount':
value = this.formatWithDelimiters(cents, 2);
break;
case 'amount_no_decimals':
value = this.formatWithDelimiters(cents, 0);
break;
case 'amount_with_comma_separator':
value = this.formatWithDelimiters(cents, 2, '.', ',');
break;
case 'amount_no_decimals_with_comma_separator':
value = this.formatWithDelimiters(cents, 0, '.', ',');
break;
case 'amount_no_decimals_with_space_separator':
value = this.formatWithDelimiters(cents, 0, ' ');
break;
case 'amount_with_apostrophe_separator':
value = this.formatWithDelimiters(cents, 2, "'");
break;
}
return formatString.replace(placeholderRegex, value);
}
}
MoneyFormatter.instance = null;
class Logger {
constructor() {
this.isEnabled = false;
const urlParams = new URLSearchParams(window.location.search);
this.isEnabled = !!urlParams.get('debug');
}
static getInstance() {
if (!this.instance) {
this.instance = new Logger();
}
return this.instance;
}
enable() {
this.isEnabled = true;
}
disable() {
this.isEnabled = false;
}
debug(data) {
if (!this.isEnabled) {
return;
}
console.log(data);
}
}
Logger.instance = null;
function inIframe() {
try {
return window.self !== window.top;
}
catch (e) {
return true;
}
}
exports.Positions = void 0;
(function (Positions) {
Positions["TOP"] = "top";
Positions["BOTTOM"] = "bottom";
Positions["BEFORE"] = "before";
Positions["AFTER"] = "after";
})(exports.Positions || (exports.Positions = {}));
function moveByElements(fromEl, toEl, type) {
switch (type) {
case exports.Positions.TOP:
toEl.prepend(fromEl);
break;
case exports.Positions.BOTTOM:
toEl.append(fromEl);
break;
case exports.Positions.BEFORE:
toEl.parentNode.insertBefore(fromEl, toEl);
break;
case exports.Positions.AFTER:
toEl.parentNode.insertBefore(fromEl, toEl.nextSibling);
break;
}
}
function moveByElementQueries(from, to, type) {
let fromEl = document.querySelector(from);
let toEl;
try {
toEl = to
? document.querySelector(to) || document.body
: document.body;
}
catch (e) {
toEl = document.body;
}
return moveByElements(fromEl, toEl, type);
}
const TranslateXPath = "translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')";
function getElementByContent(content) {
const xPathButtons = document.evaluate(`//button[contains(${TranslateXPath}, "${content}") or contains(${TranslateXPath}, "${content.replace(/ /g, "")}")]`, document, null, XPathResult.ANY_TYPE, null);
return xPathButtons.iterateNext();
}
const eventBus = EventBus.getInstance();
const cart = Cart.getInstance();
const moneyFormatter = MoneyFormatter.getInstance();
const checkout = Checkout.getInstance().setEventBus(eventBus);
const dom = Dom.getInstance();
const logger = Logger.getInstance();
function listenOnCartChange() {
Fetch.getInstance().setEventBus(eventBus);
}
exports.cart = cart;
exports.checkout = checkout;
exports.dom = dom;
exports.eventBus = eventBus;
exports.getElementByContent = getElementByContent;
exports.inIframe = inIframe;
exports.listenOnCartChange = listenOnCartChange;
exports.logger = logger;
exports.moneyFormatter = moneyFormatter;
exports.moveByElementQueries = moveByElementQueries;
exports.moveByElements = moveByElements;
Object.defineProperty(exports, '__esModule', { value: true });
}));