UNPKG

@nguyenmv2/buy-button

Version:

BuyButton.js allows merchants to build Shopify interfaces into any website

694 lines (577 loc) 21.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.NO_IMG_URL = void 0; var _merge2 = _interopRequireDefault(require("../utils/merge")); var _component = _interopRequireDefault(require("../component")); var _toggle = _interopRequireDefault(require("./toggle")); var _template = _interopRequireDefault(require("../template")); var _checkout = _interopRequireDefault(require("./checkout")); var _money = _interopRequireDefault(require("../utils/money")); var _cart = _interopRequireDefault(require("../views/cart")); var _cart2 = _interopRequireDefault(require("../updaters/cart")); var _elementClass = require("../utils/element-class"); var _focus = require("../utils/focus"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } var NO_IMG_URL = '//sdks.shopifycdn.com/buy-button/latest/no-image.jpg'; exports.NO_IMG_URL = NO_IMG_URL; var LINE_ITEM_TARGET_SELECTIONS = ['ENTITLED', 'EXPLICIT']; var CART_TARGET_SELECTION = 'ALL'; var CHECKOUT_INPUT_FIELDS = ['presentmentCurrencyCode']; /** * Renders and cart embed. * @extends Component. */ var Cart = /*#__PURE__*/ function (_Component) { _inheritsLoose(Cart, _Component); /** * create Cart. * @param {Object} config - configuration object. * @param {Object} props - data and utilities passed down from UI instance. */ function Cart(config, props) { var _this; _this = _Component.call(this, config, props) || this; _this.addVariantToCart = _this.addVariantToCart.bind(_assertThisInitialized(_this)); _this.childTemplate = new _template.default(_this.config.lineItem.templates, _this.config.lineItem.contents, _this.config.lineItem.order); _this.node = config.node || document.body.appendChild(document.createElement('div')); _this.isVisible = _this.options.startOpen; _this.lineItemCache = []; _this.moneyFormat = _this.globalConfig.moneyFormat; _this.checkout = new _checkout.default(_this.config); _this.checkoutConfig = _this.config.checkout; var toggles = _this.globalConfig.toggles || [{ node: _this.node.parentNode.insertBefore(document.createElement('div'), _this.node) }]; _this.toggles = toggles.map(function (toggle) { return new _toggle.default((0, _merge2.default)({}, config, toggle), Object.assign({}, _this.props, { cart: _assertThisInitialized(_this) })); }); _this.updater = new _cart2.default(_assertThisInitialized(_this)); _this.view = new _cart.default(_assertThisInitialized(_this)); return _this; } var _proto = Cart.prototype; _proto.createToggles = function createToggles(config) { var _this2 = this; this.toggles = this.toggles.concat(config.toggles.map(function (toggle) { return new _toggle.default((0, _merge2.default)({}, config, toggle), Object.assign({}, _this2.props, { cart: _this2 })); })); return Promise.all(this.toggles.map(function (toggle) { return toggle.init({ lineItems: _this2.lineItems }); })); } /** * get key for configuration object. * @return {String} */ ; _proto.imageForLineItem = function imageForLineItem(lineItem) { var imageSize = 180; var imageOptions = { maxWidth: imageSize, maxHeight: imageSize }; if (lineItem.variant.image) { return this.props.client.image.helpers.imageForSize(lineItem.variant.image, imageOptions); } else { return NO_IMG_URL; } } /** * sets model to null and removes checkout from localStorage * @return {Promise} promise resolving to the cart model */ ; _proto.removeCheckout = function removeCheckout() { this.model = null; localStorage.removeItem(this.localStorageCheckoutKey); return this.model; } /** * get model data either by calling client.createCart or loading from localStorage. * @return {Promise} promise resolving to cart instance. */ ; _proto.fetchData = function fetchData() { var _this3 = this; var checkoutId = localStorage.getItem(this.localStorageCheckoutKey); if (checkoutId) { return this.props.client.checkout.fetch(checkoutId).then(function (checkout) { _this3.model = checkout; if (checkout.completedAt) { return _this3.removeCheckout(); } return _this3.sanitizeCheckout(checkout).then(function (newCheckout) { _this3.updateCache(newCheckout.lineItems); return newCheckout; }); }).catch(function () { return _this3.removeCheckout(); }); } else { return Promise.resolve(null); } }; _proto.sanitizeCheckout = function sanitizeCheckout(checkout) { var lineItemsToDelete = checkout.lineItems.filter(function (item) { return !item.variant; }); if (!lineItemsToDelete.length) { return Promise.resolve(checkout); } var lineItemIds = lineItemsToDelete.map(function (item) { return item.id; }); return this.props.client.checkout.removeLineItems(checkout.id, lineItemIds).then(function (newCheckout) { return newCheckout; }); }; _proto.fetchMoneyFormat = function fetchMoneyFormat() { return this.props.client.shop.fetchInfo().then(function (res) { return res.moneyFormat; }); } /** * initializes component by creating model and rendering view. * Creates and initalizes toggle component. * @param {Object} [data] - data to initialize model with. * @return {Promise} promise resolving to instance. */ ; _proto.init = function init(data) { var _this4 = this; if (!this.moneyFormat) { this.fetchMoneyFormat().then(function (moneyFormat) { _this4.moneyFormat = moneyFormat; }); } return _Component.prototype.init.call(this, data).then(function (cart) { return _this4.toggles.map(function (toggle) { var lineItems = cart.model ? cart.model.lineItems : []; return toggle.init({ lineItems: lineItems }); }); }).then(function () { return _this4; }); }; _proto.destroy = function destroy() { _Component.prototype.destroy.call(this); this.toggles.forEach(function (toggle) { return toggle.destroy(); }); } /** * closes cart */ ; _proto.close = function close() { this.isVisible = false; this.view.render(); (0, _focus.removeTrapFocus)(this.view.wrapper); } /** * opens cart */ ; _proto.open = function open() { this.isVisible = true; this.view.render(); this.view.setFocus(); } /** * toggles cart visibility * @param {Boolean} visible - desired state. */ ; _proto.toggleVisibility = function toggleVisibility(visible) { this.isVisible = visible || !this.isVisible; this.view.render(); if (this.isVisible) { this.view.setFocus(); } }; _proto.onQuantityBlur = function onQuantityBlur(evt, target) { this.setQuantity(target, function () { return parseInt(target.value, 10); }); }; _proto.onQuantityIncrement = function onQuantityIncrement(qty, evt, target) { this.setQuantity(target, function (prevQty) { return prevQty + qty; }); }; _proto.onCheckout = function onCheckout() { this._userEvent('openCheckout'); this.props.tracker.track('Open cart checkout', {}); this.checkout.open(this.model.webUrl); } /** * set quantity for a line item. * @param {Object} target - DOM node of line item * @param {Function} fn - function to return new quantity given currrent quantity. */ ; _proto.setQuantity = function setQuantity(target, fn) { var id = target.getAttribute('data-line-item-id'); var item = this.model.lineItems.find(function (lineItem) { return lineItem.id === id; }); var newQty = fn(item.quantity); return this.props.tracker.trackMethod(this.updateItem.bind(this), 'Update Cart', this.cartItemTrackingInfo(item, newQty))(id, newQty); }; _proto.setNote = function setNote(evt) { var _this5 = this; var note = evt.target.value; return this.props.client.checkout.updateAttributes(this.model.id, { note: note }).then(function (checkout) { _this5.model = checkout; return checkout; }); } /** * set cache using line items. * @param {Array} lineItems - array of GraphModel line item objects. */ ; _proto.updateCache = function updateCache(lineItems) { var cachedLineItems = this.lineItemCache.reduce(function (acc, item) { acc[item.id] = item; return acc; }, {}); this.lineItemCache = lineItems.map(function (item) { return Object.assign({}, cachedLineItems[item.id], item); }); return this.lineItemCache; } /** * update cached line item. * @param {Number} id - lineItem id. * @param {Number} qty - quantity for line item. */ ; _proto.updateCacheItem = function updateCacheItem(lineItemId, quantity) { if (this.lineItemCache.length === 0) { return; } var lineItem = this.lineItemCache.find(function (item) { return lineItemId === item.id; }); lineItem.quantity = quantity; this.view.render(); } /** * update line item. * @param {Number} id - lineItem id. * @param {Number} qty - quantity for line item. */ ; _proto.updateItem = function updateItem(id, quantity) { var _this6 = this; this._userEvent('updateItemQuantity'); var lineItem = { id: id, quantity: quantity }; var lineItemEl = this.view.document.getElementById(id); if (lineItemEl) { var quantityEl = lineItemEl.getElementsByClassName(this.classes.lineItem.quantity)[0]; if (quantityEl) { (0, _elementClass.addClassToElement)('is-loading', quantityEl); } } return this.props.client.checkout.updateLineItems(this.model.id, [lineItem]).then(function (checkout) { _this6.model = checkout; _this6.updateCache(_this6.model.lineItems); _this6.toggles.forEach(function (toggle) { return toggle.view.render(); }); if (quantity > 0) { _this6.view.render(); } else { _this6.view.animateRemoveNode(id); } return checkout; }); } /** * add variant to cart. * @param {Object} variant - variant object. * @param {Number} [quantity=1] - quantity to be added. */ ; _proto.addVariantToCart = function addVariantToCart(variant) { var _this7 = this; var quantity = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; var openCart = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; if (quantity <= 0) { return null; } if (openCart) { this.open(); } var lineItem = { variantId: variant.id, quantity: quantity }; if (this.model) { return this.props.client.checkout.addLineItems(this.model.id, [lineItem]).then(function (checkout) { _this7.model = checkout; _this7.updateCache(_this7.model.lineItems); _this7.view.render(); _this7.toggles.forEach(function (toggle) { return toggle.view.render(); }); _this7.view.setFocus(); return checkout; }); } else { var input = _objectSpread({ lineItems: [lineItem] }, this.sanitizedCheckoutConfig); return this.props.client.checkout.create(input).then(function (checkout) { localStorage.setItem(_this7.localStorageCheckoutKey, checkout.id); _this7.model = checkout; _this7.updateCache(_this7.model.lineItems); _this7.view.render(); _this7.toggles.forEach(function (toggle) { return toggle.view.render(); }); _this7.view.setFocus(); return checkout; }); } }; /** * Remove all lineItems in the cart */ _proto.empty = function empty() { var _this8 = this; var lineItemIds = this.model.lineItems ? this.model.lineItems.map(function (item) { return item.id; }) : []; return this.props.client.checkout.removeLineItems(this.model.id, lineItemIds).then(function (checkout) { _this8.model = checkout; _this8.view.render(); _this8.toggles.forEach(function (toggle) { return toggle.view.render(); }); return checkout; }); } /** * get info about line item to be sent to tracker * @return {Object} */ ; _proto.cartItemTrackingInfo = function cartItemTrackingInfo(item, quantity) { return { id: item.variant.id, variantName: item.variant.title, productId: item.variant.product.id, name: item.title, price: item.variant.priceV2.amount, prevQuantity: item.quantity, quantity: parseFloat(quantity), sku: null }; }; _createClass(Cart, [{ key: "typeKey", get: function get() { return 'cart'; } /** * get events to be bound to DOM. * @return {Object} */ }, { key: "DOMEvents", get: function get() { var _merge; return (0, _merge2.default)({}, (_merge = {}, _defineProperty(_merge, "click ".concat(this.selectors.cart.close), this.props.closeCart.bind(this)), _defineProperty(_merge, "click ".concat(this.selectors.lineItem.quantityIncrement), this.onQuantityIncrement.bind(this, 1)), _defineProperty(_merge, "click ".concat(this.selectors.lineItem.quantityDecrement), this.onQuantityIncrement.bind(this, -1)), _defineProperty(_merge, "click ".concat(this.selectors.cart.button), this.onCheckout.bind(this)), _defineProperty(_merge, "blur ".concat(this.selectors.lineItem.quantityInput), this.onQuantityBlur.bind(this)), _defineProperty(_merge, "blur ".concat(this.selectors.cart.note), this.setNote.bind(this)), _merge), this.options.DOMEvents); } /** * get cart line items. * @return {Array} HTML */ }, { key: "lineItems", get: function get() { return this.model ? this.model.lineItems : []; } }, { key: "checkoutCurrency", get: function get() { return this.sanitizedCheckoutConfig && this.sanitizedCheckoutConfig.presentmentCurrencyCode; } /** * get HTML for cart line items. * @return {String} HTML */ }, { key: "lineItemsHtml", get: function get() { var _this9 = this; return this.lineItemCache.reduce(function (acc, lineItem) { var data = Object.assign({}, lineItem, _this9.options.viewData); var price; if (_this9.checkoutCurrency) { price = data.variant.presentmentPrices.find(function (priceObj) { return priceObj.price.currencyCode === _this9.checkoutCurrency; }).price.amount; } else { price = data.variant.priceV2.amount; } var fullPrice = price * data.quantity; var formattedPrice = (0, _money.default)(fullPrice, _this9.moneyFormat); var discountAllocations = data.discountAllocations; var _discountAllocations$ = discountAllocations.reduce(function (discountAcc, discount) { var targetSelection = discount.discountApplication.targetSelection; if (LINE_ITEM_TARGET_SELECTIONS.indexOf(targetSelection) > -1) { var discountAmount = discount.allocatedAmount.amount; var discountDisplayText = discount.discountApplication.title || discount.discountApplication.code; discountAcc.totalDiscount += discountAmount; discountAcc.discounts.push({ discount: "".concat(discountDisplayText, " (-").concat((0, _money.default)(discountAmount, _this9.moneyFormat), ")") }); } return discountAcc; }, { discounts: [], totalDiscount: 0 }), discounts = _discountAllocations$.discounts, totalDiscount = _discountAllocations$.totalDiscount; data.discounts = discounts.length > 0 ? discounts : null; data.formattedFullPrice = totalDiscount > 0 ? formattedPrice : null; data.formattedActualPrice = (0, _money.default)(fullPrice - totalDiscount, _this9.moneyFormat); data.formattedPrice = formattedPrice; data.classes = _this9.classes; data.lineItemImage = _this9.imageForLineItem(data); data.variantTitle = data.variant.title === 'Default Title' ? '' : data.variant.title; return acc + _this9.childTemplate.render({ data: data }, function (output) { return "<div id=\"".concat(lineItem.id, "\" class=").concat(_this9.classes.lineItem.lineItem, ">").concat(output, "</div>"); }); }, ''); } /** * get data to be passed to view. * @return {Object} viewData object. */ }, { key: "viewData", get: function get() { var modelData = this.model || {}; return (0, _merge2.default)(modelData, this.options.viewData, { text: this.options.text, classes: this.classes, lineItemsHtml: this.lineItemsHtml, isEmpty: this.isEmpty, formattedTotal: this.formattedTotal, discounts: this.cartDiscounts, contents: this.options.contents, cartNote: this.cartNote }); } /** * get formatted cart subtotal based on moneyFormat * @return {String} */ }, { key: "formattedTotal", get: function get() { if (!this.model) { return (0, _money.default)(0, this.moneyFormat); } var total = this.options.contents.discounts ? this.model.subtotalPriceV2.amount : this.model.lineItemsSubtotalPrice.amount; return (0, _money.default)(total, this.moneyFormat); } }, { key: "cartDiscounts", get: function get() { var _this10 = this; if (!this.options.contents.discounts || !this.model) { return []; } return this.model.discountApplications.reduce(function (discountArr, discount) { if (discount.targetSelection === CART_TARGET_SELECTION) { var discountValue = 0; if (discount.value.amount) { discountValue = discount.value.amount; } else if (discount.value.percentage) { discountValue = discount.value.percentage / 100 * _this10.model.lineItemsSubtotalPrice.amount; } if (discountValue > 0) { var discountDisplayText = discount.title || discount.code; discountArr.push({ text: discountDisplayText, amount: "-".concat((0, _money.default)(discountValue, _this10.moneyFormat)) }); } } return discountArr; }, []); } /** * whether cart is empty * @return {Boolean} */ }, { key: "isEmpty", get: function get() { if (!this.model) { return true; } return this.model.lineItems.length < 1; } }, { key: "cartNote", get: function get() { return this.model && this.model.note; } }, { key: "wrapperClass", get: function get() { return this.isVisible ? 'is-active' : ''; } }, { key: "localStorageCheckoutKey", get: function get() { return "".concat(this.props.client.config.storefrontAccessToken, ".").concat(this.props.client.config.domain, ".checkoutId"); } }, { key: "sanitizedCheckoutConfig", get: function get() { var _this11 = this; return Object.keys(this.checkoutConfig).reduce(function (result, key) { if (!CHECKOUT_INPUT_FIELDS.includes(key)) { return result; } return _objectSpread(_objectSpread({}, result), {}, _defineProperty({}, key, _this11.checkoutConfig[key])); }, {}); } }]); return Cart; }(_component.default); exports.default = Cart;