ARTICLES

Integrate JetEngine Query Builder in Bricks Query Loop (Non-official)

I like the way JetEngine managing all queries in a centralized place, I love it’s re-usability concept as well. Further more, I can also use JetEngine macros in the Query Builder which is really handy too. Let me do a simple integration while waiting Crocoblock team developing the official plugin for Bricks.

JetEngine Query Builder with macros
JetEngine Query Builder with macros
JetEngine Query Builder In Bricks Query Loop Final Result
JetEngine Query Builder In Bricks Query Loop Final Result

If you do not want to code by yourself, just download my simple plugin at the end of this tutorial.

PS: All Query types in JetEngine Query Builder are supported in the Bricks Query Loop. However, if you are not able to output certain dynamic tags by native Bricks, try use my custom dynamic tag (In Step 7) and indicate the property by yourself. If you found this tutorial or my plugin cannot fulfill your project, feel free to contact me. Otherwise please be patience and wait for Crocoblock team 🙂

Tutorial Environment Setup

  • Bricks theme 1.5.1
  • JetEngine 3.0.3.1
  • WordPress 6.0.2
  • PHP 7.4
  • Open LiteSpeed Server
  • All custom codes in this tutorial place in child theme functions.php

Step 1: Add New Query Type in Bricks Loop

To add a new query type option in Query Loop, we can simply use bricks/setup/control_options filter hook.

JE Query Builder as new query loop type.
JE Query Builder as new query loop type.
<?php

add_filter( 'bricks/setup/control_options', 'itchy_setup_je_query_controls');
function itchy_setup_je_query_controls( $control_options ) {

    // Add a new query loop type
    $control_options['queryTypes']['je_qb'] = esc_html__( 'JE Query Builder', 'bricks' );
    return $control_options;

}

Step 2: Add A New Option To Choose Query

Next, I will add a new dropdown option for all query loop supported elements. This is because I want to choose which query to be executed from JetEngine Query Builder while Bricks trigger the query. This way we can make the integration more dynamic and flexible.

Here, we will use bricks/elements/{$name}/controls filter hook to add new control. We will also use \Jet_Engine\Query_Builder\Manager::instance()->get_queries() function to get all queries defined in JetEngine Query Builder.

<?php

add_action( 'init', 'itchy_add_control_to_elements', 40 );
function itchy_add_control_to_elements() {

	// Only container, block and div element have query controls
	$elements = [ 'container', 'block', 'div' ];
	foreach ( $elements as $name ) {
		add_filter( "bricks/elements/{$name}/controls", 'itchy_add_je_controls', 40 );
	}

}

function itchy_add_je_controls( $controls ) {

	// je_qb_id will be my option key
	$je_control['je_qb_id'] = [
		'tab'         => 'content',
		'label'       => esc_html__( 'JE Queries', 'bricks' ),
		'type'        => 'select',
		'options'     => [],
		'placeholder' => esc_html__( 'Choose a query', 'bricks' ),
		'required'    => array(
			[ 'query.objectType', '=', 'je_qb' ],
			[ 'hasLoop', '!=', false ]
		),
		'rerender'    => true,
		'description' => esc_html__( 'Please create a query in JetEngine Query Builder First', 'bricks' ),
		'searchable'  => true,
		'multiple'    => false,
	];

	// Get the defined queries from JetEngine Query Builder
	$je_queries = \Jet_Engine\Query_Builder\Manager::instance()->get_queries();
	// Add the queries to the options
	foreach ( $je_queries as $id => $query ) {
		$je_control['je_qb_id']['options'][ $id ] = $query->name;
	}

	// Below 2 lines is just some php array functions to force my new control located after the query control
	$query_key_index = absint( array_search( 'query', array_keys( $controls ) ) );
	$new_controls    = array_slice( $controls, 0, $query_key_index + 1, true ) + $je_control + array_slice( $controls, $query_key_index + 1, null, true );

	return $new_controls;
}
  • line 3 – line 12 add the controls to the selected bricks elements.
  • line 23 – line 26 telling Bricks only show this select field if query loop type is JE Query Builder and Use query loop is checked. This “AND” type condition for required parameter was newly found by myself as it’s quite difficult to find an example from Bricks core elements. To target chosen queryTypes in query loop panel also spent me few hours to figure out it’s actually use query.objectType as the key. I am glad if this tiny tip help you.
  • I don’t like the new control always added at the bottom of the panel. So I use line 41 – line 42 to order my je_qb_id field after the Query field.
