1document.addEventListener('DOMContentLoaded', () => { 2 class VouchersModal extends HTMLElement { 3 constructor() { 4 super(); 5 this.voucherModal = document.getElementById('vouchers-modal'); 6 this.voucherModalBackdrop = document.getElementById('vouchers-modal-backdrop'); 7 this.onBodyClick = this.handleBodyClick.bind(this); 8 9 this.voucherModal.addEventListener('keyup', (evt) => evt.code === 'Escape' && this.close()); 10 this.querySelectorAll('.vouchers-modal__close').forEach((closeButton) => 11 closeButton.addEventListener('click', this.close.bind(this)) 12 ); 13 14 this.eventHandlers = []; // Store event handlers to remove them later 15 } 16 17 open() { 18 this.voucherModal.classList.add('animate', 'active'); 19 this.voucherModalBackdrop.classList.add('active'); 20 21 this.voucherModal.addEventListener('transitionend', () => { 22 this.voucherModal.focus(); 23 trapFocus(this.voucherModal); 24 }, { once: true }); 25 26 document.body.style.overflow = 'hidden'; 27 document.body.addEventListener('click', this.onBodyClick, { capture: true }); 28 29 // Render voucher placeholders 30 this.initVoucherWidget(); 31 } 32 33 close() { 34 this.voucherModal.classList.remove('active'); 35 this.voucherModalBackdrop.classList.remove('active'); 36 37 document.body.style.overflow = ''; 38 document.body.removeEventListener('click', this.onBodyClick, { capture: true }); 39 40 // Clear voucher placeholders 41 const $voucherElements = document.querySelectorAll("vouchers-modal [data-voucher-widget]"); 42 $voucherElements.forEach(element => element.innerHTML = ""); 43 44 // Remove all event listeners attached to buttons 45 this.eventHandlers.forEach(({ button, handler }) => { 46 button.removeEventListener("click", handler); 47 }); 48 49 // Clear stored event handlers 50 this.eventHandlers = []; 51 } 52 53 handleBodyClick(evt) { 54 const target = evt.target; 55 if (target !== this.voucherModal && !target.closest('vouchers-modal')) { 56 this.close(); 57 } 58 } 59 60 setActiveElement(element) { 61 this.activeElement = element; 62 } 63 64 // Function to process all voucher placeholders with [data-voucher-widget] 65 initVoucherWidget() { 66 const $voucherElements = $("vouchers-modal [data-voucher-widget]"); 67 68 $voucherElements.each((index, element) => { 69 const voucherData = JSON.parse(element.getAttribute("data-voucher-widget")); 70 const voucherWidget = createVoucherWidget(this.formatVoucherData(voucherData)); 71 72 element.setAttribute("data-type", voucherData.is_enabled ? "active" : "inactive"); 73 if (!voucherData.is_enabled) { 74 element.classList.add("disabled"); 75 } 76 77 element.appendChild(voucherWidget); 78 79 // Add event listener to button 80 setTimeout(() => { 81 const button = element.querySelector(".voucher-widget-button"); 82 if (button) { 83 const clickHandler = (event) => { 84 this.onButtonClick(event, voucherData); 85 }; 86 button.addEventListener("click", clickHandler); 87 // Store reference for removal later 88 this.eventHandlers.push({ button, handler: clickHandler }); 89 } 90 }, 1000); 91 }); 92 } 93 94 formatVoucherData(data) { 95 this.voucherData = { 96 title: data.title, 97 details: data, 98 } 99 100 return { 101 "discount": data.title, 102 "isShowQuantity": true, 103 "isHoverable": false, 104 "condition": getVoucherCondition(this.voucherData), 105 "badge": getPrerequisiteCondition(this.voucherData, _getPrerequisiteConditionTranslation), 106 "expiry": data.is_enabled ? " " : getVoucherUnavailableText(data, _getVoucherErrorTranslation), 107 "highlightExpiry": { 108 "isHighlightExpiry": data.is_enabled ? false : true, 109 "highlightType": "danger", 110 }, 111 "buttonText": _getVoucherButtonTextTranslation.apply, 112 "quantity": data.discount_codes_count || 1, 113 "isShowButton": data.is_enabled, 114 "icon": getVoucherIcon(this.voucherData), 115 }; 116 } 117 118 onButtonClick(event, data) { 119 event.preventDefault(); 120 121 let code = data?.soonest_expiring_discount_code 122 ?? data.discount_codes[0] 123 ?? ""; 124 this.hideErrorMsg(); 125 126 const allVouchers = document.querySelectorAll("vouchers-modal [data-voucher-widget]"); 127 const button = event.currentTarget; 128 129 if (code && button) {
130 allVouchers.forEach(voucher => voucher.style.pointerEvents = "none"); 131 button.classList.add('loading') 132 fetch('/new_cart/voucher', { 133 method: 'POST', 134 headers: { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest", }, 135 body: JSON.stringify({ 136 category: 'create', 137 voucher_code: code 138 }), 139 }) 140 .then(response => response.json()) 141 .then((data) => { 142 console.log('/new_cart/voucher', data); 143 button.classList.remove('loading'); 144 allVouchers.forEach(voucher => voucher.style.removeProperty("pointer-events")); 145 146 if (data.error && data.error.message) this.renderErrorMsg(data.error.message) 147 if (data.cart_content) { 148 document.querySelector('#cart-template , #CartTemplate').innerHTML = data.cart_content; 149 document.querySelector('vouchers-modal').close() 150 } 151 }) 152 } 153 154 } 155 156 renderErrorMsg(html) { 157 this.querySelector('.form__message').classList.remove('hidden') 158 this.querySelector('.js-error-content').innerHTML = html 159 } 160 161 hideErrorMsg() { 162 this.querySelector('.form__message').classList.add('hidden') 163 } 164 } 165 customElements.define('vouchers-modal', VouchersModal); 166 167 // TODO: To be removed 168 class VouchersList extends HTMLElement { 169 constructor() { 170 super(); 171 this.radioButtons = this.querySelectorAll('input[name="voucher_code"]'); 172 this.button = this.querySelector('button'); 173 174 this.button.addEventListener('click', this.onButtonClick.bind(this)) 175 this.radioButtons.forEach(radioButton => radioButton.addEventListener('change', (event) => { 176 this.hideErrorMsg() 177 })) 178 } 179 180 onButtonClick(event) { 181 event.preventDefault(); 182 this.hideErrorMsg(); 183 let radioValue 184 for (const radioButton of this.radioButtons) { 185 if (radioButton.checked) { 186 radioValue = radioButton.value; 187 break; 188 } 189 } 190 191 if (radioValue) { 192 this.button.classList.add('loading', 'btn--loading') 193 fetch('/new_cart/voucher', { 194 method: 'POST', 195 headers: { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest", }, 196 body: JSON.stringify({ 197 category: 'create', 198 voucher_code: radioValue 199 }), 200 }) 201 .then(response => response.json()) 202 .then((data) => { 203 console.log('/new_cart/voucher', data); 204 this.button.classList.remove('loading', 'btn--loading') 205 if (data.error && data.error.message) this.renderErrorMsg(data.error.message) 206 if (data.cart_content) { 207 document.querySelector('#cart-template , #CartTemplate').innerHTML = data.cart_content; 208 document.querySelector('vouchers-modal').close() 209 } 210 }) 211 } 212 213 } 214 215 renderErrorMsg(html) { 216 this.querySelector('.form__message').classList.remove('hidden') 217 this.querySelector('.js-error-content').innerHTML = html 218 } 219 220 hideErrorMsg() { 221 this.querySelector('.form__message').classList.add('hidden') 222 } 223 224 clearSelection() { 225 for (let radioButton of this.radioButtons) { 226 if (radioButton.checked) { 227 radioButton.checked = false; 228 break; 229 } 230 } 231 } 232 } 233 customElements.define('vouchers-list', VouchersList); 234 235 236 if (typeof DiscountInput == "undefined") { 237 class DiscountInput extends HTMLElement { 238 constructor() { 239 super(); 240 this.input = this.querySelector('input'); 241 this.button = this.querySelector('button'); 242 243 this.button.addEventListener('click', this.onButtonClick.bind(this)) 244 this.input.addEventListener('keypress', (event) => { 245 if (event.key == 'Enter' || event.keyCode == 13) { 246 event.preventDefault() 247 this.onButtonClick(event) 248 } 249 this.hideErrorMsg() 250 }) 251 } 252 253 onButtonClick(event) { 254 event.preventDefault(); 255 this.hideErrorMsg(); 256 257 if (this.input.value != '') { 258 this.button.classList.add('loading', 'btn--loading') 259 fetch('/new_cart/voucher', { 260 method: 'POST', 261 headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', }, 262 body: JSON.stringify({ 263 category: 'create', 264 voucher_code: this.input.value 265 }), 266 }) 267 .then(response => response.json()) 268 .then((data) => { 269 this.button.classList.remove('loading', 'btn--loading') 270 if (data.error && data.error.message) this.renderErrorMsg(data.error.message) 271 if (data.cart_content) {
272 document.querySelector('#cart-template , #CartTemplate').innerHTML = data.cart_content; 273 document.querySelector('vouchers-modal').close() 274 } 275 }) 276 } 277 } 278 279 renderErrorMsg(html) { 280 const formMessage = this.querySelector('.form__message'); 281 const errorContent = this.querySelector('.js-error-content'); 282 283 if ( 284 typeof html === "string" && 285 /log in/i.test(html) && 286 !/voucher-error__login/.test(html) 287 ) { 288 const loginUrl = "/account/login?redirect_uri=" + encodeURIComponent(window.location.pathname + window.location.search); 289 html = html.replace(/log in/i, `<a href="${loginUrl}" class="voucher-error__login">$&</a>`); 290 } 291 292 formMessage.classList.remove('hidden'); 293 errorContent.innerHTML = html; 294 } 295 296 hideErrorMsg() { 297 this.querySelector('.form__message').classList.add('hidden') 298 } 299 } 300 customElements.define('discount-input', DiscountInput); 301 } 302 303 // Fallback trapFocus for old themes that don't define it 304 if (typeof trapFocusHandlers === 'undefined' || typeof trapFocus !== 'function') { 305 var trapFocusHandlers = {}; 306 307 function getFocusableElements(container) { 308 return Array.from( 309 container.querySelectorAll( 310 'summary, a[href], button:enabled, [tabindex]:not([tabindex^="-"]), [draggable], area, input:not([type=hidden]):enabled, select:enabled, textarea:enabled, object, iframe' 311 ) 312 ); 313 } 314 315 function removeTrapFocus(elementToFocus = null) { 316 document.removeEventListener('focusin', trapFocusHandlers.focusin); 317 document.removeEventListener('focusout', trapFocusHandlers.focusout); 318 document.removeEventListener('keydown', trapFocusHandlers.keydown); 319 if (elementToFocus) elementToFocus.focus(); 320 } 321 322 function trapFocus(container, elementToFocus = container) { 323 var elements = getFocusableElements(container); 324 var first = elements[0]; 325 var last = elements[elements.length - 1]; 326 327 removeTrapFocus(); 328 329 trapFocusHandlers.focusin = (event) => { 330 if ( 331 event.target !== container && 332 event.target !== last && 333 event.target !== first 334 ) 335 return; 336 337 document.addEventListener('keydown', trapFocusHandlers.keydown); 338 }; 339 340 trapFocusHandlers.focusout = function() { 341 document.removeEventListener('keydown', trapFocusHandlers.keydown); 342 }; 343 344 trapFocusHandlers.keydown = function(event) { 345 if (event.code.toUpperCase() !== 'TAB') return; 346 if (event.target === last && !event.shiftKey) { 347 event.preventDefault(); 348 first.focus(); 349 } 350 if ((event.target === container || event.target === first) && event.shiftKey) { 351 event.preventDefault(); 352 last.focus(); 353 } 354 }; 355 356 document.addEventListener('focusout', trapFocusHandlers.focusout); 357 document.addEventListener('focusin', trapFocusHandlers.focusin); 358 359 elementToFocus.focus(); 360 } 361 } 362 363})
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.