From f28a467919c2850de649ef2c8efb2241a4ba1a8b Mon Sep 17 00:00:00 2001 From: louwie17 Date: Thu, 6 Oct 2022 14:27:48 -0300 Subject: [PATCH] Add/34000 attribute select control (#34744) * Replace preloaded attribute and term dropdowns with async typeahead dropdowns * Revert accidenatal change. * Add condition to show the old dropdown in certain situations * Add changelog * Use ternary condition * Move mininumInputLength down to follow the same format as the other filters * Fix the minimum input length * Update the select all to work with the async terms field * Make sure empty attributes are shown and suppress query filter * Fix code sniff error --- .../add-34000_attribute_select_control | 4 + .../woocommerce/client/legacy/css/admin.scss | 12 +- .../legacy/js/admin/meta-boxes-product.js | 106 ++++++++++++++++-- .../legacy/js/admin/wc-enhanced-select.js | 90 ++++++++++++++- .../includes/admin/class-wc-admin-assets.php | 32 +++--- .../views/html-product-attribute.php | 23 ++-- .../views/html-product-data-attributes.php | 46 +++++--- .../woocommerce/includes/class-wc-ajax.php | 92 +++++++++++++++ 8 files changed, 353 insertions(+), 52 deletions(-) create mode 100644 plugins/woocommerce/changelog/add-34000_attribute_select_control diff --git a/plugins/woocommerce/changelog/add-34000_attribute_select_control b/plugins/woocommerce/changelog/add-34000_attribute_select_control new file mode 100644 index 00000000000..a553a457016 --- /dev/null +++ b/plugins/woocommerce/changelog/add-34000_attribute_select_control @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Enable async typeahead fields for the attribute and term fields within products. diff --git a/plugins/woocommerce/client/legacy/css/admin.scss b/plugins/woocommerce/client/legacy/css/admin.scss index 10bad09da93..bc570ef2d2e 100644 --- a/plugins/woocommerce/client/legacy/css/admin.scss +++ b/plugins/woocommerce/client/legacy/css/admin.scss @@ -969,10 +969,20 @@ #product_attributes { .toolbar-top { - .button { + .button, .select2-container { margin: 1px; } } + .select2-container { + min-width: 190px; + } +} +#select2-attribute_taxonomy-results { + .select2-results__option, + .select2-results__group { + margin: 0; + padding: 8px 4px; + } } .clear { 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 d2ef0c4da33..73792e8941a 100644 --- a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js +++ b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js @@ -408,6 +408,7 @@ jQuery( function ( $ ) { } ); } + var selectedAttributes = []; $( '.product_attributes .woocommerce_attribute' ).each( function ( index, el @@ -416,17 +417,17 @@ jQuery( function ( $ ) { $( el ).css( 'display' ) !== 'none' && $( el ).is( '.taxonomy' ) ) { + selectedAttributes.push( $( el ).data( 'taxonomy' ) ); $( 'select.attribute_taxonomy' ) .find( 'option[value="' + $( el ).data( 'taxonomy' ) + '"]' ) .attr( 'disabled', 'disabled' ); } } ); + $( 'select.wc-attribute-search' ).data('disabled-items', selectedAttributes ); - // Add rows. - $( 'button.add_attribute' ).on( 'click', function () { + function add_attribute( element, attribute ) { var size = $( '.product_attributes .woocommerce_attribute' ).length; - var attribute = $( 'select.attribute_taxonomy' ).val(); - var $wrapper = $( this ).closest( '#product_attributes' ); + var $wrapper = $( element ).closest( '#product_attributes' ); var $attributes = $wrapper.find( '.product_attributes' ); var product_type = $( 'select#product-type' ).val(); var data = { @@ -474,6 +475,37 @@ jQuery( function ( $ ) { .attr( 'disabled', 'disabled' ); $( 'select.attribute_taxonomy' ).val( '' ); } + } + + $('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 ); + } + } + $( this ).val( null ); + $( this ).trigger( 'change' ); + + 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' ); + + return false; + } ); + + $( 'button.add_custom_attribute' ).on( 'click', function () { + add_attribute( this, '' ); return false; } ); @@ -489,11 +521,56 @@ jQuery( function ( $ ) { 'click', 'button.select_all_attributes', function () { - $( this ) - .closest( 'td' ) - .find( 'select option' ) - .prop( 'selected', 'selected' ); - $( this ).closest( 'td' ).find( 'select' ).trigger( 'change' ); + $( '.product_attributes' ).block( { + message: null, + overlayCSS: { + background: '#fff', + opacity: 0.6, + }, + } ); + + var $wrapper = $( this ).closest( '.woocommerce_attribute' ); + var attribute = $wrapper.data( 'taxonomy' ); + + var data = { + action: 'woocommerce_json_search_taxonomy_terms', + taxonomy: attribute, + security: wc_enhanced_select_params.search_taxonomy_terms_nonce + }; + + $.get( woocommerce_admin_meta_boxes.ajax_url, data, function ( + response + ) { + if ( response.errors ) { + // Error. + window.alert( response.errors ); + } else if ( response && response.length > 0 ) { + // Success. + response.forEach( function( term ) { + const currentItem = $wrapper + .find( 'select.attribute_values option[value="' + term.term_id + '"]' ); + console.log( currentItem ); + if ( currentItem && currentItem.length > 0 ) { + currentItem.prop( 'selected', 'selected' ); + } else { + $wrapper + .find('select.attribute_values') + .append( + '' + ); + } + }); + $wrapper + .find( 'select.attribute_values' ) + .trigger( 'change' ); + } + + $( '.product_attributes' ).unblock(); + } ); return false; } ); @@ -523,7 +600,12 @@ jQuery( function ( $ ) { 'option[value="' + $parent.data( 'taxonomy' ) + '"]' ) .prop( 'disabled', false ); - } else { + selectedAttributes = selectedAttributes.filter( attr => attr !== $parent.data( 'taxonomy' ) ); + $( 'select.wc-attribute-search' ).data( + 'disabled-items', + selectedAttributes + ); + } else{ $parent.find( 'select, input[type=text]' ).val( '' ); $parent.hide(); attribute_row_indexes(); @@ -650,12 +732,14 @@ jQuery( function ( $ ) { .find( 'option' ) .prop( 'disabled', false ); + var newSelectedAttributes = []; $( '.product_attributes .woocommerce_attribute' ).each( function ( index, el ) { if ( $( el ).css( 'display' ) !== 'none' && $( el ).is( '.taxonomy' ) ) { + newSelectedAttributes.push( $( el ).data( 'taxonomy' ) ); $( 'select.attribute_taxonomy' ) .find( 'option[value="' + @@ -666,6 +750,8 @@ jQuery( function ( $ ) { } } ); + selectedAttributes = newSelectedAttributes; + $( 'select.wc-attribute-search' ).data('disabled-items', newSelectedAttributes ); // Reload variations panel. var this_page = window.location.toString(); diff --git a/plugins/woocommerce/client/legacy/js/admin/wc-enhanced-select.js b/plugins/woocommerce/client/legacy/js/admin/wc-enhanced-select.js index 481aab3ed40..b77da8e08b1 100644 --- a/plugins/woocommerce/client/legacy/js/admin/wc-enhanced-select.js +++ b/plugins/woocommerce/client/legacy/js/admin/wc-enhanced-select.js @@ -269,7 +269,7 @@ jQuery( function( $ ) { var select2_args = $.extend( { allowClear : $( this ).data( 'allow_clear' ) ? true : false, placeholder : $( this ).data( 'placeholder' ), - minimumInputLength: $( this ).data( 'minimum_input_length' ) ? $( this ).data( 'minimum_input_length' ) : 3, + minimumInputLength: $( this ).data( 'minimum_input_length' ) ? $( this ).data( 'minimum_input_length' ) : '3', escapeMarkup : function( m ) { return m; }, @@ -304,6 +304,94 @@ jQuery( function( $ ) { $( this ).selectWoo( select2_args ).addClass( 'enhanced' ); }); + + // Ajax category search boxes + $( ':input.wc-taxonomy-term-search' ).filter( ':not(.enhanced)' ).each( function() { + var return_format = $( this ).data( 'return_id' ) ? 'id' : 'slug'; + + var select2_args = $.extend( { + allowClear : $( this ).data( 'allow_clear' ) ? true : false, + placeholder : $( this ).data( 'placeholder' ), + minimumInputLength: $( this ).data( 'minimum_input_length' ) ?? '3', + escapeMarkup : function( m ) { + return m; + }, + ajax: { + url: wc_enhanced_select_params.ajax_url, + dataType: 'json', + delay: 250, + data: function( params ) { + return { + taxonomy: $( this ).data( 'taxonomy' ), + limit: $( this ).data( 'limit' ), + term: params.term, + action: 'woocommerce_json_search_taxonomy_terms', + security: wc_enhanced_select_params.search_taxonomy_terms_nonce + }; + }, + processResults: function( data ) { + var terms = []; + if ( data ) { + $.each( data, function( id, term ) { + terms.push({ + id: 'id' === return_format ? term.term_id : term.slug, + text: term.name + }); + }); + } + return { + results: terms + }; + }, + cache: true + } + }, getEnhancedSelectFormatString() ); + + $( this ).selectWoo( select2_args ).addClass( 'enhanced' ); + }); + + $( ':input.wc-attribute-search' ).filter( ':not(.enhanced)' ).each( function() { + var select2Element = this; + var select2_args = $.extend( { + allowClear : $( this ).data( 'allow_clear' ) ? true : false, + placeholder : $( this ).data( 'placeholder' ), + minimumInputLength: $( this ).data( 'minimum_input_length' ) ?? '3', + escapeMarkup : function( m ) { + return m; + }, + ajax: { + url: wc_enhanced_select_params.ajax_url, + dataType: 'json', + delay: 250, + data: function( params ) { + return { + term: params.term, + action: 'woocommerce_json_search_product_attributes', + security: wc_enhanced_select_params.search_product_attributes_nonce + }; + }, + processResults: function( data ) { + var disabledItems = $( select2Element ).data('disabled-items') || []; + var terms = []; + if ( data ) { + $.each( data, function( id, term ) { + terms.push({ + id: term.slug, + text: term.name, + disabled: disabledItems.includes( term.slug ) + }); + }); + } + return { + results: terms + }; + }, + cache: true + } + }, getEnhancedSelectFormatString() ); + + $( this ).selectWoo( select2_args ).addClass( 'enhanced' ); + }); }) // WooCommerce Backbone Modal diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php index fa8d59a08ad..29131b2f075 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php @@ -135,21 +135,23 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) : 'wc-enhanced-select', 'wc_enhanced_select_params', array( - 'i18n_no_matches' => _x( 'No matches found', 'enhanced select', 'woocommerce' ), - 'i18n_ajax_error' => _x( 'Loading failed', 'enhanced select', 'woocommerce' ), - 'i18n_input_too_short_1' => _x( 'Please enter 1 or more characters', 'enhanced select', 'woocommerce' ), - 'i18n_input_too_short_n' => _x( 'Please enter %qty% or more characters', 'enhanced select', 'woocommerce' ), - 'i18n_input_too_long_1' => _x( 'Please delete 1 character', 'enhanced select', 'woocommerce' ), - 'i18n_input_too_long_n' => _x( 'Please delete %qty% characters', 'enhanced select', 'woocommerce' ), - 'i18n_selection_too_long_1' => _x( 'You can only select 1 item', 'enhanced select', 'woocommerce' ), - 'i18n_selection_too_long_n' => _x( 'You can only select %qty% items', 'enhanced select', 'woocommerce' ), - 'i18n_load_more' => _x( 'Loading more results…', 'enhanced select', 'woocommerce' ), - 'i18n_searching' => _x( 'Searching…', 'enhanced select', 'woocommerce' ), - 'ajax_url' => admin_url( 'admin-ajax.php' ), - 'search_products_nonce' => wp_create_nonce( 'search-products' ), - 'search_customers_nonce' => wp_create_nonce( 'search-customers' ), - 'search_categories_nonce' => wp_create_nonce( 'search-categories' ), - 'search_pages_nonce' => wp_create_nonce( 'search-pages' ), + 'i18n_no_matches' => _x( 'No matches found', 'enhanced select', 'woocommerce' ), + 'i18n_ajax_error' => _x( 'Loading failed', 'enhanced select', 'woocommerce' ), + 'i18n_input_too_short_1' => _x( 'Please enter 1 or more characters', 'enhanced select', 'woocommerce' ), + 'i18n_input_too_short_n' => _x( 'Please enter %qty% or more characters', 'enhanced select', 'woocommerce' ), + 'i18n_input_too_long_1' => _x( 'Please delete 1 character', 'enhanced select', 'woocommerce' ), + 'i18n_input_too_long_n' => _x( 'Please delete %qty% characters', 'enhanced select', 'woocommerce' ), + 'i18n_selection_too_long_1' => _x( 'You can only select 1 item', 'enhanced select', 'woocommerce' ), + 'i18n_selection_too_long_n' => _x( 'You can only select %qty% items', 'enhanced select', 'woocommerce' ), + 'i18n_load_more' => _x( 'Loading more results…', 'enhanced select', 'woocommerce' ), + 'i18n_searching' => _x( 'Searching…', 'enhanced select', 'woocommerce' ), + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'search_products_nonce' => wp_create_nonce( 'search-products' ), + 'search_customers_nonce' => wp_create_nonce( 'search-customers' ), + 'search_categories_nonce' => wp_create_nonce( 'search-categories' ), + 'search_taxonomy_terms_nonce' => wp_create_nonce( 'search-taxonomy-terms' ), + 'search_product_attributes_nonce' => wp_create_nonce( 'search-product-attributes' ), + 'search_pages_nonce' => wp_create_nonce( 'search-pages' ), ) ); diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute.php index fdc9c8aa0a0..02007bc13ae 100644 --- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute.php +++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute.php @@ -7,7 +7,7 @@ if ( ! defined( 'ABSPATH' ) ) {

-
+
get_name() ); ?>

