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

Posts Grouped by Terms in Bricks

One of the most useful features of Bricks builder is the Query Loop and it is more powerful than what appears on the surface i.e., in the editor interface.

In this tutorial, we are going to explore nested queries in Bricks and delve into outputting WooCommerce products grouped by their top-level product categories as an example.

Another use-case would be showing posts grouped by their categories.

This would not have been possible without the excellent developer reference article named Bricks Builder Useful Functions And Tips by Jenn Lee.

The main hurdle is being able to pass the term being iterated in the loop to the inner product/post loop so that the Products element or Posts query loop (set to product post type) will be automatically filtered by or restricted to the corresponding product category.

We will cover two ways of doing this:

  1. Products element inside a Terms query loop
  2. Posts query loop inside a Terms query loop

Terms QL + Products approach can be used if you want a quick start and want to show the products without making any changes visually to the layout.

If you want more control i.e., what items to show in the “product card” and in what order and how, use the Terms QL + Posts QL approach instead.

Tested in Bricks 1.5 beta.

Table of Contents

The initial steps

Edit your main Shop page with Bricks.

Add a Section.

Add a Container or a Div inside the Section’s Container.

Toggle “Use query loop” on.

Click the Query (loop) icon and set the Type to Terms and Taxonomies to Product categories (Product).

Since we want to only loop through the parent product categories, set Parent to 0.

If there are any product categories that you would like to exclude, select them in the Terms (Exclude) control.

Add a Heading inside the query loop element (Div/Container).

Set its text to:

This will show the product category names.

Products element inside a Terms query loop

Add a Products element below the Heading.

Due to a bug in the current version when the Products element is added inside a query loop, the query loop’s dynamic tags will stop working. By the time you read this tutorial there are chances that Bricks team has fixed this and so if your product category names are still appearing, you can ignore what follows below to work around this bug.

Fixing the disappearing product category name

Let’s replace the heading text with the term name using the bricks/element/settings filter.

Add the following in child theme’s functions.php or a code snippets plugin like WPCodeBox:

add_filter( 'bricks/element/settings', function( $settings, $element ) {
	// ensure that this is the Heading element inside our Terms query loop.
	if ( $element->id !== 'wqdbnj' ) return $settings;
 
	$looping_query_id = \Bricks\Query::is_any_looping();
	$looping_object_id = \Bricks\Query::get_loop_object_id( $looping_query_id );
	$looping_object_type = \Bricks\Query::get_loop_object_type( $looping_query_id );

	if ( $looping_object_id > 0 && $looping_object_type === 'term' ) {
		// get the queried object.
		$term = \Bricks\Query::get_loop_object( $looping_query_id );

		// set the heading text to the term name.
		$settings['text'] = $term->name;
	}
	return $settings;
}, 10, 2 );

Replace wqdbnj with the Bricks ID of your Heading element.

Now if you check on the frontend and in the Bricks editor (after reloading the canvas), the missing product category names should re-appear.

At this stage, all the products will be output for each product category.

Setting the product category for products

There are two ways in which we can restrict the products to the current product category in the loop.

Method 1: bricks/element/settings filter

// Set the product category of the Products element when it is looping to its parent term.
add_filter( 'bricks/element/settings', function( $settings, $element ) {
	// ensure that this is your Products element.
	if ( $element->id !== 'jymfgh' ) return $settings;

	// get the nearest or current looping query's ID.
	// since the Products element's query runs for each iteration of the parent Terms query loop, this will be the query ID of Terms query loop.
	$looping_query_id = \Bricks\Query::is_any_looping();

	// get the parent object (product_cat taxonomy term in this case) ID.
	$looping_object_id = $looping_query_id ? \Bricks\Query::get_loop_object_id( $looping_query_id ) : 0;

	// get the parent object type (term in this case).
	$looping_object_type = \Bricks\Query::get_loop_object_type( $looping_query_id );
	
	// print( '<pre>' . print_r( $settings, true ) . '</pre>' );
 
	if( $looping_object_id > 0 && $looping_object_type === 'term' ) {
		$settings['categories'] = [$looping_object_id];
	} 
 
	return $settings;
}, 10, 2 );

Replace jymfgh with the Bricks ID of the Products element.

OR

Method 2: bricks/posts/query_vars filter

add_filter( 'bricks/posts/query_vars', function( $query_vars, $settings, $element_id ) {
	// ensure that this is your Products element.
	if ( $element_id !== 'jymfgh' ) {
		return $query_vars;
	}
	
	// get the nearest or current looping query's ID.
	// since the Products element's query runs for each iteration of the parent Terms query loop, this will be the query ID of Terms query loop.
	$looping_query_id = \Bricks\Query::is_any_looping();

	// get the term ID.
	$looping_object_id = $looping_query_id ? \Bricks\Query::get_loop_object_id( $looping_query_id ) : 0;

	// get the object type.
	$looping_object_type = \Bricks\Query::get_loop_object_type( $looping_query_id );
 
	if ( $looping_object_id > 0 && $looping_object_type === 'term' ) {
		$query_vars['tax_query'] = array(
			array(
				'taxonomy' => 'product_cat',
				'terms' => $looping_object_id,
				'include_children' => false,
			)
		);
	}

	return $query_vars;
}, 10, 3 );

