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 = self.showNoMatchingVariationsMsg.bind( self ); 31 32 // Events. 33 $form.on( 'click.wc-variation-form', '.reset_variations', { variationForm: self }, self.onReset ); 34 $form.on( 'reload_product_variations', { variationForm: self }, self.onReload ); 35 $form.on( 'hide_variation', { variationForm: self }, self.onHide ); 36 $form.on( 'show_variation', { variationForm: self }, self.onShow ); 37 $form.on( 'click', '.single_add_to_cart_button', { variationForm: self }, self.onAddToCart ); 38 $form.on( 'reset_data', { variationForm: self }, self.onResetDisplayedVariation ); 39 $form.on( 'reset_focus', { variationForm: self }, self.onResetVariationFocus ); 40 $form.on( 'announce_reset', { variationForm: self }, self.onAnnounceReset ); 41 $form.on( 'clear_reset_announcement', { variationForm: self }, self.onClearResetAnnouncement ); 42 $form.on( 'reset_image', { variationForm: self }, self.onResetImage ); 43 $form.on( 'change.wc-variation-form', '.variations select', { variationForm: self }, self.onChange ); 44 $form.on( 'found_variation.wc-variation-form', { variationForm: self }, self.onFoundVariation ); 45 $form.on( 'check_variations.wc-variation-form', { variationForm: self }, self.onFindVariation ); 46 $form.on( 'update_variation_values.wc-variation-form', { variationForm: self }, self.onUpdateAttributes ); 47 $form.on( 48 'keydown.wc-variation-form', 49 '.reset_variations', 50 { variationForm: self }, 51 self.onResetKeyDown 52 ); 53 54 // Init after gallery. 55 setTimeout( function() { 56 $form.trigger( 'check_variations' ); 57 $form.trigger( 'wc_variation_form', self ); 58 self.loading = false; 59 }, 100 ); 60 }; 61 62 /** 63 * Reset all fields. 64 */ 65 VariationForm.prototype.onReset = function( event ) { 66 event.preventDefault(); 67 event.data.variationForm.$attributeFields.val( '' ).trigger( 'change' ); 68 event.data.variationForm.$form.trigger( 'announce_reset' ); 69 event.data.variationForm.$form.trigger( 'reset_data' ); 70 event.data.variationForm.$form.trigger( 'reset_focus' ); 71 }; 72 73 /** 74 * Reload variation data from the DOM. 75 */ 76 VariationForm.prototype.onReload = function( event ) { 77 var form = event.data.variationForm; 78 form.variationData = form.$form.data( 'product_variations' ); 79 form.useAjax = false === form.variationData; 80 form.$form.trigger( 'check_variations' ); 81 }; 82 83 /** 84 * When a variation is hidden. 85 */ 86 VariationForm.prototype.onHide = function( event ) { 87 event.preventDefault(); 88 event.data.variationForm.$form 89 .find( '.single_add_to_cart_button' ) 90 .removeClass( 'wc-variation-is-unavailable' ) 91 .addClass( 'disabled wc-variation-selection-needed' ); 92 event.data.variationForm.$form 93 .find( '.woocommerce-variation-add-to-cart' ) 94 .removeClass( 'woocommerce-variation-add-to-cart-enabled' ) 95 .addClass( 'woocommerce-variation-add-to-cart-disabled' ); 96 }; 97 98 /** 99 * When a variation is shown.
100 */ 101 VariationForm.prototype.onShow = function( event, variation, purchasable ) { 102 event.preventDefault(); 103 if ( purchasable ) { 104 event.data.variationForm.$form 105 .find( '.single_add_to_cart_button' ) 106 .removeClass( 'disabled wc-variation-selection-needed wc-variation-is-unavailable' ); 107 event.data.variationForm.$form 108 .find( '.woocommerce-variation-add-to-cart' ) 109 .removeClass( 'woocommerce-variation-add-to-cart-disabled' ) 110 .addClass( 'woocommerce-variation-add-to-cart-enabled' ); 111 } else { 112 event.data.variationForm.$form 113 .find( '.single_add_to_cart_button' ) 114 .removeClass( 'wc-variation-selection-needed' ) 115 .addClass( 'disabled wc-variation-is-unavailable' ); 116 event.data.variationForm.$form 117 .find( '.woocommerce-variation-add-to-cart' ) 118 .removeClass( 'woocommerce-variation-add-to-cart-enabled' ) 119 .addClass( 'woocommerce-variation-add-to-cart-disabled' ); 120 } 121 122 // If present, the media element library needs initialized on the variation description. 123 if ( wp.mediaelement ) { 124 event.data.variationForm.$form.find( '.wp-audio-shortcode, .wp-video-shortcode' ) 125 .not( '.mejs-container' ) 126 .filter( 127 function () { 128 return ! $( this ).parent().hasClass( 'mejs-mediaelement' ); 129 } 130 ) 131 .mediaelementplayer( wp.mediaelement.settings ); 132 } 133 }; 134 135 /** 136 * When the cart button is pressed. 137 */ 138 VariationForm.prototype.onAddToCart = function( event ) { 139 if ( $( this ).is('.disabled') ) { 140 event.preventDefault(); 141 142 if ( $( this ).is('.wc-variation-is-unavailable') ) { 143 window.alert( wc_add_to_cart_variation_params.i18n_unavailable_text ); 144 } else if ( $( this ).is('.wc-variation-selection-needed') ) { 145 window.alert( wc_add_to_cart_variation_params.i18n_make_a_selection_text ); 146 } 147 } 148 }; 149 150 /** 151 * When displayed variation data is reset. 152 */ 153 VariationForm.prototype.onResetDisplayedVariation = function( event ) { 154 var form = event.data.variationForm; 155 form.$product.find( '.product_meta' ).find( '.sku' ).wc_reset_content(); 156 form.$product 157 .find( '.product_weight, .woocommerce-product-attributes-item--weight .woocommerce-product-attributes-item__value' ) 158 .wc_reset_content(); 159 form.$product 160 .find( '.product_dimensions, .woocommerce-product-attributes-item--dimensions .woocommerce-product-attributes-item__value' ) 161 .wc_reset_content(); 162 form.$form.trigger( 'reset_image' ); 163 form.$singleVariation.slideUp( 200 ).trigger( 'hide_variation' ); 164 }; 165 166 /** 167 * Announce reset to screen readers. 168 */ 169 VariationForm.prototype.onAnnounceReset = function( event ) { 170 event.data.variationForm.$resetAlert.text( wc_add_to_cart_variation_params.i18n_reset_alert_text ); 171 } 172 173 /** 174 * Focus variation reset 175 */ 176 VariationForm.prototype.onResetVariationFocus = function( event ) { 177 event.data.variationForm.$attributeFields[0].focus(); 178 } 179 180 /** Clear reset announcement */ 181 VariationForm.prototype.onClearResetAnnouncement = function( event ) { 182 event.data.variationForm.$resetAlert.text( '' ); 183 } 184 185 /** 186 * When the product image is reset. 187 */ 188 VariationForm.prototype.onResetImage = function( event ) { 189 event.data.variationForm.$form.wc_variations_image_update( false ); 190 }; 191 192 /** 193 * Looks for matching variations for current selected attributes. 194 */ 195 VariationForm.prototype.onFindVariation = function( event, chosenAttributes ) { 196 var form = event.data.variationForm, 197 attributes = 'undefined' !== typeof chosenAttributes ? chosenAttributes : form.getChosenAttributes(), 198 currentAttributes = attributes.data; 199 200 if ( attributes.count && attributes.count === attributes.chosenCount ) { 201 if ( form.useAjax ) { 202 if ( form.xhr ) { 203 form.xhr.abort(); 204 } 205 form.$form.block( { message: null, overlayCSS: { background: '#fff', opacity: 0.6 } } ); 206 currentAttributes.product_id = parseInt( form.$form.data( 'product_id' ), 10 ); 207 currentAttributes.custom_data = form.$form.data( 'custom_data' ); 208 form.xhr = $.ajax( { 209 url: wc_add_to_cart_variation_params.wc_ajax_url.toString().replace( '%%endpoint%%', 'get_variation' ), 210 type: 'POST', 211 data: currentAttributes, 212 success: function( variation ) { 213 if ( variation ) {
214 form.$form.trigger( 'found_variation', [ variation ] ); 215 } else { 216 form.$form.trigger( 'reset_data' ); 217 attributes.chosenCount = 0; 218 219 if ( ! form.loading ) { 220 form.showNoMatchingVariationsMsg(); 221 } 222 } 223 }, 224 complete: function() { 225 form.$form.unblock(); 226 } 227 } ); 228 } else { 229 form.$form.trigger( 'update_variation_values' ); 230 231 var matching_variations = form.findMatchingVariations( form.variationData, currentAttributes ), 232 variation = matching_variations.shift(); 233 234 if ( variation ) { 235 form.$form.trigger( 'found_variation', [ variation ] ); 236 } else { 237 form.$form.trigger( 'reset_data' ); 238 attributes.chosenCount = 0; 239 240 if ( ! form.loading ) { 241 form.showNoMatchingVariationsMsg(); 242 } 243 } 244 } 245 } else { 246 form.$form.trigger( 'update_variation_values' ); 247 form.$form.trigger( 'reset_data' ); 248 } 249 250 // Show reset link. 251 form.toggleResetLink( attributes.chosenCount > 0 ); 252 }; 253 254 /** 255 * Triggered when a variation has been found which matches all attributes. 256 */ 257 VariationForm.prototype.onFoundVariation = function( event, variation ) { 258 var form = event.data.variationForm, 259 $sku = form.$product.find( '.product_meta' ).find( '.sku' ), 260 $weight = form.$product.find( 261 '.product_weight, .woocommerce-product-attributes-item--weight .woocommerce-product-attributes-item__value' 262 ), 263 $dimensions = form.$product.find( 264 '.product_dimensions, .woocommerce-product-attributes-item--dimensions .woocommerce-product-attributes-item__value' 265 ), 266 $qty_input = form.$singleVariationWrap.find( '.quantity input.qty[name="quantity"]' ), 267 $qty = $qty_input.closest( '.quantity' ), 268 purchasable = true, 269 variation_id = '', 270 template = false, 271 $template_html = ''; 272 273 if ( variation.sku ) { 274 $sku.wc_set_content( variation.sku ); 275 } else { 276 $sku.wc_reset_content(); 277 } 278 279 if ( variation.weight ) { 280 $weight.wc_set_content( variation.weight_html ); 281 } else { 282 $weight.wc_reset_content(); 283 } 284 285 if ( variation.dimensions ) { 286 // Decode HTML entities. 287 $dimensions.wc_set_content( $.parseHTML( variation.dimensions_html )[0].data ); 288 } else { 289 $dimensions.wc_reset_content(); 290 } 291 292 form.$form.wc_variations_image_update( variation ); 293 294 if ( ! variation.variation_is_visible ) { 295 template = wp_template( 'unavailable-variation-template' ); 296 } else { 297 template = wp_template( 'variation-template' ); 298 variation_id = variation.variation_id; 299 } 300 301 $template_html = template( { 302 variation: variation 303 } ); 304 $template_html = $template_html.replace( '/*<![CDATA[*/', '' ); 305 $template_html = $template_html.replace( '/*]]>*/', '' ); 306
307 form.$form.find( 'input[name="variation_id"], input.variation_id' ).val( variation.variation_id ).trigger( 'change' ); 308 309 // Hide or show qty input 310 if ( variation.is_sold_individually === 'yes' ) { 311 $qty_input.val( '1' ).attr( 'min', '1' ).attr( 'max', '' ).trigger( 'change' ); 312 $qty.hide(); 313 } else { 314 315 var qty_val = parseFloat( $qty_input.val() ); 316 317 if ( isNaN( qty_val ) ) { 318 qty_val = variation.min_qty; 319 } else { 320 qty_val = qty_val > parseFloat( variation.max_qty ) ? variation.max_qty : qty_val; 321 qty_val = qty_val < parseFloat( variation.min_qty ) ? variation.min_qty : qty_val; 322 } 323 324 $qty_input.attr( 'min', variation.min_qty ).attr( 'max', variation.max_qty ).val( qty_val ).trigger( 'change' ); 325 $qty.show(); 326 } 327 328 // Enable or disable the add to cart button 329 if ( ! variation.is_purchasable || ! variation.is_in_stock || ! variation.variation_is_visible ) { 330 purchasable = false; 331 } 332 333 // Add a delay before updating the live region to ensure screen readers pick up the content changes. 334 setTimeout( function() { 335 form.$singleVariation.html( $template_html ); 336 337 // Reveal 338 if ( form.$singleVariation.text().trim() ) { 339 form.$singleVariation.slideDown( 200 ).trigger( 'show_variation', [ variation, purchasable ] ); 340 } else { 341 form.$singleVariation.show().trigger( 'show_variation', [ variation, purchasable ] ); 342 } 343 }, 300); 344 345 }; 346 347 /** 348 * Triggered when an attribute field changes. 349 */ 350 VariationForm.prototype.onChange = function( event ) { 351 var form = event.data.variationForm; 352
353 form.$form.find( 'input[name="variation_id"], input.variation_id' ).val( '' ).trigger( 'change' ); 354 form.$form.trigger( 'clear_reset_announcement' ); 355 form.$form.find( '.wc-no-matching-variations' ).parent().remove(); 356 357 if ( form.useAjax ) { 358 form.$form.trigger( 'check_variations' ); 359 } else { 360 form.$form.trigger( 'woocommerce_variation_select_change' ); 361 form.$form.trigger( 'check_variations' ); 362 } 363 364 // Custom event for when variation selection has been changed 365 form.$form.trigger( 'woocommerce_variation_has_changed' ); 366 }; 367 368 /** 369 * Escape quotes in a string. 370 * @param {string} string 371 * @return {string} 372 */ 373 VariationForm.prototype.addSlashes = function( string ) { 374 string = string.replace( /'/g, '\\\'' ); 375 string = string.replace( /"/g, '\\\"' ); 376 return string; 377 }; 378 379 /** 380 * Updates attributes in the DOM to show valid values. 381 */ 382 VariationForm.prototype.onUpdateAttributes = function( event ) { 383 var form = event.data.variationForm, 384 attributes = form.getChosenAttributes(), 385 currentAttributes = attributes.data; 386 387 if ( form.useAjax ) { 388 return; 389 } 390 391 // Loop through selects and disable/enable options based on selections. 392 form.$attributeFields.each( function( index, el ) { 393 var current_attr_select = $( el ), 394 current_attr_name = current_attr_select.data( 'attribute_name' ) || current_attr_select.attr( 'name' ), 395 show_option_none = $( el ).data( 'show_option_none' ), 396 option_gt_filter = ':gt(0)', 397 attached_options_count = 0, 398 new_attr_select = $( '<select/>' ), 399 selected_attr_val = current_attr_select.val() || '', 400 selected_attr_val_valid = true; 401 402 // Reference options set at first. 403 if ( ! current_attr_select.data( 'attribute_html' ) ) { 404 var refSelect = current_attr_select.clone(); 405 406 refSelect.find( 'option' ).removeAttr( 'attached' ).prop( 'disabled', false ).prop( 'selected', false ); 407 408 // Legacy data attribute. 409 current_attr_select.data( 410 'attribute_options', 411 refSelect.find( 'option' + option_gt_filter ).get() 412 ); 413 current_attr_select.data( 'attribute_html', refSelect.html() ); 414 } 415 416 new_attr_select.html( current_attr_select.data( 'attribute_html' ) ); 417 418 // The attribute of this select field should not be taken into account when calculating its matching variations: 419 // The constraints of this attribute are shaped by the values of the other attributes. 420 var checkAttributes = $.extend( true, {}, currentAttributes ); 421 422 checkAttributes[ current_attr_name ] = ''; 423 424 var variations = form.findMatchingVariations( form.variationData, checkAttributes ); 425 426 // Loop through variations. 427 for ( var num in variations ) { 428 if ( typeof( variations[ num ] ) !== 'undefined' ) { 429 var variationAttributes = variations[ num ].attributes; 430 431 for ( var attr_name in variationAttributes ) { 432 if ( variationAttributes.hasOwnProperty( attr_name ) ) { 433 var attr_val = variationAttributes[ attr_name ], 434 variation_active = ''; 435 436 if ( attr_name === current_attr_name ) { 437 if ( variations[ num ].variation_is_active ) { 438 variation_active = 'enabled'; 439 } 440 441 if ( attr_val ) { 442 // Decode entities. 443 attr_val = $( '<div/>' ).html( attr_val ).text(); 444 445 // Attach to matching options by value. This is done to compare 446 // TEXT values rather than any HTML entities. 447 var $option_elements = new_attr_select.find( 'option' ); 448 if ( $option_elements.length ) { 449 for (var i = 0, len = $option_elements.length; i < len; i++) { 450 var $option_element = $( $option_elements[i] ), 451 option_value = $option_element.val(); 452 453 if ( attr_val === option_value ) { 454 $option_element.addClass( 'attached ' + variation_active ); 455 break; 456 } 457 } 458 } 459 } else { 460 // Attach all apart from placeholder. 461 new_attr_select.find( 'option:gt(0)' ).addClass( 'attached ' + variation_active ); 462 } 463 } 464 } 465 } 466 } 467 } 468 469 // Count available options. 470 attached_options_count = new_attr_select.find( 'option.attached' ).length; 471 472 // Check if current selection is in attached options. 473 if ( selected_attr_val ) { 474 selected_attr_val_valid = false;
475 476 if ( 0 !== attached_options_count ) { 477 new_attr_select.find( 'option.attached.enabled' ).each( function() { 478 var option_value = $( this ).val(); 479 480 if ( selected_attr_val === option_value ) { 481 selected_attr_val_valid = true; 482 return false; // break. 483 } 484 }); 485 } 486 } 487 488 // Detach the placeholder if: 489 // - Valid options exist. 490 // - The current selection is non-empty. 491 // - The current selection is valid. 492 // - Placeholders are not set to be permanently visible. 493 if ( attached_options_count > 0 && selected_attr_val && selected_attr_val_valid && ( 'no' === show_option_none ) ) { 494 new_attr_select.find( 'option:first' ).remove(); 495 option_gt_filter = ''; 496 } 497 498 // Detach unattached. 499 new_attr_select.find( 'option' + option_gt_filter + ':not(.attached)' ).remove(); 500 501 // Finally, copy to DOM and set value. 502 current_attr_select.html( new_attr_select.html() ); 503 current_attr_select.find( 'option' + option_gt_filter + ':not(.enabled)' ).prop( 'disabled', true ); 504 505 // Choose selected value. 506 if ( selected_attr_val ) { 507 // If the previously selected value is no longer available, fall back to the placeholder (it's going to be there). 508 if ( selected_attr_val_valid ) { 509 current_attr_select.val( selected_attr_val ); 510 } else { 511 current_attr_select.val( '' ).trigger( 'change' ); 512 } 513 } else { 514 current_attr_select.val( '' ); // No change event to prevent infinite loop. 515 } 516 }); 517 518 // Custom event for when variations have been updated. 519 form.$form.trigger( 'woocommerce_update_variation_values' ); 520 }; 521 522 /** 523 * Get chosen attributes from form. 524 * @return array 525 */ 526 VariationForm.prototype.getChosenAttributes = function() { 527 var data = {}; 528 var count = 0; 529 var chosen = 0; 530 531 this.$attributeFields.each( function() { 532 var attribute_name = $( this ).data( 'attribute_name' ) || $( this ).attr( 'name' ); 533 var value = $( this ).val() || ''; 534 535 if ( value.length > 0 ) { 536 chosen ++; 537 } 538 539 count ++; 540 data[ attribute_name ] = value; 541 }); 542 543 return { 544 'count' : count, 545 'chosenCount': chosen, 546 'data' : data 547 }; 548 }; 549 550 /** 551 * Find matching variations for attributes. 552 */ 553 VariationForm.prototype.findMatchingVariations = function( variations, attributes ) { 554 var matching = []; 555 for ( var i = 0; i < variations.length; i++ ) { 556 var variation = variations[i]; 557 558 if ( this.isMatch( variation.attributes, attributes ) ) { 559 matching.push( variation ); 560 } 561 } 562 return matching; 563 }; 564 565 /** 566 * See if attributes match. 567 * @return {Boolean} 568 */ 569 VariationForm.prototype.isMatch = function( variation_attributes, attributes ) { 570 var match = true; 571 for ( var attr_name in variation_attributes ) { 572 if ( variation_attributes.hasOwnProperty( attr_name ) ) { 573 var val1 = variation_attributes[ attr_name ]; 574 var val2 = attributes[ attr_name ]; 575 if ( val1 !== undefined && val2 !== undefined && val1.length !== 0 && val2.length !== 0 && val1 !== val2 ) { 576 match = false; 577 } 578 } 579 } 580 return match; 581 }; 582 583 /** 584 * Show or hide the reset link. 585 */ 586 VariationForm.prototype.toggleResetLink = function( on ) { 587 if ( on ) { 588 if ( this.$resetVariations.css( 'visibility' ) === 'hidden' ) { 589 this.$resetVariations.css( 'visibility', 'visible' ).hide().fadeIn(); 590 } 591 } else { 592 this.$resetVariations.css( 'visibility', 'hidden' ); 593 } 594 }; 595 596 /** 597 * Show no matching variation message. 598 */ 599 VariationForm.prototype.showNoMatchingVariationsMsg = function() { 600 this.$form 601 .find( '.single_variation' ) 602 .after( 603 '<div role="alert">' + 604 '<p class="wc-no-matching-variations woocommerce-info">' + 605 wc_add_to_cart_variation_params.i18n_no_matching_variations_text + 606 '</p>' + 607 '</div>' 608 ) 609 .next( 'div' ) 610 .find( '.wc-no-matching-variations' ) 611 .slideDown( 200 ); 612 }; 613 614 /** 615 * Handle reset key down event for accessibility. 616 * @param {KeyboardEvent} event - The keyboard event object 617 */ 618 VariationForm.prototype.onResetKeyDown = function ( event ) { 619 if ( event.code === 'Enter' || event.code === 'Space' ) { 620 event.preventDefault(); 621 event.data.variationForm.onReset( event ); 622 } 623 }; 624 625 /** 626 * Function to call wc_variation_form on jquery selector. 627 */ 628 $.fn.wc_variation_form = function() { 629 new VariationForm( this ); 630 return this; 631 }; 632 633 /** 634 * Stores the default text for an element so it can be reset later 635 */ 636 $.fn.wc_set_content = function( content ) { 637 if ( undefined === this.attr( 'data-o_content' ) ) { 638 this.attr( 'data-o_content', this.text() ); 639 } 640 this.text( content ); 641 }; 642 643 /** 644 * Stores the default text for an element so it can be reset later 645 */ 646 $.fn.wc_reset_content = function() { 647 if ( undefined !== this.attr( 'data-o_content' ) ) { 648 this.text( this.attr( 'data-o_content' ) ); 649 } 650 }; 651 652 /** 653 * Stores a default attribute for an element so it can be reset later 654 */ 655 $.fn.wc_set_variation_attr = function( attr, value ) { 656 if ( undefined === this.attr( 'data-o_' + attr ) ) { 657 this.attr( 'data-o_' + attr, ( ! this.attr( attr ) ) ? '' : this.attr( attr ) ); 658 } 659 if ( false === value ) { 660 this.removeAttr( attr ); 661 } else { 662 this.attr( attr, value ); 663 } 664 }; 665 666 /** 667 * Reset a default attribute for an element so it can be reset later 668 */ 669 $.fn.wc_reset_variation_attr = function( attr ) { 670 if ( undefined !== this.attr( 'data-o_' + attr ) ) { 671 this.attr( attr, this.attr( 'data-o_' + attr ) ); 672 } 673 }; 674 675 /** 676 * Reset the slide position if the variation has a different image than the current one 677 */ 678 $.fn.wc_maybe_trigger_slide_position_reset = function( variation ) { 679 var $form = $( this ), 680 $product = $form.closest( '.product' ), 681 $product_gallery = $product.find( '.images' ), 682 reset_slide_position = false, 683 new_image_id = ( variation && variation.image_id ) ? variation.image_id : ''; 684 685 if ( $form.attr( 'current-image' ) !== new_image_id ) { 686 reset_slide_position = true; 687 } 688 689 $form.attr( 'current-image', new_image_id ); 690 691 if ( reset_slide_position ) { 692 $product_gallery.trigger( 'woocommerce_gallery_reset_slide_position' ); 693 } 694 }; 695 696 /** 697 * Sets product images for the chosen variation 698 */ 699 $.fn.wc_variations_image_update = function( variation ) { 700 var $form = this, 701 $product = $form.closest( '.product' ), 702 $product_gallery = $product.find( '.images' ), 703 $gallery_nav = $product.find( '.flex-control-nav' ), 704 $gallery_img = $gallery_nav.find( 'li:eq(0) img' ), 705 $product_img_wrap = $product_gallery 706 .find( '.woocommerce-product-gallery__image, .woocommerce-product-gallery__image--placeholder' ) 707 .eq( 0 ), 708 $product_img = $product_img_wrap.find( '.wp-post-image' ), 709 $product_link = $product_img_wrap.find( 'a' ).eq( 0 ); 710 711 if ( variation && variation.image && variation.image.src && variation.image.src.length > 1 ) { 712 // See if the gallery has an image with the same original src as the image we want to switch to. 713 var galleryHasImage = $gallery_nav.find( 'li img[data-o_src="' + variation.image.gallery_thumbnail_src + '"]' ).length > 0; 714 715 // If the gallery has the image, reset the images. We'll scroll to the correct one. 716 if ( galleryHasImage ) { 717 $form.wc_variations_image_reset(); 718 } 719 720 // See if gallery has a matching image we can slide to.
721 var slideToImage = $gallery_nav.find( 'li img[src="' + variation.image.gallery_thumbnail_src + '"]' ); 722 723 if ( slideToImage.length > 0 ) { 724 slideToImage.trigger( 'flexslider-click' ); 725 $form.attr( 'current-image', variation.image_id ); 726 window.setTimeout( function() { 727 $( window ).trigger( 'resize' ); 728 $product_gallery.trigger( 'woocommerce_gallery_init_zoom' ); 729 }, 20 ); 730 return; 731 } 732 733 $product_img.wc_set_variation_attr( 'src', variation.image.src ); 734 $product_img.wc_set_variation_attr( 'height', variation.image.src_h ); 735 $product_img.wc_set_variation_attr( 'width', variation.image.src_w ); 736 $product_img.wc_set_variation_attr( 'srcset', variation.image.srcset ); 737 $product_img.wc_set_variation_attr( 'sizes', variation.image.sizes ); 738 $product_img.wc_set_variation_attr( 'title', variation.image.title ); 739 $product_img.wc_set_variation_attr( 'data-caption', variation.image.caption ); 740 $product_img.wc_set_variation_attr( 'alt', variation.image.alt ); 741 $product_img.wc_set_variation_attr( 'data-src', variation.image.full_src ); 742 $product_img.wc_set_variation_attr( 'data-large_image', variation.image.full_src ); 743 $product_img.wc_set_variation_attr( 'data-large_image_width', variation.image.full_src_w ); 744 $product_img.wc_set_variation_attr( 'data-large_image_height', variation.image.full_src_h ); 745 $product_img_wrap.wc_set_variation_attr( 'data-thumb', variation.image.src ); 746 $gallery_img.wc_set_variation_attr( 'src', variation.image.gallery_thumbnail_src ); 747 $product_link.wc_set_variation_attr( 'href', variation.image.full_src ); 748 } else { 749 $form.wc_variations_image_reset(); 750 } 751 752 window.setTimeout( function() { 753 $( window ).trigger( 'resize' ); 754 $form.wc_maybe_trigger_slide_position_reset( variation ); 755 $product_gallery.trigger( 'woocommerce_gallery_init_zoom' ); 756 }, 20 ); 757 }; 758 759 /** 760 * Reset main image to defaults. 761 */ 762 $.fn.wc_variations_image_reset = function() { 763 var $form = this, 764 $product = $form.closest( '.product' ), 765 $product_gallery = $product.find( '.images' ), 766 $gallery_nav = $product.find( '.flex-control-nav' ), 767 $gallery_img = $gallery_nav.find( 'li:eq(0) img' ), 768 $product_img_wrap = $product_gallery 769 .find( '.woocommerce-product-gallery__image, .woocommerce-product-gallery__image--placeholder' ) 770 .eq( 0 ), 771 $product_img = $product_img_wrap.find( '.wp-post-image' ), 772 $product_link = $product_img_wrap.find( 'a' ).eq( 0 ); 773 774 $product_img.wc_reset_variation_attr( 'src' ); 775 $product_img.wc_reset_variation_attr( 'width' ); 776 $product_img.wc_reset_variation_attr( 'height' ); 777 $product_img.wc_reset_variation_attr( 'srcset' ); 778 $product_img.wc_reset_variation_attr( 'sizes' ); 779 $product_img.wc_reset_variation_attr( 'title' ); 780 $product_img.wc_reset_variation_attr( 'data-caption' ); 781 $product_img.wc_reset_variation_attr( 'alt' ); 782 $product_img.wc_reset_variation_attr( 'data-src' ); 783 $product_img.wc_reset_variation_attr( 'data-large_image' ); 784 $product_img.wc_reset_variation_attr( 'data-large_image_width' ); 785 $product_img.wc_reset_variation_attr( 'data-large_image_height' ); 786 $product_img_wrap.wc_reset_variation_attr( 'data-thumb' ); 787 $gallery_img.wc_reset_variation_attr( 'src' ); 788 $product_link.wc_reset_variation_attr( 'href' ); 789 }; 790 791 $(function() { 792 if ( typeof wc_add_to_cart_variation_params !== 'undefined' ) { 793 $( '.variations_form' ).each( function() { 794 $( this ).wc_variation_form(); 795 }); 796 } 797 }); 798 799 /** 800 * Matches inline variation objects to chosen attributes 801 * @deprecated 2.6.9 802 * @type {Object} 803 */ 804 var wc_variation_form_matcher = { 805 find_matching_variations: function( product_variations, settings ) { 806 var matching = []; 807 for ( var i = 0; i < product_variations.length; i++ ) { 808 var variation = product_variations[i]; 809 810 if ( wc_variation_form_matcher.variations_match( variation.attributes, settings ) ) { 811 matching.push( variation ); 812 } 813 } 814 return matching; 815 }, 816 variations_match: function( attrs1, attrs2 ) { 817 var match = true; 818 for ( var attr_name in attrs1 ) { 819 if ( attrs1.hasOwnProperty( attr_name ) ) { 820 var val1 = attrs1[ attr_name ]; 821 var val2 = attrs2[ attr_name ]; 822 if ( val1 !== undefined && val2 !== undefined && val1.length !== 0 && val2.length !== 0 && val1 !== val2 ) { 823 match = false;
824 } 825 } 826 } 827 return match; 828 } 829 }; 830 831 /** 832 * Avoids using wp.template where possible in order to be CSP compliant. 833 * wp.template uses internally eval(). 834 * @param {string} templateId 835 * @return {Function} 836 */ 837 var wp_template = function( templateId ) { 838 var html = document.getElementById( 'tmpl-' + templateId ).textContent; 839 var hard = false; 840 // any <# #> interpolate (evaluate). 841 hard = hard || /<#\s?data\./.test( html ); 842 // any data that is NOT data.variation. 843 hard = hard || /{{{?\s?data\.(?!variation\.).+}}}?/.test( html ); 844 // any data access deeper than 1 level e.g. 845 // data.variation.object.item 846 // data.variation.object['item'] 847 // data.variation.array[0] 848 hard = hard || /{{{?\s?data\.variation\.[\w-]*[^\s}]/.test ( html ); 849 if ( hard ) { 850 return wp.template( templateId ); 851 } 852 return function template ( data ) { 853 var variation = data.variation || {}; 854 return html.replace( /({{{?)\s?data\.variation\.([\w-]*)\s?(}}}?)/g, function( _, open, key, close ) { 855 // Error in the format, ignore. 856 if ( open.length !== close.length ) { 857 return ''; 858 } 859 var replacement = variation[ key ] || ''; 860 // {{{ }}} => interpolate (unescaped). 861 // {{ }} => interpolate (escaped). 862 // https://codex.wordpress.org/Javascript_Reference/wp.template 863 if ( open.length === 2 ) { 864 return window.escape( replacement ); 865 } 866 return replacement; 867 }); 868 }; 869 }; 870 871})( 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.