Update empty state for product attributes tab (#38126)

* Remove empty state HTML

* Add empty attribute when product has no attributes

* Remove unused woocommerce_admin_meta_boxes.has_local_attributes

* Remove unused toggle_add_global_attribute_layout

* Remove unused button.add_attribute click handling (button doesn't exist anymore)

* Fix positioning of Expand / Close

* Remove unnecessary add-attribute-container div

* Refactor attribute search selection handling

* Remove empty attribute if adding an existing attribute

* Update e2e test clicking of "Add new" attribute button

* Update Tracks handling for "Add new" attribute button

* Changelog

* Fix action recorded when "Add new" button is clicked

* Remove console.log statements

* Allow propagation of click event on "Create value" button

* Move Tracks wcadmin_product_attributes_buttons action: 'add_existing' to product-tracking TS

* Make function names more descriptive. Add comment to clarify why event.preventDefault is used.
This commit is contained in:
Matt Sherman 2023-05-09 20:04:29 -04:00 committed by GitHub
parent 227695386d
commit d11f50b5a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 106 additions and 185 deletions

View File

@ -412,29 +412,44 @@ const attachProductTagsTracks = () => {
* Attaches attributes tracks.
*/
const attachAttributesTracks = () => {
function addNewTermEventHandler() {
recordEvent( 'product_attributes_add_term', {
page: 'product',
} );
}
function addNewAttributeTermTracks() {
const addNewTermButtons = document.querySelectorAll(
'.woocommerce_attribute .add_new_attribute'
);
addNewTermButtons.forEach( ( button ) => {
button.removeEventListener( 'click', addNewTermEventHandler );
button.addEventListener( 'click', addNewTermEventHandler );
} );
}
addNewAttributeTermTracks();
attachEventListenerToParentForChildren( '#product_attributes', [
{
eventName: 'click',
childQuery: '.add_new_attribute',
callback: () => {
recordEvent( 'product_attributes_add_term', {
page: 'product',
} );
},
},
] );
};
/**
* Attaches Tracks event for when a new custom attribute is added to a product.
*/
const attachAddCustomAttributeTracks = () => {
document
.querySelector( '.add_attribute' )
.querySelector( '#product_attributes .add_custom_attribute' )
?.addEventListener( 'click', () => {
setTimeout( () => {
addNewAttributeTermTracks();
}, 1000 );
recordEvent( 'product_attributes_buttons', {
action: 'add_new',
} );
} );
};
/**
* Attaches Tracks event for when an existing global attribute is added to a product.
*/
const attachAddExistingAttributeTracks = () => {
window
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Need to use jQuery to hook up to the select2:select event since the select2 component is jQuery-based
?.jQuery( 'select.wc-attribute-search' )
.on( 'select2:select', function () {
recordEvent( 'product_attributes_buttons', {
action: 'add_existing',
} );
} );
};
@ -442,29 +457,8 @@ const attachAttributesTracks = () => {
* Attaches product attributes tracks.
*/
const attachProductAttributesTracks = () => {
document
.querySelector( '#product_attributes .add_custom_attribute' )
?.addEventListener( 'click', () => {
recordEvent( 'product_attributes_buttons', {
action: 'add_first_attribute',
} );
} );
document
.querySelector( '#product_attributes .add_attribute' )
?.addEventListener( 'click', () => {
// We verify that we are not adding an existing attribute to not
// duplicate the recorded event.
const selectElement = document.querySelector(
'.attribute_taxonomy'
) as HTMLSelectElement;
// Get the index of the selected option
const selectedIndex = selectElement.selectedIndex;
if ( selectElement.options[ selectedIndex ]?.value === '' ) {
recordEvent( 'product_attributes_buttons', {
action: 'add_new',
} );
}
} );
attachAddCustomAttributeTracks();
attachAddExistingAttributeTracks();
const attributesSection = '#product_attributes';

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Update empty state for product attributes tab.

View File

@ -57,12 +57,6 @@ jQuery( function ( $ ) {
} );
} );
$( function () {
if ( ! woocommerce_admin_meta_boxes.has_local_attributes ) {
$( 'button.add_attribute' ).trigger( 'click' );
}
} );
// Catalog Visibility.
$( '#catalog-visibility' )
.find( '.edit-catalog-visibility' )
@ -400,6 +394,14 @@ jQuery( function ( $ ) {
.find( '.woocommerce_attribute' )
.get();
// If the product has no attributes, add an empty attribute to be filled out by the user.
$( function add_blank_custom_attribute_if_no_attributes() {
if ( woocommerce_attribute_items.length === 0 ) {
$( 'button.add_custom_attribute' ).trigger( 'click' );
}
} );
woocommerce_attribute_items.sort( function ( a, b ) {
var compA = parseInt( $( a ).attr( 'rel' ), 10 );
var compB = parseInt( $( b ).attr( 'rel' ), 10 );
@ -445,12 +447,6 @@ jQuery( function ( $ ) {
selectedAttributes
);
function toggle_add_global_attribute_layout() {
$( 'div.add-attribute-container' ).toggle();
$( 'div.add-global-attribute-container' ).toggle();
$( '#product_attributes > .toolbar-buttons' ).toggle();
}
function add_attribute( element, attribute ) {
var size = $( '.product_attributes .woocommerce_attribute' ).length;
var $wrapper = $( element ).closest( '#product_attributes' );
@ -504,64 +500,52 @@ jQuery( function ( $ ) {
}
}
$( 'select.wc-attribute-search' ).on( 'select2:select', function ( e ) {
if ( e.params && e.params.data && e.params.data.id ) {
add_attribute( this, e.params.data.id );
if ( ! selectedAttributes.includes( e.params.data.id ) ) {
selectedAttributes.push( e.params.data.id );
$( 'select.wc-attribute-search' ).data(
'disabled-items',
selectedAttributes
);
function add_if_not_exists( arr, item ) {
return arr.includes( item ) ? attr : [ ...arr, item ];
}
function disable_in_attribute_search( selectedAttributes ) {
$( 'select.wc-attribute-search' ).data( 'disabled-items', selectedAttributes );
}
function remove_blank_custom_attribute_if_no_other_attributes() {
const $attributes = $( '.product_attributes .woocommerce_attribute' );
if ( $attributes.length === 1 ) {
const $attribute = $attributes.first();
const $attributeName = $attribute.find( 'input[name="attribute_names[0]"]' );
const $attributeValue = $attribute.find( 'input[name="attribute_values[0]"]' );
if ( ! $attributeName.val() && ! $attributeValue.val() ) {
$attribute.remove();
}
window.wcTracks.recordEvent( 'product_attributes_buttons', {
action: 'add_existing',
} );
}
}
$( 'select.wc-attribute-search' ).on( 'select2:select', function ( e ) {
const attributeId = e?.params?.data?.id;
if ( attributeId ) {
remove_blank_custom_attribute_if_no_other_attributes();
add_attribute( this, attributeId );
selectedAttributes = add_if_not_exists( selectedAttributes, attributeId );
disable_in_attribute_search( selectedAttributes );
}
$( this ).val( null );
$( this ).trigger( 'change' );
if (
$( 'div.add-attribute-container' ).hasClass( 'hidden' ) &&
! $( 'div.add-global-attribute-container' ).hasClass( 'hidden' )
) {
toggle_add_global_attribute_layout();
}
return false;
} );
// Add rows.
$( 'button.add_attribute' ).on( 'click', function () {
var attribute = $( 'select.attribute_taxonomy' ).val();
if (
! attribute &&
$( 'select.attribute_taxonomy' ).hasClass( 'wc-attribute-search' )
) {
return;
}
add_attribute( this, attribute );
$( 'select.attribute_taxonomy' ).val( null );
$( 'select.attribute_taxonomy' ).trigger( 'change' );
// We record the event only when an existing attribute is added.
if ( attribute !== '' ) {
window.wcTracks.recordEvent( 'product_attributes_buttons', {
action: 'add_existing',
} );
}
return false;
} );
$( 'button.add_custom_attribute' ).on( 'click', function () {
add_attribute( this, '' );
if (
$( 'div.add-attribute-container' ).hasClass( 'hidden' ) &&
! $( 'div.add-global-attribute-container' ).hasClass( 'hidden' )
) {
toggle_add_global_attribute_layout();
}
return false;
} );
@ -694,16 +678,6 @@ jQuery( function ( $ ) {
action: 'remove_attribute',
} );
if (
! $( '.woocommerce_attribute_data' ).is( ':visible' ) &&
! $( 'div.add-global-attribute-container' ).hasClass(
'hidden'
) &&
$( '.product_attributes' ).find( 'input, select, textarea' )
.length === 0
) {
toggle_add_global_attribute_layout();
}
jQuery.maybe_disable_save_button();
}
return false;
@ -734,7 +708,10 @@ jQuery( function ( $ ) {
$( '.product_attributes' ).on(
'click',
'button.add_new_attribute',
function () {
function ( event ) {
// prevent form submission but allow event propagation
event.preventDefault();
$( '.product_attributes' ).block( {
message: null,
overlayCSS: {
@ -784,8 +761,6 @@ jQuery( function ( $ ) {
} else {
$( '.product_attributes' ).unblock();
}
return false;
}
);

View File

@ -416,7 +416,6 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
'rounding_precision' => wc_get_rounding_precision(),
'tax_rounding_mode' => wc_get_tax_rounding_mode(),
'product_types' => array_unique( array_merge( array( 'simple', 'grouped', 'variable', 'external' ), array_keys( wc_get_product_types() ) ) ),
'has_local_attributes' => ! empty( wc_get_attribute_taxonomies() ),
'i18n_download_permission_fail' => __( 'Could not grant access - the user may already have permission for this file or billing email is not set. Ensure the billing email is set, and the order has been saved.', 'woocommerce' ),
'i18n_permission_revoke' => __( 'Are you sure you want to revoke access to this download?', 'woocommerce' ),
'i18n_tax_rate_already_exists' => __( 'You cannot add the same tax rate twice!', 'woocommerce' ),

View File

@ -13,78 +13,27 @@ global $wc_product_attributes;
// Array of defined attribute taxonomies.
$attribute_taxonomies = wc_get_attribute_taxonomies();
// Product attributes - taxonomies and custom, ordered, with visibility and variation attributes set.
$product_attributes = $product_object->get_attributes( 'edit' );
$has_local_attributes = empty( $attribute_taxonomies );
$has_global_attributes = empty( $product_attributes );
$is_add_global_attribute_visible = ! $has_local_attributes && $has_global_attributes;
$icon_url = WC_ADMIN_IMAGES_FOLDER_URL . '/icons/global-attributes-icon.svg';
$product_attributes = $product_object->get_attributes( 'edit' );
?>
<div id="product_attributes" class="panel wc-metaboxes-wrapper hidden">
<div class="toolbar toolbar-top <?php echo $is_add_global_attribute_visible ? ' expand-close-hidden' : ''; ?>">
<div class="add-global-attribute-container<?php echo $is_add_global_attribute_visible ? '' : ' hidden'; ?>">
<div class="actions">
<button type="button" class="button add_custom_attribute"><?php esc_html_e( 'Add new', 'woocommerce' ); ?></button>
<select class="wc-attribute-search" data-placeholder="<?php esc_attr_e( 'Add existing', 'woocommerce' ); ?>" data-minimum-input-length="0">
</select>
</div>
<div class="message">
<img src="<?php echo esc_url( $icon_url ); ?>" />
<p>
<?php
esc_html_e(
'Add descriptive pieces of information that customers can use to search for this product on your store, such as “Material” or “Brand”.',
'woocommerce'
);
?>
</p>
</div>
</div>
<div class="add-attribute-container<?php echo $is_add_global_attribute_visible ? ' hidden' : ' '; ?>">
<?php
if ( $has_local_attributes && $has_global_attributes ) :
?>
<div id="message" class="inline notice woocommerce-message">
<p>
<?php
esc_html_e(
'Add descriptive pieces of information that customers can use to search for this product on your store, such as “Material” or “Brand”.',
'woocommerce'
);
?>
</p>
</div>
<?php endif; ?>
<span class="expand-close">
<a href="#" class="expand_all"><?php esc_html_e( 'Expand', 'woocommerce' ); ?></a> / <a href="#" class="close_all"><?php esc_html_e( 'Close', 'woocommerce' ); ?></a>
</span>
<?php
/**
* Filter for the attribute taxonomy filter dropdown threshold.
*
* @since 7.0.0
* @param number $threshold The threshold for showing the simple dropdown.
*/
if ( count( $attribute_taxonomies ) <= apply_filters( 'woocommerce_attribute_taxonomy_filter_threshold', 20 ) ) :
?>
<select name="attribute_taxonomy" class="attribute_taxonomy">
<option value=""><?php esc_html_e( 'Custom product attribute', 'woocommerce' ); ?></option>
<div class="toolbar toolbar-top">
<div id="message" class="inline notice woocommerce-message">
<p>
<?php
if ( ! $has_local_attributes ) {
foreach ( $attribute_taxonomies as $attr_taxonomy ) {
$attribute_taxonomy_name = wc_attribute_taxonomy_name( $attr_taxonomy->attribute_name );
$label = $attr_taxonomy->attribute_label ? $attr_taxonomy->attribute_label : $attr_taxonomy->attribute_name;
echo '<option value="' . esc_attr( $attribute_taxonomy_name ) . '">' . esc_html( $label ) . '</option>';
}
}
esc_html_e(
'Add descriptive pieces of information that customers can use to search for this product on your store, such as “Material” or “Brand”.',
'woocommerce'
);
?>
</p>
</div>
<span class="expand-close">
<a href="#" class="expand_all"><?php esc_html_e( 'Expand', 'woocommerce' ); ?></a> / <a href="#" class="close_all"><?php esc_html_e( 'Close', 'woocommerce' ); ?></a>
</span>
<div class="actions">
<button type="button" class="button add_custom_attribute"><?php esc_html_e( 'Add new', 'woocommerce' ); ?></button>
<select class="wc-attribute-search" data-placeholder="<?php esc_attr_e( 'Add existing', 'woocommerce' ); ?>" data-minimum-input-length="0">
</select>
<button type="button" class="button add_attribute"><?php esc_html_e( 'Add', 'woocommerce' ); ?></button>
<?php else : ?>
<button type="button" class="button add_custom_attribute"><?php esc_html_e( 'Add custom attribute', 'woocommerce' ); ?></button>
<select class="wc-attribute-search attribute_taxonomy" id="attribute_taxonomy" name="attribute_taxonomy" data-placeholder="<?php esc_attr_e( 'Add existing attribute', 'woocommerce' ); ?>" data-minimum-input-length="0">
</select>
<?php endif; ?>
</div>
</div>
<div class="product_attributes wc-metaboxes">
@ -104,7 +53,7 @@ $icon_url = WC_ADMIN_IMAGES_FOLDER_URL . '/icons/global-a
}
?>
</div>
<div class="toolbar toolbar-buttons<?php echo $is_add_global_attribute_visible ? ' hidden' : ''; ?>">
<div class="toolbar toolbar-buttons">
<span class="expand-close">
<a href="#" class="expand_all"><?php esc_html_e( 'Expand', 'woocommerce' ); ?></a> / <a href="#" class="close_all"><?php esc_html_e( 'Close', 'woocommerce' ); ?></a>
</span>

View File

@ -292,8 +292,8 @@ test.describe( 'Add New Variable Product Page', () => {
if ( i > 0 ) {
await test.step( "Click 'Add'.", async () => {
await page
.locator( '.add-attribute-container' )
.getByRole( 'button', { name: 'Add' } )
.locator( '#product_attributes .toolbar-top' )
.getByRole( 'button', { name: 'Add new' } )
.click();
} );
}
@ -746,7 +746,7 @@ test.describe( 'Add New Variable Product Page', () => {
// add 3 attributes
for ( let i = 0; i < 3; i++ ) {
if ( i > 0 ) {
await page.click( 'button.add_attribute' );
await page.click( 'button.add_custom_attribute' );
}
await page.waitForSelector(
`input[name="attribute_names[${ i }]"]`