Replace jymfgh with the Bricks ID of the Products element.

Posts query loop inside a Terms query loop

Add a Div under the Heading. This is going to be the parent of the query loop item.

Add a Div inside the above Div.

Click the Query (loop) icon.

Set Post type to Products.

Add your desired elements inside the Div like Product title, Product price.

In this setup, we have the outer query loop of terms (product categories in this case) and the inner query loop of posts (products in this case).

At this stage, all the products will be output for each product category.

Setting the product category for products

There are two ways in which we can restrict the products to the current product category in the loop.

Method 1: bricks/posts/query_vars filter

Add the following in child theme’s functions.php or a code snippets plugin like WPCodeBox:

// Set the product category of the inner query loop element when it is looping to its outer query loop's term.
add_filter( 'bricks/posts/query_vars', function( $query_vars, $settings, $element_id ) {
	// ensure that this is your query loop element.
	if ( $element_id !== 'zvpbvk' ) {
		return $query_vars;
	}

	// get the nearest or current looping query's ID.
	// since the inner query loop element's query runs for each iteration of the outer Terms query loop, this will be the query ID of Terms query loop.
	$looping_query_id = \Bricks\Query::is_any_looping();

	// get the term ID.
	$looping_object_id = $looping_query_id ? \Bricks\Query::get_loop_object_id( $looping_query_id ) : 0;

	// get the object type.
	$looping_object_type = \Bricks\Query::get_loop_object_type( $looping_query_id );
 
	if ( $looping_object_id > 0 && $looping_object_type === 'term' ) {
		$query_vars['tax_query'] = array(
			array(
				'taxonomy' => 'product_cat',
				'terms' => $looping_object_id,
				'include_children' => false,
			)
		);
	}

	return $query_vars;
}, 10, 3 );

Replace zvpbvk with the Bricks ID of your inner query loop element.

OR

Method 2: Using a function to get outer loop’s query ID

// Function to get looping parent query ID by level.
// $level = 1 means the parent query of the current query.
function itchy_get_bricks_looping_parent_query_id_by_level( $level = 1 ) {
	// ensure the global $bricks_loop_query variable is in scope.
	// $bricks_loop_query stores all executed query loops (not just manual Query Loops, but also other loops like from the Posts element) on a page.
	global $bricks_loop_query;

	// if there are no query loops on the page or if $level is less than 1, return false.
	if ( empty( $bricks_loop_query ) || $level < 1 ) {
		return false;
	}

	// store the current looping query ID in a variable.
	// this will be for the inner query.
	$current_query_id = \Bricks\Query::is_any_looping();
	
	// if we are not looping, return false.
	if ( ! $current_query_id ) { 
		return false;
	}
	
	// if the current loop's query ID is not present in the global $bricks_loop_query array as one of its keys, return false.
	if ( ! isset( $bricks_loop_query[ $current_query_id ] ) ) {
		return false;
	}

	// array_keys( $bricks_loop_query ) returns an array of all the keys in the $bricks_loop_query array.
	// this array will have the top-most query's ID first, then the one below that and so on until the current element's query ID.
	// by reversing it, $query_ids will be an array of the current query's ID first and then its parent query's ID and so on.
	$query_ids = array_reverse( array_keys( $bricks_loop_query ) );

	// if the supplied level (1 by default i.e., containing the query ID of the immediate parent query) is not present in the $query_ids array as a key, return false.
	if ( ! isset( $query_ids[ $level ] )) {
		return false;
	}

	// if the immediate parent query is looping, returns its query ID.
	if ( $bricks_loop_query[ $query_ids[ $level ] ]->is_looping ) {
		return $query_ids[ $level ];
	}

	return false;
}

// Function to get parent query loop's term ID.
function bl_get_parent_term_id() {
	if ( function_exists( 'itchy_get_bricks_looping_parent_query_id_by_level' ) ) {
		return \Bricks\Query::get_loop_object_id( itchy_get_bricks_looping_parent_query_id_by_level() );
	}

	return false;
}

The top function is by Jenn Lee with my notes added as comments.

The bottom function uses the function above it to return the term ID from the outer loop iteration.

Set up a taxonomy query in the products query loop like so:

Terms:

Conclusion

Being able to visually design nested query loops is an incredibly powerful feature that is possible in Bricks. With the code snippets mentioned in this article, we can build complex requirements like showing nurses working for doctors in all the hospitals as another example relatively easily.

Leave the first comment