PageSourceSearch

https://www.elive.co.nz/store/js/components/product-info.js?aba34601

js elive.co.nz collected 2026-09-25 20:12:33 UTC 10,162 bytes, 368 lines download raw bytes

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.