2018-02-15 18:16:14 +00:00
|
|
|
const { __ } = wp.i18n;
|
|
|
|
const { Toolbar, withAPIData, Dropdown } = wp.components;
|
2018-02-22 18:48:34 +00:00
|
|
|
const { TransitionGroup, CSSTransition } = ReactTransitionGroup;
|
2018-02-15 18:16:14 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* When the display mode is 'Specific products' search for and add products to the block.
|
|
|
|
*
|
|
|
|
* @todo Add the functionality and everything.
|
|
|
|
*/
|
|
|
|
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-02-16 19:40:19 +00:00
|
|
|
addProduct( id ) {
|
|
|
|
let selectedProducts = this.state.selectedProducts;
|
|
|
|
selectedProducts.push( id );
|
2018-02-15 18:16:14 +00:00
|
|
|
|
2018-02-16 19:40:19 +00:00
|
|
|
this.setState( {
|
|
|
|
selectedProducts: selectedProducts
|
|
|
|
} );
|
|
|
|
|
|
|
|
this.props.update_display_setting_callback( selectedProducts );
|
|
|
|
}
|
|
|
|
|
2018-02-22 18:48:34 +00:00
|
|
|
/**
|
|
|
|
* Remove a product from the list of selected products.
|
|
|
|
*
|
|
|
|
* @param id int Product ID.
|
|
|
|
*/
|
2018-02-16 19:40:19 +00:00
|
|
|
removeProduct( id ) {
|
|
|
|
let oldProducts = this.state.selectedProducts;
|
|
|
|
let newProducts = [];
|
|
|
|
|
|
|
|
for ( let productId of oldProducts ) {
|
|
|
|
if ( productId !== id ) {
|
|
|
|
newProducts.push( productId );
|
|
|
|
}
|
|
|
|
}
|
2018-02-15 18:16:14 +00:00
|
|
|
|
|
|
|
this.setState( {
|
2018-02-16 19:40:19 +00:00
|
|
|
selectedProducts: newProducts
|
2018-02-15 18:16:14 +00:00
|
|
|
} );
|
2018-02-16 19:40:19 +00:00
|
|
|
|
|
|
|
this.props.update_display_setting_callback( newProducts );
|
2018-02-15 18:16:14 +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 (
|
|
|
|
<div className="product-specific-select">
|
2018-02-22 18:48:34 +00:00
|
|
|
<ProductsSpecificSearchField
|
|
|
|
addProductCallback={ this.addProduct.bind( this ) }
|
|
|
|
selectedProducts={ this.state.selectedProducts }
|
|
|
|
/>
|
|
|
|
<ProductSpecificSelectedProducts
|
|
|
|
products={ this.state.selectedProducts }
|
|
|
|
removeProductCallback={ this.removeProduct.bind( this ) }
|
|
|
|
/>
|
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: '',
|
|
|
|
}
|
|
|
|
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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-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-02-22 19:06:13 +00:00
|
|
|
|
|
|
|
let cancelButton = null;
|
|
|
|
if ( this.state.searchText.length ) {
|
|
|
|
cancelButton = <span className="cancel" onClick={ () => { this.setState( { searchText: '' } ); } } >X</span>;
|
|
|
|
}
|
|
|
|
|
2018-02-16 19:40:19 +00:00
|
|
|
return (
|
2018-02-22 19:06:13 +00:00
|
|
|
<div className="product-search" ref={ this.setWrapperRef }>
|
2018-02-22 18:48:34 +00:00
|
|
|
<input type="text"
|
|
|
|
value={ this.state.searchText }
|
|
|
|
placeholder={ __( 'Search for products to display' ) }
|
|
|
|
onChange={ this.updateSearchResults }
|
|
|
|
/>
|
2018-02-22 19:06:13 +00:00
|
|
|
{ cancelButton }
|
2018-02-22 18:48:34 +00:00
|
|
|
<ProductSpecificSearchResults
|
|
|
|
searchString={ this.state.searchText }
|
|
|
|
addProductCallback={ this.props.addProductCallback }
|
|
|
|
selectedProducts={ this.props.selectedProducts }
|
|
|
|
/>
|
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
|
|
|
*/
|
|
|
|
const ProductSpecificSearchResults = withAPIData( ( props ) => {
|
|
|
|
|
|
|
|
if ( ! props.searchString.length ) {
|
|
|
|
return {
|
|
|
|
products: []
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-02-15 18:16:14 +00:00
|
|
|
return {
|
2018-02-22 18:48:34 +00:00
|
|
|
products: '/wc/v2/products?per_page=10&search=' + props.searchString,
|
2018-02-15 18:16:14 +00:00
|
|
|
};
|
2018-02-22 18:48:34 +00:00
|
|
|
} )( ( { products, addProductCallback, selectedProducts } ) => {
|
2018-02-15 18:16:14 +00:00
|
|
|
if ( ! products.data ) {
|
2018-02-16 19:40:19 +00:00
|
|
|
return null;
|
2018-02-15 18:16:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( 0 === products.data.length ) {
|
|
|
|
return __( 'No products found' );
|
|
|
|
}
|
|
|
|
|
2018-02-22 18:48:34 +00:00
|
|
|
return <ProductSpecificSearchResultsDropdown
|
|
|
|
products={ products.data }
|
|
|
|
addProductCallback={ addProductCallback }
|
|
|
|
selectedProducts={ selectedProducts }
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The dropdown of search results.
|
|
|
|
*/
|
|
|
|
class ProductSpecificSearchResultsDropdown extends React.Component {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Render dropdown.
|
|
|
|
*/
|
|
|
|
render() {
|
|
|
|
const { products, addProductCallback, selectedProducts } = this.props;
|
|
|
|
|
|
|
|
let productElements = [];
|
|
|
|
|
|
|
|
for ( let product of products ) {
|
|
|
|
if ( selectedProducts.includes( product.id ) ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
productElements.push(
|
|
|
|
<CSSTransition
|
|
|
|
key={ product.slug }
|
|
|
|
classNames="components-button--transition"
|
|
|
|
timeout={ { exit: 700 } }
|
|
|
|
>
|
|
|
|
<ProductSpecificSearchResultsDropdownElement
|
|
|
|
product={product}
|
|
|
|
addProductCallback={ addProductCallback }
|
|
|
|
/>
|
|
|
|
</CSSTransition>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-02-16 19:40:19 +00:00
|
|
|
return (
|
|
|
|
<div role="menu" className="product-search-results" aria-orientation="vertical" aria-label="{ __( 'Products list' ) }">
|
2018-02-15 18:16:14 +00:00
|
|
|
<ul>
|
2018-02-22 18:48:34 +00:00
|
|
|
<TransitionGroup>
|
|
|
|
{ productElements }
|
|
|
|
</TransitionGroup>
|
2018-02-15 18:16:14 +00:00
|
|
|
</ul>
|
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.state = {
|
|
|
|
clicked: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
this.handleClick = this.handleClick.bind( this );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add product to main list and change UI to show it was added.
|
|
|
|
*/
|
|
|
|
handleClick() {
|
|
|
|
this.setState( { clicked: true } );
|
|
|
|
this.props.addProductCallback( this.props.product.id );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Render one result in the search results.
|
|
|
|
*/
|
|
|
|
render() {
|
|
|
|
const product = this.props.product;
|
|
|
|
|
|
|
|
return (
|
|
|
|
<li>
|
|
|
|
<button type="button"
|
|
|
|
className='components-button'
|
|
|
|
id={ 'product-' + product.id }
|
|
|
|
onClick={ this.handleClick } >
|
|
|
|
<img src={ product.images[0].src } width="30px" />
|
|
|
|
<span className="product-name">{ this.state.clicked ? __( 'Added' ) : product.name }</span>
|
|
|
|
<a>{ __( 'Add' ) }</a>
|
|
|
|
</button>
|
|
|
|
</li>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2018-02-16 19:40:19 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* List preview of selected products.
|
|
|
|
*/
|
|
|
|
const ProductSpecificSelectedProducts = withAPIData( ( props ) => {
|
|
|
|
|
|
|
|
if ( ! props.products.length ) {
|
|
|
|
return {
|
|
|
|
products: []
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
products: '/wc/v2/products?include=' + props.products.join( ',' )
|
2018-02-15 18:16:14 +00:00
|
|
|
};
|
2018-02-16 19:40:19 +00:00
|
|
|
} )( ( { products, removeProductCallback } ) => {
|
|
|
|
if ( ! products.data ) {
|
|
|
|
return null;
|
|
|
|
}
|
2018-02-15 18:16:14 +00:00
|
|
|
|
2018-02-16 19:40:19 +00:00
|
|
|
if ( 0 === products.data.length ) {
|
|
|
|
return __( 'No products selected' );
|
|
|
|
}
|
2018-02-15 18:16:14 +00:00
|
|
|
|
|
|
|
return (
|
2018-02-16 19:40:19 +00:00
|
|
|
<div role="menu" className="selected-products" aria-orientation="vertical" aria-label="{ __( 'Products list' ) }">
|
|
|
|
<ul>
|
|
|
|
{ products.data.map( ( product ) => (
|
|
|
|
<li>
|
|
|
|
<button type="button" className="components-button" id={ 'product-' + product.id } >
|
|
|
|
<img src={ product.images[0].src } width="30px" />
|
|
|
|
<span className="product-name">{ product.name }</span>
|
|
|
|
<a onClick={ function() { removeProductCallback( product.id ) } } >{ __( 'X' ) }</a>
|
|
|
|
</button>
|
|
|
|
</li>
|
|
|
|
) ) }
|
|
|
|
</ul>
|
2018-02-15 18:16:14 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|