My new control for Bricks query loop.
My new control for Bricks query loop.

Step 3: Create A Query In JetEngine Query Builder

Stay calm, if your JE Queries dropdown is empty. Let’s create one in JetEngine Query Builder. I will use WC Product Query type in this tutorial.

My WC Product query setup: (Planned to execute this query in product category archive template)

  • Name: Current Category Products
  • Query Type: WC Product Query
  • General > Product Status: Publish
  • Tax Query > Taxonomy: Product Categories
  • Tax Query > Field: Term ID
  • Tax Query > Terms: Queried term (Choose from macros)
WC Product Query in JetEngine Query Builder

Step 4: Create A Bricks Product Archive Template

Create a WooCommerce – Product archive template in Bricks.

Choose the correct template type.
Choose the correct template type.

Just add a Heading element and a Div element to set query type as JE Query Builder, JE Queries as Current Category Product.

Query loop setting
Query loop setting

Inside the looping Div, I will add a Basic Text element with dummy text temporary.

Basic text element with dummy text inside the looping Div
Basic text element with dummy text inside the looping Div

Remember to set the template conditions.

Only use this template in Product Categories archive
Only use this template in Product Categories archive

Save the template, and navigate to one of your product category url. You will notice nothing happen currently. No worries, continue to next step.

Rest assure if nothing show in product category archive page
Rest assure if nothing show in product category archive page

Step 5: Apply Query Run Logic

Currently Bricks doesn’t know what query to run even if we selected the JetEngine query in query loop setting. To tell Bricks what to do on query loop, use bricks/query/run filter. To execute JetEngine query builder query, use setup_query() and get_items() after retrieved the correct query object instance.

<?php

add_filter( 'bricks/query/run', 'itchy_run_jeqb_query', 10, 2 );
function itchy_run_jeqb_query( $results, $query_obj ) {

	// Only target if query type set is je_qb
	if ( $query_obj->object_type !== 'je_qb' ) {
		return $results;
	}

	$settings = $query_obj->settings;
	$je_qb_id = absint( $settings['je_qb_id'] );

	// Return empty results if no query selected or Use Query is not checked
	if ( $je_qb_id === 0 || ! $settings['hasLoop'] ) {
		return [];
	}

	$query_builder = \Jet_Engine\Query_Builder\Manager::instance();

	// Get the query object from JetEngine based on the query id
	$je_query = $query_builder->get_query_by_id( $je_qb_id );

	// Return empty results if query not found in JetEngine Query Builder
	if ( ! $je_query ) {
		return [];
	}

	// Setup query args
	$je_query->setup_query();

	// Get the results
	return $je_query->get_items();

}

Read through my comment and understand what each line does.

Now refresh the product category archive page, do a check in WooCommerce backend. Yeah! the number of looping dummy text is correct! This prove that JetEngine query is running!

JetEngine query is running perfectly in Bricks query loop.
JetEngine query is running perfectly in Bricks query loop.

Step 6: Dynamic Data In Loop

Actually the difficult and complex part is how to make the data in the query loop presents correctly. Try set the Basic Text element data and view in frontend and builder. All data are wrong.

Preview in builder dynamic data wrong.
Preview in builder dynamic data wrong.
Dynamic data in frontend incorrect too.
Dynamic data in frontend incorrect too.

To fix this, we need to use bricks/query/loop_object filter change the global $post so those dynamic tags can work correctly.

<?php