$taxonomy, + 'orderby' => 'id', + 'order' => 'ASC', + 'hide_empty' => false, + 'fields' => 'all', + 'number' => $limit, + 'name__like' => $search_text, + 'suppress_filter' => true, + ); + + /** + * Filter the product attribute term arguments used for search. + * + * @since 3.4.0 + * @param array $args The search arguments. + */ + $terms = get_terms( apply_filters( 'woocommerce_product_attribute_terms', $args ) ); + + /** + * Filter the product attribute terms search results. + * + * @since 7.0.0 + * @param array $terms The list of matched terms. + * @param string $taxonomy The terms taxonomy. + */ + wp_send_json( apply_filters( 'woocommerce_json_search_found_product_attribute_terms', $terms, $taxonomy ) ); + } + + /** + * Search for product attributes and return json. + */ + public static function json_search_product_attributes() { + ob_start(); + + check_ajax_referer( 'search-product-attributes', 'security' ); + + if ( ! current_user_can( 'edit_products' ) ) { + wp_die( -1 ); + } + + $limit = isset( $_GET['limit'] ) ? absint( wp_unslash( $_GET['limit'] ) ) : 100; + $search_text = isset( $_GET['term'] ) ? wc_clean( wp_unslash( $_GET['term'] ) ) : ''; + + $attributes = wc_get_attribute_taxonomies(); + + $found_product_categories = array(); + + foreach ( $attributes as $attribute_obj ) { + if ( ! $search_text || false !== stripos( $attribute_obj->attribute_label, $search_text ) ) { + $found_product_categories[] = array( + 'id' => (int) $attribute_obj->attribute_id, + 'name' => $attribute_obj->attribute_label, + 'slug' => wc_attribute_taxonomy_name( $attribute_obj->attribute_name ), + 'type' => $attribute_obj->attribute_type, + 'order_by' => $attribute_obj->attribute_orderby, + 'has_archives' => (bool) $attribute_obj->attribute_public, + ); + } + if ( count( $found_product_categories ) >= $limit ) { + break; + } + } + + /** + * Filter the product category search results. + * + * @since 7.0.0 + * @param array $found_product_categories Array of matched product categories. + * @param string $search_text Search text. + */ + wp_send_json( apply_filters( 'woocommerce_json_search_found_product_categories', $found_product_categories, $search_text ) ); + } + /** * Ajax request handling for page searching. */