Add dynamic rendering + SSR to Products by Attribute (https://github.com/woocommerce/woocommerce-blocks/pull/602)

* Add dynamic rendering + SSR to Products by Attribute

* Fix spacing lint issue
This commit is contained in:
Kelly Dwan 2019-05-28 08:11:25 -04:00 committed by Mike Jolley
parent e0ff109781
commit bfa7724fe3
4 changed files with 176 additions and 126 deletions

View File

@ -2,89 +2,31 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { addQueryArgs } from '@wordpress/url';
import apiFetch from '@wordpress/api-fetch';
import { BlockControls, InspectorControls } from '@wordpress/editor';
import { BlockControls, InspectorControls, ServerSideRender } from '@wordpress/editor';
import {
Button,
Disabled,
PanelBody,
Placeholder,
Spinner,
Toolbar,
withSpokenMessages,
} from '@wordpress/components';
import classnames from 'classnames';
import { Component, Fragment } from '@wordpress/element';
import { debounce } from 'lodash';
import Gridicon from 'gridicons';
import PropTypes from 'prop-types';
/**
* Internal dependencies
*/
import getQuery from '../../utils/get-query';
import GridContentControl from '../../components/grid-content-control';
import GridLayoutControl from '../../components/grid-layout-control';
import ProductAttributeControl from '../../components/product-attribute-control';
import ProductOrderbyControl from '../../components/product-orderby-control';
import ProductPreview from '../../components/product-preview';
/**
* Component to handle edit mode of "Products by Attribute".
*/
class ProductsByAttributeBlock extends Component {
constructor() {
super( ...arguments );
this.state = {
products: [],
loaded: false,
};
this.debouncedGetProducts = debounce( this.getProducts.bind( this ), 200 );
}
componentDidMount() {
if ( this.props.attributes.attributes ) {
this.getProducts();
}
}
componentDidUpdate( prevProps ) {
const hasChange = [
'attributes',
'attrOperator',
'columns',
'orderby',
'rows',
].reduce( ( acc, key ) => {
return acc || prevProps.attributes[ key ] !== this.props.attributes[ key ];
}, false );
if ( hasChange ) {
this.debouncedGetProducts();
}
}
getProducts() {
const blockAttributes = this.props.attributes;
if ( ! blockAttributes.attributes.length ) {
// We've removed all selected attributes, or no attributes have been selected yet.
this.setState( { products: [], loaded: true } );
return;
}
apiFetch( {
path: addQueryArgs(
'/wc-blocks/v1/products',
getQuery( blockAttributes, this.props.name )
),
} )
.then( ( products ) => {
this.setState( { products, loaded: true } );
} )
.catch( () => {
this.setState( { products: [], loaded: true } );
} );
}
getInspectorControls() {
const { setAttributes } = this.props;
const {
@ -199,20 +141,8 @@ class ProductsByAttributeBlock extends Component {
}
render() {
const { setAttributes } = this.props;
const { columns, editMode, contentVisibility } = this.props.attributes;
const { loaded, products = [] } = this.state;
const classes = classnames( {
'wc-block-products-grid': true,
'wc-block-products-attribute': true,
[ `cols-${ columns }` ]: columns,
'is-loading': ! loaded,
'is-not-found': loaded && ! products.length,
'is-hidden-title': ! contentVisibility.title,
'is-hidden-price': ! contentVisibility.price,
'is-hidden-rating': ! contentVisibility.rating,
'is-hidden-button': ! contentVisibility.button,
} );
const { attributes, name, setAttributes } = this.props;
const { editMode } = attributes;
return (
<Fragment>
@ -232,27 +162,9 @@ class ProductsByAttributeBlock extends Component {
{ editMode ? (
this.renderEditMode()
) : (
<div className={ classes }>
{ products.length ? (
products.map( ( product ) => (
<ProductPreview product={ product } key={ product.id } />
) )
) : (
<Placeholder
icon={ <Gridicon icon="custom-post-type" /> }
label={ __(
'Products by Attribute',
'woo-gutenberg-products-block'
) }
>
{ ! loaded ? (
<Spinner />
) : (
__( 'No products found.', 'woo-gutenberg-products-block' )
) }
</Placeholder>
) }
</div>
<Disabled>
<ServerSideRender block={ name } attributes={ attributes } />
</Disabled>
) }
</Fragment>
);

View File

@ -2,9 +2,7 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import classnames from 'classnames';
import Gridicon from 'gridicons';
import { RawHTML } from '@wordpress/element';
import { registerBlockType } from '@wordpress/blocks';
/**
@ -12,9 +10,11 @@ import { registerBlockType } from '@wordpress/blocks';
*/
import './editor.scss';
import Block from './block';
import getShortcode from '../../utils/get-shortcode';
import { deprecatedConvertToShortcode } from '../../utils/deprecations';
registerBlockType( 'woocommerce/products-by-attribute', {
const blockTypeName = 'woocommerce/products-by-attribute';
registerBlockType( blockTypeName, {
title: __( 'Products by Attribute', 'woo-gutenberg-products-block' ),
icon: {
src: <Gridicon icon="custom-post-type" />,
@ -92,6 +92,48 @@ registerBlockType( 'woocommerce/products-by-attribute', {
},
},
deprecated: [
{
// Deprecate shortcode save method in favor of dynamic rendering.
attributes: {
attributes: {
type: 'array',
default: [],
},
attrOperator: {
type: 'string',
default: 'any',
},
columns: {
type: 'number',
default: wc_product_block_data.default_columns,
},
editMode: {
type: 'boolean',
default: true,
},
contentVisibility: {
type: 'object',
default: {
title: true,
price: true,
rating: true,
button: true,
},
},
orderby: {
type: 'string',
default: 'date',
},
rows: {
type: 'number',
default: wc_product_block_data.default_rows,
},
},
save: deprecatedConvertToShortcode( blockTypeName ),
},
],
/**
* Renders and manages the block.
*/
@ -99,29 +141,7 @@ registerBlockType( 'woocommerce/products-by-attribute', {
return <Block { ...props } />;
},
/**
* Save the block content in the post content. Block content is saved as a products shortcode.
*
* @return string
*/
save( props ) {
const {
align,
contentVisibility,
} = props.attributes; /* eslint-disable-line react/prop-types */
const classes = classnames(
align ? `align${ align }` : '',
{
'is-hidden-title': ! contentVisibility.title,
'is-hidden-price': ! contentVisibility.price,
'is-hidden-rating': ! contentVisibility.rating,
'is-hidden-button': ! contentVisibility.button,
}
);
return (
<RawHTML className={ classes }>
{ getShortcode( props, 'woocommerce/products-by-attribute' ) }
</RawHTML>
);
save() {
return null;
},
} );

View File

@ -264,9 +264,70 @@ class WGPB_Block_Library {
register_block_type(
'woocommerce/products-by-attribute',
array(
'editor_script' => 'wc-products-attribute',
'editor_style' => 'wc-block-editor',
'style' => 'wc-block-style',
'render_callback' => array( __CLASS__, 'render_products_by_attribute' ),
'editor_script' => 'wc-products-attribute',
'editor_style' => 'wc-block-editor',
'style' => 'wc-block-style',
'attributes' => array(
'attributes' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'type' => 'number',
),
'attr_slug' => array(
'type' => 'string',
),
),
),
'default' => array(),
),
'attrOperator' => array(
'type' => 'string',
'default' => 'any',
),
'columns' => array(
'type' => 'number',
'default' => wc_get_theme_support( 'product_blocks::default_columns', 3 ),
),
'contentVisibility' => array(
'type' => 'object',
'properties' => array(
'title' => array(
'type' => 'boolean',
'default' => true,
),
'price' => array(
'type' => 'boolean',
'default' => true,
),
'rating' => array(
'type' => 'boolean',
'default' => true,
),
'button' => array(
'type' => 'boolean',
'default' => true,
),
),
),
'editMode' => array(
'type' => 'boolean',
'default' => true,
),
'orderby' => array(
'type' => 'string',
'enum' => array( 'date', 'popularity', 'price_asc', 'price_desc', 'rating', 'title' ),
'default' => 'date',
),
'rows' => array(
'type' => 'number',
'default' => wc_get_theme_support( 'product_blocks::default_rows', 1 ),
),
),
)
);
register_block_type(
@ -444,6 +505,20 @@ class WGPB_Block_Library {
return $block->render();
}
/**
* Products by attribute: Include and render the dynamic block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @return string Rendered block type output.
*/
public static function render_products_by_attribute( $attributes, $content ) {
require_once dirname( __FILE__ ) . '/class-wgpb-block-products-by-attribute.php';
$block = new WGPB_Block_Products_By_Attribute( $attributes, $content );
return $block->render();
}
/**
* Top rated products: Include and render the dynamic block.
*

View File

@ -0,0 +1,43 @@
<?php
/**
* Display the Products by Attribute block in the post content.
* NOTE: DO NOT edit this file in WooCommerce core, this is generated from woocommerce-gutenberg-products-block.
*
* @package WooCommerce\Blocks
* @version 2.1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handler for getting products by attribute for display.
*/
class WGPB_Block_Products_By_Attribute extends WGPB_Block_Grid_Base {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'products-by-attribute';
/**
* Set args specific to this block
*
* @param array $query_args Query args.
*/
protected function set_block_query_args( &$query_args ) {
if ( ! empty( $this->attributes['attributes'] ) ) {
$taxonomy = sanitize_title( $this->attributes['attributes'][0]['attr_slug'] );
$terms = wp_list_pluck( $this->attributes['attributes'], 'id' );
$query_args['tax_query'][] = array(
'taxonomy' => $taxonomy,
'terms' => array_map( 'absint', $terms ),
'field' => 'term_id',
'operator' => 'all' === $this->attributes['attrOperator'] ? 'AND' : 'IN',
);
}
}
}