const { __ } = wp.i18n; const { registerBlockType, InspectorControls, BlockControls } = wp.blocks; const { Toolbar, withAPIData, Dropdown } = wp.components; const { RangeControl, ToggleControl, SelectControl } = InspectorControls; import { ProductsSpecificSelect } from './views/specific-select.jsx'; import { ProductsCategorySelect } from './views/category-select.jsx'; import { ProductsAttributeSelect } from './views/attribute-select.jsx'; /** * A setting has the following properties: * title - Display title of the setting. * description - Display description of the setting. * value - Display setting slug to set when selected. * group_container - (optional) If set the setting is a parent container. */ const PRODUCTS_BLOCK_DISPLAY_SETTINGS = { 'specific' : { title: __( 'Individual products' ), description: __( 'Hand-pick which products to display' ), value: 'specific', }, 'category' : { title: __( 'Product category' ), description: __( 'Display products from a specific category or multiple categories' ), value: 'category', }, 'filter' : { title: __( 'Filter products' ), description: __( 'E.g. featured products, or products with a specific attribute like size or color' ), value: 'filter', group_container: 'filter' }, 'featured' : { title: __( 'Featured products' ), description: '', value: 'featured', }, 'best_sellers' : { title: __( 'Best sellers' ), description: '', value: 'best_sellers', }, 'best_rated' : { title: __( 'Best rated' ), description: '', value: 'best_rated', }, 'on_sale' : { title: __( 'On sale' ), description: '', value: 'on_sale', }, 'attribute' : { title: __( 'Attribute' ), description: '', value: 'attribute', }, 'all' : { title: __( 'All products' ), description: __( 'Display all products ordered chronologically' ), value: 'all', } }; /** * One option from the list of all available ways to display products. */ class ProductsBlockSettingsEditorDisplayOption extends React.Component { render() { return (
{ this.props.update_display_callback( this.props.value ) } } >

{ this.props.title }

{ this.props.description }

); } } /** * A list of all available ways to display products. */ class ProductsBlockSettingsEditorDisplayOptions extends React.Component { render() { let classes = 'display-settings-container'; if ( this.props.existing ) { classes += ' existing'; } let display_settings = []; for ( var setting_key in PRODUCTS_BLOCK_DISPLAY_SETTINGS ) { display_settings.push( ); } let description = null; if ( ! this.props.existing ) { description =

{ __( 'Choose which products you\'d like to display:' ) }

; } return (
{ description } { display_settings }
); } } /** * The products block when in Edit mode. */ class ProductsBlockSettingsEditor extends React.Component { /** * Constructor. */ constructor( props ) { super( props ); this.state = { display: props.selected_display, menu_visible: props.selected_display ? false : true, expanded_group: '', } this.updateDisplay = this.updateDisplay.bind( this ); } /** * Update the display settings for the block. * * @param value String */ updateDisplay( value ) { // If not a group update display. let new_state = { display: value, menu_visible: false, expanded_group: '', }; const is_group = 'undefined' !== PRODUCTS_BLOCK_DISPLAY_SETTINGS[ value ].group_container && PRODUCTS_BLOCK_DISPLAY_SETTINGS[ value ].group_container; if ( is_group ) { // If the group has not been expanded, expand it. new_state = { menu_visible: true, expanded_group: value, } // If the group has already been expanded, collapse it. if ( this.state.expanded_group === PRODUCTS_BLOCK_DISPLAY_SETTINGS[ value ].group_container ) { new_state.expanded_group = ''; } } this.setState( new_state ); // Only update the display setting if a non-group setting was selected. if ( ! is_group ) { this.props.update_display_callback( value ); } } /** * Render the display settings dropdown and any extra contextual settings. */ render() { let extra_settings = null; if ( 'specific' === this.state.display ) { extra_settings = ; } else if ( 'category' === this.state.display ) { extra_settings = ; } else if ( 'attribute' === this.state.display ) { extra_settings = } const menu = this.state.menu_visible ? : null; let heading = null; if ( this.state.display ) { var menu_link = { this.setState( { menu_visible: true } ) } }>{ __( 'Display different products' ) }; if ( this.state.menu_visible ) { menu_link = { this.setState( { menu_visible: false } ) } }>{ __( 'Cancel' ) }; } heading = (
{ __( 'Displaying ' ) } { __( PRODUCTS_BLOCK_DISPLAY_SETTINGS[ this.state.display ].title ) }
{ menu_link }
); } return (

{ __( 'Products' ) }

{ heading } { menu } { extra_settings }
); } } /** * One product in the product block preview. */ class ProductPreview extends React.Component { render() { const { attributes, product } = this.props; let image = null; if ( product.images.length ) { image = } let title = null; if ( attributes.display_title ) { title =
{ product.name }
} let price = null; if ( attributes.display_price ) { price =
{ product.price }
} let add_to_cart = null; if ( attributes.display_add_to_cart ) { add_to_cart = { __( 'Add to cart' ) } } return (
{ image } { title } { price } { add_to_cart }
); } } /** * Renders a preview of what the block will look like with current settings. */ const ProductsBlockPreview = withAPIData( ( { attributes } ) => { const { columns, rows, order, display, display_setting, block_layout } = attributes; let query = { per_page: ( 'list' === block_layout ) ? rows : rows * columns, orderby: order }; if ( 'specific' === display ) { query.include = JSON.stringify( display_setting ); query.orderby = 'include'; } else if ( 'category' === display ) { query.category = display_setting.join( ',' ); } else if ( 'attribute' === display && display_setting.length ) { query.attribute = display_setting[0]; if ( display_setting.length > 1 ) { query.attribute_term = display_setting.slice( 1 ).join( ',' ); } } let query_string = '?'; for ( const key of Object.keys( query ) ) { query_string += key + '=' + query[ key ] + '&'; } return { products: '/wc/v2/products' + query_string }; } )( ( { products, attributes } ) => { if ( ! products.data ) { return __( 'Loading' ); } if ( 0 === products.data.length ) { return __( 'No products found' ); } const classes = "wc-products-block-preview " + attributes.block_layout + " cols-" + attributes.columns; return (
{ products.data.map( ( product ) => ( ) ) }
); } ); /** * Register and run the products block. */ registerBlockType( 'woocommerce/products', { title: __( 'Products' ), icon: 'universal-access-alt', // @todo Needs a good icon. category: 'widgets', attributes: { /** * Layout to use. 'grid' or 'list'. */ block_layout: { type: 'string', default: 'grid', }, /** * Number of columns. */ columns: { type: 'number', default: 3, }, /** * Number of rows. */ rows: { type: 'number', default: 1, }, /** * Whether to display product titles. */ display_title: { type: 'boolean', default: true, }, /** * Whether to display prices. */ display_price: { type: 'boolean', default: true, }, /** * Whether to display Add to Cart buttons. */ display_add_to_cart: { type: 'boolean', default: false, }, /** * Order to use for products. 'date', or 'title'. */ order: { type: 'string', default: 'date', }, /** * What types of products to display. 'all', 'specific', or 'category'. */ display: { type: 'string', default: '', }, /** * Which products to display if 'display' is 'specific' or 'category'. Array of product ids or category slugs depending on setting. */ display_setting: { type: 'array', default: [], }, /** * Whether the block is in edit or preview mode. */ edit_mode: { type: 'boolean', default: true, }, }, /** * Renders and manages the block. */ edit( props ) { const { attributes, className, focus, setAttributes, setFocus } = props; const { block_layout, rows, columns, display_title, display_price, display_add_to_cart, order, display, display_setting, edit_mode } = attributes; /** * Get the components for the sidebar settings area that is rendered while focused on a Products block. * * @return Component */ function getInspectorControls() { return (

{ __( 'Layout' ) }

setAttributes( { columns: value } ) } min={ 1 } max={ 6 } /> setAttributes( { rows: value } ) } min={ 1 } max={ 6 } /> setAttributes( { display_title: ! display_title } ) } /> setAttributes( { display_price: ! display_price } ) } /> setAttributes( { display_add_to_cart: ! display_add_to_cart } ) } /> setAttributes( { order: value } ) } />
); }; /** * Get the components for the toolbar area that appears on top of the block when focused. * * @return Component */ function getToolbarControls() { const layoutControls = [ { icon: 'list-view', title: __( 'List View' ), onClick: () => setAttributes( { block_layout: 'list' } ), isActive: 'list' === block_layout, }, { icon: 'grid-view', title: __( 'Grid View' ), onClick: () => setAttributes( { block_layout: 'grid' } ), isActive: 'grid' === block_layout, }, ]; const editButton = [ { icon: 'edit', title: __( 'Edit' ), onClick: () => setAttributes( { edit_mode: ! edit_mode } ), isActive: edit_mode, }, ]; return ( ); } /** * Get the block preview component for preview mode. * * @return Component */ function getPreview() { return ; } /** * Get the block edit component for edit mode. * * @return Component */ function getSettingsEditor() { return ( setAttributes( { display: value } ) } update_display_setting_callback={ ( value ) => setAttributes( { display_setting: value } ) } done_callback={ () => setAttributes( { edit_mode: false } ) } /> ); } return [ ( !! focus ) ? getInspectorControls() : null, ( !! focus ) ? getToolbarControls() : null, edit_mode ? getSettingsEditor() : getPreview(), ]; }, /** * Save the block content in the post content. Block content is saved as a products shortcode. * * @return string */ save( props ) { const { block_layout, rows, columns, display_title, display_price, display_add_to_cart, order, display, display_setting, className } = props.attributes; let shortcode_atts = new Map(); shortcode_atts.set( 'orderby', order ); shortcode_atts.set( 'limit', 'grid' === block_layout ? rows * columns : rows ); shortcode_atts.set( 'class', 'list' === block_layout ? className + ' list-layout' : className ); if ( 'grid' === block_layout ) { shortcode_atts.set( 'columns', columns ); } if ( ! display_title ) { shortcode_atts.set( 'show_title', 0 ); } if ( ! display_price ) { shortcode_atts.set( 'show_price', 0 ); } if ( ! display_add_to_cart ) { shortcode_atts.set( 'show_add_to_cart', 0 ); } if ( 'specific' === display ) { shortcode_atts.set( 'include', display_setting.join( ',' ) ); } if ( 'category' === display ) { shortcode_atts.set( 'category', display_setting.join( ',' ) ); } // Build the shortcode string out of the set shortcode attributes. let shortcode = '[products'; for ( let [key, value] of shortcode_atts ) { shortcode += ' ' + key + '="' + value + '"'; } shortcode += ']'; return shortcode; }, } );