This is one of the few missing features in Bricks’ native filters: the number of posts remaining when the load more button is clicked. This snippet can be added to wpCodeBox or used as an MU plugin. Add the code, then, anywhere you want to show the remaining value, add the snippet and the target query ID {fw_remaining_posts:abc123}. Hopefully, a not-too-distant release will make this obsolete.
<?php
/**
* FW Bricks Load More Remaining Posts Counter
*
* Provides:
* - {fw_remaining_posts}
* - {fw_remaining_posts:ELEMENT_ID}
*
* Works:
* - on initial render
* - after Bricks "Load more" AJAX calls
* - with multiple query loops on the same page
*
* Recommended wpCodeBox settings:
* - Type: PHP
* - Location: Frontend
* - Priority: default
*/
defined( 'ABSPATH' ) || exit;
if ( ! function_exists( 'fw_remaining_posts_value' ) ) {
/**
* Register custom dynamic data tag in Bricks picker.
*/
add_filter(
'bricks/dynamic_tags_list',
function ( $tags ) {
$tags[] = [
'name' => '{fw_remaining_posts}',
'label' => 'FW Remaining Posts Count',
'group' => esc_html__( 'Custom', 'bricks' ),
];
return $tags;
}
);
/**
* Build the replacement markup for {fw_remaining_posts}.
*
* Returns a span containing the current remaining count plus metadata
* that the JS uses to keep the count updated after AJAX "Load more".
*
* @param string|null $element_id Optional Bricks query element ID.
* @return string
*/
function fw_remaining_posts_value( ?string $element_id = null ): string {
$empty = '<span class="fw-remaining-posts" data-total="0" data-per-page="0">0</span>';
if ( ! class_exists( '\Bricks\Query' ) || ! property_exists( '\Bricks\Query', 'query_history' ) ) {
return $empty;
}
$history = \Bricks\Query::$query_history ?? [];
if ( empty( $history ) || ! is_array( $history ) ) {
return $empty;
}
$query = null;
if ( ! empty( $element_id ) ) {
foreach ( $history as $q ) {
if ( is_object( $q ) && isset( $q->element_id ) && $q->element_id === $element_id ) {
$query = $q;
break;
}
}
} else {
$last = end( $history );
$query = is_object( $last ) ? $last : null;
reset( $history );
}
if ( ! $query || empty( $query->count ) ) {
return $empty;
}
$found_posts = isset( $query->count ) ? intval( $query->count ) : 0;
$query_vars = [];
if ( isset( $query->query_vars ) && is_array( $query->query_vars ) ) {
$query_vars = $query->query_vars;
}
$posts_per_page = isset( $query_vars['posts_per_page'] )
? intval( $query_vars['posts_per_page'] )
: intval( get_option( 'posts_per_page' ) );
if ( $posts_per_page < 1 ) {
$posts_per_page = intval( get_option( 'posts_per_page' ) );
}
$current_end = isset( $query->end ) ? intval( $query->end ) : 0;
$remaining = max( 0, $found_posts - $current_end );
return sprintf(
'<span class="fw-remaining-posts" data-total="%1$d" data-per-page="%2$d">%3$d</span>',
$found_posts,
$posts_per_page,
$remaining
);
}
/**
* Handle tag when used as a standalone dynamic data value.
*
* Examples:
* {fw_remaining_posts}
* {fw_remaining_posts:abc123}
*/
add_filter(
'bricks/dynamic_data/render_tag',
function ( $tag, $post, $context ) {
if ( ! is_string( $tag ) || strpos( $tag, 'fw_remaining_posts' ) !== 0 ) {
return $tag;
}
$parts = explode( ':', $tag, 2 );
$element_id = isset( $parts[1] ) ? sanitize_key( $parts[1] ) : null;
return fw_remaining_posts_value( $element_id );
},
10,
3
);
/**
* Handle tag when typed inline in content.
*
* Examples:
* Load more ({fw_remaining_posts})
* Load more ({fw_remaining_posts:abc123})
*/
add_filter(
'bricks/dynamic_data/render_content',
function ( $content, $post, $context ) {
if ( ! is_string( $content ) || strpos( $content, '{fw_remaining_posts' ) === false ) {
return $content;
}
return preg_replace_callback(
'/\{fw_remaining_posts(?::([a-z0-9_-]+))?\}/i',
function ( $matches ) {
$element_id = isset( $matches[1] ) ? sanitize_key( $matches[1] ) : null;
return fw_remaining_posts_value( $element_id );
},
$content
);
},
10,
3
);
/**
* Output frontend JS to keep the count updated after Bricks AJAX load-more.
*/
add_action(
'wp_enqueue_scripts',
function () {
if ( is_admin() ) {
return;
}
$handle = 'fw-load-more-count-inline';
wp_register_script( $handle, '', [ 'bricks-scripts' ], null, true );
wp_enqueue_script( $handle );
$js = <<<'JS'
document.addEventListener('bricks/ajax/load_page/completed', function (e) {
const queryId = e && e.detail && e.detail.queryId ? e.detail.queryId : null;
if (!queryId) return;
const bricksData = window.bricksData || {};
const queryLoopInstances = bricksData.queryLoopInstances || {};
const instance = queryLoopInstances[queryId] || null;
if (!instance) return;
const page = parseInt(instance.page || 1, 10);
let queryVars = {};
try {
queryVars = JSON.parse(instance.queryVars || '{}');
} catch (error) {
queryVars = {};
}
const perPage = parseInt(queryVars.posts_per_page || 0, 10) || 10;
const interactions = Array.isArray(bricksData.interactions) ? bricksData.interactions : [];
interactions.forEach(function (interaction) {
if (
!interaction ||
interaction.action !== 'loadMore' ||
interaction.loadMoreQuery !== queryId
) {
return;
}
const span = interaction.el && interaction.el.querySelector
? interaction.el.querySelector('.fw-remaining-posts')
: null;
if (!span) return;
const total = parseInt(span.dataset.total || 0, 10);
const remaining = Math.max(0, total - (page * perPage));
span.textContent = String(remaining);
});
});
JS;
wp_add_inline_script( $handle, $js );
},
20
);
}
Leave a Reply