Using a PostQuery in your Template

Code

March 8, 2022

No more tb.get_posts() ! You can do a Timber PostQuery inside your Timber Posts Modules from now on. Added in Toolbox 1.2.3 Make sure to update your Toolbox version before using the PostQuery function

Previously, when doing a query inside the Timber Posts module you needed to use tb.get_posts(), but had to fetch a seperate Timber Post Object to get access to all Timber Objects:

{% set args = { post_type: 'my_post_type' } %}
{% set posts = tb.get_posts( args ) %}
{% for item in posts %}
    {# reset the WP Post Object-item as a Timber Post-item #}
    {% set item = Post( item.ID ) %}
	<h3>{{item.title}}</h3>
	<div>{{item.preview(50)}}</div>
{% endfor %}

This is quite useful and easy to use, and of course this will still do in most cases. But since Toolbox 1.2.3 there is an alternative that returns Timber Post Objects by default and also brings useful features like pagination.

Let’s showcase how easy it now is to create a paginated result container. For this we will use the Twig template below as a basis.

Twig
{% set args = { post_type: 'code', posts_per_page: 4 } %}
{% set posts = PostQuery( args ) %}
<div class="uk-child-width-1-2@s the-posts" uk-grid>
{% for item in posts %}
    <div>
        <div class="uk-card uk-card-default uk-card-body">
    	<h3>{{item.title}}</h3>
    	<div>{{item.preview(50)}}</div>
    	</div>
    </div>
{% endfor %}
</div>
<div class="uk-margin-top">
{% for item in posts.pagination.pages %}
    <a href="javascript:void(0);" 
        class="load-more pagination-item{{ item.current ? ' current' }}" 
        data-args="{{ args|json_encode|e( 'html' )}}" 
        data-page="{{item.name}}">
        {{- item.name -}}
    </a>
{% endfor %}
</div>

And this should give you a result similar to this one (using this site’s ‘code’ CPT):

HTMX Requests

Starting with Toolbox 1.4.0 you can enqueue the HTMX javascript library and register AJAX requests using Twig templates. But what are they? Dynamic content If you’ve ever tried to register an AJAX call in WordPress, you know what a pain it can be to simply return a partial layout. You… Read More

Creating a custom taxonomy filter

You can create custom filters for your layouts using Twig templates without the need for additional plugins. The fun part is that you can tweak them to your own needs and reuse them on other projects if needed. Let’s start by adding a filter that allows us to select a… Read More

Adding Global Twig Variables

Have you ever had the need to set global variables that can be used across multiple Twig templates? Then you might have tried to create a Twig Template, set a few variables and included that in another template, only to discover that you couldn’t get to those variables because they… Read More

Tabs by month

If you want to avoid the clutter of listing all posts as one big list, you might consider grouping them in accordions by month for instance. But how would you be able to do that yourself? You can do that easily in Toolbox using the Timber Posts module!… Read More
1 2 3 4 5 6

Adding a WP AJAX action hook

As you can see in the pagination code, we are using our base args (arguments for the query) and our page to set data-attributes on the links. We will be passing these to our AJAX call later using some javascript. For now, let’s concentrate on the PHP to add a WP AJAX action hook. We’ll be adding a generic function so we can use any Twig template we want for a custom call.

PHP
<?php

add_action( 'wp_ajax_twig-load-more' , 'ajax_twig_load_more_callback' );
add_action( 'wp_ajax_nopriv_twig-load-more' , 'ajax_twig_load_more_callback' );

/**
 * WP AJAX action callback
 */
function ajax_twig_load_more_callback() {
	render_twig_template( 'twig-load-more-demo.twig' );
}


/**
 * Callback to render a Twig template
 * @return [type] [description]
 */
function render_twig_template( $templatename ) {

	//if ( !is_user_logged_in() ) wp_die();

	DEFINE( 'DOING_AJAX' , true );

	$data = \Timber::get_context();

	// buffer output
	ob_start();

	// Use try to prevent failure
	try {
		// render $twig template with numberposts and offset as variables
		\Timber::render( $templatename , $data );
	} catch( Exception $e ) {
		if ( apply_filters( 'toolbox/twig_error_debug' , true ) ) echo '[ error handling twig template ] ' . $e->getMessage();
	}

	// echo result
	echo ob_get_clean();

	// wp_die() because we want it to end here
	wp_die();

}
Expand

The load_more_twig function

The render_twig_template function is added so we can call any Twig template that we have registered. It takes a variable called $templatename, which is the path and filename to that Twig template.

The ajax_twig_load_more_callback function

The ajax_twig_load_more_callback function is the callback that is getting executed when the wp_ajax action ‘twig-load-more’ is being called using the /wp-admin/admin-ajax.php?action={action} call.

To sum things up, when the call is being performed, it will execute the function/callback ajax_twig_load_more_callback(), which in turn will execute render_twig_template( 'twig-load-more-demo.twig' ). This should render the template results back to the script calling it.

Adding javascript to perform the request

In order to perform the request, we will add a bit of javascript to our layout. The following javascript will listen to clicks on the DOM elements with a .load-more class attached to it. We’ve used this class for our pagination links.

When any of the pagination links is clicked, it uses $.ajax to request our WP AJAX action ‘twig-load-more’ and passes in our link’s data-args and data-page attributes as data variables. We’ll use these to lign up our Twig request with our original call.

When the call receives the rendered template, it will replace the contents of the element with a class of .the-posts with it.

JavaScript
(function($) {

    $( '.load-more' ).on( 'click' , function() {
        // remove the current class from any load-more item
        $( '.load-more' ).each( function( index ){ $(this).removeClass( 'current' ); } )
        
        $this = $(this);
        // add current class
        $this.addClass( 'current' );
        $.ajax( {
            method: 'POST',
            url: '/wp-admin/admin-ajax.php?action=twig-load-more',
            data: {
                args: $this.data( 'args' ),
                page: $this.data( 'page' ),
            }
        } ).done( function( data ) {
            $( '.the-posts' ).html( data );
        });
    });

})(jQuery);

Adding our Twig template for paginated results

Now all that remains is adding the Twig template that will render our paginated call. For this we will add it to our Twig Templates CPT.

Make sure that the slug for the template is ‘twig-load-more-demo’, so that our template will be called correctly.

Twig
{# filename: twig-load-more-demo.twig #}
{% set args = request.post.args|merge( { paged: request.post.page , post_status: 'publish' } ) %}
{% set posts = PostQuery( args ) %}
{% for item in posts %}
    <div>
        <div class="uk-card uk-card-default uk-card-body">
        	<h3>{{item.title}}</h3>
        	<div>{{item.preview(50)}}</div>
    	</div>
    </div>
{% endfor %}

Notice that here, we are using the Timber request object that was added using the Timber::get_context() function in our PHP, to access any post data. We are merging the args, which we passed to our call, with our page number and a post_status so that we are sure that only published posts are showing.

Because we are replacing the content inside our .the-posts element only, we only render the elements, and not the surrounding container.

Beaverplugins

Web ninja with PHP/CSS/JS and Wordpress skills. Also stand-in server administrator, father of four kids and husband to a beautiful wife.
Always spends too much time figuring out ways to do simple things even quicker. So that you can benefit.