1/* global wc_cart_params */ 2jQuery( function ( $ ) { 3 // wc_cart_params is required to continue, ensure the object exists 4 if ( typeof wc_cart_params === 'undefined' ) { 5 return false; 6 } 7 8 // Utility functions for the file. 9 10 /** 11 * Gets a url for a given AJAX endpoint. 12 * 13 * @param {String} endpoint The AJAX Endpoint 14 * @return {String} The URL to use for the request 15 */ 16 var get_url = function ( endpoint ) { 17 return wc_cart_params.wc_ajax_url 18 .toString() 19 .replace( '%%endpoint%%', endpoint ); 20 }; 21 22 /** 23 * Check if a node is blocked for processing. 24 * 25 * @param {JQuery Object} $node 26 * @return {bool} True if the DOM Element is UI Blocked, false if not. 27 */ 28 var is_blocked = function ( $node ) { 29 return ( 30 $node.is( '.processing' ) || $node.parents( '.processing' ).length 31 ); 32 }; 33 34 /** 35 * Block a node visually for processing. 36 * 37 * @param {JQuery Object} $node 38 */ 39 var block = function ( $node ) { 40 if ( ! is_blocked( $node ) ) { 41 $node.addClass( 'processing' ).block( { 42 message: null, 43 overlayCSS: { 44 background: '#fff', 45 opacity: 0.6, 46 }, 47 } ); 48 } 49 }; 50 51 /** 52 * Unblock a node after processing is complete. 53 * 54 * @param {JQuery Object} $node 55 */ 56 var unblock = function ( $node ) { 57 $node.removeClass( 'processing' ).unblock(); 58 }; 59 60 /** 61 * Removes duplicate notices. 62 * 63 * @param {JQuery Object} $notices 64 */ 65 var remove_duplicate_notices = function ( $notices ) { 66 var seen = new Set(); 67 var deduplicated_notices = []; 68 69 $notices.each( function () { 70 const text = $( this ).text(); 71 72 if ( ! seen.has( text ) ) { 73 seen.add( text ); 74 deduplicated_notices.push( this ); 75 } 76 } ); 77 78 return $( deduplicated_notices ); 79 }; 80 81 /** 82 * Update the .woocommerce div with a string of html. 83 * 84 * @param {String} html_str The HTML string with which to replace the div. 85 * @param {bool} preserve_notices Should notices be kept? False by default. 86 */ 87 var update_wc_div = function ( html_str, preserve_notices ) { 88 var $html = $.parseHTML( html_str ); 89 var $new_form = $( '.woocommerce-cart-form', $html ); 90 var $new_totals = $( '.cart_totals', $html ); 91 var $notices = remove_duplicate_notices( 92 $( 93 '.woocommerce-error, .woocommerce-message, .woocommerce-info, .is-error, .is-info, .is-success', 94 $html 95 ) 96 ); 97 98 // No form, cannot do this. 99 if ( $( '.woocommerce-cart-form' ).length === 0 ) { 100 window.location.reload(); 101 return; 102 } 103 104 // Remove errors 105 if ( ! preserve_notices ) { 106 $( 107 '.woocommerce-error, .woocommerce-message, .woocommerce-info, .is-error, .is-info, .is-success, .coupon-error-notice' 108 ).remove(); 109 } 110 111 if ( $new_form.length === 0 ) { 112 // If the checkout is also displayed on this page, trigger reload instead. 113 if ( $( '.woocommerce-checkout' ).length ) { 114 window.location.reload(); 115 return; 116 } 117 118 // No items to display now! Replace all cart content. 119 var $cart_html = $( '.wc-empty-cart-message', $html ).closest( 120 '.woocommerce' 121 ); 122 $( '.woocommerce-cart-form__contents' ) 123 .closest( '.woocommerce' ) 124 .replaceWith( $cart_html ); 125 126 // Display errors 127 if ( $notices.length > 0 ) { 128 show_notice( $notices ); 129 } 130 131 // Notify plugins that the cart was emptied. 132 $( document.body ).trigger( 'wc_cart_emptied' ); 133 } else { 134 // If the checkout is also displayed on this page, trigger update event. 135 if ( $( '.woocommerce-checkout' ).length ) { 136 $( document.body ).trigger( 'update_checkout' ); 137 } 138 139 // Store the old coupon error message and value before the 140 // .woocommerce-cart-form is replaced with the new form. 141 var $old_coupon_field_val = $( '#coupon_code' ).val(); 142 var $old_coupon_error_msg = $( '#coupon_code' ) 143 .closest( '.coupon' ) 144 .find( '.coupon-error-notice' ); 145 146 $( '.woocommerce-cart-form' ).replaceWith( $new_form ); 147 $( '.woocommerce-cart-form' ) 148 .find( ':input[name="update_cart"]' ) 149 .prop( 'disabled', true ); 150 151 if ( preserve_notices && $old_coupon_error_msg.length > 0 ) { 152 var $new_coupon_field = $( '.woocommerce-cart-form' ).find( '#coupon_code' ); 153 var $new_coupon_field_wrapper = $new_coupon_field.closest( '.coupon' ); 154 155 $new_coupon_field.val( $old_coupon_field_val ); 156 // The coupon input with error needs to be focused before adding the live region 157 // with the error message, otherwise the screen reader won't read it. 158 $new_coupon_field.trigger( 'focus' ); 159 show_coupon_error( $old_coupon_error_msg, $new_coupon_field_wrapper, true ); 160 } 161 162 if ( $notices.length > 0 ) { 163 show_notice( $notices ); 164 } 165 166 update_cart_totals_div( $new_totals ); 167 } 168 169 $( document.body ).trigger( 'updated_wc_div' ); 170 }; 171 172 /** 173 * Update the .cart_totals div with a string of html. 174 * 175 * @param {String} html_str The HTML string with which to replace the div. 176 */ 177 var update_cart_totals_div = function ( html_str ) { 178 $( '.cart_totals' ).replaceWith( html_str ); 179 $( document.body ).trigger( 'updated_cart_totals' ); 180 }; 181 182 /** 183 * Shows new notices on the page. 184 * 185 * @param {Object} The Notice HTML Element in string or object form. 186 */ 187 var show_notice = function ( html_element, $target ) { 188 if ( ! $target ) { 189 $target = 190 $( '.woocommerce-notices-wrapper:first' ) || 191 $( '.wc-empty-cart-message' ).closest( '.woocommerce' ) || 192 $( '.woocommerce-cart-form' ); 193 } 194 $target.prepend( html_element ); 195 }; 196 197 /** 198 * Shows coupon form errors. 199 * 200 * @param {string|object}
200 html_element The HTML string response after applying an invalid coupon or a jQuery element. 201 * @param {Object} $target Coupon field wrapper jQuery element. 202 * @param {boolean} is_live_region Whether role="alert" should be added or not. 203 */ 204 var show_coupon_error = function ( html_element, $target, is_live_region ) { 205 if ( $target.length === 0 ) { 206 return; 207 } 208 209 var $coupon_error_el = html_element; 210 211 if ( typeof html_element === 'string' ) { 212 var msg = $( $.parseHTML( html_element ) ).text().trim(); 213 214 if ( msg === '' ) { 215 return; 216 } 217 218 $coupon_error_el = $('<p>', { 219 class: 'coupon-error-notice', 220 id: 'coupon-error-notice', 221 text: msg 222 }); 223 } 224 225 if ( is_live_region ) { 226 $coupon_error_el.attr( 'role', 'alert' ); 227 } 228 229 $target.find( '#coupon_code' ) 230 .addClass( 'has-error' ) 231 .attr( 'aria-invalid', 'true' ) 232 .attr( 'aria-describedby', 'coupon-error-notice' ); 233 $target.append( $coupon_error_el ); 234 }; 235 236 /** 237 * Handle when pressing the Space key on a link with the button role. 238 * 239 * @param {Object} event The jQuery event. 240 */ 241 var on_keydown_button = function ( event ) { 242 if ( event.key === ' ' ) { 243 event.preventDefault(); 244 245 if ( ! event.originalEvent || ! event.originalEvent.repeat ) { 246 $( event.currentTarget ).trigger( 'click' ); 247 } 248 } 249 }; 250 251 /** 252 * Object to handle AJAX calls for cart shipping changes. 253 */ 254 var cart_shipping = { 255 /** 256 * Initialize event handlers and UI state. 257 */ 258 init: function ( cart ) { 259 this.cart = cart; 260 this.toggle_shipping = this.toggle_shipping.bind( this ); 261 this.shipping_method_selected = 262 this.shipping_method_selected.bind( this ); 263 this.shipping_calculator_submit = 264 this.shipping_calculator_submit.bind( this ); 265 266 $( document ).on( 267 'click', 268 '.shipping-calculator-button', 269 this.toggle_shipping 270 ); 271 $( document ).on( 272 'keydown', 273 '.shipping-calculator-button', 274 on_keydown_button 275 ); 276 $( document ).on( 277 'change', 278 'select.shipping_method, :input[name^=shipping_method]', 279 this.shipping_method_selected 280 ); 281 $( document ).on( 282 'submit', 283 'form.woocommerce-shipping-calculator', 284 this.shipping_calculator_submit 285 ); 286 287 $( '.shipping-calculator-form' ).hide(); 288 }, 289 290 /** 291 * Toggle Shipping Calculator panel 292 */ 293 toggle_shipping: function ( event ) { 294 var $target = $( event.currentTarget ); 295 296 $( '.shipping-calculator-form' ).slideToggle( 'slow', function () { 297 var self = this; 298 299 setTimeout( function () { 300 var $form = $( self ); 301 302 $target.attr( 'aria-expanded', $form.is( ':visible' ) ? 'true' : 'false' ); 303 }, 0 ); 304 } ); 305 306 $( 'select.country_to_state, input.country_to_state' ).trigger( 307 'change' 308 ); 309 $( document.body ).trigger( 'country_to_state_changed' ); // Trigger select2 to load. 310 return false; 311 }, 312 313 /** 314 * Handles when a shipping method is selected. 315 */ 316 shipping_method_selected: function ( event ) { 317 var shipping_methods = {}; 318 319 $( 320 'select.shipping_method, :input[name^=shipping_method][type=radio]:checked, :input[name^=shipping_method][type=hidden]' 321 ).each( function () { 322 shipping_methods[ $( this ).data( 'index' ) ] = $( this ).val(); 323 } ); 324 325 block( $( 'div.cart_totals' ) ); 326 327 var data = { 328 security: wc_cart_params.update_shipping_method_nonce, 329 shipping_method: shipping_methods, 330 }; 331 332 $.ajax( { 333 type: 'post', 334 url: get_url( 'update_shipping_method' ), 335 data: data, 336 dataType: 'html', 337 success: function ( response ) { 338 update_cart_totals_div( response ); 339 340 var newCurrentTarget = document.getElementById( event.currentTarget.id ); 341 342 if ( newCurrentTarget ) { 343 newCurrentTarget.focus(); 344 } 345 }, 346 complete: function () { 347 unblock( $( 'div.cart_totals' ) ); 348 $( document.body ).trigger( 'updated_shipping_method' ); 349 }, 350 } ); 351 }, 352 353 /** 354 * Handles a shipping calculator form submit. 355 * 356 * @param {Object} evt The JQuery event. 357 */ 358 shipping_calculator_submit: function ( evt ) { 359 evt.preventDefault(); 360 361 var $form = $( evt.currentTarget ); 362 363 block( $( 'div.cart_totals' ) ); 364 block( $form ); 365 366 // Provide the submit button value because wc-form-handler expects it. 367 $( '<input />' ) 368 .attr( 'type', 'hidden' ) 369 .attr( 'name', 'calc_shipping' ) 370 .attr( 'value', 'x' ) 371 .appendTo( $form ); 372 373 // Make call to actual form post URL. 374 $.ajax( { 375 type: $form.attr( 'method' ), 376 url: $form.attr( 'action' ), 377 data: $form.serialize(), 378 dataType: 'html', 379 success: function ( response ) { 380 update_wc_div( response ); 381 }, 382 complete: function () { 383 unblock( $form ); 384 unblock( $( 'div.cart_totals' ) ); 385 }, 386 } ); 387 }, 388 }; 389 390 /** 391 * Object to handle cart UI. 392 */ 393 var cart = { 394 /** 395 * Initialize cart UI events. 396 */ 397 init: function () { 398 this.update_cart_totals = this.update_cart_totals.bind( this ); 399 this.input_keypress = this.input_keypress.bind( this ); 400 this.cart_submit = this.cart_submit.bind( this ); 401 this.submit_click = this.submit_click.bind( this ); 402 this.apply_coupon = this.apply_coupon.bind( this ); 403 this.remove_coupon_clicked = 404 this.remove_coupon_clicked.bind( this ); 405 this.remove_coupon_error = this.remove_coupon_error.bind( this ); 406 this.quantity_update = this.quantity_update.bind( this ); 407 this.item_remove_clicked = this.item_remove_clicked.bind( this ); 408 this.item_restore_clicked = this.item_restore_clicked.bind( this ); 409 this.update_cart = this.update_cart.bind( this ); 410 411 $( document ).on( 'wc_update_cart added_to_cart', function () { 412 cart.update_cart.apply( cart, [].slice.call( arguments, 1 ) ); 413 } ); 414 $( document ).on( 415 'click', 416 '.woocommerce-cart-form :input[type=submit]', 417 this.submit_click 418 ); 419 $( document ).on( 420 'keypress', 421 '.woocommerce-cart-form :input[type=number]', 422 this.input_keypress 423 ); 424 $( document ).on( 425 'submit', 426 '.woocommerce-cart-form', 427 this.cart_submit 428 ); 429 $( document ).on( 430 'click', 431 'a.woocommerce-remove-coupon', 432 this.remove_coupon_clicked 433 ); 434 $( document ).on( 435 'keydown', 436 'a.woocommerce-remove-coupon', 437 on_keydown_button 438 ); 439 $( document ).on( 440 'click', 441 '.woocommerce-cart-form .product-remove > a', 442 this.item_remove_clicked 443 ); 444 $( document ).on( 445 'keydown', 446 '.woocommerce-cart-form .product-remove > a', 447 on_keydown_button 448 ); 449 $( document ).on( 450 'click', 451 '.woocommerce-cart .restore-item', 452 this.item_restore_clicked 453 ); 454 $( document ).on( 455 'change input', 456 '.woocommerce-cart-form .cart_item :input', 457 this.input_changed 458 ); 459 $( document ).on( 460 'change input', 461 '#coupon_code', 462 this.remove_coupon_error 463 ); 464 465 $( '.woocommerce-cart-form :input[name="update_cart"]' ).prop( 466 'disabled', 467 true 468 ); 469 }, 470 471 /** 472 * After an input is changed, enable the update cart button. 473 */ 474 input_changed: function () { 475 $( '.woocommerce-cart-form :input[name="update_cart"]' ).prop( 476 'disabled', 477 false 478 ); 479 }, 480 481 /** 482 * Update entire cart via ajax. 483 */ 484 update_cart: function ( preserve_notices ) { 485 var $form = $( '.woocommerce-cart-form' ); 486 487 block( $form ); 488 block( $( 'div.cart_totals' ) ); 489 490 // Make call to actual form post URL. 491 $.ajax( { 492 type: $form.attr( 'method' ), 493 url: $form.attr( 'action' ), 494 data: $form.serialize(), 495 dataType: 'html', 496 success: function ( response ) { 497 update_wc_div( response, preserve_notices ); 498 }, 499 complete: function () { 500 unblock( $form ); 501 unblock( $( 'div.cart_totals' ) ); 502 $.scroll_to_notices( $( '[role="alert"]' ) ); 503 }, 504 } ); 505 }, 506 507 /** 508 * Update the cart after something has changed. 509 */ 510 update_cart_totals: function () { 511 block( $( 'div.cart_totals' ) ); 512 513 $.ajax( { 514 url: get_url( 'get_cart_totals' ), 515 dataType: 'html', 516 success: function ( response ) { 517 update_cart_totals_div( response ); 518 }, 519 complete: function () { 520 unblock( $( 'div.cart_totals' ) ); 521 }, 522 } ); 523 }, 524 525 /** 526 * Handle the <ENTER> key for quantity fields. 527 * 528 * @param {Object} evt The JQuery event 529 * 530 * For IE, if you hit enter on a quantity field, it makes the 531 * document.activeElement the first submit button it finds. 532 * For us, that is the Apply Coupon button. This is required 533 * to catch the event before that happens. 534 */ 535 input_keypress: function ( evt ) { 536 // Catch the enter key and don't let it submit the form. 537 if ( 13 === evt.keyCode ) { 538 var $form = $( evt.currentTarget ).parents( 'form' ); 539 540 try { 541 // If there are no validation errors, handle the submit. 542 if ( $form[ 0 ].checkValidity() ) { 543 evt.preventDefault(); 544 this.cart_submit( evt ); 545 } 546 } catch ( err ) { 547 evt.preventDefault(); 548 this.cart_submit( evt ); 549 } 550 } 551 }, 552 553 /** 554 * Handle cart form submit and route to correct logic. 555 * 556 * @param {Object} evt The JQuery event 557 */ 558 cart_submit: function ( evt ) { 559 var $submit = $( document.activeElement ), 560 $clicked = $( ':input[type=submit][clicked=true]' ), 561 $form = $( evt.currentTarget ); 562 563 // For submit events, currentTarget is form.
564 // For keypress events, currentTarget is input. 565 if ( ! $form.is( 'form' ) ) { 566 $form = $( evt.currentTarget ).parents( 'form' ); 567 } 568 569 if ( 570 0 === $form.find( '.woocommerce-cart-form__contents' ).length 571 ) { 572 return; 573 } 574 575 if ( is_blocked( $form ) ) { 576 return false; 577 } 578 579 if ( 580 $clicked.is( ':input[name="update_cart"]' ) || 581 $submit.is( 'input.qty' ) 582 ) { 583 evt.preventDefault(); 584 this.quantity_update( $form ); 585 } else if ( 586 $clicked.is( ':input[name="apply_coupon"]' ) || 587 $submit.is( '#coupon_code' ) 588 ) { 589 evt.preventDefault(); 590 this.apply_coupon( $form ); 591 } 592 }, 593 594 /** 595 * Special handling to identify which submit button was clicked. 596 * 597 * @param {Object} evt The JQuery event 598 */ 599 submit_click: function ( evt ) { 600 $( 601 ':input[type=submit]', 602 $( evt.target ).parents( 'form' ) 603 ).removeAttr( 'clicked' ); 604 $( evt.target ).attr( 'clicked', 'true' ); 605 }, 606 607 /** 608 * Apply Coupon code 609 * 610 * @param {JQuery Object} $form The cart form. 611 */ 612 apply_coupon: function ( $form ) { 613 block( $form ); 614 615 var cart = this; 616 var $text_field = $( '#coupon_code' ); 617 var coupon_code = $text_field.val(); 618 619 var data = { 620 security: wc_cart_params.apply_coupon_nonce, 621 coupon_code: coupon_code, 622 }; 623 624 $.ajax( { 625 type: 'POST', 626 url: get_url( 'apply_coupon' ), 627 data: data, 628 dataType: 'html', 629 success: function ( response ) { 630 $( 631 '.woocommerce-error, .woocommerce-message, .woocommerce-info, ' + 632 '.is-error, .is-info, .is-success, .coupon-error-notice' 633 ).remove(); 634 635 // We only want to show coupon notices if they are not errors. 636 // Coupon errors are shown under the input. 637 if ( response.indexOf( 'woocommerce-error' ) === -1 && response.indexOf( 'is-error' ) === -1 ) { 638 show_notice( response ); 639 } else { 640 var $coupon_wrapper = $text_field.closest( '.coupon' ); 641 642 if ( $coupon_wrapper.length > 0 ) { 643 show_coupon_error( response, $coupon_wrapper, false ); 644 } 645 } 646 647 $( document.body ).trigger( 'applied_coupon', [ 648 coupon_code, 649 ] ); 650 }, 651 complete: function () { 652 unblock( $form ); 653 cart.update_cart( true ); 654 }, 655 } ); 656 }, 657 658 /** 659 * Handle when a remove coupon link is clicked. 660 * 661 * @param {Object} evt The JQuery event 662 */ 663 remove_coupon_clicked: function ( evt ) { 664 evt.preventDefault(); 665 666 var cart = this; 667 var $wrapper = $( evt.currentTarget ).closest( '.cart_totals' ); 668 var coupon = $( evt.currentTarget ).attr( 'data-coupon' ); 669 670 block( $wrapper ); 671 672 var data = { 673 security: wc_cart_params.remove_coupon_nonce, 674 coupon: coupon, 675 }; 676 677 $.ajax( { 678 type: 'POST', 679 url: get_url( 'remove_coupon' ), 680 data: data, 681 dataType: 'html', 682 success: function ( response ) { 683 $( 684 '.woocommerce-error, .woocommerce-message, .woocommerce-info, .is-error, .is-info, .is-success' 685 ).remove(); 686 show_notice( response ); 687 $( document.body ).trigger( 'removed_coupon', [ coupon ] ); 688 $( '#coupon_code' ) 689 .val('') 690 .removeClass('has-error') 691 .removeAttr('aria-invalid') 692 .removeAttr('aria-describedby') 693 .closest('.coupon') 694 .find('.coupon-error-notice') 695 .remove(); 696 unblock( $wrapper ); 697 }, 698 complete: function () { 699 cart.update_cart( true ); 700 }, 701 } ); 702 }, 703 704 /** 705 * Handle when the coupon input loses focus. 706 * 707 * @param {Object} evt The JQuery event 708 */ 709 remove_coupon_error: function ( evt ) { 710 $( evt.currentTarget ) 711 .removeClass( 'has-error' ) 712 .removeAttr( 'aria-invalid' ) 713 .removeAttr( 'aria-describedby' ) 714 .closest( '.coupon' ) 715 .find( '.coupon-error-notice' ) 716 .remove(); 717 }, 718 719 /** 720 * Handle a cart Quantity Update 721 * 722 * @param {JQuery Object} $form The cart form. 723 */ 724 quantity_update: function ( $form ) { 725 block( $form ); 726 block( $( 'div.cart_totals' ) ); 727 728 // Provide the submit button value because wc-form-handler expects it. 729 $( '<input />' ) 730 .attr( 'type', 'hidden' ) 731 .attr( 'name', 'update_cart' ) 732 .attr( 'value', 'Update Cart' ) 733 .appendTo( $form ); 734 735 // Make call to actual form post URL. 736 $.ajax( { 737 type: $form.attr( 'method' ), 738 url: $form.attr( 'action' ), 739 data: $form.serialize(), 740 dataType: 'html', 741 success: function ( response ) { 742 update_wc_div( response ); 743 }, 744 complete: function () { 745 unblock( $form ); 746 unblock( $( 'div.cart_totals' ) ); 747 $.scroll_to_notices( $( '[role="alert"]' ) ); 748 }, 749 } ); 750 }, 751 752 /** 753 * Handle when a remove item link is clicked. 754 * 755 * @param {Object} evt The JQuery event 756 */ 757 item_remove_clicked: function ( evt ) { 758 evt.preventDefault(); 759 760 var $a = $( evt.currentTarget ); 761 var $form = $a.parents( 'form' ); 762 763 block( $form ); 764 block( $( 'div.cart_totals' ) ); 765 766 $.ajax( { 767 type: 'GET', 768 url: $a.attr( 'href' ), 769 dataType: 'html', 770 success: function ( response ) { 771 update_wc_div( response ); 772 }, 773 complete: function () { 774 unblock( $form ); 775 unblock( $( 'div.cart_totals' ) ); 776 $.scroll_to_notices( $( '[role="alert"]' ) ); 777 $( document.body ).trigger( 'item_removed_from_classic_cart'); 778 }, 779 } ); 780 }, 781 782 /** 783 * Handle when a restore item link is clicked. 784 * 785 * @param {Object} evt The JQuery event 786 */ 787 item_restore_clicked: function ( evt ) { 788 evt.preventDefault(); 789 790 var $a = $( evt.currentTarget ); 791 var $form = $( 'form.woocommerce-cart-form' ); 792 793 block( $form ); 794 block( $( 'div.cart_totals' ) ); 795 796 $.ajax( { 797 type: 'GET', 798 url: $a.attr( 'href' ), 799 dataType: 'html', 800 success: function ( response ) { 801 update_wc_div( response ); 802 }, 803 complete: function () { 804 unblock( $form ); 805 unblock( $( 'div.cart_totals' ) ); 806 }, 807 } ); 808 }, 809 }; 810 811 cart_shipping.init( cart ); 812 cart.init(); 813} );
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.