From 7df7e87f1348a1c9d44d793b0a53d6a597a43d43 Mon Sep 17 00:00:00 2001 From: Manish Menaria Date: Fri, 11 Aug 2023 11:37:55 +0530 Subject: [PATCH] Product Collection: Use Interactivity API for pagination (#10361) * Update router to hydrate only interactive regions * Rename link directive to navigation-link * Add navigation directives to Query and Pagination blocks * Enable the Interactivity API by default * Remove client-side navigation meta tag * Cache initial regions * Move data-wc-interactive from query to query-pagination * Add woo prefix to navigation id * Add keys and move wc-interactive back to the query block * Reuse root fragments for each interactive region * Fix navigation-id retrieval * Introduce interactivity to Product Collection block and navigation enhancements This commit brings significant improvements to the ProductCollection block. 1. A new property `parsed_block` is added to the class to hold the block with its attributes before it gets rendered. This allows for more complex manipulations of the block and its attributes. 2. Interactivity has been added to the product collection block. The block is marked as an interactive region so it can be updated during client-side navigation. The `add_navigation_id_directive` method is responsible for adding this functionality. This ensures better UX as users navigate through the products. 3. Navigation links inside the Query Pagination block are also made interactive. The `add_navigation_link_directives` function is responsible for this. All anchor tags in the pagination are given the `data-wc-navigation-link` attribute with relevant navigation payload. This includes prefetching and scroll behavior. The pagination links are given unique keys for 'previous' and 'next' navigation. 4. The `render_block` filter hook has been used to add these functionalities to the product collection and query pagination blocks during render. * Disable scroll --------- Co-authored-by: David Arenas --- src/BlockTypes/ProductCollection.php | 90 ++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/BlockTypes/ProductCollection.php b/src/BlockTypes/ProductCollection.php index 4bd7231a7b9..88b2c8289a8 100644 --- a/src/BlockTypes/ProductCollection.php +++ b/src/BlockTypes/ProductCollection.php @@ -16,6 +16,13 @@ class ProductCollection extends AbstractBlock { */ protected $block_name = 'product-collection'; + /** + * The Block with its attributes before it gets rendered + * + * @var array + */ + protected $parsed_block; + /** * All query args from WP_Query. * @@ -84,6 +91,87 @@ protected function initialize() { // Extend allowed `collection_params` for the REST API. add_filter( 'rest_product_collection_params', array( $this, 'extend_rest_query_allowed_params' ), 10, 1 ); + + // Interactivity API: Add navigation directives to the product collection block. + add_filter( 'render_block_woocommerce/product-collection', array( $this, 'add_navigation_id_directive' ), 10, 3 ); + add_filter( 'render_block_core/query-pagination', array( $this, 'add_navigation_link_directives' ), 10, 3 ); + } + + /** + * Mark the Product Collection as an interactive region so it can be updated + * during client-side navigation. + * + * @param string $block_content The block content. + * @param array $block The full block, including name and attributes. + * @param \WP_Block $instance The block instance. + */ + public function add_navigation_id_directive( $block_content, $block, $instance ) { + $is_product_collection_block = $block['attrs']['query']['isProductCollectionBlock'] ?? false; + if ( $is_product_collection_block ) { + // Enqueue the Interactivity API runtime. + wp_enqueue_script( 'wc-interactivity' ); + + $p = new \WP_HTML_Tag_Processor( $block_content ); + + // Add `data-wc-navigation-id to the query block. + if ( $p->next_tag( array( 'class_name' => 'wp-block-woocommerce-product-collection' ) ) ) { + $p->set_attribute( + 'data-wc-navigation-id', + 'wc-product-collection-' . $this->parsed_block['attrs']['queryId'] + ); + $p->set_attribute( 'data-wc-interactive', true ); + $block_content = $p->get_updated_html(); + } + } + + return $block_content; + } + + /** + * Add interactive links to all anchors inside the Query Pagination block. + * + * @param string $block_content The block content. + * @param array $block The full block, including name and attributes. + * @param \WP_Block $instance The block instance. + */ + public function add_navigation_link_directives( $block_content, $block, $instance ) { + $is_product_collection_block = $instance->context['query']['isProductCollectionBlock'] ?? false; + + if ( + $is_product_collection_block && + $instance->context['queryId'] === $this->parsed_block['attrs']['queryId'] + ) { + $p = new \WP_HTML_Tag_Processor( $block_content ); + $p->next_tag( array( 'class_name' => 'wp-block-query-pagination' ) ); + + while ( $p->next_tag( 'a' ) ) { + $class_attr = $p->get_attribute( 'class' ); + $class_list = preg_split( '/\s+/', $class_attr ); + + $is_previous = in_array( 'wp-block-query-pagination-previous', $class_list, true ); + $is_next = in_array( 'wp-block-query-pagination-next', $class_list, true ); + $is_previous_or_next = $is_previous || $is_next; + + $navigation_link_payload = array( + 'prefetch' => $is_previous_or_next, + 'scroll' => false, + ); + + $p->set_attribute( + 'data-wc-navigation-link', + wp_json_encode( $navigation_link_payload ) + ); + + if ( $is_previous ) { + $p->set_attribute( 'key', 'pagination-previous' ); + } elseif ( $is_next ) { + $p->set_attribute( 'key', 'pagination-next' ); + } + } + $block_content = $p->get_updated_html(); + } + + return $block_content; } /** @@ -150,6 +238,8 @@ public function add_support_for_filter_blocks( $pre_render, $parsed_block ) { return; } + $this->parsed_block = $parsed_block; + $this->asset_data_registry->add( 'hasFilterableProducts', true, true ); /** * It enables the page to refresh when a filter is applied, ensuring that the product collection block,