1/*global wc_add_to_cart_variation_params */ 2( function ( $, window, document, undefined ) { 3 /** 4 * VariationForm class which handles variation forms and attributes. 5 */ 6 var VariationForm = function ( $form ) { 7 var self = this; 8 9 self.$form = $form; 10 self.$attributeFields = $form.find( '.variations select' ); 11 self.$singleVariation = $form.find( '.single_variation' ); 12 self.$singleVariationWrap = $form.find( '.single_variation_wrap' ); 13 self.$resetVariations = $form.find( '.reset_variations' ); 14 self.$resetAlert = $form.find( '.reset_variations_alert' ); 15 self.$product = $form.closest( '.product' ); 16 self.variationData = $form.data( 'product_variations' ); 17 self.useAjax = false === self.variationData; 18 self.xhr = false; 19 self.loading = true; 20 21 // Initial state. 22 self.$singleVariationWrap.show(); 23 self.$form.off( '.wc-variation-form' ); 24 25 // Methods. 26 self.getChosenAttributes = self.getChosenAttributes.bind( self ); 27 self.findMatchingVariations = self.findMatchingVariations.bind( self ); 28 self.isMatch = self.isMatch.bind( self ); 29 self.toggleResetLink = self.toggleResetLink.bind( self ); 30 self.showNoMatchingVariationsMsg = 31 self.showNoMatchingVariationsMsg.bind( self ); 32 33 // Events. 34 $form.on( 35 'click.wc-variation-form', 36 '.reset_variations', 37 { variationForm: self }, 38 self.onReset 39 ); 40 $form.on( 41 'reload_product_variations', 42 { variationForm: self }, 43 self.onReload 44 ); 45 $form.on( 'hide_variation', { variationForm: self }, self.onHide ); 46 $form.on( 'show_variation', { variationForm: self }, self.onShow ); 47 $form.on( 48 'click', 49 '.single_add_to_cart_button', 50 { variationForm: self }, 51 self.onAddToCart 52 ); 53 $form.on( 54 'reset_data', 55 { variationForm: self }, 56 self.onResetDisplayedVariation 57 ); 58 $form.on( 59 'reset_focus', 60 { variationForm: self }, 61 self.onResetVariationFocus 62 ); 63 $form.on( 64 'announce_reset', 65 { variationForm: self }, 66 self.onAnnounceReset 67 ); 68 $form.on( 69 'clear_reset_announcement', 70 { variationForm: self }, 71 self.onClearResetAnnouncement 72 ); 73 $form.on( 'reset_image', { variationForm: self }, self.onResetImage ); 74 $form.on( 75 'change.wc-variation-form', 76 '.variations select', 77 { variationForm: self }, 78 self.onChange 79 ); 80 $form.on( 81 'found_variation.wc-variation-form', 82 { variationForm: self }, 83 self.onFoundVariation 84 ); 85 $form.on( 86 'check_variations.wc-variation-form', 87 { variationForm: self }, 88 self.onFindVariation 89 ); 90 $form.on( 91 'update_variation_values.wc-variation-form', 92 { variationForm: self }, 93 self.onUpdateAttributes 94 ); 95 $form.on( 96 'keydown.wc-variation-form', 97 '.reset_variations', 98 { variationForm: self }, 99 self.onResetKeyDown 100 ); 101 102 // Init after gallery. 103 setTimeout( function () { 104 $form.trigger( 'check_variations' ); 105 $form.trigger( 'wc_variation_form', self ); 106 self.loading = false; 107 }, 100 ); 108 }; 109 110 /** 111 * Reset all fields. 112 */ 113 VariationForm.prototype.onReset = function ( event ) { 114 event.preventDefault(); 115 event.data.variationForm.$attributeFields.val( '' ).trigger( 'change' ); 116 event.data.variationForm.$form.trigger( 'announce_reset' ); 117 event.data.variationForm.$form.trigger( 'reset_data' ); 118 event.data.variationForm.$form.trigger( 'reset_focus' ); 119 }; 120 121 /** 122 * Reload variation data from the DOM. 123 */ 124 VariationForm.prototype.onReload = function ( event ) { 125 var form = event.data.variationForm; 126 form.variationData = form.$form.data( 'product_variations' ); 127 form.useAjax = false === form.variationData; 128 form.$form.trigger( 'check_variations' ); 129 }; 130 131 /** 132 * When a variation is hidden. 133 */ 134 VariationForm.prototype.onHide = function ( event ) { 135 event.preventDefault(); 136 event.data.variationForm.$form 137 .find( '.single_add_to_cart_button' ) 138 .removeClass( 'wc-variation-is-unavailable' ) 139 .addClass( 'disabled wc-variation-selection-needed' ); 140 event.data.variationForm.$form 141 .find( '.woocommerce-variation-add-to-cart' ) 142 .removeClass( 'woocommerce-variation-add-to-cart-enabled' ) 143 .addClass( 'woocommerce-variation-add-to-cart-disabled' ); 144 }; 145 146 /** 147 * When a variation is shown.
148 */ 149 VariationForm.prototype.onShow = function ( 150 event, 151 variation, 152 purchasable 153 ) { 154 event.preventDefault(); 155 if ( purchasable ) { 156 event.data.variationForm.$form 157 .find( '.single_add_to_cart_button' ) 158 .removeClass( 159 'disabled wc-variation-selection-needed wc-variation-is-unavailable' 160 ); 161 event.data.variationForm.$form 162 .find( '.woocommerce-variation-add-to-cart' ) 163 .removeClass( 'woocommerce-variation-add-to-cart-disabled' ) 164 .addClass( 'woocommerce-variation-add-to-cart-enabled' ); 165 } else { 166 event.data.variationForm.$form 167 .find( '.single_add_to_cart_button' ) 168 .removeClass( 'wc-variation-selection-needed' ) 169 .addClass( 'disabled wc-variation-is-unavailable' ); 170 event.data.variationForm.$form 171 .find( '.woocommerce-variation-add-to-cart' ) 172 .removeClass( 'woocommerce-variation-add-to-cart-enabled' ) 173 .addClass( 'woocommerce-variation-add-to-cart-disabled' ); 174 } 175 176 // If present, the media element library needs initialized on the variation description. 177 if ( wp.mediaelement ) { 178 event.data.variationForm.$form 179 .find( '.wp-audio-shortcode, .wp-video-shortcode' ) 180 .not( '.mejs-container' ) 181 .filter( function () { 182 return ! $( this ).parent().hasClass( 'mejs-mediaelement' ); 183 } ) 184 .mediaelementplayer( wp.mediaelement.settings ); 185 } 186 }; 187 188 /** 189 * When the cart button is pressed. 190 */ 191 VariationForm.prototype.onAddToCart = function ( event ) { 192 if ( $( this ).is( '.disabled' ) ) { 193 event.preventDefault(); 194 195 if ( $( this ).is( '.wc-variation-is-unavailable' ) ) { 196 window.alert( 197 wc_add_to_cart_variation_params.i18n_unavailable_text 198 ); 199 } else if ( $( this ).is( '.wc-variation-selection-needed' ) ) { 200 window.alert( 201 wc_add_to_cart_variation_params.i18n_make_a_selection_text 202 ); 203 } 204 } 205 }; 206 207 /** 208 * When displayed variation data is reset. 209 */ 210 VariationForm.prototype.onResetDisplayedVariation = function ( event ) { 211 var form = event.data.variationForm; 212 form.$product.find( '.product_meta' ).find( '.sku' ).wc_reset_content(); 213 form.$product 214 .find( 215 '.product_weight, .woocommerce-product-attributes-item--weight .woocommerce-product-attributes-item__value' 216 ) 217 .wc_reset_content(); 218 form.$product 219 .find( 220 '.product_dimensions, .woocommerce-product-attributes-item--dimensions .woocommerce-product-attributes-item__value' 221 ) 222 .wc_reset_content(); 223 form.$form.trigger( 'reset_image' ); 224 form.$singleVariation.slideUp( 200 ).trigger( 'hide_variation' ); 225 }; 226 227 /** 228 * Announce reset to screen readers. 229 */ 230 VariationForm.prototype.onAnnounceReset = function ( event ) { 231 event.data.variationForm.$resetAlert.text( 232 wc_add_to_cart_variation_params.i18n_reset_alert_text 233 ); 234 }; 235 236 /** 237 * Focus variation reset 238 */ 239 VariationForm.prototype.onResetVariationFocus = function ( event ) { 240 event.data.variationForm.$attributeFields[ 0 ].focus(); 241 }; 242 243 /** Clear reset announcement */ 244 VariationForm.prototype.onClearResetAnnouncement = function ( event ) { 245 event.data.variationForm.$resetAlert.text( '' ); 246 }; 247 248 /** 249 * When the product image is reset. 250 */ 251 VariationForm.prototype.onResetImage = function ( event ) { 252 event.data.variationForm.$form.wc_variations_image_update( false ); 253 }; 254 255 /** 256 * Looks for matching variations for current selected attributes. 257 */ 258 VariationForm.prototype.onFindVariation = function ( 259 event, 260 chosenAttributes 261 ) { 262 var form = event.data.variationForm, 263 attributes = 264 'undefined' !== typeof chosenAttributes 265 ? chosenAttributes 266 : form.getChosenAttributes(), 267 currentAttributes = attributes.data; 268 269 if ( attributes.count && attributes.count === attributes.chosenCount ) { 270 if ( form.useAjax ) { 271 if ( form.xhr ) { 272 form.xhr.abort(); 273 } 274 form.$form.block( { 275 message: null, 276 overlayCSS: { background: '#fff', opacity: 0.6 }, 277 } ); 278 currentAttributes.product_id = parseInt( 279 form.$form.data( 'product_id' ), 280 10 281 ); 282 currentAttributes.custom_data = 283 form.$form.data( 'custom_data' ); 284 form.xhr = $.ajax( { 285 url: wc_add_to_cart_variation_params.wc_ajax_url 286 .toString() 287 .replace( '%%endpoint%%', 'get_variation' ), 288 type: 'POST', 289 data: currentAttributes, 290 success: function ( variation ) { 291 if ( variation ) {
292 form.$form.trigger( 'found_variation', [ 293 variation, 294 ] ); 295 } else { 296 form.$form.trigger( 'reset_data' ); 297 attributes.chosenCount = 0; 298 299 if ( ! form.loading ) { 300 form.showNoMatchingVariationsMsg(); 301 } 302 } 303 }, 304 complete: function () { 305 form.$form.unblock(); 306 }, 307 } ); 308 } else { 309 form.$form.trigger( 'update_variation_values' ); 310 311 var matching_variations = form.findMatchingVariations( 312 form.variationData, 313 currentAttributes 314 ), 315 variation = matching_variations.shift(); 316 317 if ( variation ) { 318 form.$form.trigger( 'found_variation', [ variation ] ); 319 } else { 320 form.$form.trigger( 'reset_data' ); 321 attributes.chosenCount = 0; 322 323 if ( ! form.loading ) { 324 form.showNoMatchingVariationsMsg(); 325 } 326 } 327 } 328 } else { 329 form.$form.trigger( 'update_variation_values' ); 330 form.$form.trigger( 'reset_data' ); 331 } 332 333 // Show reset link. 334 form.toggleResetLink( attributes.chosenCount > 0 ); 335 }; 336 337 /** 338 * Triggered when a variation has been found which matches all attributes. 339 */ 340 VariationForm.prototype.onFoundVariation = function ( event, variation ) { 341 var form = event.data.variationForm, 342 $sku = form.$product.find( '.product_meta' ).find( '.sku' ), 343 $weight = form.$product.find( 344 '.product_weight, .woocommerce-product-attributes-item--weight .woocommerce-product-attributes-item__value' 345 ), 346 $dimensions = form.$product.find( 347 '.product_dimensions, .woocommerce-product-attributes-item--dimensions .woocommerce-product-attributes-item__value' 348 ), 349 $qty_input = form.$singleVariationWrap.find( 350 '.quantity input.qty[name="quantity"]' 351 ), 352 $qty = $qty_input.closest( '.quantity' ), 353 purchasable = true, 354 variation_id = '', 355 template = false, 356 $template_html = ''; 357 358 if ( variation.sku ) { 359 $sku.wc_set_content( variation.sku ); 360 } else { 361 $sku.wc_reset_content(); 362 } 363 364 if ( variation.weight ) { 365 $weight.wc_set_content( variation.weight_html ); 366 } else { 367 $weight.wc_reset_content(); 368 } 369 370 if ( variation.dimensions ) { 371 // Decode HTML entities. 372 $dimensions.wc_set_content( 373 $.parseHTML( variation.dimensions_html )[ 0 ].data 374 ); 375 } else { 376 $dimensions.wc_reset_content(); 377 } 378 379 form.$form.wc_variations_image_update( variation ); 380 381 if ( ! variation.variation_is_visible ) { 382 template = wp_template( 'unavailable-variation-template' ); 383 } else { 384 template = wp_template( 'variation-template' ); 385 variation_id = variation.variation_id; 386 } 387 388 $template_html = template( { 389 variation: variation, 390 } ); 391 $template_html = $template_html.replace( '/*<![CDATA[*/', '' ); 392 $template_html = $template_html.replace( '/*]]>*/', '' ); 393 394 form.$form 395 .find( 'input[name="variation_id"], input.variation_id' ) 396 .val( variation.variation_id ) 397 .trigger( 'change' ); 398 399 // Hide or show qty input 400 if ( variation.is_sold_individually === 'yes' ) { 401 $qty_input 402 .val( '1' ) 403 .attr( 'min', '1' ) 404 .attr( 'max', '' ) 405 .trigger( 'change' ); 406 $qty.hide(); 407 } else { 408 var qty_val = parseFloat( $qty_input.val() ); 409 410 if ( isNaN( qty_val ) ) { 411 qty_val = variation.min_qty; 412 } else { 413 qty_val = 414 qty_val > parseFloat( variation.max_qty ) 415 ? variation.max_qty 416 : qty_val; 417 qty_val = 418 qty_val < parseFloat( variation.min_qty ) 419 ? variation.min_qty 420 : qty_val; 421 } 422 423 $qty_input 424 .attr( 'min', variation.min_qty ) 425 .attr( 'max', variation.max_qty ) 426 .val( qty_val ) 427 .trigger( 'change' ); 428 $qty.show(); 429 } 430 431 // Enable or disable the add to cart button 432 if ( 433 ! variation.is_purchasable || 434 ! variation.is_in_stock || 435 ! variation.variation_is_visible 436 ) { 437 purchasable = false; 438 } 439 440 // Add a delay before updating the live region to ensure screen readers pick up the content changes. 441 setTimeout( function () { 442 form.$singleVariation.html( $template_html ); 443 444 // Reveal 445 if ( form.$singleVariation.text().trim() ) { 446 form.$singleVariation 447 .slideDown( 200 ) 448 .trigger( 'show_variation', [ variation, purchasable ] ); 449 } else { 450 form.$singleVariation 451 .show() 452 .trigger( 'show_variation', [ variation, purchasable ] ); 453 } 454 }, 300 ); 455 }; 456 457 /** 458 * Triggered when an attribute field changes. 459 */ 460 VariationForm.prototype.onChange = function ( event ) { 461 var form = event.data.variationForm; 462 463 form.$form
464 .find( 'input[name="variation_id"], input.variation_id' ) 465 .val( '' ) 466 .trigger( 'change' ); 467 form.$form.trigger( 'clear_reset_announcement' ); 468 form.$form.find( '.wc-no-matching-variations' ).parent().remove(); 469 470 if ( form.useAjax ) { 471 form.$form.trigger( 'check_variations' ); 472 } else { 473 form.$form.trigger( 'woocommerce_variation_select_change' ); 474 form.$form.trigger( 'check_variations' ); 475 } 476 477 // Custom event for when variation selection has been changed 478 form.$form.trigger( 'woocommerce_variation_has_changed' ); 479 }; 480 481 /** 482 * Escape quotes in a string. 483 * @param {string} string 484 * @return {string} 485 */ 486 VariationForm.prototype.addSlashes = function ( string ) { 487 string = string.replace( /'/g, "\\'" ); 488 string = string.replace( /"/g, '\\"' ); 489 return string; 490 }; 491 492 /** 493 * Updates attributes in the DOM to show valid values. 494 */ 495 VariationForm.prototype.onUpdateAttributes = function ( event ) { 496 var form = event.data.variationForm, 497 attributes = form.getChosenAttributes(), 498 currentAttributes = attributes.data; 499 500 if ( form.useAjax ) { 501 return; 502 } 503 504 // Loop through selects and disable/enable options based on selections. 505 form.$attributeFields.each( function ( index, el ) { 506 var current_attr_select = $( el ), 507 current_attr_name = 508 current_attr_select.data( 'attribute_name' ) || 509 current_attr_select.attr( 'name' ), 510 show_option_none = $( el ).data( 'show_option_none' ), 511 option_gt_filter = ':gt(0)', 512 attached_options_count = 0, 513 new_attr_select = $( '<select/>' ), 514 selected_attr_val = current_attr_select.val() || '', 515 selected_attr_val_valid = true; 516 517 // Reference options set at first. 518 if ( ! current_attr_select.data( 'attribute_html' ) ) { 519 var refSelect = current_attr_select.clone(); 520 521 refSelect 522 .find( 'option' ) 523 .removeAttr( 'attached' ) 524 .prop( 'disabled', false ) 525 .prop( 'selected', false ); 526 527 // Legacy data attribute. 528 current_attr_select.data( 529 'attribute_options', 530 refSelect.find( 'option' + option_gt_filter ).get() 531 ); 532 current_attr_select.data( 'attribute_html', refSelect.html() ); 533 } 534 535 new_attr_select.html( 536 current_attr_select.data( 'attribute_html' ) 537 ); 538 539 // The attribute of this select field should not be taken into account when calculating its matching variations: 540 // The constraints of this attribute are shaped by the values of the other attributes. 541 var checkAttributes = $.extend( true, {}, currentAttributes ); 542 543 checkAttributes[ current_attr_name ] = ''; 544 545 var variations = form.findMatchingVariations( 546 form.variationData, 547 checkAttributes 548 ); 549 550 // Loop through variations. 551 for ( var num in variations ) { 552 if ( typeof variations[ num ] !== 'undefined' ) { 553 var variationAttributes = variations[ num ].attributes; 554 555 for ( var attr_name in variationAttributes ) { 556 if ( variationAttributes.hasOwnProperty( attr_name ) ) { 557 var attr_val = variationAttributes[ attr_name ], 558 variation_active = ''; 559 560 if ( attr_name === current_attr_name ) { 561 if ( variations[ num ].variation_is_active ) { 562 variation_active = 'enabled'; 563 } 564 565 if ( attr_val ) { 566 // Decode entities. 567 attr_val = $( '<div/>' ) 568 .html( attr_val ) 569 .text(); 570 571 // Attach to matching options by value. This is done to compare 572 // TEXT values rather than any HTML entities. 573 var $option_elements = 574 new_attr_select.find( 'option' ); 575 if ( $option_elements.length ) { 576 for ( 577 var i = 0, 578 len = $option_elements.length; 579 i < len; 580 i++ 581 ) { 582 var $option_element = $( 583 $option_elements[ i ] 584 ), 585 option_value = 586 $option_element.val(); 587 588 if ( attr_val === option_value ) { 589 $option_element.addClass( 590 'attached ' + 591 variation_active 592 ); 593 break; 594 } 595 } 596 } 597 } else { 598 // Attach all apart from placeholder. 599 new_attr_select 600 .find( 'option:gt(0)' ) 601 .addClass( 602 'attached ' + variation_active 603 ); 604 } 605 } 606 } 607 } 608 } 609 } 610 611 // Count available options. 612 attached_options_count = 613 new_attr_select.find( 'option.attached' ).length; 614 615 // Check if current selection is in attached options. 616 if ( selected_attr_val ) { 617 selected_attr_val_valid = false;
618 619 if ( 0 !== attached_options_count ) { 620 new_attr_select 621 .find( 'option.attached.enabled' ) 622 .each( function () { 623 var option_value = $( this ).val(); 624 625 if ( selected_attr_val === option_value ) { 626 selected_attr_val_valid = true; 627 return false; // break. 628 } 629 } ); 630 } 631 } 632 633 // Detach the placeholder if: 634 // - Valid options exist. 635 // - The current selection is non-empty. 636 // - The current selection is valid. 637 // - Placeholders are not set to be permanently visible. 638 if ( 639 attached_options_count > 0 && 640 selected_attr_val && 641 selected_attr_val_valid && 642 'no' === show_option_none 643 ) { 644 new_attr_select.find( 'option:first' ).remove(); 645 option_gt_filter = ''; 646 } 647 648 // Detach unattached. 649 new_attr_select 650 .find( 'option' + option_gt_filter + ':not(.attached)' ) 651 .remove(); 652 653 // Finally, copy to DOM and set value. 654 current_attr_select.html( new_attr_select.html() ); 655 current_attr_select 656 .find( 'option' + option_gt_filter + ':not(.enabled)' ) 657 .prop( 'disabled', true ); 658 659 // Choose selected value. 660 if ( selected_attr_val ) { 661 // If the previously selected value is no longer available, fall back to the placeholder (it's going to be there). 662 if ( selected_attr_val_valid ) { 663 current_attr_select.val( selected_attr_val ); 664 } else { 665 current_attr_select.val( '' ).trigger( 'change' ); 666 } 667 } else { 668 current_attr_select.val( '' ); // No change event to prevent infinite loop. 669 } 670 } ); 671 672 // Custom event for when variations have been updated. 673 form.$form.trigger( 'woocommerce_update_variation_values' ); 674 }; 675 676 /** 677 * Get chosen attributes from form. 678 * @return array 679 */ 680 VariationForm.prototype.getChosenAttributes = function () { 681 var data = {}; 682 var count = 0; 683 var chosen = 0; 684 685 this.$attributeFields.each( function () { 686 var attribute_name = 687 $( this ).data( 'attribute_name' ) || $( this ).attr( 'name' ); 688 var value = $( this ).val() || ''; 689 690 if ( value.length > 0 ) { 691 chosen++; 692 } 693 694 count++; 695 data[ attribute_name ] = value; 696 } ); 697 698 return { 699 count: count, 700 chosenCount: chosen, 701 data: data, 702 }; 703 }; 704 705 /** 706 * Find matching variations for attributes. 707 */ 708 VariationForm.prototype.findMatchingVariations = function ( 709 variations, 710 attributes 711 ) { 712 var matching = []; 713 for ( var i = 0; i < variations.length; i++ ) { 714 var variation = variations[ i ]; 715 716 if ( this.isMatch( variation.attributes, attributes ) ) { 717 matching.push( variation ); 718 } 719 } 720 return matching; 721 }; 722 723 /** 724 * See if attributes match. 725 * @return {Boolean} 726 */ 727 VariationForm.prototype.isMatch = function ( 728 variation_attributes, 729 attributes 730 ) { 731 var match = true; 732 for ( var attr_name in variation_attributes ) { 733 if ( variation_attributes.hasOwnProperty( attr_name ) ) { 734 var val1 = variation_attributes[ attr_name ]; 735 var val2 = attributes[ attr_name ]; 736 if ( 737 val1 !== undefined && 738 val2 !== undefined && 739 val1.length !== 0 && 740 val2.length !== 0 && 741 val1 !== val2 742 ) { 743 match = false; 744 } 745 } 746 } 747 return match; 748 }; 749 750 /** 751 * Show or hide the reset link. 752 */ 753 VariationForm.prototype.toggleResetLink = function ( on ) { 754 if ( on ) { 755 if ( this.$resetVariations.css( 'visibility' ) === 'hidden' ) { 756 this.$resetVariations 757 .css( 'visibility', 'visible' ) 758 .hide() 759 .fadeIn(); 760 } 761 } else { 762 this.$resetVariations.css( 'visibility', 'hidden' ); 763 } 764 }; 765 766 /** 767 * Show no matching variation message. 768 */ 769 VariationForm.prototype.showNoMatchingVariationsMsg = function () { 770 this.$form 771 .find( '.single_variation' ) 772 .after( 773 '<div role="alert">' + 774 '<p class="wc-no-matching-variations woocommerce-info">' + 775 wc_add_to_cart_variation_params.i18n_no_matching_variations_text + 776 '</p>' + 777 '</div>' 778 ) 779 .next( 'div' ) 780 .find( '.wc-no-matching-variations' ) 781 .slideDown( 200 ); 782 }; 783 784 /** 785 * Handle reset key down event for accessibility. 786 * @param {KeyboardEvent} event - The keyboard event object 787 */
788 VariationForm.prototype.onResetKeyDown = function ( event ) { 789 if ( event.code === 'Enter' || event.code === 'Space' ) { 790 event.preventDefault(); 791 event.data.variationForm.onReset( event ); 792 } 793 }; 794 795 /** 796 * Function to call wc_variation_form on jquery selector. 797 */ 798 $.fn.wc_variation_form = function () { 799 new VariationForm( this ); 800 return this; 801 }; 802 803 /** 804 * Stores the default text for an element so it can be reset later 805 */ 806 $.fn.wc_set_content = function ( content ) { 807 if ( undefined === this.attr( 'data-o_content' ) ) { 808 this.attr( 'data-o_content', this.text() ); 809 } 810 this.text( content ); 811 }; 812 813 /** 814 * Stores the default text for an element so it can be reset later 815 */ 816 $.fn.wc_reset_content = function () { 817 if ( undefined !== this.attr( 'data-o_content' ) ) { 818 this.text( this.attr( 'data-o_content' ) ); 819 } 820 }; 821 822 /** 823 * Stores a default attribute for an element so it can be reset later 824 */ 825 $.fn.wc_set_variation_attr = function ( attr, value ) { 826 if ( undefined === this.attr( 'data-o_' + attr ) ) { 827 this.attr( 828 'data-o_' + attr, 829 ! this.attr( attr ) ? '' : this.attr( attr ) 830 ); 831 } 832 if ( false === value ) { 833 this.removeAttr( attr ); 834 } else { 835 this.attr( attr, value ); 836 } 837 }; 838 839 /** 840 * Reset a default attribute for an element so it can be reset later 841 */ 842 $.fn.wc_reset_variation_attr = function ( attr ) { 843 if ( undefined !== this.attr( 'data-o_' + attr ) ) { 844 this.attr( attr, this.attr( 'data-o_' + attr ) ); 845 } 846 }; 847 848 /** 849 * Reset the slide position if the variation has a different image than the current one 850 */ 851 $.fn.wc_maybe_trigger_slide_position_reset = function ( variation ) { 852 var $form = $( this ), 853 $product = $form.closest( '.product' ), 854 $product_gallery = $product.find( '.images' ), 855 reset_slide_position = false, 856 new_image_id = 857 variation && variation.image_id ? variation.image_id : ''; 858 859 if ( $form.attr( 'current-image' ) !== new_image_id ) { 860 reset_slide_position = true; 861 } 862 863 $form.attr( 'current-image', new_image_id ); 864 865 if ( reset_slide_position ) { 866 $product_gallery.trigger( 867 'woocommerce_gallery_reset_slide_position' 868 ); 869 } 870 }; 871 872 /** 873 * Check whether the current gallery is using variation-specific markup. 874 */ 875 $.fn.wc_variations_gallery_is_active = function () { 876 return 'yes' === $( this ).attr( 'data-product_gallery_active' ); 877 }; 878 879 /** 880 * Get the default product gallery HTML used for restoring classic templates. 881 * 882 * Prefer the snapshot embedded in this form. Fall back to the page-level 883 * defaults for older template overrides that don't include it. 884 */ 885 $.fn.wc_get_default_product_gallery_html = function () { 886 var $form = $( this ); 887 var $template = $form.find( '.wc-product-gallery-default-template' ); 888 889 if ( $template.length ) { 890 return $.trim( $template.html() ); 891 } 892 893 var defaults = window.wc_variation_gallery_defaults || {}; 894 return defaults[ $form.data( 'product_id' ) ] || ''; 895 }; 896 897 /** 898 * Find the gallery root within a container. 899 */ 900 $.fn.wc_find_gallery_root = function () { 901 return $( this ) 902 .find( '.woocommerce-product-gallery' ) 903 .addBack( '.woocommerce-product-gallery' ) 904 .first(); 905 }; 906 907 /** 908 * Replace the current classic product gallery markup and reinitialize gallery behaviors. 909 */ 910 $.fn.wc_variations_gallery_replace = function ( gallery_html, variation ) { 911 var $form = $( this ), 912 $product = $form.closest( '.product' ), 913 $current_gallery = $product.wc_find_gallery_root(), 914 $parsed_gallery = $( $.parseHTML( $.trim( gallery_html ) ) ), 915 $new_gallery = $parsed_gallery.wc_find_gallery_root(), 916 current_image_id = 917 variation && variation.image_id ? variation.image_id : ''; 918 919 if ( ! $current_gallery.length || ! $new_gallery.length ) { 920 return false; 921 } 922 923 /** 924 * Notify subscribers that the current gallery DOM is about to be 925 * removed and replaced. 926 * 927 * @event wc-product-gallery-before-destroy 928 * @since 10.8.0 929 * @param {HTMLElement} galleryEl - The gallery root element being torn down. 930 * @param {Object} params - The `wc_single_product_params` object. 931 */ 932 if ( typeof wc_single_product_params !== 'undefined' ) { 933 $current_gallery.trigger( 'wc-product-gallery-before-destroy', [ 934 $current_gallery[ 0 ], 935 wc_single_product_params, 936 ] ); 937 } 938 939 $current_gallery.replaceWith( $new_gallery ); 940 941 if ( 942 typeof $.fn.wc_product_gallery === 'function' && 943 typeof wc_single_product_params !== 'undefined' 944 ) { 945 $new_gallery.trigger( 'wc-product-gallery-before-init', [ 946 $new_gallery[ 0 ], 947 wc_single_product_params, 948 ] ); 949 950 $new_gallery.wc_product_gallery( wc_single_product_params ); 951 952 $new_gallery.trigger( 'wc-product-gallery-after-init', [ 953 $new_gallery[ 0 ], 954 wc_single_product_params, 955 ] ); 956 } else { 957 $new_gallery.css( 'opacity', 1 ); 958 } 959 960 $form.attr( 'current-image', current_image_id );
961 $form.attr( 'data-product_gallery_active', 'yes' ); 962 963 return true; 964 }; 965 966 /** 967 * Restore the default classic product gallery markup after a variation gallery swap. 968 */ 969 $.fn.wc_variations_gallery_reset = function () { 970 var $form = $( this ), 971 default_gallery_html = $form.wc_get_default_product_gallery_html(), 972 reset_gallery = false; 973 974 if ( default_gallery_html.length ) { 975 reset_gallery = $form.wc_variations_gallery_replace( 976 default_gallery_html, 977 false 978 ); 979 } 980 981 if ( reset_gallery ) { 982 $form.removeAttr( 'data-product_gallery_active' ); 983 $form.attr( 'current-image', '' ); 984 } 985 986 return reset_gallery; 987 }; 988 989 /** 990 * Sets product images for the chosen variation 991 */ 992 $.fn.wc_variations_image_update = function ( variation ) { 993 var $form = this, 994 $product = $form.closest( '.product' ), 995 $product_gallery, 996 $gallery_nav, 997 $gallery_img, 998 $product_img_wrap, 999 $product_img, 1000 $product_link; 1001 1002 if ( 1003 variation && 1004 variation.gallery_images_html && 1005 variation.gallery_images_html.length && 1006 $form.wc_variations_gallery_replace( 1007 variation.gallery_images_html, 1008 variation 1009 ) 1010 ) { 1011 window.setTimeout( function () { 1012 $( window ).trigger( 'resize' ); 1013 }, 20 ); 1014 1015 return; 1016 } 1017 1018 if ( $form.wc_variations_gallery_is_active() ) { 1019 var reset_succeeded = $form.wc_variations_gallery_reset(); 1020 1021 if ( ! variation ) { 1022 if ( reset_succeeded ) { 1023 window.setTimeout( function () { 1024 $( window ).trigger( 'resize' ); 1025 }, 20 ); 1026 } 1027 return; 1028 } 1029 1030 if ( ! reset_succeeded ) { 1031 // Don't fall through to the single-image mutation path without 1032 // a clean reset snapshot. 1033 return; 1034 } 1035 } 1036 1037 $product_gallery = $product.find( '.images' ); 1038 $gallery_nav = $product.find( '.flex-control-nav' ); 1039 $gallery_img = $gallery_nav.find( 'li:eq(0) img' ); 1040 $product_img_wrap = $product_gallery 1041 .find( 1042 '.woocommerce-product-gallery__image, .woocommerce-product-gallery__image--placeholder' 1043 ) 1044 .eq( 0 ); 1045 $product_img = $product_img_wrap.find( '.wp-post-image' ); 1046 $product_link = $product_img_wrap.find( 'a' ).eq( 0 ); 1047 1048 if ( 1049 variation && 1050 variation.image && 1051 variation.image.src && 1052 variation.image.src.length > 1 1053 ) { 1054 // See if the gallery has an image with the same original src as the image we want to switch to. 1055 var galleryHasImage = 1056 $gallery_nav.find( 1057 'li img[data-o_src="' + 1058 variation.image.gallery_thumbnail_src + 1059 '"]' 1060 ).length > 0; 1061 1062 // If the gallery has the image, reset the images. We'll scroll to the correct one. 1063 if ( galleryHasImage ) { 1064 $form.wc_variations_image_reset(); 1065 } 1066 1067 // See if gallery has a matching image we can slide to. 1068 var slideToImage = $gallery_nav.find( 1069 'li img[src="' + variation.image.gallery_thumbnail_src + '"]' 1070 ); 1071 1072 if ( slideToImage.length > 0 ) { 1073 slideToImage.trigger( 'flexslider-click' ); 1074 $form.attr( 'current-image', variation.image_id ); 1075 window.setTimeout( function () { 1076 $( window ).trigger( 'resize' ); 1077 $product_gallery.trigger( 'woocommerce_gallery_init_zoom' ); 1078 }, 20 ); 1079 return; 1080 } 1081 1082 $product_img.wc_set_variation_attr( 'src', variation.image.src ); 1083 $product_img.wc_set_variation_attr( 1084 'height', 1085 variation.image.src_h 1086 ); 1087 $product_img.wc_set_variation_attr( 1088 'width', 1089 variation.image.src_w 1090 ); 1091 $product_img.wc_set_variation_attr( 1092 'srcset', 1093 variation.image.srcset 1094 ); 1095 $product_img.wc_set_variation_attr( 1096 'sizes', 1097 variation.image.sizes 1098 ); 1099 $product_img.wc_set_variation_attr( 1100 'title', 1101 variation.image.title 1102 ); 1103 $product_img.wc_set_variation_attr( 1104 'data-caption', 1105 variation.image.caption 1106 ); 1107 $product_img.wc_set_variation_attr( 'alt', variation.image.alt ); 1108 $product_img.wc_set_variation_attr( 1109 'data-src', 1110 variation.image.full_src 1111 ); 1112 $product_img.wc_set_variation_attr( 1113 'data-large_image', 1114 variation.image.full_src 1115 ); 1116 $product_img.wc_set_variation_attr( 1117 'data-large_image_width', 1118 variation.image.full_src_w 1119 ); 1120 $product_img.wc_set_variation_attr( 1121 'data-large_image_height', 1122 variation.image.full_src_h 1123 ); 1124 $product_img_wrap.wc_set_variation_attr( 1125 'data-thumb', 1126 variation.image.src 1127 ); 1128 $gallery_img.wc_set_variation_attr( 1129 'src', 1130 variation.image.gallery_thumbnail_src 1131 ); 1132 $product_link.wc_set_variation_attr( 1133 'href', 1134 variation.image.full_src 1135 ); 1136 } else { 1137 $form.wc_variations_image_reset(); 1138 } 1139 1140 window.setTimeout( function () { 1141 $( window ).trigger( 'resize' ); 1142 $form.wc_maybe_trigger_slide_position_reset( variation ); 1143 $product_gallery.trigger( 'woocommerce_gallery_init_zoom' ); 1144 }, 20 ); 1145 }; 1146 1147 /** 1148 * Reset main image to defaults. 1149 */ 1150 $.fn.wc_variations_image_reset = function () { 1151 var $form = this, 1152 $product = $form.closest( '.product' ), 1153 $product_gallery = $product.find( '.images' ), 1154 $gallery_nav = $product.find( '.flex-control-nav' ), 1155 $gallery_img = $gallery_nav.find( 'li:eq(0) img' ), 1156 $product_img_wrap = $product_gallery 1157 .find( 1158 '.woocommerce-product-gallery__image, .woocommerce-product-gallery__image--placeholder' 1159 ) 1160 .eq( 0 ), 1161 $product_img = $product_img_wrap.find( '.wp-post-image' ), 1162 $product_link = $product_img_wrap.find( 'a' ).eq( 0 ); 1163 1164 if ( $form.wc_variations_gallery_is_active() ) { 1165 $form.wc_variations_gallery_reset(); 1166 return; 1167 } 1168 1169 $product_img.wc_reset_variation_attr( 'src' ); 1170 $product_img.wc_reset_variation_attr( 'width' ); 1171 $product_img.wc_reset_variation_attr( 'height' ); 1172 $product_img.wc_reset_variation_attr( 'srcset' ); 1173 $product_img.wc_reset_variation_attr( 'sizes' ); 1174 $product_img.wc_reset_variation_attr( 'title' ); 1175 $product_img.wc_reset_variation_attr( 'data-caption' ); 1176 $product_img.wc_reset_variation_attr( 'alt' ); 1177 $product_img.wc_reset_variation_attr( 'data-src' ); 1178 $product_img.wc_reset_variation_attr( 'data-large_image' ); 1179 $product_img.wc_reset_variation_attr( 'data-large_image_width' ); 1180 $product_img.wc_reset_variation_attr( 'data-large_image_height' ); 1181 $product_img_wrap.wc_reset_variation_attr( 'data-thumb' ); 1182 $gallery_img.wc_reset_variation_attr( 'src' ); 1183 $product_link.wc_reset_variation_attr( 'href' ); 1184 }; 1185 1186 $( function () { 1187 if ( typeof wc_add_to_cart_variation_params !== 'undefined' ) { 1188 $( '.variations_form' ).each( function () { 1189 $( this ).wc_variation_form(); 1190 } ); 1191 } 1192 } ); 1193 1194 /** 1195 * Matches inline variation objects to chosen attributes 1196 * @deprecated 2.6.9 1197 * @type {Object} 1198 */ 1199 var wc_variation_form_matcher = { 1200 find_matching_variations: function ( product_variations, settings ) { 1201 var matching = []; 1202 for ( var i = 0; i < product_variations.length; i++ ) { 1203 var variation = product_variations[ i ]; 1204 1205 if ( 1206 wc_variation_form_matcher.variations_match( 1207 variation.attributes, 1208 settings 1209 ) 1210 ) { 1211 matching.push( variation ); 1212 } 1213 } 1214 return matching; 1215 }, 1216 variations_match: function ( attrs1, attrs2 ) { 1217 var match = true; 1218 for ( var attr_name in attrs1 ) { 1219 if ( attrs1.hasOwnProperty( attr_name ) ) { 1220 var val1 = attrs1[ attr_name ]; 1221 var val2 = attrs2[ attr_name ]; 1222 if ( 1223 val1 !== undefined && 1224 val2 !== undefined && 1225 val1.length !== 0 && 1226 val2.length !== 0 && 1227 val1 !== val2 1228 ) { 1229 match = false;
1230 } 1231 } 1232 } 1233 return match; 1234 }, 1235 }; 1236 1237 /** 1238 * Avoids using wp.template where possible in order to be CSP compliant. 1239 * wp.template uses internally eval(). 1240 * @param {string} templateId 1241 * @return {Function} 1242 */ 1243 var wp_template = function ( templateId ) { 1244 var html = document.getElementById( 'tmpl-' + templateId ).textContent; 1245 var hard = false; 1246 // any <# #> interpolate (evaluate). 1247 hard = hard || /<#\s?data\./.test( html ); 1248 // any data that is NOT data.variation. 1249 hard = hard || /{{{?\s?data\.(?!variation\.).+}}}?/.test( html ); 1250 // any data access deeper than 1 level e.g. 1251 // data.variation.object.item 1252 // data.variation.object['item'] 1253 // data.variation.array[0] 1254 hard = hard || /{{{?\s?data\.variation\.[\w-]*[^\s}]/.test( html ); 1255 if ( hard ) { 1256 return wp.template( templateId ); 1257 } 1258 return function template( data ) { 1259 var variation = data.variation || {}; 1260 return html.replace( 1261 /({{{?)\s?data\.variation\.([\w-]*)\s?(}}}?)/g, 1262 function ( _, open, key, close ) { 1263 // Error in the format, ignore. 1264 if ( open.length !== close.length ) { 1265 return ''; 1266 } 1267 var replacement = variation[ key ] || ''; 1268 // {{{ }}} => interpolate (unescaped). 1269 // {{ }} => interpolate (escaped). 1270 // https://codex.wordpress.org/Javascript_Reference/wp.template 1271 if ( open.length === 2 ) { 1272 return window.escape( replacement ); 1273 } 1274 return replacement; 1275 } 1276 ); 1277 }; 1278 }; 1279} )( jQuery, window, document );
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.