暂无描述

add-to-cart-variation.js 28KB

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