UNPKG

shopify-cart

Version:
489 lines (432 loc) 14.4 kB
import Q from '@butsandcats/ajax-queue' export default class Cart { constructor (config = {}) { // Check each of the parameters exist and assign them to their default type if not. const { cart = {}, selectors = {}, options = {} } = config // Define the default selectors and options for the class const defaultSelectors = { decreaseQuantity: '[data-minus-one]', increaseQuantity: '[data-plus-one]', addItem: '[data-add-to-cart]', quickAdd: '[data-quick-add]', quickAddQuantity: '[data-quick-add-qty]', quickAddProperties: '[data-quick-add-properties]', removeItem: '[data-remove-item]' } // This object will be used to define default callbacks, money formatting and other options const defaultOptions = { getUrl: '/cart.json', timeOut: 1000 } // Create a new queue this.queue = new Q({ method: 'POST', completedAllRequestsEvent: 'cart:requestsCompleted', completedRequestEvent: 'cart:requestCompleted' }) this.defaultBeforeSend = (type, item) => { const response = { type: type, item: item } const sendEvent = new CustomEvent('cart:sending', { detail: { response: response } }) document.dispatchEvent(sendEvent) return response } this.defaultSuccess = (response) => { this.getCart() } this.defaultError = (resp) => { const response = JSON.parse(resp) const errorEvent = new CustomEvent('cart:error', { detail: { response: response } }) this.preparingItemsArray = this.createItemArray() document.dispatchEvent(errorEvent) return response } // Merge and add the settings into the prototype this.cart = cart // Keep track of items in an id centric structure this.items = this.createItems(cart) this.selectors = Object.assign(defaultSelectors, selectors) this.options = Object.assign(defaultOptions, options) // Debounce requests this.requestTimeout = null this.preparingItemsArray = null // Build the event listeners from the selectors this.buildEventListeners(this.selectors) return this } buildEventListeners (selectors) { this.allSelectors = { addItem: { selector: selectors.addItem, listen: 'click', callback: () => this.addToCart }, removeItemSelectors: { selector: selectors.removeItem, listen: 'click', callback: () => this.removeFromCart }, decreaseQuantitySelectors: { selector: selectors.decreaseQuantity, listen: 'click', callback: () => this.decreaseQuantity }, increaseQuantitySelectors: { selector: selectors.increaseQuantity, listen: 'click', callback: () => this.increaseQuantity }, quickAddSelectors: { selector: selectors.quickAdd, listen: 'click', callback: () => this.quickAdd } } function on (eventName, selector, fn) { document.addEventListener(eventName, function (event) { var possibleTargets = document.querySelectorAll(selector) var target = event.target for (var i = 0, l = possibleTargets.length; i < l; i++) { var el = target var p = possibleTargets[i] while (el && el !== document) { if (el === p) { event.preventDefault() event.elem = p return fn.call(p, event) } el = el.parentNode } } }) } for (const key of Object.keys(this.allSelectors)) { const { listen, selector, callback } = this.allSelectors[key] on(listen, selector, callback) } // return this to and the ability to chain functions return this } addToCart (event) { const idInput = document.querySelector('[name=id]') const quantityInput = document.querySelector('[name=quantity]') const propertyInputs = document.querySelectorAll('[name*=properties]') const id = Number(idInput.value || idInput.options[idInput.selectedIndex].value) const quantity = Number(quantityInput.value) const properties = {} // Create an object of properties from the properties input fields for (let input = 0; input < propertyInputs.length; input += 1) { const element = propertyInputs[input] if (element.type === 'radio' && !element.checked) { continue } const key = element.getAttribute('name').split('[')[1].split(']')[0] const value = element.value properties[key] = value } const data = { id: id, quantity: quantity, properties: properties } this.addItem(data) } quickAdd (event) { const idAttribute = this.getDataAttribute(this.selectors.quickAdd) const qtyAttribute = this.getDataAttribute(this.selectors.quickAddQuantity) const propertiesAttribute = this.getDataAttribute(this.selectors.quickAddProperties) const id = Number(event.elem.getAttribute(idAttribute)) const quantity = Number(event.elem.getAttribute(qtyAttribute)) || 1 const properties = JSON.parse(event.elem.getAttribute(propertiesAttribute)) const data = { id: id, quantity: quantity, properties: properties } const config = { beforeRequestType: 'quickAdd' } this.addItem(data, config) } removeFromCart (event) { const attribute = this.getDataAttribute(this.selectors.removeItem) const line = Number(event.elem.getAttribute(attribute)) this.removeItemByLine(line) } decreaseQuantity (event) { const attribute = this.getDataAttribute(this.selectors.decreaseQuantity) const line = Number(event.elem.getAttribute(attribute)) const index = line - 1 const quantity = this.cart.items[index].quantity - 1 const config = { beforeRequestType: 'decreaseQuantity' } this.updateItemByLine(line, quantity, config) } increaseQuantity (event) { const attribute = this.getDataAttribute(this.selectors.increaseQuantity) const line = Number(event.elem.getAttribute(attribute)) const index = line - 1 const quantity = this.cart.items[index].quantity + 1 const config = { beforeRequestType: 'increaseQuantity' } this.updateItemByLine(line, quantity, config) } getCart (options = {}) { options.success = options.success || function (response) { const cart = JSON.parse(response) this.cart = cart const updateEvent = new CustomEvent('cart:updated', { detail: { response: cart } }) document.dispatchEvent(updateEvent) return this.cart } options.error = options.error || this.defaultError const request = { url: this.options.getUrl, method: 'GET', success: options.success, error: options.error } // Add the request to the ajax request queue this.queue.add(request) } addItem (item = {}, options = {}) { if (item.id === undefined) { return false } item.quantity = item.quantity || 1 item.properties = item.properties || {} // Define callback functions for after the ajax request has been completed options.beforeRequestType = options.beforeRequestType || 'addItem' options.beforeSend = options.beforeSend || this.defaultBeforeSend options.success = options.success || function (lineItem) { const item = JSON.parse(lineItem) this.getCart({ success: (response) => { const cart = JSON.parse(response) const addedEvent = new CustomEvent('cart:itemAdded', { detail: { response: { item: item, cart: cart } } }) this.preparingItemsArray = this.createItemArray() document.dispatchEvent(addedEvent) } }) } options.error = options.error || this.defaultError // Build the ajax request const request = { url: '/cart/add.js', data: item, success: options.success, error: options.error } // Send a beforeSend event and dd the request to the ajax request queue options.beforeSend(options.beforeRequestType, item.id) this.queue.add(request) return this } removeItemById (id, options = {}) { const data = { updates: {} } data.updates[id] = 0 options.beforeRequestType = options.beforeRequestType || 'removeItemById' options.beforeSend = options.beforeSend || this.defaultBeforeSend options.success = options.success || this.defaultSuccess options.error = options.error || this.defaultError const request = { url: '/cart/update.js', data: data, error: options.error, success: options.success } options.beforeSend(options.beforeRequestType, id) this.queue.add(request) return this } removeItemByLine (line, options = {}) { options.beforeRequestType = options.beforeRequestType || 'removeItemByLine' options.beforeSend = options.beforeSend || this.defaultBeforeSend options.success = options.success || this.defaultSuccess options.error = options.error || this.defaultError const request = { url: '/cart/change.js', data: { line: line, quantity: 0 }, success: options.success, error: options.error } options.beforeSend(options.beforeRequestType, line) this.queue.add(request) return this } updateItemById (id, quantity, options = {}) { const data = { updates: {} } data.updates[id] = quantity options.beforeRequestType = options.beforeRequestType || 'updateItemById' options.beforeSend = options.beforeSend || this.defaultBeforeSend options.success = options.success || this.defaultSuccess options.error = options.error || this.defaultError const request = { url: '/cart/update.js', data: data, error: options.error, success: options.success } options.beforeSend(options.beforeRequestType, id) this.queue.add(request) return this } updateItemByLine (line, quantity = 0, config) { clearTimeout(this.requestTimeout) this.preparingItemsArray = this.preparingItemsArray || this.createItemArray() this.preparingItemsArray[line - 1] = quantity const sendRequest = () => { const options = config || {} options.beforeRequestType = options.beforeRequestType || 'updateItemByLine' options.beforeSend = options.beforeSend || this.defaultBeforeSend options.success = options.success || this.defaultSuccess options.error = options.error || this.defaultError const updates = this.preparingItemsArray const request = { url: '/cart/update.js', data: { updates: updates }, success: options.success, error: options.error } options.beforeSend(options.beforeRequestType, line) this.queue.add(request) const newArray = [] for (let i = 0; i < this.preparingItemsArray.length; i += 1) { if (this.preparingItemsArray[i] > 0) { newArray.push(this.preparingItemsArray[i]) } } this.preparingItemsArray = newArray } this.requestTimeout = setTimeout(sendRequest, this.options.timeOut) return this } changeItemByLine (line, quantity, options = {}) { options.beforeRequestType = options.beforeRequestType || 'changeItemByLine' options.beforeSend = options.beforeSend || this.defaultBeforeSend options.success = options.success || this.defaultSuccess options.error = options.error || this.defaultError const data = { line: line, quantity: quantity } if (options.properties !== undefined) { data.properties = options.properties } const request = { url: '/cart/change.js', data: data, success: options.success, error: options.error } options.beforeSend(options.beforeRequestType, data) this.queue.add(request) return this } getShippingRates (options = {}) { options.success = options.success || this.defaultSuccess options.error = options.error || this.defaultError const request = { url: '/cart/shipping_rates.json?' + options.data, method: 'GET', success: options.success, error: options.error } this.queue.add(request) return this } setAttributes (options = {}) { options.beforeRequestType = options.beforeRequestType || 'setAttributes' options.beforeSend = options.beforeSend || this.defaultBeforeSend options.success = options.success || this.defaultSuccess options.error = options.error || this.defaultError const attributes = options.attributes || {} const data = { attributes: attributes } const request = { url: '/cart/update.js', success: options.success, error: options.error, data: data } if (attributes !== {}) { options.beforeSend(options.beforeRequestType, data) this.queue.add(request) } return this } setNote (options = {}) { options.beforeRequestType = options.beforeRequestType || 'setNote' options.beforeSend = options.beforeSend || this.defaultBeforeSend options.success = options.success || this.defaultSuccess options.error = options.error || this.defaultError const note = options.note || null const data = { note: note } const request = { url: '/cart/update.js', success: options.success, error: options.error, data: data } options.beforeSend(options.beforeRequestType, data) this.queue.add(request) return this } getDataAttribute (selector) { return selector.replace('[', '').replace(']', '') } createItems (cart) { const items = {} for (let i = 0; i < cart.items.length; i++) { items[cart.items[i].id] = { line: i + 1, quantity: cart.items[i].quantity, key: cart.items[i].key } } return items } createItemArray (cartObj) { const array = [] const cart = cartObj || this.cart for (let i = 0; i < cart.items.length; i++) { array.push(cart.items[i].quantity) } return array } }