/** * External dependencies */ import { __, _n, sprintf } from '@wordpress/i18n'; import { addQueryArgs } from '@wordpress/url'; import apiFetch from '@wordpress/api-fetch'; import { Component, Fragment } from '@wordpress/element'; import { debounce, find } from 'lodash'; import PropTypes from 'prop-types'; import { SearchListControl, SearchListItem } from '@woocommerce/components'; import { SelectControl, Spinner } from '@wordpress/components'; /** * Internal dependencies */ import './style.scss'; class ProductAttributeControl extends Component { constructor() { super( ...arguments ); this.state = { list: [], loading: true, attribute: 0, termsList: {}, termsLoading: true, }; this.debouncedGetTerms = debounce( this.getTerms.bind( this ), 200 ); this.renderItem = this.renderItem.bind( this ); this.onSelectAttribute = this.onSelectAttribute.bind( this ); } componentDidMount() { const { selected } = this.props; apiFetch( { path: addQueryArgs( '/wc/blocks/products/attributes', { per_page: -1 } ), } ) .then( ( list ) => { list = list.map( ( item ) => ( { ...item, parent: 0 } ) ); this.setState( ( { attribute } ) => { if ( ! attribute && selected.length > 0 ) { const item = find( list, { slug: selected[ 0 ].attr_slug } ); attribute = item ? item.id : 0; } return { list, attribute, loading: false }; } ); } ) .catch( () => { this.setState( { list: [], loading: false } ); } ); } componentWillUnmount() { this.debouncedGetTerms.cancel(); } componentDidUpdate( prevProps, prevState ) { if ( prevState.attribute !== this.state.attribute ) { this.debouncedGetTerms(); } } getTerms() { const { attribute, termsList } = this.state; if ( ! attribute ) { return; } if ( ! termsList[ attribute ] ) { this.setState( { termsLoading: true } ); } apiFetch( { path: addQueryArgs( `/wc/blocks/products/attributes/${ attribute }/terms`, { per_page: -1, } ), } ) .then( ( terms ) => { terms = terms.map( ( term ) => ( { ...term, parent: attribute, attr_slug: term.attribute.slug } ) ); this.setState( ( prevState ) => ( { termsList: { ...prevState.termsList, [ attribute ]: terms }, termsLoading: false, } ) ); } ) .catch( () => { this.setState( { termsLoading: false } ); } ); } onSelectAttribute( item ) { return () => { this.props.onChange( [] ); this.setState( { attribute: item.id === this.state.attribute ? 0 : item.id, } ); }; } renderItem( args ) { const { item, search, depth = 0 } = args; const { attribute, termsLoading } = this.state; const classes = [ 'woocommerce-product-attributes__item', 'woocommerce-search-list__item', ]; if ( search.length ) { classes.push( 'is-searching' ); } if ( depth === 0 && item.parent !== 0 ) { classes.push( 'is-skip-level' ); } if ( ! item.breadcrumbs.length ) { return [ , attribute === item.id && termsLoading && (
), ]; } return ( ); } render() { const { attribute, list, loading, termsList } = this.state; const { onChange, onOperatorChange, operator, selected } = this.props; const currentTerms = termsList[ attribute ] || []; const currentList = [ ...list, ...currentTerms ]; const messages = { clear: __( 'Clear all product attributes', 'woo-gutenberg-products-block' ), list: __( 'Product Attributes', 'woo-gutenberg-products-block' ), noItems: __( "Your store doesn't have any product attributes.", 'woo-gutenberg-products-block' ), search: __( 'Search for product attributes', 'woo-gutenberg-products-block' ), selected: ( n ) => sprintf( _n( '%d attribute selected', '%d attributes selected', n, 'woo-gutenberg-products-block' ), n ), updated: __( 'Product attribute search results updated.', 'woo-gutenberg-products-block' ), }; return ( find( currentList, { id } ) ) .filter( Boolean ) } onChange={ onChange } renderItem={ this.renderItem } messages={ messages } isHierarchical /> { !! onOperatorChange && (
) }
); } } ProductAttributeControl.propTypes = { /** * Callback to update the selected product attributes. */ onChange: PropTypes.func.isRequired, /** * Callback to update the category operator. If not passed in, setting is not used. */ onOperatorChange: PropTypes.func, /** * Setting for whether products should match all or any selected categories. */ operator: PropTypes.oneOf( [ 'all', 'any' ] ), /** * The list of currently selected attribute slug/ID pairs. */ selected: PropTypes.array.isRequired, }; ProductAttributeControl.defaultProps = { operator: 'any', }; export default ProductAttributeControl;