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 * Sets product images for the chosen variation 874 */ 875 $.fn.wc_variations_image_update = function ( variation ) { 876 var $form = this, 877 $product = $form.closest( '.product' ), 878 $product_gallery = $product.find( '.images' ), 879 $gallery_nav = $product.find( '.flex-control-nav' ), 880 $gallery_img = $gallery_nav.find( 'li:eq(0) img' ), 881 $product_img_wrap = $product_gallery 882 .find( 883 '.woocommerce-product-gallery__image, .woocommerce-product-gallery__image--placeholder' 884 ) 885 .eq( 0 ), 886 $product_img = $product_img_wrap.find( '.wp-post-image' ), 887 $product_link = $product_img_wrap.find( 'a' ).eq( 0 ); 888 889 if ( 890 variation && 891 variation.image && 892 variation.image.src && 893 variation.image.src.length > 1 894 ) { 895 // See if the gallery has an image with the same original src as the image we want to switch to. 896 var galleryHasImage = 897 $gallery_nav.find( 898 'li img[data-o_src="' + 899 variation.image.gallery_thumbnail_src + 900 '"]' 901 ).length > 0; 902 903 // If the gallery has the image, reset the images. We'll scroll to the correct one. 904 if ( galleryHasImage ) { 905 $form.wc_variations_image_reset(); 906 } 907 908 // See if gallery has a matching image we can slide to. 909 var slideToImage = $gallery_nav.find( 910 'li img[src="' + variation.image.gallery_thumbnail_src + '"]' 911 ); 912 913 if ( slideToImage.length > 0 ) { 914 slideToImage.trigger( 'flexslider-click' ); 915 $form.attr( 'current-image', variation.image_id ); 916 window.setTimeout( function () { 917 $( window ).trigger( 'resize' ); 918 $product_gallery.trigger( 'woocommerce_gallery_init_zoom' ); 919 }, 20 ); 920 return; 921 } 922 923 $product_img.wc_set_variation_attr( 'src', variation.image.src ); 924 $product_img.wc_set_variation_attr( 925 'height', 926 variation.image.src_h 927 ); 928 $product_img.wc_set_variation_attr( 929 'width', 930 variation.image.src_w 931 ); 932 $product_img.wc_set_variation_attr( 933 'srcset', 934 variation.image.srcset 935 ); 936 $product_img.wc_set_variation_attr( 937 'sizes', 938 variation.image.sizes 939 ); 940 $product_img.wc_set_variation_attr( 941 'title', 942 variation.image.title 943 ); 944 $product_img.wc_set_variation_attr( 945 'data-caption', 946 variation.image.caption 947 ); 948 $product_img.wc_set_variation_attr( 'alt', variation.image.alt ); 949 $product_img.wc_set_variation_attr( 950 'data-src', 951 variation.image.full_src 952 ); 953 $product_img.wc_set_variation_attr( 954 'data-large_image', 955 variation.image.full_src 956 ); 957 $product_img.wc_set_variation_attr( 958 'data-large_image_width', 959 variation.image.full_src_w 960 ); 961 $product_img.wc_set_variation_attr( 962 'data-large_image_height', 963 variation.image.full_src_h 964 ); 965 $product_img_wrap.wc_set_variation_attr( 966 'data-thumb', 967 variation.image.src 968 ); 969 $gallery_img.wc_set_variation_attr( 970 'src', 971 variation.image.gallery_thumbnail_src 972 ); 973 $product_link.wc_set_variation_attr( 974 'href', 975 variation.image.full_src 976 ); 977 } else { 978 $form.wc_variations_image_reset(); 979 } 980 981 window.setTimeout( function () { 982 $( window ).trigger( 'resize' ); 983 $form.wc_maybe_trigger_slide_position_reset( variation ); 984 $product_gallery.trigger( 'woocommerce_gallery_init_zoom' ); 985 }, 20 ); 986 }; 987 988 /** 989 * Reset main image to defaults. 990 */ 991 $.fn.wc_variations_image_reset = function () { 992 var $form = this, 993 $product = $form.closest( '.product' ), 994 $product_gallery = $product.find( '.images' ), 995 $gallery_nav = $product.find( '.flex-control-nav' ), 996 $gallery_img = $gallery_nav.find( 'li:eq(0) img' ), 997 $product_img_wrap = $product_gallery 998 .find( 999 '.woocommerce-product-gallery__image, .woocommerce-product-gallery__image--placeholder' 1000 ) 1001 .eq( 0 ), 1002 $product_img = $product_img_wrap.find( '.wp-post-image' ), 1003 $product_link = $product_img_wrap.find( 'a' ).eq( 0 ); 1004 1005 $product_img.wc_reset_variation_attr( 'src' ); 1006 $product_img.wc_reset_variation_attr( 'width' ); 1007 $product_img.wc_reset_variation_attr( 'height' ); 1008 $product_img.wc_reset_variation_attr( 'srcset' ); 1009 $product_img.wc_reset_variation_attr( 'sizes' ); 1010 $product_img.wc_reset_variation_attr( 'title' ); 1011 $product_img.wc_reset_variation_attr( 'data-caption' ); 1012 $product_img.wc_reset_variation_attr( 'alt' ); 1013 $product_img.wc_reset_variation_attr( 'data-src' ); 1014 $product_img.wc_reset_variation_attr( 'data-large_image' ); 1015 $product_img.wc_reset_variation_attr( 'data-large_image_width' ); 1016 $product_img.wc_reset_variation_attr( 'data-large_image_height' ); 1017 $product_img_wrap.wc_reset_variation_attr( 'data-thumb' ); 1018 $gallery_img.wc_reset_variation_attr( 'src' ); 1019 $product_link.wc_reset_variation_attr( 'href' ); 1020 }; 1021 1022 $( function () { 1023 if ( typeof wc_add_to_cart_variation_params !== 'undefined' ) { 1024 $( '.variations_form' ).each( function () { 1025 $( this ).wc_variation_form(); 1026 } ); 1027 } 1028 } ); 1029 1030 /** 1031 * Matches inline variation objects to chosen attributes 1032 * @deprecated 2.6.9 1033 * @type {Object} 1034 */ 1035 var wc_variation_form_matcher = { 1036 find_matching_variations: function ( product_variations, settings ) { 1037 var matching = []; 1038 for ( var i = 0; i < product_variations.length; i++ ) { 1039 var variation = product_variations[ i ]; 1040 1041 if ( 1042 wc_variation_form_matcher.variations_match( 1043 variation.attributes, 1044 settings 1045 ) 1046 ) { 1047 matching.push( variation ); 1048 } 1049 } 1050 return matching; 1051 }, 1052 variations_match: function ( attrs1, attrs2 ) { 1053 var match = true; 1054 for ( var attr_name in attrs1 ) { 1055 if ( attrs1.hasOwnProperty( attr_name ) ) { 1056 var val1 = attrs1[ attr_name ]; 1057 var val2 = attrs2[ attr_name ]; 1058 if ( 1059 val1 !== undefined && 1060 val2 !== undefined && 1061 val1.length !== 0 && 1062 val2.length !== 0 && 1063 val1 !== val2 1064 ) { 1065 match = false;
1066 } 1067 } 1068 } 1069 return match; 1070 }, 1071 }; 1072 1073 /** 1074 * Avoids using wp.template where possible in order to be CSP compliant. 1075 * wp.template uses internally eval(). 1076 * @param {string} templateId 1077 * @return {Function} 1078 */ 1079 var wp_template = function ( templateId ) { 1080 var html = document.getElementById( 'tmpl-' + templateId ).textContent; 1081 var hard = false; 1082 // any <# #> interpolate (evaluate). 1083 hard = hard || /<#\s?data\./.test( html ); 1084 // any data that is NOT data.variation. 1085 hard = hard || /{{{?\s?data\.(?!variation\.).+}}}?/.test( html ); 1086 // any data access deeper than 1 level e.g. 1087 // data.variation.object.item 1088 // data.variation.object['item'] 1089 // data.variation.array[0] 1090 hard = hard || /{{{?\s?data\.variation\.[\w-]*[^\s}]/.test( html ); 1091 if ( hard ) { 1092 return wp.template( templateId ); 1093 } 1094 return function template( data ) { 1095 var variation = data.variation || {}; 1096 return html.replace( 1097 /({{{?)\s?data\.variation\.([\w-]*)\s?(}}}?)/g, 1098 function ( _, open, key, close ) { 1099 // Error in the format, ignore. 1100 if ( open.length !== close.length ) { 1101 return ''; 1102 } 1103 var replacement = variation[ key ] || ''; 1104 // {{{ }}} => interpolate (unescaped). 1105 // {{ }} => interpolate (escaped). 1106 // https://codex.wordpress.org/Javascript_Reference/wp.template 1107 if ( open.length === 2 ) { 1108 return window.escape( replacement ); 1109 } 1110 return replacement; 1111 } 1112 ); 1113 }; 1114 }; 1115} )( 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.