add_filter( 'bricks/query/loop_object', 'itchy_set_jeqb_loop_object', 10, 3 );
function itchy_set_jeqb_loop_object( $loop_object, $loop_key, $query ) {
	if ( $query->object_type !== 'je_qb' ) {
		return $loop_object;
	}

	// $loop_object coming from the itchy_run_jeqb_query() function result
	// It could be in different format depending on the query type
	// Our main goal is to assign the correct post object to the $post global variable
	// So in the loop, when you use get_the_ID() or get_the_title() it will return the correct value

	global $post;

	// I only tested on JetEngine Posts Query, Terms Query, Comments Query and WC Products Query
	// I didn't set WP_Term condition because it's not related to the $post global variable
	if ( is_a( $loop_object, 'WP_Post' ) ) {
		$post = $loop_object;
	} elseif ( is_a( $loop_object, 'WC_Product' ) ) {
		// $post should be a WP_Post object
		$post = get_post( $loop_object->get_id() );
	} elseif ( is_a( $loop_object, 'WP_Comment' ) ) {
		// A comment should refer to a post, so I set the $post global variable to the comment's post
		// You might want to change this to $loop_object->comment_ID
		$post = get_post( $loop_object->comment_post_ID );
	}

	setup_postdata( $post ); // Honestly, this line might not be necessary

	// We still return the $loop_object so \Bricks\Query::get_loop_object() can use it
	return $loop_object;
}

This part is a little complicated, please read through my comment and kindly share with me if anything wrong.

With this filter applied, dynamic tags in frontend and builder should render correctly now.

Frontend dynamic data match with Woo products.
Frontend dynamic data match with Woo products.
Dynamic data in builder rendering well.
Dynamic data in builder rendering well.

Step 7: Create Custom Dynamic Tag (Bonus)

In previous step, I can say JetEngine Query Builder already integrated successfully. However, I wish to create my custom dynamic tag to improve my development speed in Bricks Builder.

Instead of relying on the dynamic tags provided by Bricks, I want to output the properties from query result by using my custom tag.

Many data can be used in my loop.
Many data can be used in my loop.

Based on above image query result example, imagine that I can use {itchy_extra:price} can output “11.05”, {itchy_extra:slug} can output “wordpress-pennant”, {itchy_extra:catalog_visibility} can output “visible” etc. This is pretty cool right?

Okay, let’s use bricks/dynamic_tags_list filter to create our own tag.

<?php

add_filter( 'bricks/dynamic_tags_list', 'add_itchy_tag_to_builder' );
function add_itchy_tag_to_builder( $tags ) {
	// Note that property is just a dummy tag, should be replaced with the actual property when using
	$tags[] = [
		'name'  => '{itchy_extra:property}',
		'label' => 'Itchy Object Property',
		'group' => 'Itchy Extra Data'
	];

	return $tags;
}

Refresh your builder, click on the thunder icon, check if the new tag appears. Add it into the Basic Text element.

Use my custom tag in Basic Text element for testing.
Use my custom tag in Basic Text element for testing.

Change the tag to { itchy_extra:status }, and you will noticed the rendered data still the same.

Change to itchy_extra:status
Change to itchy_extra:status

Basically we need to use bricks/dynamic_data/render_content, bricks/frontend/render_data and bricks/dynamic_data/render_tag filters to make the custom tag works.

Some complex code here:

<?php

add_filter( 'bricks/dynamic_data/render_content', 'render_itchy_tag', 10, 3 );
add_filter( 'bricks/frontend/render_data', 'render_itchy_tag', 10, 2 );
function render_itchy_tag( $content, $post, $context = 'text' ) {

	// Only look for content starts with {itchy_extra:
	if ( strpos( $content, '{itchy_extra:' ) === false ) {
		return $content;
	}

	// Regex to match itchy_extra: tag
	preg_match_all( '/{(itchy_extra:[^}]+)}/', $content, $matches );

	// Nothing grouped in the regex, return the original content
	if ( empty( $matches[0] ) ) {
		return $content;
	}

	// Get a list of tags to exclude from the Dynamic Data logic
	$exclude_tags = apply_filters( 'bricks/dynamic_data/exclude_tags', [] );

	foreach ( $matches[1] as $key => $match ) {
		$tag = $matches[0][ $key ];

		if ( in_array( $match, $exclude_tags ) ) {
			continue;
		}

		// Get the dynamic data value, $match is the tag name without the curly brackets
		$value   = get_itchy_tag_value( $match, $post, $context );

		// Replace the tag with the transformed value
		$content = str_replace( $tag, $value, $content );
	}

	return $content;
}

