UNPKG

@brodsolutions/bulk-order-chart

Version:

Handles the root functionality of the bulk order chart on Bigcommerce product pages

419 lines (418 loc) 19.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BulkOrderChart = void 0; // @ts-ignore const stencil_utils_1 = __importDefault(require("@bigcommerce/stencil-utils")); class BulkOrderChart { constructor(context, config) { if (!context.product) { throw new Error('Bulk order chart requires product object to be injected on custom product template.'); } this.context = context; const baseConfig = { show_price_range: false, variant: 'Size', show_price_or_discount: 'price', messages: { required_field: 'Verify all required fields are filled out', required_input: 'Please select a value from the chart', min_purchase_note: `Please note: you must purchase at least ${this.context.product.min_purchase_quantity || 1} total quantity` }, colors: { black: '#666666', red: '#FF0000', green: '#036603' } }; this.config = Object.assign(Object.assign({}, baseConfig), config); } init() { const option = this.getBulkChartVariantOptions(); this.HMTLElements().showBulkChartCheckbox.addEventListener('change', () => { var _a, _b; const variantToHide = (_a = document.querySelector(`[name="attribute[${option.id}]"]`)) === null || _a === void 0 ? void 0 : _a.closest(".form-field"); if (!variantToHide) { throw new Error(`Could not find the variant element with a name "attribute[${option.id}]".`); } variantToHide.style.display = 'none'; const firstVariantOption = variantToHide.querySelector("[name]"); switch (firstVariantOption.type) { case 'radio': firstVariantOption.setAttribute("checked", ""); break; case 'select-one': firstVariantOption.lastElementChild.setAttribute("selected", ""); break; default: throw new Error('Invalid input type'); } this.HMTLElements().quantitySelection.style.display = 'none'; const checkboxWrapper = this.HMTLElements().showBulkChartCheckbox.closest('.form-field'); checkboxWrapper.style.display = 'none'; this.HMTLElements().addToCartButton.disabled = false; const bulkRates = this.getBulkDiscountRates(); const bulkHeader = this.createBulkChartHeader(bulkRates); const bulkRows = option.values.map(value => this.createTableRow({ bulkRates, option, value })); const bulkOrderChart = this.createBulkChart({ header: bulkHeader, rows: bulkRows }); (_b = document .getElementById('bulk-order-chart-field')) === null || _b === void 0 ? void 0 : _b.appendChild(bulkOrderChart); // replace later to listen to add to cart bulkOrderChart.addEventListener('input', () => { this.updateBulkTotal({ bulkRates }); }); this.HMTLElements().addToCartButton.addEventListener('click', e => { e.preventDefault(); const formLabels = Array.from(this.HMTLElements().productOptionsForm.querySelectorAll('.form-label')); formLabels.forEach(label => label.removeAttribute('style')); const inputsAreValid = this.allRequiredOptionsValueValues(); this.hideMinPurchaseNote(); if (!inputsAreValid.valid) { this.showErrorOnInvalidOptions({ invalidOptions: inputsAreValid.elements }); this.showMinPurchaseNote({ message: this.config.messages.required_field, color: this.config.colors.red }); return; } this.addToCart({ bulkValues: this.HMTLElements().bulkInputsWithValues }); }); }); } HMTLElements() { let showBulkChartCheckbox = document.getElementById('show-bulk-order-chart'); if (!showBulkChartCheckbox) { throw new Error('Show bulk order chart checkbox is missing - required to trigger the chart display'); } let quantitySelection = document.querySelector('.form-field--increments'); if (!quantitySelection) { throw new Error('Could not find a div element with the class name "form-field--increments".'); } let bulkInputs = Array.from(document.querySelectorAll('.bulk-input')); if (!bulkInputs) { throw new Error('Could not find any inputs with the class name "bulk-input".'); } let bulkInputsWithValues = bulkInputs.filter(input => input.value); let productOptionsForm = document.querySelector('[data-cart-item-add]'); if (!productOptionsForm) { throw new Error('Could not find a form with the attribute "[data-cart-item-add]".'); } let addToCartButton = productOptionsForm.querySelector('#form-action-addToCart'); if (!addToCartButton) { throw new Error('Could not find a button with the id "form-action-addToCart".'); } let cartQuantityValue = document.querySelector('.cart-quantity'); if (!cartQuantityValue) { throw new Error('Could not find a span element with the class name "cart-quantity".'); } return { showBulkChartCheckbox, quantitySelection, bulkInputs, bulkInputsWithValues, productOptionsForm, addToCartButton, cartQuantityValue }; } createBulkChart({ header, rows }) { var _a; const widgetContainer = document.createElement('div'); const minPurchaseNote = this.createMinPurchaseNote(); const bulkTotal = this.createBulkTotalContainer(); const bulkOrderChart = document.createElement('div'); bulkOrderChart.id = 'bulk-order-chart-wrapper'; bulkOrderChart.innerHTML = ` <table id="bulk-order-chart"> <thead id="bulk-order-chart-head"></thead> <tbody id="bulk-order-chart-body"></tbody> </table> `; (_a = bulkOrderChart.querySelector('#bulk-order-chart-head')) === null || _a === void 0 ? void 0 : _a.appendChild(header); rows.forEach(row => { var _a; return (_a = bulkOrderChart.querySelector('#bulk-order-chart-body')) === null || _a === void 0 ? void 0 : _a.appendChild(row); }); widgetContainer.appendChild(minPurchaseNote); widgetContainer.appendChild(bulkOrderChart); widgetContainer.appendChild(bulkTotal); return widgetContainer; } createMinPurchaseNote() { const minPurchaseNote = document.createElement('p'); minPurchaseNote.id = 'bulk-min-purchase-note'; minPurchaseNote.style.display = 'none'; minPurchaseNote.innerText = this.config.messages.min_purchase_note; return minPurchaseNote; } createBulkTotalContainer() { const bulkTotal = document.createElement('div'); bulkTotal.id = 'bulk-total-wrapper'; bulkTotal.innerHTML = ` <div> <span style='margin-right: 10px;'>Total:</span> $<input id='bulk-total-value' type='text' value='0.00' style='border: none; padding: 0; width: 64px;' readonly min='0'> </div> <span id='bulk-savings' style='color: ${this.config.colors.green};'></span> `; return bulkTotal; } showMinPurchaseNote({ message = this.config.messages.min_purchase_note, color = this.config.colors.black }) { const minPurchaseNote = document.getElementById('bulk-min-purchase-note'); if (!minPurchaseNote) { throw new Error('Bulk chart must have an element with an ID of minPurchaseNote'); } minPurchaseNote.style.display = 'block'; minPurchaseNote.style.color = color; minPurchaseNote.innerText = message; } hideMinPurchaseNote() { const minPurchaseNote = document.getElementById('bulk-min-purchase-note'); if (!minPurchaseNote) { throw new Error('Bulk chart must have an element with an ID of minPurchaseNote'); } if (minPurchaseNote.style.display !== 'none') minPurchaseNote.style.display = 'none'; } getBulkDiscountRates() { const bulkDiscountRates = this.context.product.bulk_discount_rates; if (!bulkDiscountRates) { throw new Error('Bulk chart must have bulk discount rates'); } return bulkDiscountRates.map(rate => { return { min: rate.min, max: rate.max, discount: rate.discount.value, type: rate.type }; }); } createBulkChartHeader(bulkRates) { const bulkChartHeader = document.createElement('tr'); const firstColumnEmpty = document.createElement('th'); firstColumnEmpty.innerText = this.config.variant; bulkChartHeader.appendChild(firstColumnEmpty); const basePriceColumn = document.createElement('th'); if (this.config.show_price_range) { basePriceColumn.innerText = `${this.context.product.min_purchase_quantity || 1} - ${bulkRates[0].min - 1}`; } else { basePriceColumn.innerText = `${this.context.product.min_purchase_quantity || 1}`; } bulkChartHeader.appendChild(basePriceColumn); // append each bulk rate as a header column for (const bulkRate of bulkRates) { const th = document.createElement('th'); if (this.config.show_price_range) { th.innerText = bulkRate.max > 0 ? `${bulkRate.min} - ${bulkRate.max}` : `${bulkRate.min}+`; } else { th.innerText = bulkRate.min.toString(); } bulkChartHeader.appendChild(th); } // append the heading for the qty column after all bulk rates are added const quantityHeading = document.createElement('th'); quantityHeading.innerText = 'Quantity'; bulkChartHeader.appendChild(quantityHeading); return bulkChartHeader; } getBulkChartVariantOptions() { let options = this.context.product.options.filter(option => option.display_name.toLowerCase() === this.config.variant.toLowerCase())[0]; if (!options) { options = this.context.product.options[0]; } return options; } createTableRow({ bulkRates, option, value }) { const tr = document.createElement('tr'); const price = this.context.product.price.without_tax.value; const variant = { option_id: 0, variant_id: 0 }; variant.option_id = option.id; variant.variant_id = value.id; const td = document.createElement('td'); td.innerText = value.label; tr.appendChild(td); const basePriceTd = document.createElement('td'); basePriceTd.innerText = `$${price}`; tr.appendChild(basePriceTd); for (const bulkRate of bulkRates) { const td = document.createElement('td'); const discountedPrice = this.calculateDiscount({ bulkRate, price }); td.innerText = discountedPrice; tr.appendChild(td); } const inputTd = document.createElement('td'); inputTd.appendChild(this.createQuantityInput(Object.assign({}, variant))); tr.appendChild(inputTd); return tr; } calculateDiscount({ bulkRate, price }) { switch (bulkRate.type) { case 'fixed': return `$${bulkRate.discount.toFixed(2)}`; case 'percent': if (this.config.show_price_or_discount === 'price') { const percentOff = price * (bulkRate.discount / 100); const discountAmount = price - percentOff; return `$${discountAmount.toFixed(2)}`; } return `${bulkRate.discount}% off`; case 'price': // dollar off if (this.config.show_price_or_discount === 'price') { const discountAmount = price - bulkRate.discount; return `$${discountAmount.toFixed(2)}`; } return `$${bulkRate.discount.toFixed(2)} off`; default: throw new Error('Invalid bulk rate discount type'); } } calculateDiscountTotal({ bulkRates, totalQuantity }) { const basePrice = this.context.product.price.without_tax.value; const bulkRate = bulkRates .sort((a, b) => b.min - a.min) .filter(bulkRate => totalQuantity >= bulkRate.min)[0]; if (!bulkRate) return basePrice * totalQuantity; let discountAmount; switch (bulkRate.type) { case 'fixed': return bulkRate.discount * totalQuantity; case 'percent': const percentOff = basePrice * (bulkRate.discount / 100); discountAmount = basePrice - percentOff; return Number(discountAmount.toFixed(2)) * totalQuantity; case 'price': // dollar off discountAmount = basePrice - bulkRate.discount; return discountAmount * totalQuantity; default: throw new Error('Invalid bulk rate discount type'); } } createQuantityInput({ option_id, variant_id }) { const quantityInput = document.createElement('input'); quantityInput.type = 'number'; quantityInput.className = 'bulk-input'; quantityInput.placeholder = '0'; quantityInput.setAttribute('data-option-id', variant_id.toString()); quantityInput.name = `qty[${option_id}_${variant_id}]`; return quantityInput; } getTotalQuantityOnBulkChart() { return this.HMTLElements().bulkInputsWithValues.reduce((acc, curr) => curr.valueAsNumber + acc, 0); } allRequiredOptionsValueValues() { const requiredFields = Array.from(document.querySelectorAll('.productView-options .form-field [name]')); const invalidFields = []; for (const field of requiredFields) { if (!field.validity.valid) { invalidFields.push(field); } } return { elements: Array.from(invalidFields), valid: invalidFields.length <= 0 }; } updateBulkTotal({ bulkRates }) { const basePrice = this.context.product.price.without_tax.value; const totalQuantity = this.getTotalQuantityOnBulkChart(); const totalDiscountPrice = this.calculateDiscountTotal({ bulkRates, totalQuantity }); const totalSavings = basePrice * totalQuantity - totalDiscountPrice; const bulkTotalInput = document.getElementById('bulk-total-value'); if (!bulkTotalInput) { throw new Error('Could not find an element with the id "bulk-total-value'); } bulkTotalInput.value = totalDiscountPrice.toFixed(2); const bulkSavingsSpan = document.getElementById('bulk-savings'); if (!bulkSavingsSpan) { throw new Error('Could not find an element with the id "bulk-savings".'); } if (totalSavings > 0) { bulkSavingsSpan.innerText = `You save $${totalSavings.toFixed(2)}!`; } else { bulkSavingsSpan.innerText = ''; } } showErrorOnInvalidOptions({ invalidOptions }) { for (const option of invalidOptions) { const fieldWrapper = option.closest('.form-field'); const fieldLabel = fieldWrapper.querySelector('.form-label'); fieldLabel.style.color = this.config.colors.red; } } addToCart({ bulkValues }) { if (bulkValues.length <= 0) { this.showMinPurchaseNote({ message: `You must add at least ${this.context.product.max_purchase_quantity || 1} product${this.context.product.min_purchase_quantity <= 1 ? '' : 's'} to your cart.`, color: this.config.colors.red }); return; } const item = bulkValues[bulkValues.length - 1]; // _item is unused, but is still returned from .match() so has to be accounted for const [_item, optionId, variantId] = item.name.match(/\[(\d+)_(\d+)\]/) || []; if (!optionId || !variantId) { throw new Error('Could not get Option ID or Variant ID from bulk input.'); } const formData = new FormData(); formData.append('action', 'add'); formData.append('product_id', `${this.context.product.id}`); formData.append(`attribute[${optionId}]`, variantId); formData.append('qty[]', item.value); for (const [key, value] of new FormData(this.HMTLElements().productOptionsForm).entries()) { if (Array.from(formData.keys()).some(k => k === key)) continue; if (!value) continue; if (key.includes('qty')) continue; formData.append(key, value); } bulkValues.pop(); this.HMTLElements().cartQuantityValue.style.display = 'inline-block'; this.HMTLElements().cartQuantityValue.innerHTML = (Number(this.HMTLElements().cartQuantityValue.innerHTML) + Number(item.value)).toString(); // @ts-ignore stencil_utils_1.default.api.cart.itemAdd(formData, (err, res) => { if (bulkValues.length > 0) { this.HMTLElements().addToCartButton.value = 'Adding to Cart...'; this.HMTLElements().addToCartButton.innerText = 'Adding to Cart...'; this.HMTLElements().addToCartButton.disabled = true; return this.addToCart({ bulkValues }); } else { this.HMTLElements().addToCartButton.innerText = 'Added to Cart!'; this.HMTLElements().addToCartButton.value = 'Added to Cart!'; setTimeout(() => { this.HMTLElements().addToCartButton.innerText = 'Add To Cart'; this.HMTLElements().addToCartButton.value = 'Add To Cart'; this.HMTLElements().addToCartButton.disabled = false; this.HMTLElements().bulkInputs.forEach(input => (input.value = '')); document.getElementById('bulk-savings').innerText = ''; const bulkTotalInput = document.getElementById('bulk-total-value'); bulkTotalInput.value = '0.00'; }, 3000); } }); } } exports.BulkOrderChart = BulkOrderChart;