1(function ($) { 2 'use strict'; 3 4 var selectors = { 5 root: '[data-sbs-cart-drawer]', 6 drawer: '.sbs-cart-drawer', 7 toggle: '.sbs-cart-toggle', 8 overlay: '.sbs-cart-drawer__overlay', 9 close: '[data-sbs-cart-close]', 10 standardCartForm: 'form.cart:not(.vtajaxform)', 11 variationTableCartForm: 'form.vtajaxform', 12 checkoutProcessing: '[data-sbs-checkout-processing]', 13 checkoutProcessingMessage: '[data-sbs-checkout-processing-message]', 14 deliverySchedule: '[data-sbs-delivery-schedule]', 15 legalModal: '[data-sbs-legal-modal]', 16 legalContent: '[data-sbs-legal-content]', 17 legalTitle: '[data-sbs-legal-title]', 18 legalOriginal: '[data-sbs-legal-original]', 19 legalClose: '[data-sbs-legal-close]' 20 }; 21 22 var lastTrigger = null; 23 var cartPageRefreshRequest = null; 24 var cartPageRefreshPending = false; 25 var checkoutCartSyncTimer = null; 26 var checkoutProcessingTimer = null; 27 var placeOrderMarkup = null; 28 var lastLegalTrigger = null; 29 var legalContentCache = {}; 30 var deliveryScheduleState = { 31 date: '', 32 window: '' 33 }; 34 35 function getRoot() { 36 return $(selectors.root); 37 } 38 39 function openDrawer(trigger) { 40 var $root = getRoot(); 41 42 if (!$root.length) { 43 return; 44 } 45 46 lastTrigger = trigger || document.activeElement; 47 $root.addClass('is-open'); 48 $root.find(selectors.drawer).attr('aria-hidden', 'false'); 49 $root.find(selectors.toggle).attr('aria-expanded', 'true'); 50 $root.find(selectors.overlay).prop('hidden', false); 51 $('body').addClass('sbs-cart-drawer-open'); 52 53 window.setTimeout(function () { 54 $root.find('.sbs-cart-drawer__close').trigger('focus'); 55 }, 220); 56 } 57 58 function closeDrawer(restoreFocus) { 59 var $root = getRoot(); 60 61 $root.removeClass('is-open'); 62 $root.find(selectors.drawer).attr('aria-hidden', 'true'); 63 $root.find(selectors.toggle).attr('aria-expanded', 'false'); 64 $root.find(selectors.overlay).prop('hidden', true); 65 $('body').removeClass('sbs-cart-drawer-open'); 66 67 if (restoreFocus !== false && lastTrigger && typeof lastTrigger.focus === 'function') { 68 lastTrigger.focus(); 69 } 70 } 71 72 function sanitizeLegalHtml(html) { 73 var parser = new window.DOMParser(); 74 var documentFragment = parser.parseFromString('<div id="sbs-legal-sanitize-root">' + html + '</div>', 'text/html'); 75 var root = documentFragment.getElementById('sbs-legal-sanitize-root'); 76 77 if (!root) { 78 return ''; 79 } 80 81 root.querySelectorAll('script, style, iframe, object, embed, form, link, meta').forEach(function (node) { 82 node.remove(); 83 }); 84 85 root.querySelectorAll('[id]').forEach(function (node) { 86 node.removeAttribute('id'); 87 }); 88 89 root.querySelectorAll('a[href]').forEach(function (link) { 90 link.setAttribute('target', '_blank'); 91 link.setAttribute('rel', 'noopener'); 92 }); 93 94 return root.innerHTML.trim(); 95 } 96 97 function extractLegalPageContent(html) { 98 var parser = new window.DOMParser(); 99 var responseDocument = parser.parseFromString(html, 'text/html'); 100 var content = responseDocument.querySelector('.post-content, .entry-content, article .fusion-post-content, main article, main #content, #main .fusion-row'); 101 102 return content ? sanitizeLegalHtml(content.innerHTML) : ''; 103 } 104 105 function getLegalContent(type, url) { 106 var cacheKey = type + '|' + url; 107 var inlineTerms; 108 109 if (legalContentCache[cacheKey]) { 110 return $.Deferred().resolve(legalContentCache[cacheKey]).promise(); 111 } 112 113 if (type === 'terms') { 114 inlineTerms = $('.woocommerce-terms-and-conditions').first().html(); 115 116 if (inlineTerms) { 117 legalContentCache[cacheKey] = sanitizeLegalHtml(inlineTerms); 118 return $.Deferred().resolve(legalContentCache[cacheKey]).promise(); 119 } 120 } 121 122 return $.ajax({ 123 url: url, 124 type: 'GET', 125 cache: true, 126 dataType: 'html' 127 }).then(function (html) { 128 var content = extractLegalPageContent(html); 129 130 if (!content) { 131 return $.Deferred().reject().promise(); 132 } 133 134 legalContentCache[cacheKey] = content; 135 return content; 136 }); 137 } 138 139 function closeLegalModal(restoreFocus) { 140 var $modal = $(selectors.legalModal); 141 142 if (!$modal.length || $modal.prop('hidden')) { 143 return; 144 } 145 146 $modal.prop('hidden', true).attr('aria-hidden', 'true').removeClass('is-open'); 147 $('body').removeClass('sbs-legal-modal-open'); 148 149 if (restoreFocus !== false && lastLegalTrigger && typeof lastLegalTrigger.focus === 'function') { 150 lastLegalTrigger.focus(); 151 } 152 } 153 154 function openLegalModal(link, type) { 155 var $modal = $(selectors.legalModal); 156 var $content = $modal.find(selectors.legalContent); 157 var url = link.href; 158 var title = type === 'terms' ? SBSCartDrawer.i18n.termsTitle : SBSCartDrawer.i18n.privacyTitle; 159 var $loading; 160 161 if (!$modal.length || !url) { 162 return; 163 } 164 165 lastLegalTrigger = link; 166 $modal.find(selectors.legalTitle).text(title); 167 $modal.find(selectors.legalOriginal).attr('href', url).text(SBSCartDrawer.i18n.legalOpenFull); 168 $loading = $('<div class="sbs-checkout-legal-modal__loading" role="status"><span aria-hidden="true"></span>
168<p></p></div>'); 169 $loading.find('p').text(SBSCartDrawer.i18n.legalLoading); 170 $content.empty().append($loading); 171 $modal.prop('hidden', false).attr('aria-hidden', 'false').addClass('is-open'); 172 $('body').addClass('sbs-legal-modal-open'); 173 174 window.setTimeout(function () { 175 $modal.find('.sbs-checkout-legal-modal__close').trigger('focus'); 176 }, 20); 177 178 getLegalContent(type, url).done(function (html) { 179 if (!$modal.hasClass('is-open')) { 180 return; 181 } 182 183 $content.html(html).attr('aria-busy', 'false'); 184 $content.scrollTop(0); 185 }).fail(function () { 186 var $error = $('<div class="sbs-checkout-legal-modal__error" role="alert"></div>'); 187 $('<p></p>').text(SBSCartDrawer.i18n.legalError).appendTo($error); 188 $('<a target="_blank" rel="noopener"></a>').attr('href', url).text(SBSCartDrawer.i18n.legalOpenFull).appendTo($error); 189 $content.empty().append($error).attr('aria-busy', 'false'); 190 }); 191 } 192 193 function prepareCheckoutLegalLinks() { 194 if (!$('body').hasClass('woocommerce-checkout') || $('body').hasClass('woocommerce-order-received')) { 195 return; 196 } 197 198 $('.woocommerce-terms-and-conditions-link') 199 .removeClass('woocommerce-terms-and-conditions-link--open') 200 .addClass('woocommerce-terms-and-conditions-link--closed') 201 .attr('aria-haspopup', 'dialog'); 202 $('.woocommerce-privacy-policy-link').attr('aria-haspopup', 'dialog'); 203 } 204 205 document.addEventListener('click', function (event) { 206 var link = event.target && event.target.closest ? event.target.closest('a.woocommerce-privacy-policy-link, a.woocommerce-terms-and-conditions-link') : null; 207 var type; 208 209 if (!link || !document.body.classList.contains('woocommerce-checkout') || document.body.classList.contains('woocommerce-order-received')) { 210 return; 211 } 212 213 event.preventDefault(); 214 event.stopPropagation(); 215 event.stopImmediatePropagation(); 216 type = link.classList.contains('woocommerce-terms-and-conditions-link') ? 'terms' : 'privacy'; 217 openLegalModal(link, type); 218 }, true); 219 220 function showError($form, message) { 221 var $notice = $('<p class="sbs-cart-drawer__form-error" role="alert"></p>').text(message); 222 $form.find('.sbs-cart-drawer__form-error').remove(); 223 $form.prepend($notice); 224 } 225 226 function normalizeCartFormData($form, $button) { 227 var data = $form.serializeArray(); 228 var hasProductId = data.some(function (field) { 229 return field.name === 'product_id'; 230 }); 231 232 if (!hasProductId) { 233 data.push({ 234 name: 'product_id', 235 value: $button.val() || $form.find('[name="add-to-cart"]').val() 236 }); 237 } 238 239 return $.param(data); 240 } 241 242 function normalizeVariationTableData($form) { 243 var serialized = $form.serializeArray(); 244 var variationId = serialized.find(function (field) { 245 return field.name === 'variation_id'; 246 }); 247 var data = serialized.filter(function (field) { 248 return !['quantity', 'product_id', 'variation_id', 'add-to-cart'].includes(field.name); 249 }); 250 var quantity = $form.closest('tr').find('input[name="var_quantity"]').first().val(); 251 252 data.push({ 253 name: 'product_id', 254 value: variationId ? variationId.value : $form.attr('data-variation_id') 255 }); 256 data.push({ 257 name: 'quantity', 258 value: quantity || $form.find('input[name="quantity"]').val() || 1 259 }); 260 261 return $.param(data); 262 } 263 264 function submitAjaxCart($form, $button, data) { 265 if ($button.hasClass('loading') || $button.is(':disabled')) { 266 return; 267 } 268 269 var originalMarkup = $button.html(); 270 $button.prop('disabled', true).addClass('loading').text(SBSCartDrawer.i18n.adding); 271 $form.find('.sbs-cart-drawer__form-error').remove(); 272 273 $.ajax({ 274 type: 'POST', 275 url: SBSCartDrawer.wcAjaxUrl.replace('%%endpoint%%', 'add_to_cart'), 276 data: data, 277 dataType: 'json' 278 }).done(function (response) { 279 if (!response || response.error) { 280 showError($form, SBSCartDrawer.i18n.error); 281 return; 282 } 283 284 if (response.fragments) { 285 $.each(response.fragments, function (selector, html) { 286 $(selector).replaceWith(html); 287 }); 288 } 289 290 $(document.body).trigger('added_to_cart', [response.fragments, response.cart_hash, $button]); 291 }).fail(function () { 292 showError($form, SBSCartDrawer.i18n.error); 293 }).always(function () { 294 $button.prop('disabled', false).removeClass('loading').html(originalMarkup); 295 }); 296 } 297 298 function submitVariationTableCart(form) { 299 var $form = $(form); 300 var $button = $form.find('.single_add_to_cart_button').first(); 301 302 if (!$button.length || $button.is(':disabled') || $('body').is('.woocommerce-cart, .woocommerce-checkout')) { 303 return; 304 } 305 306 submitAjaxCart($form, $button, normalizeVariationTableData($form)); 307 } 308 309 // Standard WooCommerce loop buttons and the installed variations-table plugin 310 // both emit this event after a successful AJAX request. 311 $(document.body).on('added_to_cart', function (event, fragments, cartHash, $button) { 312 var trigger = $button && $button.length ? $button.get(0) : document.activeElement; 313 openDrawer(trigger); 314 }); 315 316 // WooCommerce does not AJAX-submit single-product forms by default. Handle 317 // those here, while leaving .vtajaxform to the existing variations plugin. 318 $(document).on('submit', selectors.standardCartForm, function (event) { 319 var $form = $(this); 320 var $button = $form.find('.single_add_to_cart_button').first(); 321 322 if (!$button.length || $button.is(':disabled') || $('body').is('.woocommerce-cart, .woocommerce-checkout')) { 323 return; 324 } 325 326 event.preventDefault(); 327 328 submitAjaxCart($form, $button, normalizeCartFormData($form, $button)); 329 }); 330 331 // Avada intercepts these button clicks before the variations-table form can 332 // submit. Capture the click first and route it to WooCommerce directly. 333 document.addEventListener('click', function (event) { 334 var button = event.target && event.target.closest ? event.target.closest('form.vtajaxform .single_add_to_cart_button') : null; 335 336 if (!button) { 337 return; 338 } 339 340 event.preventDefault(); 341 event.stopImmediatePropagation(); 342 submitVariationTableCart(button.closest('form.vtajaxform')); 343 }, true); 344 345 // Also capture keyboard and programmatic form submissions before the legacy 346 // variations-table handler. 347 document.addEventListener('submit', function (event) { 348 var form = event.target; 349 350 if (!form || !form.matches || !form.matches(selectors.variationTableCartForm)) { 351 return; 352 } 353 354 event.preventDefault(); 355 event.stopImmediatePropagation(); 356 submitVariationTableCart(form); 357 }, true); 358 359 function enhanceCheckoutShipping() { 360 var isCheckout = $('body').hasClass('woocommerce-checkout'); 361 362 $('#shipping_method').each(function (index) { 363 var $methods = $(this); 364 var introId = 'sbs-shipping-choice-intro-' + index; 365 366 $methods.closest('tr.woocommerce-shipping-totals').addClass('sbs-shipping-choice'); 367 368 if (isCheckout) { 369 var $intro = $methods.prev('.sbs-shipping-choice__intro'); 370 371 if (!$intro.length) { 372 $intro = $('<p class="sbs-shipping-choice__intro"></p>').insertBefore($methods); 373 } 374 375 $intro.attr('id', introId).text(SBSCartDrawer.i18n.shippingInstruction); 376 $methods.find('input.shipping_method').attr('aria-describedby', introId); 377 } else { 378 $methods.prev('.sbs-shipping-choice__intro').remove(); 379 $methods.find('input.shipping_method').removeAttr('aria-describedby'); 380 } 381 }); 382 383 if (isCheckout) { 384 organizeCheckoutReview(); 385 } 386 } 387 388 function organizeCheckoutReview() { 389 var $review = $('#order_review'); 390 var $table = $review.find('table.woocommerce-checkout-review-order-table').first(); 391 var $shippingRow = $table.find('tr.woocommerce-shipping-totals').first(); 392 var $methods = $shippingRow.find('#shipping_method').first(); 393 var $intro = $methods.prev('.sbs-shipping-choice__intro'); 394 var $scheduleRow = $table.find('tr.sbs-delivery-schedule-row').first(); 395 var $schedule = $scheduleRow.find(selectors.deliverySchedule).first(); 396 var $payment = $review.find('#payment').first(); 397 398 if (!$review.length || !$table.length || !$shippingRow.length || !$methods.length || !$payment.length) { 399 return; 400 } 401 402 var $grid = $review.children('.sbs-checkout-review-grid').first(); 403 404 if (!$grid.length) { 405 $grid = $('<div class="sbs-checkout-review-grid"></div>').prependTo($review); 406 } 407 408 if (!$table.parent().is($grid)) { 409 $grid.append($table); 410 } 411 412 if (!$payment.parent().is($grid)) { 413 $grid.append($payment); 414 } 415 416 var $priorSchedule = $grid.children('.sbs-checkout-fulfillment-panel').find(selectors.deliverySchedule).first(); 417 418 if ($priorSchedule.length) { 419 deliveryScheduleState.date = $priorSchedule.find('#sbs_requested_delivery_date').val() || deliveryScheduleState.date; 420 deliveryScheduleState.window = $priorSchedule.find('#sbs_requested_delivery_window').val() || deliveryScheduleState.window; 421 } 422 423 // WooCommerce replaces the review table after every totals update. Remove 424 // the prior panel, then relocate the fresh, authoritative shipping inputs. 425 $grid.children('.sbs-checkout-fulfillment-panel').remove(); 426 427 var title = $.trim($shippingRow.children('th').first().text()) || SBSCartDrawer.i18n.shippingTitle; 428 var $panel = $('<aside class="sbs-checkout-fulfillment-panel" aria-labelledby="sbs-checkout-fulfillment-title"></aside>'); 429 430 $('<h3 id="sbs-checkout-fulfillment-title"></h3>').text(title).appendTo($panel); 431 $panel.append($intro, $methods); 432 433 if ($schedule.length) { 434 $panel.append($schedule); 435 $scheduleRow.addClass('sbs-delivery-schedule-row--relocated'); 436 } 437 438 $shippingRow.addClass('sbs-shipping-choice--relocated'); 439 $panel.insertAfter($table); 440 syncDeliverySchedule($schedule); 441 } 442 443 function deliveryScheduleConfig() { 444 return SBSCartDrawer.deliverySchedule && SBSCartDrawer.deliverySchedule.dates 445 ? SBSCartDrawer.deliverySchedule 446 : { dates: {} }; 447 } 448 449 function selectedShippingMethod() { 450 var $checked = $('#shipping_method input.shipping_method:checked').first(); 451 452 if ($checked.length) { 453 return String($checked.val() || ''); 454 } 455 456 return String($('#shipping_method input.shipping_method, #shipping_method select.shipping_method').first().val() || ''); 457 } 458 459 function deliveryIsSelected() { 460 return /^flat_rate(?::|$)/.test(selectedShippingMethod()); 461 } 462 463 function deliveryWindowsForDate(date) { 464 var dates = deliveryScheduleConfig().dates || {}; 465 return dates[date] && Array.isArray(dates[date].windows) ? dates[date].windows : []; 466 } 467 468 function populateDeliveryWindows($schedule, preferredWindow) { 469 var $date = $schedule.find('#sbs_requested_delivery_date'); 470 var $window = $schedule.find('#sbs_requested_delivery_window'); 471 var windows = deliveryWindowsForDate(String($date.val() || '')); 472 var selected = preferredWindow || String($window.val() || ''); 473 474 $window.empty().append( 475 $('<option></option>').attr('value', '').text(SBSCartDrawer.i18n.windowPlaceholder) 476 ); 477 478 $.each(windows, function (index, label) { 479 $('<option></option>').attr('value', label).text(label).appendTo($window); 480 }); 481 482 if (selected && windows.indexOf(selected) !== -1) { 483 $window.val(selected); 484 } else { 485 $window.val(''); 486 deliveryScheduleState.window = ''; 487 } 488 } 489 490 function syncDeliverySchedule($schedule) { 491 $schedule = $schedule && $schedule.length ? $schedule : $(selectors.deliverySchedule).first(); 492 493 if (!$schedule.length) { 494 return; 495 } 496 497 var active = deliveryIsSelected(); 498 var $date = $schedule.find('#sbs_requested_delivery_date'); 499 var $window = $schedule.find('#sbs_requested_delivery_window'); 500 501 if (deliveryScheduleState.date && $date.find('option[value="' + deliveryScheduleState.date.replace(/"/g, '\\"') + '"]').length) { 502 $date.val(deliveryScheduleState.date); 503 } else if ($date.val()) { 504 deliveryScheduleState.date = String($date.val()); 505 } 506 507 populateDeliveryWindows($schedule, deliveryScheduleState.window); 508 509 $schedule 510 .prop('hidden', !active) 511 .toggleClass('is-visible', active) 512 .attr('aria-hidden', active ? 'false' : 'true'); 513 514 $date.add($window) 515 .prop('disabled', !active) 516 .prop('required', active) 517 .attr('aria-required', active ? 'true' : 'false'); 518 } 519 520 function isLandscapeContractor() { 521 return $('input[name="billing_who_is_making_this_purchase"]:checked').val() === 'Landscape Contractor'; 522 } 523
524 function syncCompanyRequirement() { 525 var $field = $('#billing_company_name_field'); 526 var $input = $('#billing_company_name'); 527 var $label = $field.find('label[for="billing_company_name"]').first(); 528 var isRequired = isLandscapeContractor(); 529 530 if (!$field.length || !$input.length || !$label.length) { 531 return; 532 } 533 534 $field.toggleClass('validate-required', isRequired); 535 $input.prop('required', isRequired).attr('aria-required', isRequired ? 'true' : 'false'); 536 $label.find('.required, .optional').remove(); 537 538 if (isRequired) { 539 $('<abbr class="required" title="Required Field">*</abbr>').appendTo($label); 540 } else { 541 $('<span class="optional"></span>').text('(' + SBSCartDrawer.i18n.companyOptional + ')').appendTo($label); 542 } 543 } 544 545 function addressPartText(html) { 546 return $('<div></div>').html(html).text().replace(/\s+/g, ' ').trim(); 547 } 548 549 function cleanAddressTail(html) { 550 var $content = $('<div></div>').html(html); 551 var $phone = $content.find('[class*="phone"]').first(); 552 var phone = $.trim($phone.text()); 553 var firstTextNode = $content.contents().filter(function () { 554 return this.nodeType === 3 && $.trim(this.nodeValue); 555 }).first(); 556 557 // Flexible Checkout Fields can output the shipping phone once as plain 558 // text and a second time in WooCommerce's phone element. Keep one copy. 559 if (phone && firstTextNode.length && $.trim(firstTextNode.get(0).nodeValue) === phone) { 560 firstTextNode.remove(); 561 } 562 563 return $.trim($content.html()); 564 } 565 566 function normalizeOrderConfirmationAddresses() { 567 if (!$('body').hasClass('woocommerce-order-received')) { 568 return; 569 } 570 571 $('.sbs-confirmation-customer-details address, .sbs-order-confirmation-details .woocommerce-customer-details address').each(function () { 572 var $address = $(this); 573 574 if ($address.hasClass('sbs-address-normalized')) { 575 return; 576 } 577 578 var parts = String($address.html() || '').split(/<br\s*\/?\s*>/i).map(function (part) { 579 return $.trim(part); 580 }).filter(function (part) { 581 return addressPartText(part) !== ''; 582 }); 583 584 if (parts.length < 4) { 585 return; 586 } 587 588 var firstText = addressPartText(parts[0]); 589 var nameStart = firstText.indexOf(':') !== -1 ? 1 : 0; 590 var postcodeIndex = -1; 591 592 $.each(parts, function (index, part) { 593 if (/^\d{5}(?:-\d{4})?$/.test(addressPartText(part))) { 594 postcodeIndex = index; 595 } 596 }); 597 598 var normalized = []; 599 600 $.each(parts.slice(0, nameStart), function (index, part) { 601 normalized.push({ className: 'sbs-address-line--context', html: part }); 602 }); 603 604 if (parts[nameStart] && parts[nameStart + 1]) { 605 normalized.push({ 606 className: 'sbs-address-line--name', 607 html: $('<div></div>').text(addressPartText(parts[nameStart]) + ' ' + addressPartText(parts[nameStart + 1])).html() 608 }); 609 } 610 611 var localityStart = postcodeIndex >= nameStart + 4 ? postcodeIndex - 2 : -1; 612 var middleEnd = localityStart === -1 ? parts.length : localityStart; 613 614 $.each(parts.slice(nameStart + 2, middleEnd), function (index, part) { 615 normalized.push({ className: 'sbs-address-line--street', html: part }); 616 }); 617 618 if (localityStart !== -1) { 619 var locality = addressPartText(parts[localityStart]) + ', ' + addressPartText(parts[localityStart + 1]) + ' ' + addressPartText(parts[postcodeIndex]); 620 normalized.push({ className: 'sbs-address-line--locality', html: $('<div></div>').text(locality).html() }); 621 622 $.each(parts.slice(postcodeIndex + 1), function (index, part) { 623 normalized.push({ className: 'sbs-address-line--contact', html: cleanAddressTail(part) }); 624 }); 625 } 626 627 $address.empty().addClass('sbs-address-normalized'); 628 629 $.each(normalized, function (index, part) { 630 $('<div class="sbs-address-line"></div>').addClass(part.className).html(part.html).appendTo($address); 631 }); 632 }); 633 } 634 635 function showCheckoutProcessing() { 636 var $indicator = $(selectors.checkoutProcessing); 637 var $button = $('#place_order'); 638 639 if (!$indicator.length || $('body').hasClass('woocommerce-order-received')) { 640 return; 641 } 642 643 if ($button.length && placeOrderMarkup === null) { 644 placeOrderMarkup = $button.html(); 645 } 646 647 $button 648 .prop('disabled', true) 649 .attr('aria-disabled', 'true') 650 .addClass('sbs-place-order-processing'); 651 652 $indicator 653 .prop('hidden', false) 654 .attr('aria-hidden', 'false'); 655 656 $indicator.find(selectors.checkoutProcessingMessage).text(SBSCartDrawer.i18n.processingMessage); 657 $('body').addClass('sbs-checkout-order-processing'); 658 659 window.clearTimeout(checkoutProcessingTimer); 660 checkoutProcessingTimer = window.setTimeout(function () { 661 if ($('body').hasClass('sbs-checkout-order-processing')) { 662 $indicator.find(selectors.checkoutProcessingMessage).text(SBSCartDrawer.i18n.processingLonger); 663 } 664 }, 12000); 665 } 666 667 function hideCheckoutProcessing() { 668 var $button = $('#place_order'); 669 670 window.clearTimeout(checkoutProcessingTimer); 671 checkoutProcessingTimer = null; 672 673 $(selectors.checkoutProcessing) 674 .prop('hidden', true) 675 .attr('aria-hidden', 'true'); 676 677 $('body').removeClass('sbs-checkout-order-processing'); 678 679 $button 680 .prop('disabled', false) 681 .removeAttr('aria-disabled') 682 .removeClass('sbs-place-order-processing'); 683 684 if ($button.length && placeOrderMarkup !== null) { 685 $button.html(placeOrderMarkup); 686 } 687 688 placeOrderMarkup = null; 689 } 690 691 function bindCheckoutProcessing() { 692 $('form.checkout') 693 .off('checkout_place_order.sbsCheckoutProcessing') 694 .on('checkout_place_order.sbsCheckoutProcessing', function () { 695 showCheckoutProcessing(); 696 }); 697 } 698 699 function refreshMainCartFromSession() { 700 var $currentForm; 701 var $currentTotals; 702 var $currentTableWrapper; 703 var $currentTotalsWrapper; 704 705 if (!$('body').hasClass('woocommerce-cart')) { 706 return; 707 } 708 709 if (cartPageRefreshRequest) { 710 cartPageRefreshPending = true; 711 return; 712 } 713 714 cartPageRefreshPending = false;
715 716 $currentForm = $('form.woocommerce-cart-form').first(); 717 $currentTotals = $('.cart_totals').first(); 718 $currentTableWrapper = $('.fusion-woo-cart_table-wrapper').first(); 719 $currentTotalsWrapper = $('.fusion-woo-cart-totals-wrapper').first(); 720 721 if ((!$currentTableWrapper.length || !$currentTotalsWrapper.length) && (!$currentForm.length || !$currentTotals.length)) { 722 window.location.reload(); 723 return; 724 } 725 726 $('body').addClass('sbs-cart-page-refreshing'); 727 728 cartPageRefreshRequest = $.ajax({ 729 url: window.location.href, 730 type: 'GET', 731 cache: false, 732 data: { 733 sbs_cart_refresh: Date.now() 734 }, 735 dataType: 'html' 736 }).done(function (html) { 737 var $response = $('<div></div>').append($.parseHTML(html, document, true)); 738 var $freshForm = $response.find('form.woocommerce-cart-form').first(); 739 var $freshTotals = $response.find('.cart_totals').first(); 740 var $freshTableWrapper = $response.find('.fusion-woo-cart_table-wrapper').first(); 741 var $freshTotalsWrapper = $response.find('.fusion-woo-cart-totals-wrapper').first(); 742 743 if ($currentTableWrapper.length && $currentTotalsWrapper.length && $freshTableWrapper.length && $freshTotalsWrapper.length) { 744 $currentTableWrapper.replaceWith($freshTableWrapper); 745 $currentTotalsWrapper.replaceWith($freshTotalsWrapper); 746 } else if ($currentForm.length && $currentTotals.length && $freshForm.length && $freshTotals.length) { 747 $currentForm.replaceWith($freshForm); 748 $currentTotals.replaceWith($freshTotals); 749 } else { 750 window.location.reload(); 751 return; 752 } 753 754 enhanceCheckoutShipping(); 755 $(document.body).trigger('updated_wc_div'); 756 }).fail(function () { 757 window.location.reload(); 758 }).always(function () { 759 var shouldRefreshAgain = cartPageRefreshPending; 760 761 cartPageRefreshRequest = null; 762 cartPageRefreshPending = false; 763 $('body').removeClass('sbs-cart-page-refreshing'); 764 765 if (shouldRefreshAgain) { 766 refreshMainCartFromSession(); 767 } 768 }); 769 } 770 771 function refreshCheckoutFromSession() { 772 if (!$('body').hasClass('woocommerce-checkout') || $('body').hasClass('woocommerce-order-received')) { 773 return; 774 } 775 776 window.clearTimeout(checkoutCartSyncTimer); 777 checkoutCartSyncTimer = window.setTimeout(function () { 778 checkoutCartSyncTimer = null; 779 $(document.body).trigger('update_checkout'); 780 }, 80); 781 } 782 783 function syncPageAfterDrawerRemoval() { 784 refreshMainCartFromSession(); 785 refreshCheckoutFromSession(); 786 } 787 788 enhanceCheckoutShipping(); 789 syncCompanyRequirement(); 790 bindCheckoutProcessing(); 791 normalizeOrderConfirmationAddresses(); 792 $(prepareCheckoutLegalLinks); 793 $(document.body).on('updated_checkout updated_shipping_method', function () { 794 enhanceCheckoutShipping(); 795 syncCompanyRequirement(); 796 bindCheckoutProcessing(); 797 }); 798 799 $(document).on('change', '#sbs_requested_delivery_date', function () { 800 var $schedule = $(this).closest(selectors.deliverySchedule); 801 deliveryScheduleState.date = String($(this).val() || ''); 802 deliveryScheduleState.window = ''; 803 populateDeliveryWindows($schedule, ''); 804 }); 805 806 $(document).on('change', '#sbs_requested_delivery_window', function () { 807 deliveryScheduleState.window = String($(this).val() || ''); 808 }); 809 810 $(document).on('change', '#shipping_method input.shipping_method, #shipping_method select.shipping_method', function () { 811 syncDeliverySchedule(); 812 }); 813 814 $(document.body).on('checkout_error', hideCheckoutProcessing); 815 $(document.body).on('removed_from_cart.sbsCartPageSync', syncPageAfterDrawerRemoval); 816 $(document).on('change', 'input[name="billing_who_is_making_this_purchase"]', syncCompanyRequirement); 817 818 $(document).ajaxError(function (event, request, settings) { 819 if (settings && /(?:wc-ajax=|wc_ajax=)checkout/.test(settings.url || '')) { 820 hideCheckoutProcessing(); 821 } 822 }); 823 824 $(window).on('pageshow', hideCheckoutProcessing); 825 826 $(document).on('click', selectors.toggle, function () { 827 openDrawer(this); 828 }); 829 830 $(document).on('click', selectors.close, function (event) { 831 event.preventDefault(); 832 closeDrawer(); 833 }); 834 835 $(document).on('keydown', function (event) { 836 if (event.key === 'Escape' && $(selectors.legalModal).hasClass('is-open')) { 837 event.preventDefault(); 838 closeLegalModal(); 839 return; 840 } 841 842 if (event.key === 'Escape' && getRoot().hasClass('is-open')) { 843 closeDrawer(); 844 } 845 }); 846 847 $(document).on('click', selectors.legalClose, function (event) { 848 event.preventDefault(); 849 closeLegalModal(); 850 }); 851 852 $(document).on('keydown', selectors.legalModal, function (event) {
853 var $focusable; 854 var first; 855 var last; 856 857 if (event.key !== 'Tab') { 858 return; 859 } 860 861 $focusable = $(this).find('a:visible, button:visible, [tabindex]:visible').filter(':not([tabindex="-1"])'); 862 first = $focusable.get(0); 863 last = $focusable.get($focusable.length - 1); 864 865 if (!first || !last) { 866 return; 867 } 868 869 if (event.shiftKey && document.activeElement === first) { 870 event.preventDefault(); 871 last.focus(); 872 } else if (!event.shiftKey && document.activeElement === last) { 873 event.preventDefault(); 874 first.focus(); 875 } 876 }); 877 878 // Keep keyboard focus inside the open drawer. 879 $(document).on('keydown', selectors.drawer, function (event) { 880 if (event.key !== 'Tab') { 881 return; 882 } 883 884 var $focusable = $(this).find('a:visible, button:visible, [tabindex]:visible').filter(':not([tabindex="-1"])'); 885 var first = $focusable.get(0); 886 var last = $focusable.get($focusable.length - 1); 887 888 if (event.shiftKey && document.activeElement === first) { 889 event.preventDefault(); 890 last.focus(); 891 } else if (!event.shiftKey && document.activeElement === last) { 892 event.preventDefault(); 893 first.focus(); 894 } 895 }); 896 897 // Public adapter hook for future SBS-specific cart integrations. 898 $(document).on('sbs_cart_drawer_open', function (event, trigger) { 899 openDrawer(trigger); 900 }); 901})(jQuery);
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.