UNPKG

ez-shp-storefront

Version:

A helper function collection for Shopify storefront.

430 lines (415 loc) 16.4 kB
(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 }); }));