50% discount on BricksLabs Pro Membership Lifetime Access. Use coupon: GOBRICKS

WooCommerce Product Loop Template in Bricks

default-loop-custoom-loop

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)

my-product-loop-structure
My simple product loop structure

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.

my-div-as-clickable-link
I set my div as clickable link too.

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.

dynamic-data-not-populate-in-builder-mode
Perfectly fine if the dynamic data looks weird like this.

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.
New file and folder in child theme.

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;
?>
How to check your template Id?

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?

template-applied-but-incorrect-data
Template applied but incorrect dynamic data.

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);
check-post-id-in-bricks-element
Troubleshooting in front end.

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:

  1. Before rendering our product loop template, save the original data in a temporary variable
  2. Change the \Bricks\Database:$page_data post_id related values to $product->get_id()
  3. Render our product loop template, it should now using WooCommerce product Id as element’s post Id
  4. 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~~!

bricks-custom-product-loop-template-completed
Great! The dynamic data working now!

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.

Change template status to draft to temporary stop using it.

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.

Ensure products element without any fields, so it will use over product loop template.

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*

Leave the first comment