1/* 2* Spinner.js 3*************************************************************** 4* Adds Increment/Decrement buttons to the right of quantity text boxes. 5* 6* Copyright 2021 AmeriCommerce 7*/ 8 9const getDefaults = () => ({ 10 increment : 1, 11 delay : 250, 12 interval : 50, 13 allowNegative: false, 14 minQty : 0, 15 maxQty : false, 16 small: themeSettings.enableSmallSpinner, 17 defaultInputAria: "Product Quantity", 18 customClass: {}, 19}); 20 21function Spinner (element, options) { 22 const spinnerCode = () => { 23 if(themeSettings.enableSpinner) { 24 if(typeof element == "string") { 25 const elements = document.querySelectorAll(element); 26 elements.forEach(function(el){ 27 new SpinnerEL(el, options); 28 }); 29 } 30 else 31 new SpinnerEL(element, options); 32 } 33 } 34 if (/complete|interactive|loaded/.test(document.readyState)) { 35 spinnerCode(); 36 } 37 else 38 document.addEventListener("DOMContentLoaded", function() { 39 spinnerCode(); 40 }); 41} 42 43function SpinnerEL (element, options) { 44 var self = this; 45 this.element = typeof element == "string" ? document.querySelector(element) : element; 46 this.isProductPage = document.body.classList.contains('ProductDetails'); 47 this.dataWrapper = this.element.closest(".qty-wrapper"); 48 this.element.value = this.element.value || 0; 49 const defaults = getDefaults(); 50 51 this.quantities = this.dataWrapper ? { 52 minQty : parseInt(self.dataWrapper.dataset["qtyMin"] || defaults.minQty, 10) || 0, 53 maxQty : parseInt(self.dataWrapper.dataset["qtyMax"] || defaults.maxQty, 10) || 0, 54 increment : parseInt(self.dataWrapper.dataset["qtyInc"] || defaults.increment) || 1, 55 allowNegative: self.dataWrapper.dataset["qtyNegative"] || defaults.allowNegative, 56 small: self.dataWrapper.dataset["qtySmall"] || defaults.small, 57 justify: self.dataWrapper.dataset["qtyJustify"], 58 inputAria: self.dataWrapper.dataset["qtyAria"] || defaults.defaultInputAria || "" 59 } : {minQty: 0, maxQty: 0, increment: 1, inputAria: "" }; 60 61 this._defaults = {...defaults, ...this.quantities}; 62 63 this.options = {...this._defaults, ...options}; 64 65 if(typeof this.options.small == "string") 66 this.options.small = this.options.small.toLowerCase() == "true"; 67 if(typeof this.options.allowNegative == "string") 68 this.options.allowNegative = this.options.allowNegative.toLowerCase() == "true"; 69 70 this.updatePrice = undefined; 71 this.variantController = undefined; 72 73 if(window.variantController!=null && window.variantController.registry.length){ 74 this.variantController = variantController.registry[0]; 75 this.updatePrice = this.variantController.updatePrice.bind(this.variantController); 76 } 77 this.out = true; 78 this.wrapper = null; 79 this.up = null 80 this.down = null; 81 82 this.interval = null; 83 this.timeout = null; 84 85 this.val = parseInt(this.element.value); 86 this.prevVal = this.val; 87 88 this.init(); 89} 90SpinnerEL.prototype = { 91 init: function(){ 92 93 const parentJustify = this.element.parentNode.getAttribute("qty-justify") || this.options.justify; 94 const { customClass, small, inputAria } = this.options; 95 96 // Apply classes to input 97 this.element.classList.add("border-0"); 98 if(!isLabelled(this.element)) 99 this.element.setAttribute("aria-label", inputAria) 100 101 // Wrap element node 102 const wrapper = document.createElement('div'); 103 wrapper.classList.add("qty-spinner", "input-group", "d-inline-flex", "align-items-center"); 104 if(parentJustify) 105 wrapper.classList.add(`justify-content-${parentJustify}`); 106 if(typeof customClass.wrapper == "string") wrapper.classList.add(...customClass.wrapper.split(/\s+/)); 107 if(small) wrapper.classList.add("input-group-sm"); 108 this.element.parentNode.insertBefore(wrapper, this.element); 109 wrapper.appendChild(this.element); 110 this.wrapper = this.element.parentElement; 111 112 // Create Buttons 113 this.up = document.createElement("div"); 114 if(typeof customClass.up == "string") this.up.classList.add(...customClass.up.split(/\s+/)); 115 this.up.innerHTML = `<a class='btn btn-outline-dark ${this.options.small?"btn-sm":""} spinnerBtn'><icon class='fas fa-plus'></icon></a>`; 116 117 this.down = document.createElement("div"); 118 if(typeof customClass.down == "string") this.down.classList.add(...customClass.down.split(/\s+/)); 119 this.down.innerHTML = `<a class='btn btn-outline-dark ${this.options.small?"btn-sm":""} spinnerBtn'><icon class='fas fa-minus'></icon></a>`; 120 121 this.wrapper.insertBefore(this.down, this.element) 122 this.wrapper.appendChild(this.up); 123 124 if(!this.variantController){ 125 this.updatePrice = this.updatePriceFallback; 126 } 127 128 this.bind(); 129 }, 130 131 updatePriceFallback: function(){ 132 if (this.isProductPage) { 133 var itemID = $("#hfItemID").val(); 134 var quantity = $(this.element).val(); 135 var params = { 136 itemID: itemID, 137 quantity: quantity, 138 variantIDs: [], 139 personalizationIds: [], 140 personalizationStrings: [] 141 }; 142 // "{'itemID': " + itemID + ", 'quantity': " + quantity + "}"; 143 var self = this; 144 //console.log(params); 145 $.ajax({ 146 type: "POST", 147 contentType: "application/json; charset=utf-8", 148 url: "/Store/Controls/ScriptService.asmx/GetPrice", 149 dataType: "json", 150 data: JSON.stringify(params), 151 success: function(response){ 152 //console.log(response); 153 $(".PriceToUpdate").html(response.d.price); 154 } 155 }); 156 } 157 }, 158 159 bind: function(){ 160 var self = this; 161 162 function down (dec) { 163 if(self.out) { 164 self.prevVal = this.val; 165 self.out = false;
166 } 167 168 if(dec) 169 self.decrement(); 170 else 171 self.increment(); 172 173 174 self.timeout = setTimeout(function(){ 175 self.interval = setInterval($.proxy(dec?self.decrement:self.increment, self), self.options.interval); 176 }, self.options.delay); 177 } 178 function out () { 179 self.out = true; 180 self.updatePrice(); 181 clearTimeout(self.timeout); 182 clearInterval(self.interval); 183 if(self.val !== self.prevVal) 184 self.element.dispatchEvent(new Event("change")); 185 self.prevVal = self.val; 186 } 187 188 this.up.addEventListener('mousedown', down.bind(self, false)); 189 this.up.addEventListener('mouseup', out); 190 this.up.addEventListener('mouseleave', out); 191 192 this.down.addEventListener('mousedown', down.bind(self, true)); 193 this.down.addEventListener('mouseup', out); 194 this.down.addEventListener('mouseleave', out); 195 }, 196 197 increment: function(){ 198 var self = this; 199 this.val = parseInt(self.element.value, 10) + this.options.increment; 200 if (!this.options.maxQty || this.val <= this.options.maxQty) 201 this.element.value = this.val; 202 this.element.setAttribute("value", this.element.value); 203 }, 204 205 decrement: function(){ 206 var self = this; 207 this.val = parseInt(self.element.value, 10) - this.options.increment; 208 if (this.val >= this.options.minQty || this.options.allowNegative) 209 this.element.value = this.val; 210 211 this.element.setAttribute("value", this.element.value); 212 } 213};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.