1/* global wc_add_to_cart_variation_params */ 2 3( function( $, window, document ) { 4 'use strict'; 5 /** 6 * Handles variation forms and attributes. 7 * 8 * @param {Object} $form The product form 9 * @param {Object} element The product element 10 * @param {Object} currentCart The current cart form 11 * @param {Object} variableProductContainers The container for the variable product 12 * @param {Object} epoObject The EPO Object 13 */ 14 var VariationForm = function( $form, element, currentCart, variableProductContainers, epoObject ) { 15 var self = this; 16 17 self.field = element; 18 self.currentCart = currentCart; 19 self.variableProductContainers = variableProductContainers; 20 self.epoObject = epoObject; 21 self.$form = $form; 22 self.$wrap = $form.closest( '.tc-epo-element-product-container-wrap' ); 23 self.$attributeFields = $form.find( '.tc-epo-variable-product-selector' ); 24 self.$singleVariation = $form.find( '.tc-epo-element-single-variation' ); 25 self.$resetVariations = $form.find( '.tc-epo-element-variable-reset-variations' ); 26 self.variationData = $form.data( 'product_variations' ); 27 28 self.useAjax = false === self.variationData; 29 self.xhr = false; 30 self.loading = true; 31 self.variationId = $form.closest( '.tc-epo-element-product-li-container' ).find( '.tc-epo-element-product-container-variation-id input.product-variation-id' ); 32 33 // Methods. 34 self.getChosenAttributes = self.getChosenAttributes.bind( self ); 35 self.findMatchingVariations = self.findMatchingVariations.bind( self ); 36 self.isMatch = self.isMatch.bind( self ); 37 self.toggleResetLink = self.toggleResetLink.bind( self ); 38 39 // Events. 40 $form.on( 'click.tc-variation-form', '.tc-epo-element-variable-reset-variations', { variationForm: self }, self.onReset ); 41 $form.on( 'tc_hide_variation', { variationForm: self }, self.onHide ); 42 $form.on( 'tc_show_variation', { variationForm: self }, self.onShow ); 43 $form.on( 'tc_reset_data', { variationForm: self }, self.onResetDisplayedVariation ); 44 $form.on( 'tc_reset_image', { variationForm: self }, self.onResetImage ); 45 $form.on( 'change.tc-variation-form', '.tc-epo-variable-product-selector', { variationForm: self }, self.onChange ); 46 $form.on( 'tc_found_variation.tc-variation-form', { variationForm: self }, self.onFoundVariation ); 47 $form.on( 'tc_check_variations.tc-variation-form', { variationForm: self }, self.onFindVariation ); 48 $form.on( 'tc_update_variation_values.tc-variation-form', { variationForm: self }, self.onUpdateAttributes ); 49 $form.on( 'refresh.tc-variation-form', { variationForm: self }, self.onRefreshContainer ); 50 $form.on( 'update_field.tc-variation-form', { variationForm: self }, self.onUpdateField ); 51 52 self.variationId.val( '' ); 53 54 // Init after gallery. 55 setTimeout( function() { 56 $form.trigger( 'refresh' ); 57 $form.trigger( 'tc_variation_form' ); 58 self.loading = false; 59 }, 100 ); 60 }; 61 62 /** 63 * Refresh variations container 64 * 65 * @param {Event} event The event Object. 66 */ 67 VariationForm.prototype.onRefreshContainer = function( event ) { 68 var form = event.data.variationForm; 69 70 form.variableProductContainers.find( '.tc-epo-element-product-container' ).removeClass( 'variations_form' ); 71 72 form.$form.addClass( 'variations_form' ); 73 74 form.$form.trigger( 'tc_check_variations.tc-variation-form' ); 75 }; 76 77 /** 78 * Reset all fields. 79 * 80 * @param {Event} event The event Object. 81 * @param {Object} variation The variation Object. 82 */ 83 VariationForm.prototype.onUpdateField = function( event, variation ) { 84 var form = event.data.variationForm; 85 var field = form.field; 86 var associatedSetter; 87 var associatedPrice; 88 var associatedRawPrice; 89 var associatedOriginalPrice; 90 var associatedRawOriginalPrice; 91 92 event.preventDefault(); 93 94 if ( ! variation ) { 95 associatedSetter = field; 96 if ( field.is( 'select' ) ) { 97 associatedSetter = field.find( 'option:selected' ); 98 } 99 100 associatedPrice = 0; 101 associatedRawPrice = 0; 102 associatedOriginalPrice = 0; 103 associatedRawOriginalPrice = 0; 104 105 field.data( 'price_set', 1 ); 106 associatedSetter.data( 'associated_price_set', 1 ); 107 associatedSetter.data( 'price_set', 1 ); 108 associatedSetter.data( 'raw_price', associatedRawPrice ); 109 associatedSetter.data( 'raw_original_price', associatedRawOriginalPrice ); 110 associatedSetter.data( 'price', associatedPrice ); 111 associatedSetter.data( 'original_price', associatedOriginalPrice ); 112 113 field.data( 'price-changed', 1 ); 114 115 form.$form.trigger( { 116 type: 'tm-epo-update', 117 norules: 2 118 } ); 119 form.currentCart.trigger( { 120 type: 'tm-epo-update', 121 norules: 2 122 } ); 123 } 124 }; 125 126 /** 127 * Reset all fields. 128 * 129 * @param {Event} event The event Object. 130 */ 131 VariationForm.prototype.onReset = function( event ) { 132 event.preventDefault(); 133 event.data.variationForm.$attributeFields.val( '' ).trigger( 'change' ); 134 event.data.variationForm.$form.trigger( 'tc_reset_data' ); 135 }; 136 137 /** 138 * When a variation is hidden. 139 * 140 * @param {Event} event The event Object. 141 */ 142 VariationForm.prototype.onHide = function( event ) { 143 event.preventDefault(); 144 }; 145 /** 146 * When a variation is shown.
147 * 148 * @param {Event} event The event Object. 149 */ 150 VariationForm.prototype.onShow = function( event ) { 151 event.preventDefault(); 152 }; 153 /** 154 * When displayed variation data is reset. 155 * 156 * @param {Event} event The event Object. 157 */ 158 VariationForm.prototype.onResetDisplayedVariation = function( event ) { 159 var form = event.data.variationForm; 160 form.$form.find( '.product-meta' ).find( '.tc-product-sku' ).tc_reset_content(); 161 form.$form.trigger( 'tc_reset_image' ); 162 form.$singleVariation.slideUp( 200 ).trigger( 'tc_hide_variation' ); 163 }; 164 165 /** 166 * When the product image is reset. 167 * 168 * @param {Event} event The event Object. 169 */ 170 VariationForm.prototype.onResetImage = function( event ) { 171 event.data.variationForm.$form.tc_variations_image_update( false ); 172 }; 173 174 /** 175 * Looks for matching variations for current selected attributes. 176 * 177 * @param {Event} event The event Object. 178 */ 179 VariationForm.prototype.onFindVariation = function( event ) { 180 var form = event.data.variationForm; 181 var attributes = form.getChosenAttributes(); 182 var currentAttributes = attributes.data; 183 var cpfElement; 184 var matching_variations; 185 var variation; 186 187 if ( attributes.count === attributes.chosenCount ) { 188 if ( form.useAjax ) { 189 if ( typeof wc_add_to_cart_variation_params === 'undefined' ) { 190 return; 191 } 192 if ( form.xhr ) { 193 form.xhr.abort(); 194 } 195 cpfElement = form.$form.closest( '.cpf-type-product' ); 196 form.$form.block( { message: null, overlayCSS: { background: '#fff', opacity: 0.6 } } ); 197 currentAttributes.product_id = parseInt( form.$form.data( 'product_id' ), 10 ); 198 currentAttributes.discount = cpfElement.attr( 'data-discount' ); 199 currentAttributes.discount_type = cpfElement.attr( 'data-discount-type' ); 200 currentAttributes.discount_exclude_addons = cpfElement.attr( 'data-discount-exclude-addons' ); 201 202 form.xhr = $.ajax( { 203 url: wc_add_to_cart_variation_params.wc_ajax_url.toString().replace( '%%endpoint%%', 'get_variation' ), 204 type: 'POST', 205 data: currentAttributes, 206 success: function( svariation ) { 207 if ( svariation ) { 208 form.$form.trigger( 'tc_found_variation', [ svariation ] ); 209 } else { 210 form.$form.trigger( 'tc_reset_data' ); 211 attributes.chosenCount = 0; 212 213 if ( ! form.loading ) { 214 form.$form.find( '.tc-epo-element-single-variation' ).after( '<p class="wc-no-matching-variations woocommerce-info">' + wc_add_to_cart_variation_params.i18n_no_matching_variations_text + '</p>' ); 215 form.$form.find( '.wc-no-matching-variations' ).slideDown( 200 ); 216 form.$form.trigger( 'update_field', [] ); 217 } 218 } 219 }, 220 complete: function() { 221 form.$form.unblock(); 222 } 223 } ); 224 } else { 225 form.$form.trigger( 'tc_update_variation_values' ); 226 227 matching_variations = form.findMatchingVariations( form.variationData, currentAttributes ); 228 variation = matching_variations.shift(); 229 230 if ( variation ) { 231 form.$form.trigger( 'tc_found_variation', [ variation ] ); 232 } else { 233 form.$form.trigger( 'tc_reset_data' ); 234 attributes.chosenCount = 0; 235 236 if ( ! form.loading ) { 237 form.$form.find( '.tc-epo-element-single-variation' ).after( '<p class="wc-no-matching-variations woocommerce-info">' + wc_add_to_cart_variation_params.i18n_no_matching_variations_text + '</p>' ); 238 form.$form.find( '.wc-no-matching-variations' ).slideDown( 200 ); 239 } 240 } 241 } 242 } else { 243 form.variationId.val( '' ).trigger( 'change' ); 244 form.$form.trigger( 'tc_update_variation_values' ); 245 form.$form.trigger( 'tc_reset_data' ); 246 form.$form.trigger( 'update_field', [] ); 247 } 248 249 // Show reset link. 250 form.toggleResetLink( attributes.chosenCount > 0 ); 251 }; 252 253 /** 254 * Triggered when a variation has been found which matches all attributes. 255 * 256 * @param {Event} event The event Object. 257 * @param {Object} variation The variation Object. 258 */ 259 VariationForm.prototype.onFoundVariation = function( event, variation ) { 260 var form = event.data.variationForm, 261 $sku = form.$form.find( '.product-meta' ).find( '.tc-product-sku' ), 262 $qtyWrap = form.$wrap.find( '.tm-quantity-alt' ), 263 $qty = $qtyWrap.find( 'input.tm-qty-alt' ), 264 qtyMin = $.epoAPI.math.toInt( $qty.attr( 'data-min' ) ), 265 qtyMax = $.epoAPI.math.toInt( $qty.attr( 'data-max' ) ), 266 dataQtyMax = $qty.attr( 'data-max' ), 267 purchasable = true, 268 template = false, 269 $template_html = ''; 270 271 if ( variation.sku ) { 272 $sku.tc_set_content( variation.sku ); 273 } else { 274 $sku.tc_reset_content(); 275 } 276 277 form.$form.tc_variations_image_update( variation ); 278 if ( ! variation.variation_is_visible ) { 279 template = wpTemplate( 'unavailable-variation-template' ); 280 } else { 281 template = wpTemplate( 'variation-template' ); 282 } 283 284 if ( form.field.attr( 'data-no-price' ) ) { 285 variation.display_price = ''; 286 variation.display_regular_price = ''; 287 variation.price_html = ''; 288 } 289 290 form.$form.find( '.tc-epo-element-product-container-cart' ).find( '.stock' ).remove(); 291 292 $template_html = template( { 293 variation: variation 294 } ); 295 $template_html = $template_html.replace( '/*<![CDATA[*/', '' ); 296 $template_html = $template_html.replace( '/*]]>*/', '' ); 297 form.$singleVariation.html( $template_html ); 298 form.variationId.val( variation.variation_id ).trigger( 'change' ); 299 300 // Hide or show qty input 301 if ( variation.is_sold_individually === 'yes' ) { 302 $qty.val( '1' ).attr( 'min', '1' ).attr( 'max', '1' ); 303 $qtyWrap.hide(); 304 } else { 305 if ( variation.min_qty !== '' && variation.min_qty > qtyMin && qtyMin > 0 ) { 306 qtyMin = variation.min_qty; 307 } 308 309 if ( variation.max_qty !== '' && variation.max_qty < qtyMax ) { 310 qtyMax = variation.max_qty; 311 } 312 313 if ( qtyMin !== '' ) { 314 $qty.attr( 'min', qtyMin ); 315 } else { 316 $qty.removeAttr( 'min' ); 317 } 318 if ( dataQtyMax !== '' ) { 319 $qty.attr( 'max', qtyMax ); 320 } else { 321 $qty.removeAttr( 'max' ); 322 } 323 324 if ( qtyMax && $.epoAPI.math.toInt( $qty.val() ) > qtyMax ) { 325 $qty.val( qtyMax ); 326 } 327 if ( qtyMin > $.epoAPI.math.toInt( $qty.val() ) ) { 328 $qty.val( qtyMin ); 329 } 330 $qtyWrap.show(); 331 } 332 333 if ( ! variation.is_purchasable || ! variation.is_in_stock || ! variation.variation_is_visible ) { 334 purchasable = false;
335 $qty.val( '0' ).attr( 'min', '0' ).attr( 'max', '0' ); 336 $qtyWrap.hide(); 337 } 338 339 $qty.trigger( 'change' ); 340 341 // Reveal 342 if ( $.epoAPI.util.trim( form.$singleVariation.text() ) ) { 343 form.$singleVariation.slideDown( 200 ).trigger( 'tc_show_variation', [ variation, purchasable ] ); 344 } else { 345 form.$singleVariation.show().trigger( 'tc_show_variation', [ variation, purchasable ] ); 346 } 347 form.$form.trigger( 'update_field', [ variation ] ); 348 }; 349 350 /** 351 * Triggered when an attribute field changes. 352 * 353 * @param {Event} event The event Object. 354 */ 355 VariationForm.prototype.onChange = function( event ) { 356 var form = event.data.variationForm; 357 var field = form.field; 358 359 form.variationId.val( '' ).trigger( 'change' ); 360 form.$form.find( '.wc-no-matching-variations' ).remove(); 361 362 if ( form.useAjax ) { 363 form.$form.trigger( 'tc_check_variations' ); 364 } else { 365 form.$form.trigger( 'woocommerce_variation_select_change' ); 366 form.$form.trigger( 'tc_check_variations' ); 367 } 368 369 // Custom event for when variation selection has been changed 370 form.$form.trigger( 'woocommerce_variation_has_changed' ); 371 372 setTimeout( function() { 373 field.closest( '.tcowl-carousel' ).trigger( 'refresh.owl.carousel' ); 374 }, 50 ); 375 }; 376 377 /** 378 * Updates attributes in the DOM to show valid values. 379 * 380 * @param {Event} event The event Object. 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.toArray().forEach( function( el ) { 393 var current_attr_select = $( el ); 394 var current_attr_name = current_attr_select.data( 'attribute_name' ) || current_attr_select.attr( 'name' ); 395 var show_option_none = $( el ).data( 'show_option_none' ); 396 var option_gt_filter = ':gt(0)'; 397 var attached_options_count = 0; 398 var new_attr_select = $( '<select/>' ); 399 var selected_attr_val = current_attr_select.val() || ''; 400 var selected_attr_val_valid = true; 401 var refSelect; 402 var checkAttributes; 403 var variations; 404 var num; 405 var i; 406 var len; 407 var variationAttributes; 408 var attr_name; 409 var attr_val; 410 var variation_active; 411 var $option_elements; 412 var $option_element; 413 var option_value; 414 415 // Reference options set at first. 416 if ( ! current_attr_select.data( 'attribute_html' ) ) { 417 refSelect = current_attr_select.clone(); 418 419 refSelect.find( 'option' ).prop( 'disabled', false ).prop( 'selected', false ).removeAttr( 'attached' ); 420 421 current_attr_select.data( 'attribute_options', refSelect.find( 'option' + option_gt_filter ).get() ); // Legacy data attribute. 422 current_attr_select.data( 'attribute_html', refSelect.html() ); 423 } 424 425 new_attr_select.html( current_attr_select.data( 'attribute_html' ) ); 426 427 // The attribute of this select field should not be taken into account when calculating its matching variations: 428 // The constraints of this attribute are shaped by the values of the other attributes. 429 checkAttributes = $.extend( true, {}, currentAttributes ); 430 431 checkAttributes[ current_attr_name ] = ''; 432 433 variations = form.findMatchingVariations( form.variationData, checkAttributes ); 434 435 // Loop through variations. 436 for ( num in variations ) { 437 if ( typeof variations[ num ] !== 'undefined' ) { 438 variationAttributes = variations[ num ].attributes; 439 440 for ( attr_name in variationAttributes ) { 441 if ( Object.prototype.hasOwnProperty.call( variationAttributes, attr_name ) ) { 442 attr_val = variationAttributes[ attr_name ]; 443 variation_active = ''; 444 445 if ( attr_name === current_attr_name ) { 446 if ( variations[ num ].variation_is_active ) { 447 variation_active = 'enabled'; 448 } 449 450 if ( attr_val ) { 451 // Decode entities. 452 attr_val = $( '<div/>' ).html( attr_val ).text(); 453 454 // Attach to matching options by value. This is done to compare 455 // TEXT values rather than any HTML entities. 456 $option_elements = new_attr_select.find( 'option' ); 457 if ( $option_elements.length ) { 458 for ( i = 0, len = $option_elements.length; i < len; i++ ) { 459 $option_element = $( $option_elements[ i ] ); 460 option_value = $option_element.val(); 461 462 if ( attr_val === option_value ) { 463 $option_element.addClass( 'attached ' + variation_active ); 464 break; 465 } 466 } 467 } 468 } else { 469 // Attach all apart from placeholder. 470 new_attr_select.find( 'option:gt(0)' ).addClass( 'attached ' + variation_active ); 471 } 472 } 473 } 474 } 475 } 476 } 477 478 // Count available options. 479 attached_options_count = new_attr_select.find( 'option.attached' ).length; 480 481 // Check if current selection is in attached options. 482 if ( selected_attr_val ) { 483 selected_attr_val_valid = false;
484 485 if ( 0 !== attached_options_count ) { 486 new_attr_select.find( 'option.attached.enabled' ).toArray().forEach( function( o ) { 487 var thisVal = $( o ).val(); 488 489 if ( selected_attr_val === thisVal ) { 490 selected_attr_val_valid = true; 491 return false; // break. 492 } 493 } ); 494 } 495 } 496 497 /** 498 * Detach the placeholder if: 499 * - Valid options exist. 500 * - The current selection is non-empty. 501 * - The current selection is valid. 502 * - Placeholders are not set to be permanently visible. 503 */ 504 if ( attached_options_count > 0 && selected_attr_val && selected_attr_val_valid && 'no' === show_option_none ) { 505 new_attr_select.find( 'option:first' ).remove(); 506 option_gt_filter = ''; 507 } 508 509 // Detach unattached. 510 new_attr_select.find( 'option' + option_gt_filter + ':not(.attached)' ).remove(); 511 512 // Finally, copy to DOM and set value. 513 current_attr_select.html( new_attr_select.html() ); 514 current_attr_select.find( 'option' + option_gt_filter + ':not(.enabled)' ).prop( 'disabled', true ); 515 516 // Choose selected value. 517 if ( selected_attr_val ) { 518 // If the previously selected value is no longer available, fall back to the placeholder (it's going to be there). 519 if ( selected_attr_val_valid ) { 520 current_attr_select.val( selected_attr_val ); 521 } else { 522 current_attr_select.val( '' ).trigger( 'change' ); 523 } 524 } else { 525 current_attr_select.val( '' ); // No change event to prevent infinite loop. 526 } 527 } ); 528 529 // Custom event for when variations have been updated. 530 form.$form.trigger( 'woocommerce_update_variation_values' ); 531 }; 532 533 /** 534 * Get chosen attributes from form. 535 */ 536 VariationForm.prototype.getChosenAttributes = function() { 537 var data = {}; 538 var count = 0; 539 var chosen = 0; 540 541 this.$attributeFields.toArray().forEach( function( f ) { 542 var field = $( f ); 543 var attribute_name = field.data( 'attribute_name' ) || field.attr( 'name' ); 544 var value = field.val() || ''; 545 546 if ( value.length > 0 ) { 547 chosen++; 548 } 549 550 count++; 551 data[ attribute_name ] = value; 552 } ); 553 554 return { 555 count: count, 556 chosenCount: chosen, 557 data: data 558 }; 559 }; 560 561 /** 562 * Find matching variations for attributes. 563 * 564 * @param {Array} variations Variations. 565 * @param {Array} attributes attributes. 566 */ 567 VariationForm.prototype.findMatchingVariations = function( variations, attributes ) { 568 var matching = []; 569 var i; 570 var variation; 571 for ( i = 0; i < variations.length; i++ ) { 572 variation = variations[ i ]; 573 574 if ( this.isMatch( variation.attributes, attributes ) ) { 575 matching.push( variation ); 576 } 577 } 578 return matching; 579 }; 580 581 /** 582 * See if attributes match. 583 * 584 * @param {Array} variation_attributes Variation attributes. 585 * @param {Array} attributes attributes. 586 */ 587 VariationForm.prototype.isMatch = function( variation_attributes, attributes ) { 588 var match = true; 589 var val1; 590 var val2; 591 var attr_name; 592 for ( attr_name in variation_attributes ) { 593 if ( Object.prototype.hasOwnProperty.call( variation_attributes, attr_name ) ) { 594 val1 = variation_attributes[ attr_name ]; 595 val2 = attributes[ attr_name ]; 596 if ( val1 !== undefined && val2 !== undefined && val1.length !== 0 && val2.length !== 0 && val1 !== val2 ) { 597 match = false; 598 } 599 } 600 } 601 return match; 602 }; 603 604 /** 605 * Show or hide the reset link. 606 * 607 * @param {bool} on true or false. 608 */ 609 VariationForm.prototype.toggleResetLink = function( on ) { 610 if ( on ) { 611 if ( this.$resetVariations.css( 'visibility' ) === 'hidden' ) { 612 this.$resetVariations.css( 'visibility', 'visible' ).hide().fadeIn(); 613 } 614 } else { 615 this.$resetVariations.css( 'visibility', 'hidden' ); 616 } 617 }; 618 619 /** 620 * Function to call tc_variation_form on jquery selector. 621 * 622 * @param {Object} element The product element 623 * @param {Object} currentCart The current cart form 624 * @param {Object} variableProductContainers The container for the variable product 625 * @param {Object} thisEpoObject The EPO Object 626 */ 627 $.fn.tc_product_variation_form = function( element, currentCart, variableProductContainers, thisEpoObject ) { 628 new VariationForm( this, element, currentCart, variableProductContainers, thisEpoObject ); 629 this.trigger( 'tc_product_variation_form' ); 630 return this; 631 }; 632 633 /** 634 * Stores the default text for an element so it can be reset later 635 * 636 * @param {string} content Content to set. 637 */ 638 $.fn.tc_set_content = function( content ) { 639 if ( undefined === this.attr( 'data-o_content' ) ) { 640 this.attr( 'data-o_content', this.text() ); 641 } 642 this.text( content ); 643 }; 644 /** 645 * Stores the default text for an element so it can be reset later 646 */ 647 $.fn.tc_reset_content = function() { 648 if ( undefined !== this.attr( 'data-o_content' ) ) { 649 this.text( this.attr( 'data-o_content' ) ); 650 } 651 }; 652 653 /** 654 * Stores a default attribute for an element so it can be reset later 655 * 656 * @param {string} attr Attribute name. 657 * @param {string} value Value name. 658 */ 659 $.fn.tc_set_variation_attr = function( attr, value ) { 660 if ( undefined === this.attr( 'data-o_' + attr ) ) { 661 this.attr( 'data-o_' + attr, ! this.attr( attr ) ? '' : this.attr( attr ) ); 662 } 663 if ( false === value ) { 664 this.removeAttr( attr ); 665 } else { 666 this.attr( attr, value ); 667 } 668 }; 669 670 /** 671 * Reset a default attribute for an element so it can be reset later 672 * 673 * @param {string} attr Attribute name. 674 */ 675 $.fn.tc_reset_variation_attr = function( attr ) { 676 if ( undefined !== this.attr( 'data-o_' + attr ) ) { 677 this.attr( attr, this.attr( 'data-o_' + attr ) ); 678 } 679 }; 680 /** 681 * Sets product images for the chosen variation 682 * 683 * @param {Object} variation variation object. 684 */ 685 $.fn.tc_variations_image_update = function( variation ) { 686 var $form = this, 687 $product_img_wrap = $form.find( '.tc-product-image, .woocommerce-product-gallery__image, .woocommerce-product-gallery__image--placeholder' ).eq( 0 ), 688 $product_img = $product_img_wrap.find( '.wp-post-image' ), 689 $product_link = $product_img_wrap.find( 'a' ).eq( 0 ); 690 691 if ( variation && variation.image && variation.image.src && variation.image.src.length > 1 ) { 692 $product_img.tc_set_variation_attr( 'src', variation.image.src ); 693 $product_img.tc_set_variation_attr( 'height', variation.image.src_h ); 694 $product_img.tc_set_variation_attr( 'width', variation.image.src_w ); 695 $product_img.tc_set_variation_attr( 'srcset', variation.image.srcset ); 696 $product_img.tc_set_variation_attr( 'sizes', variation.image.sizes ); 697 $product_img.tc_set_variation_attr( 'title', variation.image.title ); 698 $product_img.tc_set_variation_attr( 'data-caption', variation.image.caption ); 699 $product_img.tc_set_variation_attr( 'alt', variation.image.alt ); 700 $product_img.tc_set_variation_attr( 'data-src', variation.image.full_src ); 701 $product_img.tc_set_variation_attr( 'data-large_image', variation.image.full_src ); 702 $product_img.tc_set_variation_attr( 'data-large_image_width', variation.image.full_src_w ); 703 $product_img.tc_set_variation_attr( 'data-large_image_height', variation.image.full_src_h ); 704 $product_img_wrap.tc_set_variation_attr( 'data-thumb', variation.image.src ); 705 $product_link.tc_set_variation_attr( 'href', variation.image.full_src ); 706 } else { 707 $form.tc_variations_image_reset(); 708 } 709 }; 710 711 /** 712 * Reset main image to defaults. 713 */ 714 $.fn.tc_variations_image_reset = function() { 715 var $form = this, 716 $product_img_wrap = $form.find( '.tc-product-image, .woocommerce-product-gallery__image, .woocommerce-product-gallery__image--placeholder' ).eq( 0 ), 717 $product_img = $product_img_wrap.find( '.wp-post-image' ), 718 $product_link = $product_img_wrap.find( 'a' ).eq( 0 ); 719 720 $product_img.tc_reset_variation_attr( 'src' ); 721 $product_img.tc_reset_variation_attr( 'width' ); 722 $product_img.tc_reset_variation_attr( 'height' ); 723 $product_img.tc_reset_variation_attr( 'srcset' ); 724 $product_img.tc_reset_variation_attr( 'sizes' ); 725 $product_img.tc_reset_variation_attr( 'title' ); 726 $product_img.tc_reset_variation_attr( 'data-caption' ); 727 $product_img.tc_reset_variation_attr( 'alt' ); 728 $product_img.tc_reset_variation_attr( 'data-src' ); 729 $product_img.tc_reset_variation_attr( 'data-large_image' ); 730 $product_img.tc_reset_variation_attr( 'data-large_image_width' ); 731 $product_img.tc_reset_variation_attr( 'data-large_image_height' ); 732 $product_img_wrap.tc_reset_variation_attr( 'data-thumb' ); 733 $product_link.tc_reset_variation_attr( 'href' ); 734 }; 735 736 /** 737 * Avoids using wp.template where possible in order to be CSP compliant. 738 * wp.template uses internally eval(). 739 * 740 * @param {string} templateId The id of the template. 741 */ 742 function wpTemplate( templateId ) { 743 var html = document.getElementById( 'tmpl-' + templateId ).textContent; 744 var hard = false;
745 // any <# #> interpolate (evaluate). 746 hard = hard || /<#\s?data\./.test( html ); 747 // any data that is NOT data.variation. 748 hard = hard || /{{{?\s?data\.(?!variation\.).+}}}?/.test( html ); 749 // any data access deeper than 1 level e.g. 750 // data.variation.object.item 751 // data.variation.object['item'] 752 // data.variation.array[0] 753 hard = hard || /{{{?\s?data\.variation\.[\w-]*[^\s}]/.test( html ); 754 if ( hard ) { 755 return window.wp.template( templateId ); 756 } 757 return function template( data ) { 758 var variation = data.variation || {}; 759 var replacement; 760 return html.replace( /({{{?)\s?data\.variation\.([\w-]*)\s?(}}}?)/g, function( _, open, key, close ) { 761 // Error in the format, ignore. 762 if ( open.length !== close.length ) { 763 return ''; 764 } 765 replacement = variation[ key ] || ''; 766 // {{{ }}} => interpolate (unescaped). 767 // {{ }} => interpolate (escaped). 768 // https://codex.wordpress.org/Javascript_Reference/wp.template 769 if ( open.length === 2 ) { 770 return window.escape( replacement ); 771 } 772 return replacement; 773 } ); 774 }; 775 } 776}( window.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.