Use Server Side Rendering for Product Category List block (https://github.com/woocommerce/woocommerce-blocks/pull/1024)
* Convert product list to SSR block * Remove PRODUCT_CATEGORIES from block data * onclick event handling * Empty placeholder * code style * depth css class * hasCount handling * Deprecation handling of old saved markup * Code style * Legacy attribute mapping
This commit is contained in:
parent
7906937c9a
commit
e227dff18e
|
@ -2,157 +2,175 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Component, createRef, Fragment } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { HOME_URL } from '@woocommerce/block-settings';
|
||||
import { Fragment } from 'react';
|
||||
import { InspectorControls, ServerSideRender } from '@wordpress/editor';
|
||||
import PropTypes from 'prop-types';
|
||||
import { PanelBody, ToggleControl, Placeholder } from '@wordpress/components';
|
||||
import { IconFolder } from '@woocommerce/block-components/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import withComponentId from '@woocommerce/base-hocs/with-component-id';
|
||||
import ToggleButtonControl from '@woocommerce/block-components/toggle-button-control';
|
||||
|
||||
/**
|
||||
* Component displaying the categories as dropdown or list.
|
||||
*/
|
||||
class ProductCategoriesBlock extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
this.select = createRef();
|
||||
this.onNavigate = this.onNavigate.bind( this );
|
||||
this.renderList = this.renderList.bind( this );
|
||||
this.renderOptions = this.renderOptions.bind( this );
|
||||
}
|
||||
|
||||
onNavigate() {
|
||||
const { isPreview = false } = this.props;
|
||||
const url = this.select.current.value;
|
||||
if ( 'false' === url ) {
|
||||
return;
|
||||
}
|
||||
const home = HOME_URL;
|
||||
|
||||
if ( ! isPreview && 0 === url.indexOf( home ) ) {
|
||||
document.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
renderList( items, depth = 0 ) {
|
||||
const { isPreview = false } = this.props;
|
||||
const { hasCount } = this.props.attributes;
|
||||
const parentKey = 'parent-' + items[ 0 ].term_id;
|
||||
const ProductCategoriesBlock = ( { attributes, setAttributes, name } ) => {
|
||||
const getInspectorControls = () => {
|
||||
const { hasCount, hasEmpty, isDropdown, isHierarchical } = attributes;
|
||||
|
||||
return (
|
||||
<ul key={ parentKey } className="wc-block-product-categories-list">
|
||||
{ items.map( ( cat ) => {
|
||||
const count = hasCount ? (
|
||||
<span className="wc-block-product-categories-list-item-count">
|
||||
{ cat.count }
|
||||
</span>
|
||||
) : null;
|
||||
return [
|
||||
<li
|
||||
key={ cat.term_id }
|
||||
className="wc-block-product-categories-list-item"
|
||||
>
|
||||
{ /* eslint-disable-next-line jsx-a11y/anchor-is-valid */ }
|
||||
<a href={ isPreview ? null : cat.link }>
|
||||
{ cat.name }
|
||||
</a>
|
||||
{ count }
|
||||
</li>,
|
||||
!! cat.children &&
|
||||
!! cat.children.length &&
|
||||
this.renderList( cat.children, depth + 1 ),
|
||||
];
|
||||
} ) }
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
renderOptions( items, depth = 0 ) {
|
||||
const { hasCount } = this.props.attributes;
|
||||
|
||||
return items.map( ( cat ) => {
|
||||
const count = hasCount ? `(${ cat.count })` : null;
|
||||
return [
|
||||
<option key={ cat.term_id } value={ cat.link }>
|
||||
{ '–'.repeat( depth ) } { cat.name } { count }
|
||||
</option>,
|
||||
!! cat.children &&
|
||||
!! cat.children.length &&
|
||||
this.renderOptions( cat.children, depth + 1 ),
|
||||
];
|
||||
} );
|
||||
}
|
||||
|
||||
render() {
|
||||
const { attributes, categories, componentId } = this.props;
|
||||
const { className, isDropdown } = attributes;
|
||||
const classes = classnames( 'wc-block-product-categories', className, {
|
||||
'is-dropdown': isDropdown,
|
||||
'is-list': ! isDropdown,
|
||||
} );
|
||||
|
||||
const selectId = `prod-categories-${ componentId }`;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{ categories.length > 0 && (
|
||||
<div className={ classes }>
|
||||
{ isDropdown ? (
|
||||
<Fragment>
|
||||
<div className="wc-block-product-categories__dropdown">
|
||||
<label
|
||||
className="screen-reader-text"
|
||||
htmlFor={ selectId }
|
||||
>
|
||||
{ __(
|
||||
'Select a category',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</label>
|
||||
<select id={ selectId } ref={ this.select }>
|
||||
<option value="false" hidden>
|
||||
{ __(
|
||||
'Select a category',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</option>
|
||||
{ this.renderOptions( categories ) }
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="wc-block-product-categories__button"
|
||||
aria-label={ __(
|
||||
'Go to category',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
icon="arrow-right-alt2"
|
||||
onClick={ this.onNavigate }
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
focusable="false"
|
||||
className="dashicon dashicons-arrow-right-alt2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M6 15l5-5-5-5 1-2 7 7-7 7z" />
|
||||
</svg>
|
||||
</button>
|
||||
</Fragment>
|
||||
) : (
|
||||
this.renderList( categories )
|
||||
<InspectorControls key="inspector">
|
||||
<PanelBody
|
||||
title={ __( 'Content', 'woo-gutenberg-products-block' ) }
|
||||
initialOpen
|
||||
>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Show product count',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</div>
|
||||
) }
|
||||
</Fragment>
|
||||
help={
|
||||
hasCount
|
||||
? __(
|
||||
'Product count is visible.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
: __(
|
||||
'Product count is hidden.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
}
|
||||
checked={ hasCount }
|
||||
onChange={ () =>
|
||||
setAttributes( { hasCount: ! hasCount } )
|
||||
}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Show hierarchy',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
help={
|
||||
isHierarchical
|
||||
? __(
|
||||
'Hierarchy is visible.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
: __(
|
||||
'Hierarchy is hidden.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
}
|
||||
checked={ isHierarchical }
|
||||
onChange={ () =>
|
||||
setAttributes( {
|
||||
isHierarchical: ! isHierarchical,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Show empty categories',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
help={
|
||||
hasEmpty
|
||||
? __(
|
||||
'Empty categories are visible.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
: __(
|
||||
'Empty categories are hidden.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
}
|
||||
checked={ hasEmpty }
|
||||
onChange={ () =>
|
||||
setAttributes( { hasEmpty: ! hasEmpty } )
|
||||
}
|
||||
/>
|
||||
</PanelBody>
|
||||
<PanelBody
|
||||
title={ __(
|
||||
'List Settings',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
initialOpen
|
||||
>
|
||||
<ToggleButtonControl
|
||||
label={ __(
|
||||
'Display style',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
value={ isDropdown ? 'dropdown' : 'list' }
|
||||
options={ [
|
||||
{
|
||||
label: __(
|
||||
'List',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
value: 'list',
|
||||
},
|
||||
{
|
||||
label: __(
|
||||
'Dropdown',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
value: 'dropdown',
|
||||
},
|
||||
] }
|
||||
onChange={ ( value ) =>
|
||||
setAttributes( {
|
||||
isDropdown: 'dropdown' === value,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default withComponentId( ProductCategoriesBlock );
|
||||
return (
|
||||
<Fragment>
|
||||
{ getInspectorControls() }
|
||||
<ServerSideRender
|
||||
block={ name }
|
||||
attributes={ attributes }
|
||||
EmptyResponsePlaceholder={ () => (
|
||||
<Placeholder
|
||||
icon={ <IconFolder /> }
|
||||
label={ __(
|
||||
'Product Categories List',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
className="wc-block-product-categories"
|
||||
>
|
||||
{ __(
|
||||
"This block shows product categories for your store. To use it, you'll first need to create a product and assign it to a category.",
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</Placeholder>
|
||||
) }
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
ProductCategoriesBlock.propTypes = {
|
||||
/**
|
||||
* The attributes for this block
|
||||
*/
|
||||
attributes: PropTypes.object.isRequired,
|
||||
/**
|
||||
* The register block name.
|
||||
*/
|
||||
name: PropTypes.string.isRequired,
|
||||
/**
|
||||
* A callback to update attributes
|
||||
*/
|
||||
setAttributes: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ProductCategoriesBlock;
|
||||
|
|
|
@ -1,155 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Fragment } from '@wordpress/element';
|
||||
import { InspectorControls } from '@wordpress/editor';
|
||||
import { PanelBody, ToggleControl, Placeholder } from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './editor.scss';
|
||||
import Block from './block.js';
|
||||
import ToggleButtonControl from '@woocommerce/block-components/toggle-button-control';
|
||||
import getCategories from './get-categories';
|
||||
import { IconFolder } from '@woocommerce/block-components/icons';
|
||||
|
||||
export default function( { attributes, setAttributes } ) {
|
||||
const { hasCount, hasEmpty, isDropdown, isHierarchical } = attributes;
|
||||
const categories = getCategories( attributes );
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<InspectorControls key="inspector">
|
||||
<PanelBody
|
||||
title={ __( 'Content', 'woo-gutenberg-products-block' ) }
|
||||
initialOpen
|
||||
>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Show product count',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
help={
|
||||
hasCount
|
||||
? __(
|
||||
'Product count is visible.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
: __(
|
||||
'Product count is hidden.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
}
|
||||
checked={ hasCount }
|
||||
onChange={ () =>
|
||||
setAttributes( { hasCount: ! hasCount } )
|
||||
}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Show hierarchy',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
help={
|
||||
isHierarchical
|
||||
? __(
|
||||
'Hierarchy is visible.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
: __(
|
||||
'Hierarchy is hidden.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
}
|
||||
checked={ isHierarchical }
|
||||
onChange={ () =>
|
||||
setAttributes( {
|
||||
isHierarchical: ! isHierarchical,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Show empty categories',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
help={
|
||||
hasEmpty
|
||||
? __(
|
||||
'Empty categories are visible.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
: __(
|
||||
'Empty categories are hidden.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
}
|
||||
checked={ hasEmpty }
|
||||
onChange={ () =>
|
||||
setAttributes( { hasEmpty: ! hasEmpty } )
|
||||
}
|
||||
/>
|
||||
</PanelBody>
|
||||
<PanelBody
|
||||
title={ __(
|
||||
'List Settings',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
initialOpen
|
||||
>
|
||||
<ToggleButtonControl
|
||||
label={ __(
|
||||
'Display style',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
value={ isDropdown ? 'dropdown' : 'list' }
|
||||
options={ [
|
||||
{
|
||||
label: __(
|
||||
'List',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
value: 'list',
|
||||
},
|
||||
{
|
||||
label: __(
|
||||
'Dropdown',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
value: 'dropdown',
|
||||
},
|
||||
] }
|
||||
onChange={ ( value ) =>
|
||||
setAttributes( {
|
||||
isDropdown: 'dropdown' === value,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
{ categories.length > 0 ? (
|
||||
<Block
|
||||
attributes={ attributes }
|
||||
categories={ categories }
|
||||
isPreview
|
||||
/>
|
||||
) : (
|
||||
<Placeholder
|
||||
className="wc-block-product-categories"
|
||||
icon={ <IconFolder /> }
|
||||
label={ __(
|
||||
'Product Categories List',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
>
|
||||
{ __(
|
||||
"This block shows product categories for your store. In order to preview this you'll first need to create a product and assign it to a category.",
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</Placeholder>
|
||||
) }
|
||||
</Fragment>
|
||||
);
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Block from './block.js';
|
||||
import getCategories from './get-categories';
|
||||
import renderFrontend from '../../utils/render-frontend.js';
|
||||
|
||||
const getProps = ( el ) => {
|
||||
const attributes = {
|
||||
hasCount: el.dataset.hasCount === 'true',
|
||||
hasEmpty: el.dataset.hasEmpty === 'true',
|
||||
isDropdown: el.dataset.isDropdown === 'true',
|
||||
isHierarchical: el.dataset.isHierarchical === 'true',
|
||||
};
|
||||
|
||||
return {
|
||||
attributes,
|
||||
categories: getCategories( attributes ),
|
||||
};
|
||||
};
|
||||
|
||||
renderFrontend( '.wp-block-woocommerce-product-categories', Block, getProps );
|
|
@ -1,19 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { PRODUCT_CATEGORIES } from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { buildTermsTree } from './hierarchy';
|
||||
|
||||
/**
|
||||
* Returns categories in tree form.
|
||||
*/
|
||||
export default function( { hasEmpty, isHierarchical } ) {
|
||||
const categories = PRODUCT_CATEGORIES.filter(
|
||||
( cat ) => hasEmpty || !! cat.count
|
||||
);
|
||||
return isHierarchical ? buildTermsTree( categories ) : categories;
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/**
|
||||
* Returns terms in a tree form.
|
||||
*
|
||||
* @param {Array} list Array of terms in flat format.
|
||||
*
|
||||
* @return {Array} Array of terms in tree format.
|
||||
*/
|
||||
export function buildTermsTree( list = [] ) {
|
||||
// Group terms by the parent ID.
|
||||
const termsByParent = list.reduce(
|
||||
( r, v, i, a, k = v.parent ) => (
|
||||
( r[ k ] || ( r[ k ] = [] ) ).push( v ), r
|
||||
),
|
||||
{}
|
||||
);
|
||||
|
||||
const fillWithChildren = ( terms ) => {
|
||||
return terms.map( ( term ) => {
|
||||
const children = termsByParent[ term.term_id ];
|
||||
delete termsByParent[ term.term_id ];
|
||||
return {
|
||||
...term,
|
||||
children:
|
||||
children && children.length
|
||||
? fillWithChildren( children )
|
||||
: [],
|
||||
};
|
||||
} );
|
||||
};
|
||||
|
||||
const tree = fillWithChildren( termsByParent[ '0' ] || [] );
|
||||
delete termsByParent[ '0' ];
|
||||
|
||||
Object.keys( termsByParent ).forEach( function( terms ) {
|
||||
tree.push( ...fillWithChildren( terms || [] ) );
|
||||
} );
|
||||
|
||||
return tree;
|
||||
}
|
|
@ -9,7 +9,7 @@ import { registerBlockType } from '@wordpress/blocks';
|
|||
*/
|
||||
import './editor.scss';
|
||||
import './style.scss';
|
||||
import edit from './edit.js';
|
||||
import Block from './block.js';
|
||||
import { IconFolder } from '@woocommerce/block-components/icons';
|
||||
|
||||
registerBlockType( 'woocommerce/product-categories', {
|
||||
|
@ -35,9 +35,6 @@ registerBlockType( 'woocommerce/product-categories', {
|
|||
hasCount: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
source: 'attribute',
|
||||
selector: 'div',
|
||||
attribute: 'data-has-count',
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -46,9 +43,6 @@ registerBlockType( 'woocommerce/product-categories', {
|
|||
hasEmpty: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
source: 'attribute',
|
||||
selector: 'div',
|
||||
attribute: 'data-has-empty',
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -57,9 +51,6 @@ registerBlockType( 'woocommerce/product-categories', {
|
|||
isDropdown: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
source: 'attribute',
|
||||
selector: 'div',
|
||||
attribute: 'data-is-dropdown',
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -68,53 +59,102 @@ registerBlockType( 'woocommerce/product-categories', {
|
|||
isHierarchical: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
source: 'attribute',
|
||||
selector: 'div',
|
||||
attribute: 'data-is-hierarchical',
|
||||
},
|
||||
},
|
||||
|
||||
edit,
|
||||
deprecated: [
|
||||
{
|
||||
// Deprecate HTML save method in favor of dynamic rendering.
|
||||
attributes: {
|
||||
hasCount: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
source: 'attribute',
|
||||
selector: 'div',
|
||||
attribute: 'data-has-count',
|
||||
},
|
||||
hasEmpty: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
source: 'attribute',
|
||||
selector: 'div',
|
||||
attribute: 'data-has-empty',
|
||||
},
|
||||
isDropdown: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
source: 'attribute',
|
||||
selector: 'div',
|
||||
attribute: 'data-is-dropdown',
|
||||
},
|
||||
isHierarchical: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
source: 'attribute',
|
||||
selector: 'div',
|
||||
attribute: 'data-is-hierarchical',
|
||||
},
|
||||
},
|
||||
migrate( attributes ) {
|
||||
return attributes;
|
||||
},
|
||||
save( props ) {
|
||||
const {
|
||||
hasCount,
|
||||
hasEmpty,
|
||||
isDropdown,
|
||||
isHierarchical,
|
||||
} = props.attributes;
|
||||
const data = {};
|
||||
if ( hasCount ) {
|
||||
data[ 'data-has-count' ] = true;
|
||||
}
|
||||
if ( hasEmpty ) {
|
||||
data[ 'data-has-empty' ] = true;
|
||||
}
|
||||
if ( isDropdown ) {
|
||||
data[ 'data-is-dropdown' ] = true;
|
||||
}
|
||||
if ( isHierarchical ) {
|
||||
data[ 'data-is-hierarchical' ] = true;
|
||||
}
|
||||
return (
|
||||
<div className="is-loading" { ...data }>
|
||||
{ isDropdown ? (
|
||||
<span
|
||||
aria-hidden
|
||||
className="wc-block-product-categories__placeholder"
|
||||
/>
|
||||
) : (
|
||||
<ul aria-hidden>
|
||||
<li>
|
||||
<span className="wc-block-product-categories__placeholder" />
|
||||
</li>
|
||||
<li>
|
||||
<span className="wc-block-product-categories__placeholder" />
|
||||
</li>
|
||||
<li>
|
||||
<span className="wc-block-product-categories__placeholder" />
|
||||
</li>
|
||||
</ul>
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
/**
|
||||
* Save the props to post content.
|
||||
* Renders and manages the block.
|
||||
*/
|
||||
save( { attributes } ) {
|
||||
const { hasCount, hasEmpty, isDropdown, isHierarchical } = attributes;
|
||||
const data = {};
|
||||
if ( hasCount ) {
|
||||
data[ 'data-has-count' ] = true;
|
||||
}
|
||||
if ( hasEmpty ) {
|
||||
data[ 'data-has-empty' ] = true;
|
||||
}
|
||||
if ( isDropdown ) {
|
||||
data[ 'data-is-dropdown' ] = true;
|
||||
}
|
||||
if ( isHierarchical ) {
|
||||
data[ 'data-is-hierarchical' ] = true;
|
||||
}
|
||||
return (
|
||||
<div className="is-loading" { ...data }>
|
||||
{ isDropdown ? (
|
||||
<span
|
||||
aria-hidden
|
||||
className="wc-block-product-categories__placeholder"
|
||||
/>
|
||||
) : (
|
||||
<ul aria-hidden>
|
||||
<li>
|
||||
<span className="wc-block-product-categories__placeholder" />
|
||||
</li>
|
||||
<li>
|
||||
<span className="wc-block-product-categories__placeholder" />
|
||||
</li>
|
||||
<li>
|
||||
<span className="wc-block-product-categories__placeholder" />
|
||||
</li>
|
||||
</ul>
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
edit( props ) {
|
||||
return <Block { ...props } />;
|
||||
},
|
||||
|
||||
/**
|
||||
* Save nothing; rendered by server.
|
||||
*/
|
||||
save() {
|
||||
return null;
|
||||
},
|
||||
} );
|
||||
|
|
|
@ -16,4 +16,3 @@ export const IS_LARGE_CATALOG = getSetting( 'isLargeCatalog' );
|
|||
export const LIMIT_TAGS = getSetting( 'limitTags' );
|
||||
export const HAS_TAGS = getSetting( 'hasTags', true );
|
||||
export const HOME_URL = getSetting( 'homeUrl ', '' );
|
||||
export const PRODUCT_CATEGORIES = getSetting( 'productCategories', [] );
|
||||
|
|
|
@ -237,8 +237,6 @@ const getFrontConfig = ( options = {} ) => {
|
|||
};
|
||||
return {
|
||||
entry: {
|
||||
'product-categories':
|
||||
'./assets/js/blocks/product-categories/frontend.js',
|
||||
reviews: './assets/js/blocks/reviews/frontend.js',
|
||||
},
|
||||
output: {
|
||||
|
|
|
@ -84,18 +84,8 @@ class Assets {
|
|||
* @since $VID:$ returned merged data along with incoming $settings
|
||||
*/
|
||||
public static function get_wc_block_data( $settings ) {
|
||||
$tag_count = wp_count_terms( 'product_tag' );
|
||||
$product_counts = wp_count_posts( 'product' );
|
||||
$product_categories = get_terms(
|
||||
'product_cat',
|
||||
array(
|
||||
'hide_empty' => false,
|
||||
'pad_counts' => true,
|
||||
)
|
||||
);
|
||||
foreach ( $product_categories as &$category ) {
|
||||
$category->link = get_term_link( $category->term_id, 'product_cat' );
|
||||
}
|
||||
$tag_count = wp_count_terms( 'product_tag' );
|
||||
$product_counts = wp_count_posts( 'product' );
|
||||
|
||||
// Global settings used in each block.
|
||||
return array_merge(
|
||||
|
@ -114,7 +104,6 @@ class Assets {
|
|||
'isLargeCatalog' => $product_counts->publish > 200,
|
||||
'limitTags' => $tag_count > 100,
|
||||
'hasTags' => $tag_count > 0,
|
||||
'productCategories' => $product_categories,
|
||||
'homeUrl' => esc_url( home_url( '/' ) ),
|
||||
'showAvatars' => '1' === get_option( 'show_avatars' ),
|
||||
'enableReviewRating' => 'yes' === get_option( 'woocommerce_enable_review_rating' ),
|
||||
|
|
|
@ -12,7 +12,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
/**
|
||||
* ProductCategories class.
|
||||
*/
|
||||
class ProductCategories extends AbstractBlock {
|
||||
class ProductCategories extends AbstractDynamicBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
|
@ -22,31 +22,267 @@ class ProductCategories extends AbstractBlock {
|
|||
protected $block_name = 'product-categories';
|
||||
|
||||
/**
|
||||
* Registers the block type with WordPress.
|
||||
* Default attribute values, should match what's set in JS `registerBlockType`.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public function register_block_type() {
|
||||
register_block_type(
|
||||
$this->namespace . '/' . $this->block_name,
|
||||
protected $defaults = array(
|
||||
'hasCount' => true,
|
||||
'hasEmpty' => false,
|
||||
'isDropdown' => false,
|
||||
'isHierarchical' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Get block attributes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_attributes() {
|
||||
return array_merge(
|
||||
parent::get_attributes(),
|
||||
array(
|
||||
'render_callback' => array( $this, 'render' ),
|
||||
'editor_script' => 'wc-' . $this->block_name,
|
||||
'editor_style' => 'wc-block-editor',
|
||||
'style' => 'wc-block-style',
|
||||
'script' => 'wc-' . $this->block_name . '-frontend',
|
||||
'className' => $this->get_schema_string(),
|
||||
'hasCount' => $this->get_schema_boolean( true ),
|
||||
'hasEmpty' => $this->get_schema_boolean( false ),
|
||||
'isDropdown' => $this->get_schema_boolean( false ),
|
||||
'isHierarchical' => $this->get_schema_boolean( true ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append frontend scripts when rendering the Product Categories List block.
|
||||
* Render the Product Categories List block.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param string $content Block content. Default empty string.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
public function render( $attributes = array(), $content = '' ) {
|
||||
\Automattic\WooCommerce\Blocks\Assets::register_block_script( $this->block_name . '-frontend' );
|
||||
$uid = uniqid( 'product-categories-' );
|
||||
$categories = $this->get_categories( $attributes );
|
||||
|
||||
return $content;
|
||||
if ( ! $categories ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( ! empty( $content ) ) {
|
||||
// Deal with legacy attributes (before this was an SSR block) that differ from defaults.
|
||||
if ( strstr( $content, 'data-has-count="false"' ) ) {
|
||||
$attributes['hasCount'] = false;
|
||||
}
|
||||
if ( strstr( $content, 'data-is-dropdown="true"' ) ) {
|
||||
$attributes['isDropdown'] = true;
|
||||
}
|
||||
if ( strstr( $content, 'data-is-hierarchical="false"' ) ) {
|
||||
$attributes['isHierarchical'] = false;
|
||||
}
|
||||
if ( strstr( $content, 'data-has-empty="true"' ) ) {
|
||||
$attributes['hasEmpty'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$output = '<div class="wc-block-product-categories ' . esc_attr( $attributes['className'] ) . ' ' . ( $attributes['isDropdown'] ? 'is-dropdown' : 'is-list' ) . '">';
|
||||
$output .= ! empty( $attributes['isDropdown'] ) ? $this->renderDropdown( $categories, $attributes, $uid ) : $this->renderList( $categories, $attributes, $uid );
|
||||
$output .= '</div>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get categories (terms) from the db.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_categories( $attributes ) {
|
||||
$hierarchical = wc_string_to_bool( $attributes['isHierarchical'] );
|
||||
$categories = get_terms(
|
||||
'product_cat',
|
||||
[
|
||||
'hide_empty' => ! $attributes['hasEmpty'],
|
||||
'pad_counts' => true,
|
||||
'hierarchical' => true,
|
||||
]
|
||||
);
|
||||
|
||||
if ( ! $categories ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $hierarchical ? $this->build_category_tree( $categories ) : $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build hierarchical tree of categories.
|
||||
*
|
||||
* @param array $categories List of terms.
|
||||
* @return array
|
||||
*/
|
||||
protected function build_category_tree( $categories ) {
|
||||
$categories_by_parent = [];
|
||||
|
||||
foreach ( $categories as $category ) {
|
||||
if ( ! isset( $categories_by_parent[ 'cat-' . $category->parent ] ) ) {
|
||||
$categories_by_parent[ 'cat-' . $category->parent ] = [];
|
||||
}
|
||||
$categories_by_parent[ 'cat-' . $category->parent ][] = $category;
|
||||
}
|
||||
|
||||
$tree = $categories_by_parent['cat-0'];
|
||||
unset( $categories_by_parent['cat-0'] );
|
||||
|
||||
foreach ( $tree as $category ) {
|
||||
if ( ! empty( $categories_by_parent[ 'cat-' . $category->term_id ] ) ) {
|
||||
$category->children = $this->fill_category_children( $categories_by_parent[ 'cat-' . $category->term_id ], $categories_by_parent );
|
||||
}
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build hierarchical tree of categories by appending children in the tree.
|
||||
*
|
||||
* @param array $categories List of terms.
|
||||
* @param array $categories_by_parent List of terms grouped by parent.
|
||||
* @return array
|
||||
*/
|
||||
protected function fill_category_children( $categories, $categories_by_parent ) {
|
||||
foreach ( $categories as $category ) {
|
||||
if ( ! empty( $categories_by_parent[ 'cat-' . $category->term_id ] ) ) {
|
||||
$category->children = $this->fill_category_children( $categories_by_parent[ 'cat-' . $category->term_id ], $categories_by_parent );
|
||||
}
|
||||
}
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the category list as a dropdown.
|
||||
*
|
||||
* @param array $categories List of terms.
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param int $uid Unique ID for the rendered block, used for HTML IDs.
|
||||
* @return string Rendered output.
|
||||
*/
|
||||
protected function renderDropdown( $categories, $attributes, $uid ) {
|
||||
$output = '
|
||||
<div class="wc-block-product-categories__dropdown">
|
||||
<label
|
||||
class="screen-reader-text"
|
||||
for="' . esc_attr( $uid ) . '-select"
|
||||
>
|
||||
' . esc_html__( 'Select a category', 'woo-gutenberg-products-block' ) . '
|
||||
</label>
|
||||
<select id="' . esc_attr( $uid ) . '-select">
|
||||
<option value="false" hidden>
|
||||
' . esc_html__( 'Select a category', 'woo-gutenberg-products-block' ) . '
|
||||
</option>
|
||||
' . $this->renderDropdownOptions( $categories, $attributes, $uid ) . '
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="wc-block-product-categories__button"
|
||||
aria-label="' . esc_html__( 'Go to category', 'woo-gutenberg-products-block' ) . '"
|
||||
onclick="const url = document.getElementById( \'' . esc_attr( $uid ) . '-select\' ).value; if ( \'false\' !== url ) document.location.href = url;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
focusable="false"
|
||||
class="dashicon dashicons-arrow-right-alt2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M6 15l5-5-5-5 1-2 7 7-7 7z" />
|
||||
</svg>
|
||||
</button>
|
||||
';
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render dropdown options list.
|
||||
*
|
||||
* @param array $categories List of terms.
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param int $uid Unique ID for the rendered block, used for HTML IDs.
|
||||
* @param int $depth Current depth.
|
||||
* @return string Rendered output.
|
||||
*/
|
||||
protected function renderDropdownOptions( $categories, $attributes, $uid, $depth = 0 ) {
|
||||
$output = '';
|
||||
|
||||
foreach ( $categories as $category ) {
|
||||
$output .= '
|
||||
<option value="' . esc_attr( get_term_link( $category->term_id, 'product_cat' ) ) . '">
|
||||
' . str_repeat( '-', $depth ) . '
|
||||
' . esc_html( $category->name ) . '
|
||||
' . $this->getCount( $category, $attributes ) . '
|
||||
</option>
|
||||
' . ( ! empty( $category->children ) ? $this->renderDropdownOptions( $category->children, $attributes, $uid, $depth + 1 ) : '' ) . '
|
||||
';
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the category list as a list.
|
||||
*
|
||||
* @param array $categories List of terms.
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param int $uid Unique ID for the rendered block, used for HTML IDs.
|
||||
* @param int $depth Current depth.
|
||||
* @return string Rendered output.
|
||||
*/
|
||||
protected function renderList( $categories, $attributes, $uid, $depth = 0 ) {
|
||||
$output = '<ul class="wc-block-product-categories-list wc-block-product-categories-list--depth-' . absint( $depth ) . '">' . $this->renderListItems( $categories, $attributes, $uid, $depth ) . '</ul>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a list of terms.
|
||||
*
|
||||
* @param array $categories List of terms.
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param int $uid Unique ID for the rendered block, used for HTML IDs.
|
||||
* @param int $depth Current depth.
|
||||
* @return string Rendered output.
|
||||
*/
|
||||
protected function renderListItems( $categories, $attributes, $uid, $depth = 0 ) {
|
||||
$output = '';
|
||||
|
||||
foreach ( $categories as $category ) {
|
||||
$output .= '
|
||||
<li class="wc-block-product-categories-list-item">
|
||||
<a href="' . esc_attr( get_term_link( $category->term_id, 'product_cat' ) ) . '">
|
||||
' . esc_html( $category->name ) . '
|
||||
</a>
|
||||
' . $this->getCount( $category, $attributes ) . '
|
||||
' . ( ! empty( $category->children ) ? $this->renderList( $category->children, $attributes, $uid, $depth + 1 ) : '' ) . '
|
||||
</li>
|
||||
';
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the count, if displaying.
|
||||
*
|
||||
* @param object $category Term object.
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @return string
|
||||
*/
|
||||
protected function getCount( $category, $attributes ) {
|
||||
if ( empty( $attributes['hasCount'] ) ) {
|
||||
return '';
|
||||
}
|
||||
return $attributes['isDropdown'] ? '(' . absint( $category->count ) . ')' : '<span class="wc-block-product-categories-list-item-count">' . absint( $category->count ) . '</span>';
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue