2018-02-15 18:16:14 +00:00
|
|
|
const { __ } = wp.i18n;
|
2018-09-05 18:30:46 +00:00
|
|
|
const { Toolbar, Dropdown, Dashicon } = wp.components;
|
|
|
|
const { apiFetch } = wp;
|
2018-02-15 18:16:14 +00:00
|
|
|
|
2018-03-02 19:18:42 +00:00
|
|
|
/**
|
|
|
|
* Product data cache.
|
|
|
|
* Reduces the number of API calls and makes UI smoother and faster.
|
|
|
|
*/
|
|
|
|
const PRODUCT_DATA = {};
|
|
|
|
|
2018-02-15 18:16:14 +00:00
|
|
|
/**
|
|
|
|
* When the display mode is 'Specific products' search for and add products to the block.
|
|
|
|
*/
|
|
|
|
export class ProductsSpecificSelect extends React.Component {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor.
|
|
|
|
*/
|
|
|
|
constructor( props ) {
|
|
|
|
super( props );
|
|
|
|
|
|
|
|
this.state = {
|
2018-02-16 19:40:19 +00:00
|
|
|
selectedProducts: props.selected_display_setting || [],
|
2018-02-15 18:16:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-22 18:48:34 +00:00
|
|
|
/**
|
|
|
|
* Add a product to the list of selected products.
|
|
|
|
*
|
|
|
|
* @param id int Product ID.
|
|
|
|
*/
|
2018-04-06 22:57:20 +00:00
|
|
|
addOrRemoveProduct( id ) {
|
2018-02-16 19:40:19 +00:00
|
|
|
let selectedProducts = this.state.selectedProducts;
|
2018-04-06 22:57:20 +00:00
|
|
|
|
|
|
|
if ( ! selectedProducts.includes( id ) ) {
|
|
|
|
selectedProducts.push( id );
|
|
|
|
} else {
|
|
|
|
selectedProducts = selectedProducts.filter( product => product !== id );
|
|
|
|
}
|
2018-02-15 18:16:14 +00:00
|
|
|
|
2018-02-16 19:40:19 +00:00
|
|
|
this.setState( {
|
|
|
|
selectedProducts: selectedProducts
|
|
|
|
} );
|
|
|
|
|
2018-02-23 19:38:59 +00:00
|
|
|
/**
|
|
|
|
* We need to copy the existing data into a new array.
|
2018-02-26 22:52:12 +00:00
|
|
|
* We can't just push the new product onto the end of the existing array because Gutenberg seems
|
2018-02-23 19:38:59 +00:00
|
|
|
* to do some sort of check by reference to determine whether to *actually* update the attribute
|
|
|
|
* and will not update it if we just pass back the same array with an extra element on the end.
|
|
|
|
*/
|
|
|
|
this.props.update_display_setting_callback( selectedProducts.slice() );
|
2018-02-16 19:40:19 +00:00
|
|
|
}
|
|
|
|
|
2018-02-22 18:48:34 +00:00
|
|
|
/**
|
|
|
|
* Render the product specific select screen.
|
|
|
|
*/
|
2018-02-15 18:16:14 +00:00
|
|
|
render() {
|
|
|
|
return (
|
2018-02-26 22:52:12 +00:00
|
|
|
<div className="wc-products-list-card wc-products-list-card--specific">
|
|
|
|
<ProductsSpecificSearchField
|
2018-04-06 22:57:20 +00:00
|
|
|
addOrRemoveProductCallback={ this.addOrRemoveProduct.bind( this ) }
|
2018-02-26 22:52:12 +00:00
|
|
|
selectedProducts={ this.state.selectedProducts }
|
2018-02-22 18:48:34 +00:00
|
|
|
/>
|
2018-02-26 22:52:12 +00:00
|
|
|
<ProductSpecificSelectedProducts
|
2018-04-06 22:57:20 +00:00
|
|
|
columns={ this.props.attributes.columns }
|
2018-03-02 19:18:42 +00:00
|
|
|
productIds={ this.state.selectedProducts }
|
2018-04-06 22:57:20 +00:00
|
|
|
addOrRemoveProduct={ this.addOrRemoveProduct.bind( this ) }
|
2018-02-22 18:48:34 +00:00
|
|
|
/>
|
2018-02-16 19:40:19 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Product search area
|
|
|
|
*/
|
|
|
|
class ProductsSpecificSearchField extends React.Component {
|
|
|
|
|
2018-02-22 18:48:34 +00:00
|
|
|
/**
|
|
|
|
* Constructor.
|
|
|
|
*/
|
2018-02-16 19:40:19 +00:00
|
|
|
constructor( props ) {
|
|
|
|
super( props );
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
searchText: '',
|
2018-04-10 15:34:53 +00:00
|
|
|
dropdownOpen: false,
|
2018-02-16 19:40:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this.updateSearchResults = this.updateSearchResults.bind( this );
|
2018-02-22 19:06:13 +00:00
|
|
|
this.setWrapperRef = this.setWrapperRef.bind( this );
|
|
|
|
this.handleClickOutside = this.handleClickOutside.bind( this );
|
2018-04-10 15:34:53 +00:00
|
|
|
this.isDropdownOpen = this.isDropdownOpen.bind( this );
|
2018-02-22 19:06:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hook in the listener for closing menu when clicked outside.
|
|
|
|
*/
|
|
|
|
componentDidMount() {
|
|
|
|
document.addEventListener( 'mousedown', this.handleClickOutside );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove the listener for closing menu when clicked outside.
|
|
|
|
*/
|
|
|
|
componentWillUnmount() {
|
|
|
|
document.removeEventListener( 'mousedown', this.handleClickOutside );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the wrapper reference.
|
|
|
|
*
|
|
|
|
* @param node DOMNode
|
|
|
|
*/
|
|
|
|
setWrapperRef( node ) {
|
|
|
|
this.wrapperRef = node;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Close the menu when user clicks outside the search area.
|
|
|
|
*/
|
|
|
|
handleClickOutside( evt ) {
|
|
|
|
if ( this.wrapperRef && ! this.wrapperRef.contains( event.target ) ) {
|
|
|
|
this.setState( {
|
|
|
|
searchText: '',
|
|
|
|
} );
|
|
|
|
}
|
2018-02-16 19:40:19 +00:00
|
|
|
}
|
|
|
|
|
2018-04-10 15:34:53 +00:00
|
|
|
isDropdownOpen( isOpen ) {
|
|
|
|
this.setState( {
|
|
|
|
dropdownOpen: !! isOpen,
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
2018-02-22 18:48:34 +00:00
|
|
|
/**
|
|
|
|
* Event handler for updating results when text is typed into the input.
|
|
|
|
*
|
|
|
|
* @param evt Event object.
|
|
|
|
*/
|
2018-02-16 19:40:19 +00:00
|
|
|
updateSearchResults( evt ) {
|
|
|
|
this.setState( {
|
|
|
|
searchText: evt.target.value,
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
2018-02-22 18:48:34 +00:00
|
|
|
/**
|
|
|
|
* Render the product search UI.
|
|
|
|
*/
|
2018-02-16 19:40:19 +00:00
|
|
|
render() {
|
2018-04-10 15:34:53 +00:00
|
|
|
const divClass = 'wc-products-list-card__search-wrapper';
|
|
|
|
|
2018-02-16 19:40:19 +00:00
|
|
|
return (
|
2018-04-10 15:34:53 +00:00
|
|
|
<div className={ divClass + ( this.state.dropdownOpen ? ' ' + divClass + '--with-results' : '' ) } ref={ this.setWrapperRef }>
|
2018-04-06 22:57:20 +00:00
|
|
|
<div className="wc-products-list-card__input-wrapper">
|
|
|
|
<Dashicon icon="search" />
|
|
|
|
<input type="search"
|
|
|
|
className="wc-products-list-card__search"
|
|
|
|
value={ this.state.searchText }
|
|
|
|
placeholder={ __( 'Search for products to display' ) }
|
|
|
|
onChange={ this.updateSearchResults }
|
|
|
|
/>
|
|
|
|
</div>
|
2018-02-22 18:48:34 +00:00
|
|
|
<ProductSpecificSearchResults
|
|
|
|
searchString={ this.state.searchText }
|
2018-04-06 22:57:20 +00:00
|
|
|
addOrRemoveProductCallback={ this.props.addOrRemoveProductCallback }
|
2018-02-22 18:48:34 +00:00
|
|
|
selectedProducts={ this.props.selectedProducts }
|
2018-04-10 15:34:53 +00:00
|
|
|
isDropdownOpenCallback={ this.isDropdownOpen }
|
2018-02-22 18:48:34 +00:00
|
|
|
/>
|
2018-02-15 18:16:14 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-16 19:40:19 +00:00
|
|
|
/**
|
2018-02-22 18:48:34 +00:00
|
|
|
* Render product search results based on the text entered into the textbox.
|
2018-02-16 19:40:19 +00:00
|
|
|
*/
|
2018-09-06 16:29:17 +00:00
|
|
|
class ProductSpecificSearchResults extends React.Component {
|
2018-02-16 19:40:19 +00:00
|
|
|
|
2018-09-06 16:29:17 +00:00
|
|
|
/**
|
|
|
|
* Constructor.
|
|
|
|
*/
|
|
|
|
constructor( props ) {
|
|
|
|
super( props );
|
|
|
|
this.state = {
|
|
|
|
products: [],
|
|
|
|
query: '',
|
2018-09-06 18:47:51 +00:00
|
|
|
loaded: false
|
2018-09-06 16:29:17 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
this.updateResults = this.updateResults.bind( this );
|
|
|
|
this.getQuery = this.getQuery.bind( this );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the preview when component is first loaded.
|
|
|
|
*/
|
|
|
|
componentDidMount() {
|
|
|
|
this.updateResults();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the preview when component is updated.
|
|
|
|
*/
|
|
|
|
componentDidUpdate() {
|
|
|
|
if ( this.getQuery() !== this.state.query ) {
|
|
|
|
this.updateResults();
|
2018-02-16 19:40:19 +00:00
|
|
|
}
|
2018-09-06 16:29:17 +00:00
|
|
|
}
|
2018-02-16 19:40:19 +00:00
|
|
|
|
2018-09-06 16:29:17 +00:00
|
|
|
/**
|
|
|
|
* Get the endpoint for the current state of the component.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
getQuery() {
|
|
|
|
if ( ! this.props.searchString.length ) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
return '/wc/v2/products?per_page=10&search=' + this.props.searchString;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the search results.
|
|
|
|
*/
|
|
|
|
updateResults() {
|
|
|
|
const self = this;
|
|
|
|
const query = this.getQuery();
|
|
|
|
|
|
|
|
self.setState( {
|
2018-09-06 18:47:51 +00:00
|
|
|
query: query,
|
|
|
|
loaded: false
|
2018-09-06 16:29:17 +00:00
|
|
|
} );
|
|
|
|
|
|
|
|
if ( query.length ) {
|
|
|
|
apiFetch( { path: query } ).then( products => {
|
2018-09-06 16:58:55 +00:00
|
|
|
// Only update the results if they are for the latest query.
|
|
|
|
if ( query === self.getQuery() ) {
|
|
|
|
self.setState( {
|
|
|
|
products: products,
|
2018-09-06 18:47:51 +00:00
|
|
|
loaded: true
|
2018-09-06 16:58:55 +00:00
|
|
|
} );
|
|
|
|
}
|
2018-09-06 16:29:17 +00:00
|
|
|
} );
|
|
|
|
} else {
|
|
|
|
self.setState( {
|
|
|
|
products: [],
|
2018-09-06 18:47:51 +00:00
|
|
|
loaded: true
|
2018-09-06 16:29:17 +00:00
|
|
|
} );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Render.
|
|
|
|
*/
|
|
|
|
render() {
|
2018-09-06 18:47:51 +00:00
|
|
|
if ( ! this.state.loaded || ! this.state.query.length ) {
|
2018-02-16 19:40:19 +00:00
|
|
|
return null;
|
2018-02-15 18:16:14 +00:00
|
|
|
}
|
|
|
|
|
2018-09-06 16:29:17 +00:00
|
|
|
if ( 0 === this.state.products.length ) {
|
2018-04-06 22:57:20 +00:00
|
|
|
return <span className="wc-products-list-card__search-no-results"> { __( 'No products found' ) } </span>;
|
2018-02-15 18:16:14 +00:00
|
|
|
}
|
|
|
|
|
2018-03-02 19:18:42 +00:00
|
|
|
// Populate the cache.
|
2018-09-06 16:29:17 +00:00
|
|
|
for ( let product of this.state.products ) {
|
2018-03-02 19:18:42 +00:00
|
|
|
PRODUCT_DATA[ product.id ] = product;
|
|
|
|
}
|
|
|
|
|
2018-02-26 22:52:12 +00:00
|
|
|
return <ProductSpecificSearchResultsDropdown
|
2018-09-06 16:29:17 +00:00
|
|
|
products={ this.state.products }
|
|
|
|
addOrRemoveProductCallback={ this.props.addOrRemoveProductCallback }
|
|
|
|
selectedProducts={ this.props.selectedProducts }
|
|
|
|
isDropdownOpenCallback={ this.props.isDropdownOpenCallback }
|
2018-02-22 18:48:34 +00:00
|
|
|
/>
|
2018-09-06 16:29:17 +00:00
|
|
|
|
2018-02-22 18:48:34 +00:00
|
|
|
}
|
2018-09-06 16:29:17 +00:00
|
|
|
}
|
2018-02-22 18:48:34 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The dropdown of search results.
|
|
|
|
*/
|
|
|
|
class ProductSpecificSearchResultsDropdown extends React.Component {
|
|
|
|
|
2018-04-10 15:34:53 +00:00
|
|
|
/**
|
|
|
|
* Set the state of the dropdown to open.
|
|
|
|
*/
|
|
|
|
componentDidMount() {
|
|
|
|
this.props.isDropdownOpenCallback( true );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the state of the dropdown to closed.
|
|
|
|
*/
|
|
|
|
componentWillUnmount() {
|
|
|
|
this.props.isDropdownOpenCallback( false );
|
|
|
|
}
|
|
|
|
|
2018-02-22 18:48:34 +00:00
|
|
|
/**
|
|
|
|
* Render dropdown.
|
|
|
|
*/
|
|
|
|
render() {
|
2018-04-06 22:57:20 +00:00
|
|
|
const { products, addOrRemoveProductCallback, selectedProducts } = this.props;
|
2018-02-22 18:48:34 +00:00
|
|
|
|
|
|
|
let productElements = [];
|
|
|
|
|
|
|
|
for ( let product of products ) {
|
|
|
|
productElements.push(
|
2018-04-06 22:57:20 +00:00
|
|
|
<ProductSpecificSearchResultsDropdownElement
|
|
|
|
product={product}
|
|
|
|
addOrRemoveProductCallback={ addOrRemoveProductCallback }
|
|
|
|
selected={ selectedProducts.includes( product.id ) }
|
|
|
|
/>
|
2018-02-22 18:48:34 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-02-16 19:40:19 +00:00
|
|
|
return (
|
2018-04-06 22:57:20 +00:00
|
|
|
<div role="menu" className="wc-products-list-card__search-results" aria-orientation="vertical" aria-label={ __( 'Products list' ) }>
|
|
|
|
<div>
|
|
|
|
{ productElements }
|
|
|
|
</div>
|
2018-02-16 19:40:19 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2018-02-22 18:48:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* One search result.
|
|
|
|
*/
|
|
|
|
class ProductSpecificSearchResultsDropdownElement extends React.Component {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor.
|
|
|
|
*/
|
|
|
|
constructor( props ) {
|
|
|
|
super( props );
|
|
|
|
|
|
|
|
this.handleClick = this.handleClick.bind( this );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add product to main list and change UI to show it was added.
|
|
|
|
*/
|
|
|
|
handleClick() {
|
2018-04-06 22:57:20 +00:00
|
|
|
this.props.addOrRemoveProductCallback( this.props.product.id );
|
2018-02-22 18:48:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Render one result in the search results.
|
|
|
|
*/
|
|
|
|
render() {
|
|
|
|
const product = this.props.product;
|
2018-04-06 22:57:20 +00:00
|
|
|
let icon = this.props.selected ? <Dashicon icon="yes" /> : null;
|
2018-02-22 18:48:34 +00:00
|
|
|
|
|
|
|
return (
|
2018-04-06 22:57:20 +00:00
|
|
|
<div className={ 'wc-products-list-card__content' + ( this.props.selected ? ' wc-products-list-card__content--added' : '' ) } onClick={ this.handleClick }>
|
2018-02-26 22:52:12 +00:00
|
|
|
<img src={ product.images[0].src } />
|
2018-04-06 22:57:20 +00:00
|
|
|
<span className="wc-products-list-card__content-item-name">{ product.name }</span>
|
2018-04-06 23:34:47 +00:00
|
|
|
{ icon }
|
2018-02-26 22:52:12 +00:00
|
|
|
</div>
|
2018-02-22 18:48:34 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2018-02-16 19:40:19 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* List preview of selected products.
|
|
|
|
*/
|
2018-09-06 16:29:17 +00:00
|
|
|
class ProductSpecificSelectedProducts extends React.Component {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor
|
|
|
|
*/
|
|
|
|
constructor( props ) {
|
|
|
|
super( props );
|
|
|
|
this.state = {
|
|
|
|
query: '',
|
2018-09-06 16:58:55 +00:00
|
|
|
loaded: false,
|
2018-09-06 16:29:17 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
this.updateProductCache = this.updateProductCache.bind( this );
|
|
|
|
this.getQuery = this.getQuery.bind( this );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the preview when component is first loaded.
|
|
|
|
*/
|
|
|
|
componentDidMount() {
|
|
|
|
this.updateProductCache();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the preview when component is updated.
|
|
|
|
*/
|
|
|
|
componentDidUpdate() {
|
2018-09-06 16:58:55 +00:00
|
|
|
if ( this.state.loaded && this.getQuery() !== this.state.query ) {
|
2018-09-06 16:29:17 +00:00
|
|
|
this.updateProductCache();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the endpoint for the current state of the component.
|
|
|
|
*/
|
|
|
|
getQuery() {
|
|
|
|
if ( ! this.props.productIds.length ) {
|
|
|
|
return '';
|
2018-02-16 19:40:19 +00:00
|
|
|
}
|
|
|
|
|
2018-03-02 19:18:42 +00:00
|
|
|
// Determine which products are not already in the cache and only fetch uncached products.
|
|
|
|
let uncachedProducts = [];
|
2018-09-06 16:29:17 +00:00
|
|
|
for( const productId of this.props.productIds ) {
|
2018-03-02 19:18:42 +00:00
|
|
|
if ( ! PRODUCT_DATA.hasOwnProperty( productId ) ) {
|
|
|
|
uncachedProducts.push( productId );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-06 16:29:17 +00:00
|
|
|
return uncachedProducts.length ? '/wc/v2/products?include=' + uncachedProducts.join( ',' ) : '';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add newly fetched products to the cache.
|
|
|
|
*/
|
|
|
|
updateProductCache() {
|
|
|
|
const self = this;
|
|
|
|
const query = this.getQuery();
|
|
|
|
|
|
|
|
self.setState( {
|
2018-09-06 16:58:55 +00:00
|
|
|
query: query,
|
|
|
|
loaded: false,
|
2018-09-06 16:29:17 +00:00
|
|
|
} );
|
2018-03-02 19:18:42 +00:00
|
|
|
|
|
|
|
// Add new products to cache.
|
2018-09-06 16:29:17 +00:00
|
|
|
if ( query.length ) {
|
|
|
|
apiFetch( { path: query } ).then( products => {
|
|
|
|
if ( products.length ) {
|
|
|
|
for ( const product of products ) {
|
|
|
|
PRODUCT_DATA[ product.id ] = product;
|
|
|
|
}
|
|
|
|
}
|
2018-09-06 16:58:55 +00:00
|
|
|
|
|
|
|
self.setState( {
|
|
|
|
loaded: true,
|
|
|
|
} );
|
2018-09-06 16:29:17 +00:00
|
|
|
} );
|
2018-02-16 19:40:19 +00:00
|
|
|
}
|
2018-09-06 16:29:17 +00:00
|
|
|
}
|
2018-02-15 18:16:14 +00:00
|
|
|
|
2018-09-06 16:29:17 +00:00
|
|
|
/**
|
|
|
|
* Render.
|
|
|
|
*/
|
|
|
|
render() {
|
|
|
|
const self = this;
|
2018-03-02 19:18:42 +00:00
|
|
|
const productElements = [];
|
|
|
|
|
2018-09-06 16:29:17 +00:00
|
|
|
for ( const productId of this.props.productIds ) {
|
2018-03-02 19:18:42 +00:00
|
|
|
|
|
|
|
// Skip products that aren't in the cache yet or failed to fetch.
|
|
|
|
if ( ! PRODUCT_DATA.hasOwnProperty( productId ) ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const productData = PRODUCT_DATA[ productId ];
|
|
|
|
|
|
|
|
productElements.push(
|
2018-06-04 17:48:48 +00:00
|
|
|
<li className="wc-products-list-card__item" key={ productData.id + '-specific-select-edit' } >
|
2018-03-02 19:18:42 +00:00
|
|
|
<div className="wc-products-list-card__content">
|
|
|
|
<img src={ productData.images[0].src } />
|
|
|
|
<span className="wc-products-list-card__content-item-name">{ productData.name }</span>
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
id={ 'product-' + productData.id }
|
2018-09-06 16:29:17 +00:00
|
|
|
onClick={ function() { self.props.addOrRemoveProduct( productData.id ) } } >
|
2018-04-06 22:57:20 +00:00
|
|
|
<Dashicon icon="no-alt" />
|
2018-03-02 19:18:42 +00:00
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-02-15 18:16:14 +00:00
|
|
|
return (
|
2018-09-06 16:29:17 +00:00
|
|
|
<div className={ 'wc-products-list-card__results-wrapper wc-products-list-card__results-wrapper--cols-' + this.props.columns }>
|
2018-04-06 22:57:20 +00:00
|
|
|
<div role="menu" className="wc-products-list-card__results" aria-orientation="vertical" aria-label={ __( 'Selected products' ) }>
|
|
|
|
|
2018-04-09 13:46:42 +00:00
|
|
|
{ productElements.length > 0 && <h3>{ __( 'Selected products' ) }</h3> }
|
2018-04-06 22:57:20 +00:00
|
|
|
|
2018-02-27 11:56:18 +00:00
|
|
|
<ul>
|
2018-03-02 19:18:42 +00:00
|
|
|
{ productElements }
|
2018-02-27 11:56:18 +00:00
|
|
|
</ul>
|
|
|
|
</div>
|
2018-02-15 18:16:14 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2018-09-06 16:29:17 +00:00
|
|
|
}
|