Agreed that the query loop in Bricks is really powerful and handy, but no point duplicating my custom WooCommerce product loop into each page manually right? Sometimes you design your WooCommerce great in Bricks, but the products element still uses the default Woo style and you may spend time redesigning that.
Why don’t we just create 1 standard product loop template design, and set it to override the default WooCommerce product loop? Let me guide you in this tutorial. As always, I am not just giving you the final working code, I will explain the problems you might encounter along the tutorial too, prepare a cup of coffee and hope you like this.
**Please note that once you decided to override the WooCommerce product loop, some WooCommerce hooks wouldn’t be able to use anymore. But who cares? I just want to control it by my Bricks template**
**Ignore my bad template design :)**
Tutorial Environment Setup
- Bricks theme v1.5 beta
- WordPress 6.0.1
- PHP 7.4
- Open LiteSpeed Server
- All custom codes in this tutorial place in child theme
functions.php
, and also child theme woocommerce/content-product.php (will be covered later)
Step 1: Create A Custom Product Loop Design
This is easy – create a new section type template, and design as you like. Of course the data should be dynamic. Ex.: if you wish to show the product price, just use a basic text element and set {woo_product_price}
as the content, use an image element and choose featured image as the source etc. (Find a suitable dynamic data from the little lightning icon)
I did set my div as a clickable link as well, so visitor will be clicking this whole block and navigate to the single product page later.
No worries if the content not populated in the builder mode and this is fine because Bricks does not know how to populate the dynamic data yet.
Step 2: Create New PHP File In Child Theme
To replace the default WooCommerce product loop, just use the overriding template mechanism by WooCommerce. (Reference)
- Create a new folder woocommerce in your child theme. (You should use child theme, you can download from Bricks Account page)
- Create a new file content-product.php in the woocommerce folder.
Copy below codes into the newly created content-product.php file. Remember to save~
<?php
defined( 'ABSPATH' ) || exit;
global $product;
// Ensure visibility.
if ( empty( $product ) || ! $product->is_visible() ) {
return;
}
// Replace 856 with your product loop template ID.
$template_id = 856;
// Just to check if the template exists
$template_data = \Bricks\Database::get_data( $template_id, 'content' );
$template_post_status = get_post_status( $template_id );
// Only use our template if the template is published and exists.
if ( $template_data && $template_post_status === 'publish' ) :
$ori['post_id'] = \Bricks\Database::$page_data['post_id'];
$ori['original_post_id']= \Bricks\Database::$page_data['original_post_id'];
$ori['preview_or_post_id'] = \Bricks\Database::$page_data['preview_or_post_id'];
do_action( 'itchy/template_output/before', $template_id, $product->ID, $ori );
// This is the product loop template.
echo do_shortcode( '[bricks_template id="' . $template_id . '"]' );
do_action( 'itchy/template_output/after', $template_id, $product->ID, $ori );
else:
// Below code is from WooCommerce default template
?>
<li <?php wc_product_class( '', $product ); ?>>
<?php
/**
* Hook: woocommerce_before_shop_loop_item.
*
* @hooked woocommerce_template_loop_product_link_open - 10
*/
do_action( 'woocommerce_before_shop_loop_item' );
/**
* Hook: woocommerce_before_shop_loop_item_title.
*
* @hooked woocommerce_show_product_loop_sale_flash - 10
* @hooked woocommerce_template_loop_product_thumbnail - 10
*/
do_action( 'woocommerce_before_shop_loop_item_title' );
/**
* Hook: woocommerce_shop_loop_item_title.
*
* @hooked woocommerce_template_loop_product_title - 10
*/
do_action( 'woocommerce_shop_loop_item_title' );
/**
* Hook: woocommerce_after_shop_loop_item_title.
*
* @hooked woocommerce_template_loop_rating - 5
* @hooked woocommerce_template_loop_price - 10
*/
do_action( 'woocommerce_after_shop_loop_item_title' );
/**
* Hook: woocommerce_after_shop_loop_item.
*
* @hooked woocommerce_template_loop_product_link_close - 5
* @hooked woocommerce_template_loop_add_to_cart - 10
*/
do_action( 'woocommerce_after_shop_loop_item' );
?>
</li>
<?php
endif;
?>
Remember to replace the $template_id
to yours. For now, please ignore those $ori
array, do_action
things. Will be explained later. The “main character” in above codes is the do_shortcode( '[bricks_template id="' . $template_id . '"]' )
. This simply tells Bricks to display our template via shortcode.
Let’s check our WooCommerce Shop page or anywhere which using WooCommerce product loop like Related Products, Upsell etc.
But hey!! Why they show like this??! Jenn, are you kidding me?
The Bricks template did apply but how about the image, product title, and price?
Let’s fix this up!
Step 3: Understand The Problem
Well, what’s going on actually? The dynamic data is unable to populate properly because the Bricks elements are using the wrong post Id in our template while executing the dynamic functions. We can identify this by looking at the Heading element rendering “Shop” as the post title instead of each product’s title.
To prove this, we can use bricks/element/settings
filter hook to test. Copy below code into functions.php, change the $element->id
to your own element Id.
add_filter( 'bricks/element/settings', function( $settings, $element ) {
// My heading element Id is cafvof, only target this element
if ( $element->id !== 'cafvof' ) return $settings;
// Let's check what $post_id is used for populating dynamic data here
var_dump( $element->post_id );
// Let's check what is the global $product ID too
global $product;
var_dump( $product->get_id() );
return $settings;
}, 10, 2);
Look at that, as you can see, $element->post_id
in each product loop always showing 171, which is my Shop page’s post Id. Now you understand why it’s not displaying product’s featured image, product’s prices? Yes, because shop page doesn’t has featured image and no product price at all!
Is this a bug? Nope, Bricks working correctly, imagine if you are currently designing and using Bricks element in a page with post_id = 100, then each element in this page should use 100 as the post_id when executing dynamic data in the background. Of course Bricks will dynamically change the $element->post_id
when necessary, like in a query loop, AJAX or REST call etc.
Hence, we need to come out some code to dynamically change the $element->post_id
in the product loop too.
Step 4: Dynamically Change The Element’s Post Id
If you read my Bricks Builder Useful Functions & Tips article in my personal blog before, I highlighted that we could use bricks/builder/data_post_id
to change the Bricks element’s post ID. I also suggested other ways to change the post ID in that article too.
The important array keys and values in \Bricks\Database:$page_data
are post_id, original_post_id and preview_or_post_id
So, my plan will be like this:
- Before rendering our product loop template, save the original data in a temporary variable
- Change the \Bricks\Database:$page_data post_id related values to $product->get_id()
- Render our product loop template, it should now using WooCommerce product Id as element’s post Id
- Once finished 1 single loop, restore the original data back to \Bricks\Database:$page_data
You might ask why I need to restore the original data as next product loop should already overwritten it. Hmm… my answer is simple, I just like to clean my own shit every time before I leave.. And I hope my code will not causing other unknown side effects.
Now you understand why there are $ori array and do_action in content-product.php ?
// Previous codes from child-theme/woocommerce/content-product.php
// Only use our template if the template is published and exists.
if ( $template_data && $template_post_status === 'publish' ) :
// Save some original data into $ori array
$ori['post_id'] = \Bricks\Database::$page_data['post_id'];
$ori['original_post_id']= \Bricks\Database::$page_data['original_post_id'];
$ori['preview_or_post_id'] = \Bricks\Database::$page_data['preview_or_post_id'];
// Create a custom action hook and so I can use in functions.php
// I may use this same action hook in other woocommerce template in future too!
do_action( 'itchy/template_output/before', $template_id, $product->ID, $ori );
// This is the product loop template.
echo do_shortcode( '[bricks_template id="' . $template_id . '"]' );
// Another custom action hook to some cleanup
do_action( 'itchy/template_output/after', $template_id, $product->ID, $ori );
else:
Yup, added some comments, and hope you understand it.
Okay, now let’s hook on our custom action hook in functions.php
// 2) Change the \Bricks\Database:$page_data post_id related values to $product->get_id()
add_action( 'itchy/template_output/before', 'itchy_change_data_post_id', 10, 3 );
function itchy_change_data_post_id( $template_id, $id, $ori ) {
// Only target my product loop template, in future you might have more different templates to be hooked into
if ( $template_id !== 856 ) return;
// Use filter hook to change the $post_id used for populating dynamic data
// I use anonymous function to do this as it is easier to pass the $id and $ori to the function
// I use $id from content-product.php, which is the product ID
// I use priority 34 to make sure it is unique, this is a little tricky, I will use 34 priority to remove my filter hook later
add_filter( 'bricks/builder/data_post_id', function( $element_post_id ) use ( $id ) {
return $id;
}, 34 );
// Different elements use different way to set the post_id
// I feel more safe to manually set product ID here too, sorry if I am wrong, because there are many different logic in Bricks codebase
// Without doing this, some dynamic data will not be populated correctly too
\Bricks\Database::$page_data['original_post_id'] = $id;
\Bricks\Database::$page_data['preview_or_post_id'] = $id;
\Bricks\Database::$page_data['post_id'] = $id;
}
// 4) Once finished 1 single loop, restore the original data back to \Bricks\Database:$page_data
add_action( 'itchy/template_output/after', 'itchy_revert_data_post_id', 10, 3 );
function itchy_revert_data_post_id( $template_id, $id, $ori ) {
// Only target my product loop template, in future you might have more different templates to be hooked into
if ( $template_id !== 856 ) return;
// I want to remove my anonymous filter function
global $wp_filter;
// callbacks[34] is the anonymous function I added above, use unset to remove the filter
if ( isset( $wp_filter[ 'bricks/builder/data_post_id' ]->callbacks[34] ) ) {
unset( $wp_filter[ 'bricks/builder/data_post_id' ]->callbacks[34] );
}
// I feel more safe to manually set original values here
\Bricks\Database::$page_data['original_post_id'] = $ori['original_post_id'];
\Bricks\Database::$page_data['preview_or_post_id'] = $ori['preview_or_post_id'];
\Bricks\Database::$page_data['post_id'] = $ori['post_id'];
}
Please read through my comments.
Let’s check in front end now. Tada~~!
However, if you look at the $element->post_id
, you will noticed it’s showing empty string. Frankly speaking, I don’t understand why it’s showing empty string, hope someone can explain to me π
How To Disable My Product Loop Template?
Just change the template status to draft, then the default WooCommerce product loop should be using now. Or just remove or rename content-product.php
to maybe backup_content-product.php
.
Why Bricks Products Element Not Using My Custom Product Loop Template?
If you found that the products element not applying your custom product loop template, please check and ensure the fields are empty in Bricks products element. So it will fall back to use WooCommerce template, and so it triggers our template overriding mechanism.
Conclusion
Bricks is a very flexible and powerful theme, if you are able to do some tweaks and custom codes, majority features are possible! Appreciate Bricks’ team efforts and I would definitely like to read and understand their codes for more tutorials in future.
*Remember to remove the codes in Step 3 if you are in production site.*
*Remember to design your product loop responsive, suggest to use div, set align-self stretch*