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
This commit is contained in:
louwie17 2022-10-06 14:27:48 -03:00 committed by GitHub
parent d756494bcc
commit f28a467919
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 353 additions and 52 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: enhancement
Enable async typeahead fields for the attribute and term fields within products.

View File

@ -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 {

View File

@ -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(
'<option value="' +
term.term_id +
'" selected="selected">' +
term.name +
'</option>'
);
}
});
$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();

View File

@ -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

View File

@ -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&hellip;', 'enhanced select', 'woocommerce' ),
'i18n_searching' => _x( 'Searching&hellip;', '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&hellip;', 'enhanced select', 'woocommerce' ),
'i18n_searching' => _x( 'Searching&hellip;', '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' ),
)
);

View File

@ -38,18 +38,23 @@ if ( ! defined( 'ABSPATH' ) ) {
if ( 'select' === $attribute_taxonomy->attribute_type ) {
?>
<select multiple="multiple" data-placeholder="<?php esc_attr_e( 'Select terms', 'woocommerce' ); ?>" class="multiselect attribute_values wc-enhanced-select" name="attribute_values[<?php echo esc_attr( $i ); ?>][]">
<select multiple="multiple" data-minimum_input_length="0" data-limit="50" data-return_id="id" data-placeholder="<?php esc_attr_e( 'Select terms', 'woocommerce' ); ?>" class="multiselect attribute_values wc-taxonomy-term-search" name="attribute_values[<?php echo esc_attr( $i ); ?>][]" data-taxonomy="<?php echo esc_attr( $attribute->get_taxonomy() ); ?>">
<?php
$args = array(
$args = array(
'orderby' => ! empty( $attribute_taxonomy->attribute_orderby ) ? $attribute_taxonomy->attribute_orderby : 'name',
'hide_empty' => 0,
);
$all_terms = get_terms( $attribute->get_taxonomy(), apply_filters( 'woocommerce_product_attribute_terms', $args ) );
if ( $all_terms ) {
foreach ( $all_terms as $term ) {
$options = $attribute->get_options();
$options = ! empty( $options ) ? $options : array();
echo '<option value="' . esc_attr( $term->term_id ) . '"' . wc_selected( $term->term_id, $options ) . '>' . esc_html( apply_filters( 'woocommerce_product_attribute_term_name', $term->name, $term ) ) . '</option>';
$selected_terms = $attribute->get_terms();
if ( $selected_terms ) {
foreach ( $selected_terms as $selected_term ) {
/**
* Filter the selected attribute term name.
*
* @since 3.4.0
* @param string $name Name of selected term.
* @param array $term The selected term object.
*/
echo '<option value="' . esc_attr( $selected_term->term_id ) . '" selected="selected">' . esc_html( apply_filters( 'woocommerce_product_attribute_term_name', $selected_term->name, $selected_term ) ) . '</option>';
}
}
?>

View File

@ -8,24 +8,38 @@ if ( ! defined( 'ABSPATH' ) ) {
<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>
<select name="attribute_taxonomy" class="attribute_taxonomy">
<option value=""><?php esc_html_e( 'Custom product attribute', 'woocommerce' ); ?></option>
<?php
global $wc_product_attributes;
// Array of defined attribute taxonomies.
$attribute_taxonomies = wc_get_attribute_taxonomies();
if ( ! empty( $attribute_taxonomies ) ) {
foreach ( $attribute_taxonomies as $tax ) {
$attribute_taxonomy_name = wc_attribute_taxonomy_name( $tax->attribute_name );
$label = $tax->attribute_label ? $tax->attribute_label : $tax->attribute_name;
echo '<option value="' . esc_attr( $attribute_taxonomy_name ) . '">' . esc_html( $label ) . '</option>';
}
}
<?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( wc_get_attribute_taxonomies() ) <= apply_filters( 'woocommerce_attribute_taxonomy_filter_threshold', 20 ) ) :
?>
</select>
<button type="button" class="button add_attribute"><?php esc_html_e( 'Add', 'woocommerce' ); ?></button>
<select name="attribute_taxonomy" class="attribute_taxonomy">
<option value=""><?php esc_html_e( 'Custom product attribute', 'woocommerce' ); ?></option>
<?php
global $wc_product_attributes;
// Array of defined attribute taxonomies.
$attribute_taxonomies = wc_get_attribute_taxonomies();
if ( ! empty( $attribute_taxonomies ) ) {
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>';
}
}
?>
</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 class="product_attributes wc-metaboxes">
<?php

View File

@ -154,6 +154,8 @@ class WC_AJAX {
'json_search_downloadable_products_and_variations',
'json_search_customers',
'json_search_categories',
'json_search_taxonomy_terms',
'json_search_product_attributes',
'json_search_pages',
'term_ordering',
'product_ordering',
@ -1728,6 +1730,96 @@ class WC_AJAX {
wp_send_json( apply_filters( 'woocommerce_json_search_found_categories', $found_categories ) );
}
/**
* Search for taxonomy terms and return json.
*/
public static function json_search_taxonomy_terms() {
ob_start();
check_ajax_referer( 'search-taxonomy-terms', 'security' );
if ( ! current_user_can( 'edit_products' ) ) {
wp_die( -1 );
}
$search_text = isset( $_GET['term'] ) ? wc_clean( wp_unslash( $_GET['term'] ) ) : '';
$limit = isset( $_GET['limit'] ) ? absint( wp_unslash( $_GET['limit'] ) ) : null;
$taxonomy = isset( $_GET['taxonomy'] ) ? wc_clean( wp_unslash( $_GET['taxonomy'] ) ) : '';
$args = array(
'taxonomy' => $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.
*/