Product Collection - New 'No Results' block with default UI (https://github.com/woocommerce/woocommerce-blocks/pull/11783)

* Add custom 'No Results' block for Product Collection

This commit introduces a new 'No Results' block, replacing the default core 'query-no-results' block within product collection block. The 'No Results' block provides a tailored experience for WooCommerce stores, displaying a custom message when no products are found in a query.

Key changes include:
- A new block type 'woocommerce/no-results' is registered with a complete configuration and content structure.
- Edit and save functions are implemented for the block, allowing for custom content and styles within the block editor and on the front end.
- Webpack entries are updated to include the new block in the build process.
- A PHP class NoResults is added for server-side rendering, which only displays the block when the product query returns no results, enhancing performance.
- The ProductCollectionUtils utility class is extended to support the new block's query needs.

The new block enriches the user experience by providing clearer communication when no products match the collection criteria and allows store owners to customize the message and presentation.

* Update description

* Rename 'no-results' block to 'product-collection-no-results'

Changes include:
- Updating the block name in `edit.tsx` within the `product-collection` directory.
- Modifying the block.json file in the `no-results` inner block to reflect the new name.
- Adjusting the className in `edit.tsx` for the `no-results` inner block.
- Altering the webpack entries in `webpack-entries.js` to recognize the new block name.
- Renaming the block in `NoResults.php` to align with this update.

This renaming aims to make the block's purpose more clear and to maintain a consistent naming scheme within our product collection blocks.

* Rename NoResults to ProductCollectionNoResults for clarity

This commit renames the `NoResults` class to `ProductCollectionNoResults`. The change aims to enhance clarity and specificity about the class's purpose, indicating that it specifically handles no-result scenarios within product collections.

Changes made:
- Renamed `NoResults.php` to `ProductCollectionNoResults.php`.
- Updated the class name from `NoResults` to `ProductCollectionNoResults` in the file.
- Modified the reference in `BlockTypesController.php` to use the new class name.

This renaming ensures better readability and understanding of the class's role in the context of product collections. The primary change is the renaming, with no significant alterations in the class functionality.

* Update No-Results Message Formatting in Product Collection Block

This commit simplifies the layout and message content for the 'No results found' message in the product collection block's no-results edit component. The changes include:

1. Removal of the full stop in the 'No results found' string for consistency.
2. Replacing the 'core/group' block with a 'core/paragraph' block.
3. Streamlining the message content to be more concise and integrated into fewer text blocks.
4. Direct links for 'clearing any filters' and navigating to the 'store's home' are now included in the same paragraph.

---------

Co-authored-by: Paulo Arromba <17236129+wavvves@users.noreply.github.com>
This commit is contained in:
Manish Menaria 2023-11-20 18:48:59 +05:30 committed by GitHub
parent ddcd32897b
commit 26550194d3
10 changed files with 312 additions and 15 deletions

View File

@ -75,7 +75,7 @@ export const INNER_BLOCKS_TEMPLATE: InnerBlockTemplate[] = [
}, },
}, },
], ],
[ 'core/query-no-results' ], [ 'woocommerce/product-collection-no-results' ],
]; ];
const Edit = ( props: BlockEditProps< ProductCollectionAttributes > ) => { const Edit = ( props: BlockEditProps< ProductCollectionAttributes > ) => {

View File

@ -0,0 +1,35 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "woocommerce/product-collection-no-results",
"title": "No results",
"version": "1.0.0",
"category": "woocommerce",
"description": "The contents of this block will display when there are no products found.",
"textdomain": "woo-gutenberg-products-block",
"keywords": [ "Product Collection" ],
"usesContext": [ "queryId", "query" ],
"ancestor": [ "woocommerce/product-collection" ],
"supports": {
"align": true,
"reusable": false,
"html": false,
"color": {
"gradients": true,
"link": true
},
"typography": {
"fontSize": true,
"lineHeight": true,
"__experimentalFontFamily": true,
"__experimentalFontWeight": true,
"__experimentalFontStyle": true,
"__experimentalTextTransform": true,
"__experimentalTextDecoration": true,
"__experimentalLetterSpacing": true,
"__experimentalDefaultControls": {
"fontSize": true
}
}
}
}

View File

@ -0,0 +1,65 @@
/**
* External dependencies
*/
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
import { Template } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
const TEMPLATE: Template[] = [
[
'core/group',
{
layout: {
type: 'flex',
orientation: 'vertical',
justifyContent: 'center',
flexWrap: 'wrap',
},
},
[
[
'core/paragraph',
{
textAlign: 'center',
fontSize: 'medium',
content: `<strong>${ __(
'No results found',
'woo-gutenberg-products-block'
) }</strong>`,
},
],
[
'core/paragraph',
{
content: `${ __(
'You can try',
'woo-gutenberg-products-block'
) } <a href="#" class="wc-link-clear-any-filters">${ __(
'clearing any filters',
'woo-gutenberg-products-block'
) }</a> ${ __(
'or head to our',
'woo-gutenberg-products-block'
) } <a href="#" class="wc-link-stores-home">${ __(
"store's home",
'woo-gutenberg-products-block'
) }</a>`,
},
],
],
],
];
const Edit = () => {
const blockProps = useBlockProps( {
className: 'wc-block-product-collection-no-results',
} );
return (
<div { ...blockProps }>
<InnerBlocks template={ TEMPLATE } />
</div>
);
};
export default Edit;

View File

@ -0,0 +1,21 @@
/**
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
import { loop as loopIcon } from '@wordpress/icons';
/**
* Internal dependencies
*/
import metadata from './block.json';
import edit from './edit';
import save from './save';
registerBlockType( metadata, {
icon: loopIcon,
supports: {
...metadata.supports,
},
edit,
save,
} );

View File

@ -0,0 +1,9 @@
/**
* External dependencies
*/
import { InnerBlocks } from '@wordpress/block-editor';
export default function NoResultsSave() {
// @ts-expect-error: `InnerBlocks.Content` is a component that is typed in WordPress core
return <InnerBlocks.Content />;
}

View File

@ -50,6 +50,9 @@ const blocks = {
'product-category': {}, 'product-category': {},
'product-categories': {}, 'product-categories': {},
'product-collection': {}, 'product-collection': {},
'product-collection-no-results': {
customDir: 'product-collection/inner-blocks/no-results',
},
'product-gallery': { 'product-gallery': {
isExperimental: true, isExperimental: true,
}, },

View File

@ -0,0 +1,141 @@
<?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\ProductCollectionUtils;
/**
* ProductCollectionNoResults class.
*/
class ProductCollectionNoResults extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-collection-no-results';
/**
* Render the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
*
* @return string | void Rendered block output.
*/
protected function render( $attributes, $content, $block ) {
$content = trim( $content );
if ( empty( $content ) ) {
return '';
}
$query = ProductCollectionUtils::prepare_and_execute_query( $block );
// If the query has products, don't render the block.
if ( $query->post_count > 0 ) {
return '';
}
// Update the anchor tag URLs.
$updated_html_content = $this->modify_anchor_tag_urls( trim( $content ) );
$wrapper_attributes = get_block_wrapper_attributes();
return sprintf(
'<div %1$s>%2$s</div>',
$wrapper_attributes,
$updated_html_content
);
}
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*/
protected function get_block_type_script( $key = null ) {
return null;
}
/**
* Set the URL attributes for "clearing any filters" and "Store's home" links.
*
* @param string $content Block content.
*/
protected function modify_anchor_tag_urls( $content ) {
$processor = new \WP_HTML_Tag_Processor( trim( $content ) );
// Set the URL attribute for the "clear any filters" link.
if ( $processor->next_tag(
array(
'tag_name' => 'a',
'class_name' => 'wc-link-clear-any-filters',
)
) ) {
$processor->set_attribute( 'href', $this->get_current_url_without_filters() );
}
// Set the URL attribute for the "Store's home" link.
if ( $processor->next_tag(
array(
'tag_name' => 'a',
'class_name' => 'wc-link-stores-home',
)
) ) {
$processor->set_attribute( 'href', home_url() );
}
return $processor->get_updated_html();
}
/**
* Get current URL without filter query parameters which will be used
* for the "clear any filters" link.
*/
protected function get_current_url_without_filters() {
$protocol = is_ssl() ? 'https' : 'http';
// Check the existence and sanitize HTTP_HOST and REQUEST_URI in the $_SERVER superglobal.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$http_host = isset( $_SERVER['HTTP_HOST'] ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : '';
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
// Sanitize the host and URI.
$http_host = sanitize_text_field( $http_host );
$request_uri = esc_url_raw( $request_uri );
// Construct the full URL.
$current_url = $protocol . '://' . $http_host . $request_uri;
// Parse the URL to extract the query string.
$parsed_url = wp_parse_url( $current_url );
$query_string = isset( $parsed_url['query'] ) ? $parsed_url['query'] : '';
// Convert the query string into an associative array.
parse_str( $query_string, $query_params );
// Remove the filter query parameters.
$params_to_remove = array( 'min_price', 'max_price', 'rating_filter', 'filter_', 'query_type_' );
foreach ( $query_params as $key => $value ) {
foreach ( $params_to_remove as $param ) {
if ( strpos( $key, $param ) === 0 ) {
unset( $query_params[ $key ] );
break;
}
}
}
// Rebuild the query string without the removed parameters.
$new_query_string = http_build_query( $query_params );
// Reconstruct the URL.
$new_url = $parsed_url['scheme'] . '://' . $parsed_url['host'];
$new_url .= isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
$new_url .= $new_query_string ? '?' . $new_query_string : '';
return $new_url;
}
}

View File

@ -2,8 +2,8 @@
namespace Automattic\WooCommerce\Blocks\BlockTypes; namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\ProductCollectionUtils;
use WP_Block; use WP_Block;
use WP_Query;
/** /**
* ProductTemplate class. * ProductTemplate class.
@ -36,19 +36,7 @@ class ProductTemplate extends AbstractBlock {
* @return string | void Rendered block output. * @return string | void Rendered block output.
*/ */
protected function render( $attributes, $content, $block ) { protected function render( $attributes, $content, $block ) {
$page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page'; $query = ProductCollectionUtils::prepare_and_execute_query( $block );
// phpcs:ignore WordPress.Security.NonceVerification
$page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ];
// Use global query if needed.
$use_global_query = ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] );
if ( $use_global_query ) {
global $wp_query;
$query = clone $wp_query;
} else {
$query_args = build_query_vars_from_query_block( $block, $page );
$query = new WP_Query( $query_args );
}
if ( ! $query->have_posts() ) { if ( ! $query->have_posts() ) {
return ''; return '';

View File

@ -239,6 +239,7 @@ final class BlockTypesController {
'ProductCategories', 'ProductCategories',
'ProductCategory', 'ProductCategory',
'ProductCollection', 'ProductCollection',
'ProductCollectionNoResults',
'ProductImage', 'ProductImage',
'ProductImageGallery', 'ProductImageGallery',
'ProductNew', 'ProductNew',

View File

@ -0,0 +1,34 @@
<?php
namespace Automattic\WooCommerce\Blocks\Utils;
use WP_Query;
/**
* Utility methods used for the Product Collection block.
* {@internal This class and its methods are not intended for public use.}
*/
class ProductCollectionUtils {
/**
* Prepare and execute a query for the Product Collection block.
* This method is used by the Product Collection block and the No Results block.
*
* @param WP_Block $block Block instance.
*/
public static function prepare_and_execute_query( $block ) {
$page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page';
// phpcs:ignore WordPress.Security.NonceVerification
$page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ];
// Use global query if needed.
$use_global_query = ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] );
if ( $use_global_query ) {
global $wp_query;
$query = clone $wp_query;
} else {
$query_args = build_query_vars_from_query_block( $block, $page );
$query = new WP_Query( $query_args );
}
return $query;
}
}