From 3edd8f48cb4e8abe757f77fdd8e8fe1d5e215ae8 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Thu, 9 Mar 2023 10:32:24 -0300 Subject: [PATCH] Add validation to enable Save attributes and Save variations buttons (#37046) * Add validation for attributes and variations * Enable save button when data is valid # Conflicts: # plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-variations.php * Add changelog * Remove if * Remove validation while saving # Conflicts: # plugins/woocommerce/client/legacy/js/admin/meta-boxes-product-variation.js * Rename method `is_attribute_or_variation_empty` * Add button title when disabled # Conflicts: # plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-variations.php * Fix typo * Fix e2e tests * Convert functions into global fn * Use maybe_disable_save_button * Fix validation * Refactor `is_attribute_or_variation_empty` --------- Co-authored-by: Fernando Marichal --- ...7021_add_validation_when_saving_attributes | 4 + .../js/admin/meta-boxes-product-variation.js | 20 +- .../legacy/js/admin/meta-boxes-product.js | 4 +- .../client/legacy/js/admin/meta-boxes.js | 183 +++++++++++++----- .../includes/admin/class-wc-admin-assets.php | 1 + .../views/html-product-data-attributes.php | 2 +- .../views/html-product-data-variations.php | 2 +- .../merchant/create-variable-product.spec.js | 3 + 8 files changed, 155 insertions(+), 64 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-37021_add_validation_when_saving_attributes diff --git a/plugins/woocommerce/changelog/fix-37021_add_validation_when_saving_attributes b/plugins/woocommerce/changelog/fix-37021_add_validation_when_saving_attributes new file mode 100644 index 000000000000..81560990d51e --- /dev/null +++ b/plugins/woocommerce/changelog/fix-37021_add_validation_when_saving_attributes @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Add validation when saving attributes and variations diff --git a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product-variation.js b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product-variation.js index f576eee97561..b4ddb08d1863 100644 --- a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product-variation.js +++ b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product-variation.js @@ -34,7 +34,11 @@ jQuery( function ( $ ) { this.open_modal_to_set_variations_price ) .on( 'reload', this.reload ) - .on( 'click', 'button.create-variations', this.create_variations); + .on( + 'click', + 'button.create-variations', + this.create_variations + ); $( 'input.variable_is_downloadable, input.variable_is_virtual, input.variable_manage_stock' @@ -52,18 +56,10 @@ jQuery( function ( $ ) { ); }, - create_variations: function() { + create_variations: function () { var new_attribute_data = $( '.woocommerce_variation_new_attribute_data' ); - var attribute_name = new_attribute_data.find( 'input[name^="attribute_names"]' ).val(); - var attribute_value = new_attribute_data - .find( 'textarea[name^="attribute_values"]' ) - .val(); - - if ( ! attribute_name || ! attribute_value ) { - return; - } $( '#variable_product_options' ).block( { message: null, @@ -110,9 +106,7 @@ jQuery( function ( $ ) { '#variable_product_options_inner' ) ); - $( '#variable_product_options' ).trigger( - 'reload' - ); + $( '#variable_product_options' ).trigger( 'reload' ); $( '#product_attributes > .product_attributes' ).replaceWith( diff --git a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js index 835c141afbac..3227cd4b7edb 100644 --- a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js +++ b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js @@ -482,6 +482,7 @@ jQuery( function ( $ ) { $wrapper.unblock(); $( document.body ).trigger( 'woocommerce_added_attribute' ); + jQuery.maybe_disable_save_button(); } ); if ( attribute ) { @@ -664,6 +665,7 @@ jQuery( function ( $ ) { ) { toggle_add_global_attribute_layout(); } + jQuery.maybe_disable_save_button(); } return false; } ); @@ -756,6 +758,7 @@ jQuery( function ( $ ) { opacity: 0.6, }, } ); + var original_data = $( '.product_attributes' ).find( 'input, select, textarea' ); @@ -829,7 +832,6 @@ jQuery( function ( $ ) { ); $( document.body ).trigger( 'woocommerce_attributes_saved' ); - } } ); } ); diff --git a/plugins/woocommerce/client/legacy/js/admin/meta-boxes.js b/plugins/woocommerce/client/legacy/js/admin/meta-boxes.js index a815b38315c9..5e58c4a902a2 100644 --- a/plugins/woocommerce/client/legacy/js/admin/meta-boxes.js +++ b/plugins/woocommerce/client/legacy/js/admin/meta-boxes.js @@ -1,22 +1,82 @@ jQuery( function ( $ ) { + /** + * Function to check if the attribute and variation fields are empty. + */ + jQuery.is_attribute_or_variation_empty = function ( + attributes_and_variations_data + ) { + var has_empty_fields = false; + attributes_and_variations_data.each( function () { + var $this = $( this ); + // Check if the field is checkbox or a search field. + if ( + $this.hasClass( 'checkbox' ) || + $this.filter( '[class*=search__field]' ).length + ) { + return; + } + + var is_empty = $this.is( 'select' ) + ? $this.find( ':selected' ).length === 0 + : ! $this.val(); + if ( is_empty ) { + has_empty_fields = true; + } + } ); + return has_empty_fields; + }; + + /** + * Function to maybe disable the save button. + */ + jQuery.maybe_disable_save_button = function () { + var $tab = $( '.product_attributes' ); + var $save_button = $( 'button.save_attributes' ); + if ( + $( '.woocommerce_variation_new_attribute_data' ).is( ':visible' ) + ) { + $tab = $( '.woocommerce_variation_new_attribute_data' ); + $save_button = $( 'button.create-variations' ); + } + + var attributes_and_variations_data = $tab.find( + 'input, select, textarea' + ); + if ( + jQuery.is_attribute_or_variation_empty( + attributes_and_variations_data + ) + ) { + if ( ! $save_button.is( ':disabled' ) ) { + $save_button.attr( 'disabled', 'disabled' ); + $save_button.attr( + 'title', + woocommerce_admin_meta_boxes.i18n_save_attribute_variation_tip + ); + } + return; + } + $save_button.removeAttr( 'disabled' ); + $save_button.removeAttr( 'title' ); + }; // Run tipTip function runTipTip() { // Remove any lingering tooltips $( '#tiptip_holder' ).removeAttr( 'style' ); $( '#tiptip_arrow' ).removeAttr( 'style' ); - $( '.tips' ).tipTip({ - 'attribute': 'data-tip', - 'fadeIn': 50, - 'fadeOut': 50, - 'delay': 200, - 'keepAlive': true - }); + $( '.tips' ).tipTip( { + attribute: 'data-tip', + fadeIn: 50, + fadeOut: 50, + delay: 200, + keepAlive: true, + } ); } runTipTip(); - $( '.wc-metaboxes-wrapper' ).on( 'click', '.wc-metabox > h3', function() { + $( '.wc-metaboxes-wrapper' ).on( 'click', '.wc-metabox > h3', function () { var metabox = $( this ).parent( '.wc-metabox' ); if ( metabox.hasClass( 'closed' ) ) { @@ -30,51 +90,78 @@ jQuery( function ( $ ) { } else { metabox.addClass( 'open' ); } - }); + } ); // Tabbed Panels - $( document.body ).on( 'wc-init-tabbed-panels', function() { - $( 'ul.wc-tabs' ).show(); - $( 'ul.wc-tabs a' ).on( 'click', function( e ) { - e.preventDefault(); - var panel_wrap = $( this ).closest( 'div.panel-wrap' ); - $( 'ul.wc-tabs li', panel_wrap ).removeClass( 'active' ); - $( this ).parent().addClass( 'active' ); - $( 'div.panel', panel_wrap ).hide(); - $( $( this ).attr( 'href' ) ).show(); - }); - $( 'div.panel-wrap' ).each( function() { - $( this ).find( 'ul.wc-tabs li' ).eq( 0 ).find( 'a' ).trigger( 'click' ); - }); - }).trigger( 'wc-init-tabbed-panels' ); + $( document.body ) + .on( 'wc-init-tabbed-panels', function () { + $( 'ul.wc-tabs' ).show(); + $( 'ul.wc-tabs a' ).on( 'click', function ( e ) { + e.preventDefault(); + var panel_wrap = $( this ).closest( 'div.panel-wrap' ); + $( 'ul.wc-tabs li', panel_wrap ).removeClass( 'active' ); + $( this ).parent().addClass( 'active' ); + $( 'div.panel', panel_wrap ).hide(); + $( $( this ).attr( 'href' ) ).show(); + } ); + $( 'div.panel-wrap' ).each( function () { + $( this ) + .find( 'ul.wc-tabs li' ) + .eq( 0 ) + .find( 'a' ) + .trigger( 'click' ); + } ); + } ) + .trigger( 'wc-init-tabbed-panels' ); // Date Picker - $( document.body ).on( 'wc-init-datepickers', function() { - $( '.date-picker-field, .date-picker' ).datepicker({ - dateFormat: 'yy-mm-dd', - numberOfMonths: 1, - showButtonPanel: true - }); - }).trigger( 'wc-init-datepickers' ); + $( document.body ) + .on( 'wc-init-datepickers', function () { + $( '.date-picker-field, .date-picker' ).datepicker( { + dateFormat: 'yy-mm-dd', + numberOfMonths: 1, + showButtonPanel: true, + } ); + } ) + .trigger( 'wc-init-datepickers' ); // Meta-Boxes - Open/close - $( '.wc-metaboxes-wrapper' ).on( 'click', '.wc-metabox h3', function( event ) { - // If the user clicks on some form input inside the h3, like a select list (for variations), the box should not be toggled - if ( $( event.target ).filter( ':input, option, .sort' ).length ) { - return; - } + $( '.wc-metaboxes-wrapper' ) + .on( 'click', '.wc-metabox h3', function ( event ) { + // If the user clicks on some form input inside the h3, like a select list (for variations), the box should not be toggled + if ( $( event.target ).filter( ':input, option, .sort' ).length ) { + return; + } - $( this ).next( '.wc-metabox-content' ).stop().slideToggle(); - }) - .on( 'click', '.expand_all', function() { - $( this ).closest( '.wc-metaboxes-wrapper' ).find( '.wc-metabox > .wc-metabox-content' ).show(); - return false; - }) - .on( 'click', '.close_all', function() { - $( this ).closest( '.wc-metaboxes-wrapper' ).find( '.wc-metabox > .wc-metabox-content' ).hide(); - return false; - }); - $( '.wc-metabox.closed' ).each( function() { + $( this ).next( '.wc-metabox-content' ).stop().slideToggle(); + } ) + .on( 'click', '.expand_all', function () { + $( this ) + .closest( '.wc-metaboxes-wrapper' ) + .find( '.wc-metabox > .wc-metabox-content' ) + .show(); + return false; + } ) + .on( 'click', '.close_all', function () { + $( this ) + .closest( '.wc-metaboxes-wrapper' ) + .find( '.wc-metabox > .wc-metabox-content' ) + .hide(); + return false; + } ); + $( '.wc-metabox.closed' ).each( function () { $( this ).find( '.wc-metabox-content' ).hide(); - }); -}); + } ); + + $( '.product_attributes, .woocommerce_variation_new_attribute_data' ).on( + 'keyup', + 'input, textarea', + jQuery.maybe_disable_save_button + ); + + $( '#product_attributes' ).on( + 'change', + 'select.attribute_values', + jQuery.maybe_disable_save_button + ); +} ); diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php index 1b6f454ea8fc..d1385cd38e0c 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php @@ -419,6 +419,7 @@ public function admin_scripts() { 'i18n_product_other_tip' => __( 'Product types define available product details and attributes, such as downloadable files and variations. They’re also used for analytics and inventory management.', 'woocommerce' ), 'i18n_product_description_tip' => __( 'Describe this product. What makes it unique? What are its most important features?', 'woocommerce' ), 'i18n_product_short_description_tip' => __( 'Summarize this product in 1-2 short sentences. We’ll show it at the top of the page.', 'woocommerce' ), + 'i18n_save_attribute_variation_tip' => __( 'Make sure you enter the name and values for each attribute.', 'woocommerce' ), /* translators: %1$s: maximum file size */ 'i18n_product_image_tip' => sprintf( __( 'For best results, upload JPEG or PNG files that are 1000 by 1000 pixels or larger. Maximum upload file size: %1$s.', 'woocommerce' ) , size_format( wp_max_upload_size() ) ), ); diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php index d40339ffdd4f..7b7915fdcbc5 100644 --- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php +++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php @@ -106,7 +106,7 @@ / - + diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-variations.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-variations.php index df96f963a1e4..c6b0e1bb2543 100644 --- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-variations.php +++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-variations.php @@ -29,7 +29,7 @@ require __DIR__ . '/html-product-attribute-inner.php'; ?>
- +
diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-variable-product.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-variable-product.spec.js index 84fc26abc9de..22409e425999 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-variable-product.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-variable-product.spec.js @@ -64,6 +64,7 @@ test.describe.serial( 'Add New Variable Product Page', () => { 'val1 | val2' ); } + await page.keyboard.press( 'ArrowUp' ); await page.click( 'text=Save attributes' ); // Save before going to the Variations tab to prevent variations from all attributes to be automatically created @@ -138,6 +139,7 @@ test.describe.serial( 'Add New Variable Product Page', () => { await page.fill( 'input[name="variable_length[2]"]', productLength ); await page.fill( 'input[name="variable_width[2]"]', productWidth ); await page.fill( 'input[name="variable_height[2]"]', productHeight ); + await page.keyboard.press( 'ArrowUp' ); await page.click( 'button.save-variation-changes' ); // bulk-edit variations @@ -211,6 +213,7 @@ test.describe.serial( 'Add New Variable Product Page', () => { `textarea[name="attribute_values[${ i }]"]`, 'val1 | val2' ); + await page.keyboard.press( 'ArrowUp' ); await page.click( 'text=Save attributes' ); await expect( page