Query block
WordPress query loop block with filters and AJAX loading.
Properties (t2.json)
Search field: Show search input icon button
features
Active filter: Show remove icon.
Style properties
Gap
- --t2-query-gap
- Default: 1rem
Screenshot
Introduction
This block allows you to list the results of a WP_Query
, with extra features such as AJAX loading, taxonomy filtering and sorting logic, and search feature.
It can be inserted in a post content and configured using the block editor, or used inside a theme template and configured via attributes.
Contrary to the core Query Loop block, the results posts template, pagination and no-results blocks are automatically part of the block render output and are not needed as child inner blocks of this T2 Query block. Both results post template and no-results text can be customized via template files or filters (see below) to fit your needs.
Usage
In the editor
Simply add the Query loop with filters block to your page content and configure it using the sidebar settings.
In a template or programmatically
Output this block using the <!-- wp:t2/query /-->
directive and pass attributes to it. The complete list of attributes can be found below.
Example:
<!-- wp:t2/query { "inheritQuery": true, "id": "search", "enableSearchField": true, "postType": "any", "enablePostTypeFilter": true, "enableTaxonomyFilters": false } /-->
Available attributes
Attribute | Type | Description | Default value |
---|---|---|---|
id |
string | An optional block identifier that can be used for specific customization, like loading highly-specific template files for a single block instance only. | "" |
query |
object | An object representing the WP_Query for that block, if you want to configure the query more precisely than the few available settings. |
|
inheritQuery |
boolean | Whether to use the global WP_Query if the current page is a template page (archive, search, etc). |
false |
postType |
string | The post type to be queried. | "post" |
postsPerPage |
integer | The number of results to display per page. | 9 |
enablePagination |
boolean | Whether to display a pagination after the list of results. | true |
enableSearchField |
boolean | Whether to display a search input field before the filters. Useful on the site search page. | false |
enableTaxonomyFilters |
boolean | Whether to display a list of filter dropdowns to filter results on specific taxonomies. | true |
allowMultipleValuesSelection |
boolean | Controls whether multiple values can be selected in a taxonomy filter dropdown (<select multiple> ). |
false |
taxonomies |
string[] | The list of taxonomies to use for the taxonomy filter dropdowns. | ["category"] |
enablePostTypeFilter |
boolean | Whether to display a post type filter dropdown (useful on the search page to filter results per content type). | false |
enableSortByDropdown |
boolean | Whether to display a "Sort by" dropdown. | true |
enableActiveFiltersPills |
boolean | Whether to display the list of active taxonomy terms filters before the query results. | true |
Template hierarchy for the query results
The following template files can be placed in your theme:
Path for block-based themes | Path for PHP-templates themes |
---|---|
/parts/query/<block_id>/result-<post_type>.html |
/templates/query/<block_id>/result-<post_type>.php |
/parts/query/<block_id>/result.html |
/templates/query/<block_id>/result.php |
/parts/query/result-<post_type>.html |
/templates/query/result-<post_type>.php |
/parts/query/result.html |
/templates/query/result.php |
If none of these files exist, then the T2 Featured Content template (default, or specific to the queried post type) will be used instead.
By passing a unique id
in a block attribute, you can display the block results differently on a specific page using one of the high-priority template files listed above. Note: in the block editor, you can use the native HTML Anchor setting field to set the id
attribute.
For instance, if your search.html
template uses the following block directive:
<!-- wp:t2/query { "id": "search", "enableSearchField": true, "postType": "any" } /-->
You can display a unique output for the search results page by creating a /parts/query/search/result.html
template file. You can be even more specific by creating /parts/query/search/result-<post_type>.html
to render different output depending on the result's post type.
Hooks
Rendering
The default Query block rendered markup will be output in the following order:
Component | Hook in charge of component render |
---|---|
Search input field | t2/query/search_html |
Filters/sort-by form | t2/query/filters_html |
List of active filters pills | t2/query/active_filters_html |
Results items or no-results text | t2/query/results_html |
Pagination | t2/query/pagination_html |
Use the t2/query/render_hooks
filter to:
- alter the default component order (for example, to display the search input field after the filters from),
- inject new components in the block rendering (for example, to output an extra title before the list of results).
/**
* @param array $order The component order.
* @param array $attributes The block attributes.
* @param WP_Query $query The WP_Query.
*/
$hooks = apply_filters( 't2/query/render_hooks', [ 'search', 'filters', 'active_filters', 'results', 'pagination' ], $attributes, $query );
If you have injected a new component in that list, use the t2/query/{$component}_html
action to output its content. For instance, if you've registered a new results_title
item, you can output its markup with:
add_action( 't2/query/results_title_html', function( array $attributes, \WP_Query $query ): void {
echo 'Results title goes here…';
} );
Query modification
Using a block attribute
You can customize the WP_Query
being run in the block by passing a query
attribute to the block directive.
<!-- wp:t2/query { "query": { "post_type": [ "post", "page" ], "meta_query": { … } } } /-->
Any valid WP_Query
argument can be passed to the query
object attribute. This query will serve as a "base query" during AJAX requests and extra parameters will be applied on top of it based on the user request (filtering, ordering, searching, changing page, etc.).
Using filters
You can use the following two filters to programmatically alter the WP_Query
arguments.
Alter the initial WP_Query
on first page load
/**
* @param array $args The WP_Query arguments.
* @param array $attributes The block attributes.
*/
$args = apply_filters( 't2/query/args', array $args, array $attributes );
Alter the WP_Query
during AJAX requests (filtering, paginating, etc)
/**
* @param array $args The WP_Query arguments.
* @param array $data The AJAX request input data.
*/
$args = apply_filters( 't2/query/get_results/args', array $args, array $data );
Navigate to the /inc/integrations/event.php
file to see how these two filters are used in action.
A note on security
By default, this block only accepts a restricted list of WP_Query
arguments. This is a security measure, as those arguments are passed from the frontend to the backend via an AJAX request.
Use the t2/query/secured_query_args
filter to deliberately accept new query arguments.
/**
* @param array $new_args The secured list of query arguments.
* @param array $args The original query arguments.
*/
$args = apply_filters( 't2/query/secured_query_args', array $new_args, array $args );
The only arguments allowed by default are:
- author
- paged
- tax_query
- meta_query
- date_query
- meta_key
- meta_value
- s
- sentence
- post_type
- post_status ("publish" value only)
- posts_per_page
- order
- orderby
- all public taxonomy query variables
AJAX response
Use the t2/query/get_results/response
filter to inject extra data in the AJAX response sent to the frontend.
/**
* @param array|WP_Error $response The AJAX response being sent back to the client.
* @param array $data The AJAX request input data.
* @param WP_Query $query The WP_Query which fetched the new results.
*/
$response = apply_filters( 't2/query/get_results/response', $response, $data, $query );
Fragments
When an AJAX request runs to refresh the query results, you can inject new data on the frontend.
The AJAX response object has a fragments
property where keys represent a DOM selector and values represent the HTML markup to be injected in those DOM elements.
Use the above t2/query/get_results/response
filter to inject new fragments in the $response['fragments']
item.
Filters form
<fieldset>
) markup
Single filter (Use the t2/query/filters
filter to inject new sections/fields in the filters form.
/**
* @param array $filters An array where each item is a single filter HTML markup.
* @param array $attributes The block attributes.
* @param WP_Query $query The WP_Query that run on page load.
*/
$filters = apply_filters( 't2/query/filters', $filters, $attributes, $query );
Each item in the $filters
array contain the HTML output of a specific filter.
Browse the /inc/templating/filters.php
file to see the default filters output or see a complete example below on how to inject a new field and alter the results query.
Taxonomy filter label
You can use the t2/query/filters_taxonomy/label
to alter the label displayed before a taxonomy dropdown. By default, the taxonomy singular name is used.
/**
* @param string $label The filter dropdown label.
* @param string $taxonomy The taxonomy slug.
* @param array $attributes The block attributes.
* @param WP_Query $query The WP_Query that run on page load.
*/
$label = apply_filters( 't2/query/filters_taxonomy/label', $label, $taxonomy_slug, $attributes, $query );
Post type filter options
Use the t2/query/filters_post_type/post_types
filter to modify the list of post types displayed in the post type filter dropdown. By default, all public post types (except media attachments) are listed.
/**
* @param array $options The post type options (keys = post type slug, value = post type readable name).
* @param array $attributes The block attributes.
* @param WP_Query $query The WP_Query that run on page load.
*/
$options = apply_filters( 't2/query/filters_post_type/post_types', $options, $attributes, $query );
Sort-by filter dropdown
Use the t2/query/filters_sortby/options
filter to modify the options available in the "Sort by" dropdown.
/**
* @param array $options The sort-by dropdown options (keys = option value, value = option readable label).
* @param array $attributes The block attributes.
* @param WP_Query $query The WP_Query that run on page load.
*/
$options = apply_filters( 't2/query/filters_sortby/options', $options, $attributes, $query );
The default options are latest first, oldest first and alphabetically. If the query post type corresponds to an event or a product, more appropriate sort-by options are used instead.
Look at the /inc/integrations/event.php
or /inc/integrations/woocommerce.php
files to see how it is being used and how new values are being used in the WP_Query
calls.
No-results text
Use the t2/query/no_results_text
filter to modify the text displayed if the query does not return any results.
/**
* @param string $text The "no results found" text.
* @param array $attributes The block attributes.
* @param WP_Query $query The WP_Query.
*/
$text = apply_filters( 't2/query/no_results_text', esc_html__( 'No results found.', 't2' ), $attributes, $query );
Pagination arguments
Use the t2/query/pagination_args
filter to modify the arguments passed to the paginate_links()
core pagination function used to display the pagination links after the list of results.
/**
* @param string $args The arguments passed to the paginate_links() function.
* @param array $attributes The block attributes.
* @param WP_Query $query The WP_Query.
*/
$pagination_args = apply_filters( 't2/query/pagination_args', [
'current' => max( 1, $query->query['paged'] ?? 1 ),
'total' => $query->max_num_pages,
'format' => '?qp=%#%',
'prev_text' => sprintf( '%2$s%1$s', esc_html__( 'Previous', 't2' ), T2\Icons\get_icon\get_icon( 'arrowBack' ) ),
'next_text' => sprintf( '%1$s%2$s', esc_html__( 'Next', 't2' ), T2\Icons\get_icon\get_icon( 'arrowForward' ) ),
'echo' => false,
], $attributes, $query );
Global frontend JSON data
Use the t2/query/frontend_json_data
filter to modify the JSON object sent to the frontend containing global data vital to the plugin logic.
$data = apply_filters( 't2/query/frontend_json_data', [
'api' => [ 'root' => $root 'nonce' => $nonce ],
'features' => [
'useFancyFiltersDropdowns' => true,
],
] );
See a complete example below on how to disable the fancy-dropdowns library.
JavaScript hooks
Block settings in the editor
In the editor sidebar, you can inject new fields after the default block settings.
Each tab (Query and Filters) provides a <SlotFillProvider>
in which custom components can be added externally via your custom theme or plugin.
Query tab
Use the T2.Query.BlockSettings.QueryTab
filter and <Fill>
name to inject new settings fields in the Query tab. Your component will receive the attributes
object and setAttributes
function from the Query block.
Filters tab
Use the T2.Query.BlockSettings.FiltersTab
filter and <Fill>
name to inject new settings fields in the Filters tab. Your component will receive the attributes
object and setAttributes
function from the Query block.
Block setting: post type options
Use the T2.Query.Block.PostTypesOptions
to modify the options array used to populate the "Post type" query setting field. Each item in this array needs a value
property (saved in the attribute value) and a readable label
property.
See the /inc/integrations/woocommerce.js
file to see how to implement a JavaScript filter.
Block setting: taxonomy options
Use the T2.Query.Block.TaxonomiesOptions
to modify the options array used to populate the "Taxonomy" filters setting fields. Each item in this array needs a value
property (saved in the attribute value), a readable label
property and a postTypes
array property containing a list of valid post types for this taxonomy.
See the /inc/integrations/woocommerce.js
file to see how to use this filter.
Frontend: send extra parameters in the AJAX request
Use the T2.Query.RequestParams
filter to modify the request parameters sent from the frontend to the backend after the user interacts with the block (for filtering, sorting, changing page, searching, etc).
/**
* @param {object} requestParams The AJAX request parameters.
* @param {Node} $block The Query block DOM element.
*/
requestParams = applyFilters( 'T2.Query.RequestParams', requestParams, $block );
Frontend: run extra logic after user interaction
Use the T2.Query.ResultsRefreshed
action to run new logic after the AJAX response has been (successfully) received.
/**
* @param {Node} $block The Query block DOM element.
* @param {object} response The AJAX response.
*/
doAction( 'T2.Query.ResultsRefreshed', $block, response );
Frontend: modify the TomSelect library options on fancy select dropdowns
Use the T2.Query.TomSelectSettings
filter to modify the settings used to initialize the TomSelect JS library on the filters select dropdowns. Visit the library docs page to get a list of available settings.
/**
* @param {object} settings The settings object.
* @param {Node} $select The filters select dropdown being enhanced by the TomSelect library.
*/
settings = applyFilters('T2.Query.TomSelectSettings', settings, $select);
Integrations
Dekode Events extension
For queries that are loading Event posts from the Dekode Events extension, the "Sort by" dropdown filter will only display the following options:
- upcoming events,
- past and upcoming events.
The posts will be sorted by start date instead of publication date.
For more technical details on this integration, look at the /inc/integrations/event.php
file.
WooCommerce
If WooCommerce is activated, products attributes can be used as taxonomy filters.
For queries that are loading WooCommerce products, the "Sort by" dropdown filter will display the WooCommerce catalog order-by options (popularity, rating, price, etc.).
For more technical details on this integration, look at the /inc/integrations/woocommerce.php
file.
How to…?
Add extra fields in the Query block sidebar settings
import { Fill } from '@wordpress/components';
import { addFilter } from '@wordpress/hooks';
addFilter(
'T2.Query.BlockSettings.QueryTab',
'Theme.Query.BlockSettings.QueryTab',
() => {
return ({ attributes, setAttributes }) => {
return (
<>
<Fill name="T2.Query.BlockSettings.QueryTab">
<p>Add your own fields in here!</p>
</Fill>
</>
);
};
},
);
Add extra content in the block markup (on page load and after an AJAX request)
/**
* Inject a new "results_title" hook before the results.
*/
function alter_hooks( array $variable ): array {
return [ 'search', 'filters', 'active_filters', 'results_title', 'results', 'pagination' ];
}
add_filter( 't2/query/render_hooks', __NAMESPACE__ . '\\alter_hooks', 10, 1 );
/**
* Add a title before the results (page load).
*/
function add_results_title( array $attributes, \WP_Query $query ): void {
if ( ! $query->have_posts() ) {
printf( '<h2 class="results-title">%s</h2>', esc_html__( 'No results', 't2' ) );
return;
}
$posts_count = $query->found_posts;
$posts_count = number_format_i18n( $posts_count );
printf( '<h2 class="results-title">%s</h2>', esc_html( sprintf( _n( '%s result', '%s results', $posts_count, 't2' ), $posts_count ) ) );
}
add_action( 't2/query/results_title_html', __NAMESPACE__ . '\\add_results_title', 10, 2 );
/**
* Inject the title before the results in a new AJAX fragment.
*/
function inject_results_title_fragment( array $response, array $data, \WP_Query $query ): array {
if ( $query->have_posts() ) {
$posts_count = $query->found_posts;
$posts_count = number_format_i18n( $posts_count );
$title = sprintf( _n( '%s result', '%s results', $posts_count, 't2' ), $posts_count );
$response['fragments']['.results-title'] = sprintf( '<h2 class="results-title">%s</h2>', esc_html( $title ) );
} else {
$response['fragments']['.results-title'] = '';
}
return $response;
}
add_filter( 't2/query/get_results/response', __NAMESPACE__ . '\\inject_results_title_fragment', 10, 3 );
Add a new filter field on the frontend and modify the AJAX query
/**
* Display a new arbitrary meta dropdown filter in the filters form.
*/
function add_new_meta_filter( array $filters, array $attributes, \WP_Query $query ): array {
$filters[] = '<fieldset>
<label for="custom-meta-filter">Meta filter</label>
<select name="custom_meta_filter" id="custom-meta-filter">
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
</fieldset>';
return $filters;
}
add_filter( 't2/query/filters', __NAMESPACE__ . '\\add_new_meta_filter', 10, 3 );
/**
* Change the WP_Query args based on our new meta filter.
*/
function modify_ajax_request_query_args( array $args, array $data ) {
if ( isset( $data['filters']['custom_meta_filter'] ) ) {
if ( ! isset( $args['meta_query'] ) ) {
$args['meta_query'] = [];
}
$args['meta_query'][] = [
'key' => 'custom_meta_filter',
'value' => sanitize_text_field( wp_unslash( $data['filters']['custom_meta_filter'] ) ),
];
}
return $args;
}
add_filter( 't2/query/get_results/args', __NAMESPACE__ . '\\modify_ajax_request_query_args', 10, 2 );
/**
* Todo: hook into the t2/query/args filter to alter the initial WP_Query that runs on page load.
*/
Use the frontend JavaScript hooks
import { addAction, addFilter } from '@wordpress/hooks';
/**
* Pass a new "extraParam" in the AJAX payload.
*/
addFilter(
'T2.Query.RequestParams',
'Theme.Query.RequestParams',
(requestParams, $block) => {
return {
...requestParams,
extraParam: 'someExtraValue',
};
},
);
/**
* After the results have been refreshed, scroll to the top of the block.
*/
addAction(
'T2.Query.ResultsRefreshed',
'Theme.Query.ResultsRefreshed',
($block, response) => {
$block.scrollIntoView({ behavior: 'smooth' });
},
);
/**
* If the results refresh logic fails, display an alert message.
*/
addAction(
'T2.Query.RefreshResultsFailed',
'Theme.Query.RefreshResultsFailed',
($block, ajaxResponse, requestParams) => {
alert("Something went terribly wrong");
},
);
Disable the TomSelect fancy-dropdown library
function disable_fancy_dropdown( array $data ): array {
$data['features']['useFancyFiltersDropdowns'] = false;
return $data;
}
add_filter( 't2/query/frontend_json_data', __NAMESPACE__ . '\\disable_fancy_dropdown', 10, 1 );