add_filter( 'bricks/dynamic_data/render_tag', 'get_itchy_tag_value', 10, 3 );
function get_itchy_tag_value( $tag, $post, $context = 'text' ) {

	// Only look for dynamic tag starts with itchy_extra:
	if ( strpos( $tag, 'itchy_extra:' ) === false ) {
		return $tag;
	}

	// Get the property name
	$property = str_replace( 'itchy_extra:', '', $tag );

	// Use Bricks function to get the correct object
	if ( \Bricks\Query::is_looping() ) {
		$queried_object = \Bricks\Query::get_loop_object();
	} else {
		$queried_object = \Bricks\Helpers::get_queried_object( $post->ID );
	}

	// If property is not found in the object, return a hint
	$value = $queried_object->{$property} ? $queried_object->{$property} : "Property '{$property}' not found in current object.";

	return $value;

}
  • Please note that $content in render_itchy_tag() could be a big chunk strings on html + text + some other dynamic tag entered by us in the builder. Line 7 – Line 18 playing important role, early return if it’s not related to our custom dynamic tag.
  • get_itchy_tag_value() is the actual function transform tag to value.
  • Note that I did not restrict the usage of itchy_extra: for JetEngine queries only, you can use this dynamic tag anywhere as long as you know what property to retrieve. However, the $queried_object in my code above might not perfect, you could improve it so the custom tag can be used in different places.
  • I only test on my own environment and my own scenario, be cautious when accessing the property, it might causes error and kindly feedback to improve the code.
Try the dynamic tag. Use whichever dynamic tag you like.
Try the dynamic tag. Use whichever dynamic tag you like.

With the new custom tag, I feel much more freedom and easy to get value from query result especially from JetEngine query builder (visually preview is a plus).

Download This Plugin

I bundled the code into this simple plugin (v1.0.0). Free to download and use before official integration release by Crocoblock.

Conclusion

If you are following my articles, you will noticed a discussion drawer section added. I am using the same way to retrieve the comments via JetEngine Query Builder.

Love Bricks so much, with this add-on plus the help of my Conditional Visibility plugin, a dynamic WordPress website can be easily created in Bricks Theme.

Welcome to use the discussion section and I will be very much appreciated if you can share your find out and tips with me.

Have fun and happy coding.

Action & Filter hooks used in this tutorial:

  • bricks/setup/control_options
  • bricks/elements/{$name}/controls
  • bricks/query/run
  • bricks/query/loop_object
  • bricks/dynamic_tags_list
  • bricks/dynamic_data/render_content
  • bricks/frontend/render_data
  • bricks/dynamic_data/render_tag
  • \Jet_Engine\Query_Builder\Manager::instance()->get_queries()
  • \Jet_Engine\Query_Builder\Manager::instance()->get_query_by_id()
  • \Jet_Engine\Query_Builder\Manager::instance()->get_query_by_id($id)->setup_query()
  • \Jet_Engine\Query_Builder\Manager::instance()->get_query_by_id($id)->get_items()
  • About Author

    I like to solve problems by coding. I like websites, web applications and everything viewable from browser. Knowledge sharing can grows my confidence. A simple "thank you" will make my day :)
    More about me

Subscribe For Notification

Get notification whenever new article published.

Discussion

COMMENT

Kudos! Well done. Works like a charm. Thanks so much for your efforts.

Reply
By Ken Sim (2 months ago)

You're most welcome.

By [email protected] (2 months ago)

Hi, thanks for this great feature. Is there any way to be able to place pagination or a "load more button" on those queries?

Reply
By Yannick García (4 weeks ago)

Sorry Yannick, this gonna wait for Crocoblock team to implement it in official plugin.

By [email protected] (4 weeks ago)