1customElements.define('product-info', class extends HTMLElement { 2 static get observedAttributes() { 3 return [ 4 'for', 5 'initial-discount', 6 'increase-per-item', 7 'maximum-discount', 8 'min-quantity', 9 ] 10 } 11 12 // Elements 13 input = null 14 discountAmountElement = null 15 quantityStepUpElement = null 16 quantityStepDownElement = null 17 totalAmountElement = null 18 19 constructor() { 20 super() 21 22 // Elements 23 this.input = this.querySelector(`#${this.for}`) 24 this.discountAmountElement = this.querySelector('discount-amount') 25 this.priceElement = this.querySelector('product-price') 26 this.totalAmountElement = this.querySelector('total-amount') 27 this.rrpDiscountElement = this.querySelector('rrp-discount') 28 29 this.quantityStepUpElement = this.querySelector('.step-up') 30 this.quantityStepDownElement = this.querySelector('.step-down') 31 32 // early return if no input 33 if (!this.input) return 34 35 // add quantity change event listener 36 this.input.addEventListener('input', this) 37 38 // add quantity step event listeners 39 if (this.quantityStepUpElement && this.quantityStepDownElement) { 40 this.quantityStepUpElement.addEventListener('click', this) 41 this.quantityStepDownElement.addEventListener('click', this) 42 } 43 } 44 45 connectedCallback() { 46 // Set initial discount if we have an input 47 if (this.input) { 48 this.calculateDiscount() 49 50 this.checkMinQuantity() 51 } 52 } 53 54 disconnectedCallback() { 55 this.input?.removeEventListener('input', this) 56 this.quantityStepUpElement?.removeEventListener('click', this) 57 this.quantityStepDownElement?.removeEventListener('click', this) 58 } 59 60 /** 61 * Handle events in the Web Component 62 * @param {Event} event The Event object 63 */ 64 handleEvent(event) { 65 switch (event.type) { 66 case 'input': 67 this.calculateDiscount() 68 break 69 70 case 'initial-discount': 71 case 'increase-per-item': 72 case 'maximum-discount': 73 case 'min-quantity': 74 this.calculateDiscount() 75 break 76 case 'click': 77 if (event.currentTarget === this.quantityStepUpElement) { 78 this.input.stepUp() 79 this.input.dispatchEvent(new Event('input', { bubbles: true })) 80 } 81 else if (event.currentTarget === this.quantityStepDownElement) { 82 this.input.stepDown() 83 this.input.dispatchEvent(new Event('input', { bubbles: true })) 84 } 85 86 this.checkMinQuantity() 87 88 break 89 } 90 } 91 92 attributeChangedCallback(name, oldValue, newValue) { 93 switch (name) { 94 case 'for': 95 this.input = this.querySelector(`#${newValue}`) 96 break 97 } 98 } 99 100 checkMinQuantity() { 101 if (this.input.value <= this.minQuantity) { 102 this.quantityStepDownElement.disabled = true 103 } else { 104 this.quantityStepDownElement.disabled = false 105 } 106 } 107 108 calculateDiscount() { 109 // console.log('product-info loaded'); 110 if (this.quantity >= 1 && this.disabled === false) { 111 // reset discount 112 // this.discountPerItem = 0 113 let discount = 0, 114 eligibleItems = 0 115 116 const startQty = Math.max(2, (this.minQuantity || 1) + 1) 117 118 if (this.quantity >= startQty) { 119 // set initial discount and eligible items 120 discount = this.initialDiscount 121 eligibleItems = this.quantity - startQty 122 123 // increase discount based on eligible items 124 discount += this.increasePerItem * eligibleItems 125 126 // cap discount at maximum 127 if (discount > this.maximumDiscount) { 128 discount = this.maximumDiscount 129 } 130 } 131 132 // apply discount 133 this.discountPerItem = discount 134 this.discount = discount * this.quantity 135 136 // dispatch event 137 this.dispatchEvent(new CustomEvent('discount-change', { 138 detail: { 139 discount: this.discount, 140 formatted: this.money(this.discount, this.priceElement?.hasAttribute('fancy')), 141 }, 142 bubbles: true 143 })) 144 } 145 } 146 147 money(value, fancy = false) { 148 let money = new Intl.NumberFormat('en-NZ', { 149 style: 'currency', 150 currency: 'NZD', 151 }).format(value) 152 153 if (fancy == true) { 154 return money.replace('$', '<sup>$</sup>') 155 .replace(/(\.[\d]*)$/, '<sup>$1</sup>') 156 } 157 158 return money 159 } 160 161 get for() { 162 return this.getAttribute('for') || 'quantity' 163 } 164 165 /** 166 * @param {string} value 167 */ 168 set for(value) { 169 this.setAttribute('for', value) 170 } 171 172 get disabled() { 173 return this.hasAttribute('disabled') 174 } 175 176 get rrp() { 177 return Number(this.getAttribute('rrp')) || 0 178 } 179 180 set rrp(value) { 181 this.setAttribute('rrp', value) 182 } 183 184 /** 185 * @param {number} value 186 */ 187 set discount(value) { 188 if (this.discountAmountElement) { 189 let discounrMessage = this.noDiscountMessage.replace(':discount', this.money(value)) 190 if (value > 0) { 191 discounrMessage = this.discountMessage.replace(':discount', this.money(value)) 192 } 193 194 this.discountAmountElement.innerHTML = discounrMessage 195 } 196 197 } 198 199 /** 200 * @param {number} value 201 */ 202 set discountPerItem(value) { 203 if (this.priceElement) { 204 this.priceElement.innerHTML = this.money( 205 Number(this.priceElement.getAttribute('rrp') || 0) - value, 206 this.priceElement.hasAttribute('fancy') 207 ) 208 } 209 210 if (this.rrpDiscountElement) { 211 this.rrpDiscountElement.setAttribute('discount', value) 212 } 213 214 if (this.totalAmountElement) { 215 const itemPrice = this.totalAmountElement.getAttribute('rrp') 216 const total = (Number(this.quantity * itemPrice || 0) - (this.quantity * value)).toFixed(2) 217 218 this.totalAmountElement.setAttribute('discount', this.quantity * value) 219 this.totalAmountElement.setAttribute('total', total) 220 this.totalAmountElement.innerHTML = this.money( 221 total, 222 this.totalAmountElement.hasAttribute('fancy') 223 ) 224 } 225 } 226 227 get discountMessage() { 228 if (this.hasAttribute('discount-message')) { 229 return this.getAttribute('discount-message') 230 } 231 232 if (this.quantity == 1) { 233 return 'Add 1 more for a Discount' 234 } 235 236 return 'Includes Discount of <strong>:discount</strong>' 237 } 238 239 set discountMessage(value) { 240 this.setAttribute('discount-message', value) 241 } 242 243 get noDiscountMessage() { 244 return this.getAttribute('no-discount-message') || 'Add <strong>1</strong> more for a <strong>Discount</strong>' 245 } 246 247 set noDiscountMessage(value) { 248 this.setAttribute('no-discount-message', value) 249 } 250 251 get initialDiscount() { 252 return Number(this.getAttribute('initial-discount')) || 0 253 } 254 255 set initialDiscount(value) { 256 this.setAttribute('initial-discount', value) 257 } 258 259 get increasePerItem() { 260 return Number(this.getAttribute('increase-per-item')) || 0 261 } 262 263 set increasePerItem(value) { 264 this.setAttribute('increase-per-item', value) 265 } 266 267 get maximumDiscount() { 268 return Number(this.getAttribute('maximum-discount')) || 0 269 } 270 271 set maximumDiscount(value) { 272 this.setAttribute('maximum-discount', value) 273 } 274 275 get minQuantity() { 276 return Number(this.getAttribute('min-quantity')) || 1 277 } 278 279 set minQuantity(value) { 280 this.setAttribute('min-quantity', value) 281 } 282 283 get quantity() { 284 return Math.floor(Number(this.input.value) || 0) 285 } 286 287 set quantity(value) { 288 this.input.value = value 289 } 290}) 291 292customElements.define('rrp-discount', class extends HTMLElement { 293 static get observedAttributes() { 294 return [ 295 'rrp',
296 'saving', 297 'discount', 298 "render-as-percentage" 299 ] 300 } 301 302 constructor() { 303 super() 304 } 305 306 connectedCallback() { 307 this.render() 308 } 309 310 attributeChangedCallback(name, oldValue, newValue) { 311 this.render() 312 } 313 314 render() { 315 if (this.saving > 0 || this.discount > 0) { 316 if (this.renderAsPercentage && this.rrp > 0) { 317 const totalSavings = this.saving + this.discount; 318 const savingsPercentage = Math.round((totalSavings / this.rrp) * 100); 319 this.innerHTML = savingsPercentage > 0 ? savingsPercentage : 1 + '%' 320 } else { 321 this.innerHTML = this.money(this.saving + this.discount) 322 } 323 } 324 } 325 326 money(value) { 327 return new Intl.NumberFormat('en-NZ', { 328 style: 'currency', 329 currency: 'NZD', 330 }).format(value) 331 } 332 333 get renderAsPercentage() { 334 return this.hasAttribute('as-percentage') 335 } 336 337 get saving() { 338 return Number(this.getAttribute('saving')) || 0 339 } 340 341 set saving(value) { 342 this.setAttribute('saving', value) 343 } 344 345 get price() { 346 return Number(this.getAttribute('price')) || 0 347 } 348 349 set price(value) { 350 this.setAttribute('price', value) 351 } 352 353 get rrp() { 354 return Number(this.getAttribute('rrp')) || 0 355 } 356 357 set rrp(value) { 358 this.setAttribute('rrp', value) 359 } 360 361 get discount() { 362 return Number(this.getAttribute('discount')) || 0 363 } 364 365 set discount(value) { 366 this.setAttribute('discount', value) 367 } 368})
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.