Oxygen Builder's Repeater and Easy Posts elements are quick and easy ways to display a list of posts.
This guide will show you different ways of inserting "other items" within a list for whatever reason you need.
To avoid confusion I will use "Other Item(s)" for items that are not queried by the original Repeater/Easy Post query
Repeater/Easy Posts elements show a list of posts. That list is restricted by the parameters set in the query used to fetch the posts.
You can format the appearance of each item, and the layout of the list, and that's all. Sometimes that's all you need but some other times you may want to alter the list to include other items that were not originally fetched. Other items like:
Oxygen Builder Course - Coming Soon!
The Oxygen Builder Mastery course will bring you from beginner to professional - ACF, MetaBox & WooCommerce modules included.
I will use some of the examples above to show you the various ways of adding other items to a list.
The methods from super simple to complicated are:
the_posts
hookthe_posts
hook again but adding non-Post items like Terms (Categories/Taxonomies)In my examples I will use a custom post type Destinations which is just a list of cities.
We'll use JQuery to insert an element at some index inside the Repeater/Easy Post.
example-repeater-1
.div
with height 100% and width 100% and I made it a simple banner with a call to action button. Set its ID to guide-ad-banner
.Insert a Code Block into your page and add the following to the Javascript section, or inside <script>
tags in the PHP / HTML section.
jQuery(($) => {
const allRepeaterDivs = $("#example-repeater-1 > div");
const insertionIndex = allRepeaterDivs.length > 2 ? 2 : allRepeaterDivs.length - 1;
allRepeaterDivs.eq(insertionIndex).after($("#guide-ad-banner")[0]);
});
Code language: JavaScript (javascript)
Here is a quick explanation of the script.
div
s that are children of #example-repeater-1
. These are the individual items in the repeater.#guide-ad-banner
after the 3rd item, making my other item the 4th item.On the front end, it takes the item with id guide-ad-banner
and inserts it into the 4th spot of repeater with id #example-repeater-1
.
the_posts
hookThe the_posts
filter hook in WordPress gives you access to the posts that have been fetched by a query. reference
We can use this hook to insert other posts that were not included in the original query.
For this example, I will insert a regular Post at the end of my repeater that used a queried for my destinations.
Add this PHP code to the Code Block that is above the repeater.
<?php
function b58_add_cta_last( $posts, $query ) {
$cta_post = get_post( 347 );
$posts[] = $cta_post;
return $posts;
}
add_filter( 'the_posts', 'b58_add_cta_last', 10, 2 );
?>
Code language: HTML, XML (xml)
Let's go over this code.
get_post
to get the post with ID 347. This is the post that I want to insert at the end of the repeater.$posts
array.the_posts
filter hook.Add the following PHP to the Code Block under the repeater
<?php
remove_filter( 'the_posts', 'b58_add_cta_last', 10, 2 );
?>
Code language: HTML, XML (xml)
This removes the previously added function as one to be used the the_posts
filter hook is called. If it is not removed it will affect other queries run later on.
The post that we got with ID 347 was appended to the end of the repeater. From the image you see that there is a list of destinations and at the end is our regular post. So it could have been an article that talks about travel or how to book, or whatever.
If you add dynamic data like custom fields to the repeated items and that meta key does not exist on the other item, then it will be blank in that div. So you should use a condition to check if the meta key exists to avoid any weird layout issues.
This will involve editing the PHP Template for the Easy Posts element.
For this example, I will create a Reusable Part that contains a repeater with a list of posts, then insert it into an Easy Post element that queried for a different set of posts. I won't be using the Destinations CPT for this.
no_found_rows
= true to disable pagination.That is the structure of the Reusable Part.
While this repeater exists inside the Easy Post element, its query should be independent of the Easy Post. One way the Easy Post element can affect the inner list is via pagination. If someone clicks on page 2 for the Easy Post element, by default the inner list will also fetch page 2. We have to prevent that.
First Code block above the repeater will be adding a pre_get_posts
action that sets the paged query argument to 1, which makes it always return the first page.
If this repeater is on a static home page then use the page = 1 instead. (reference: WP_Query documentation)
<?php
function b58_set_paged( $query ) {
// use paged if the repeater is on an archive page
// or page other than a static home page.
$query->set( "paged", 1 );
// use page if this repeater is on a static home page.
// $query->set( "page", 1 );
return $query;
}
add_action( 'pre_get_posts', 'b58_set_paged' );
?>
Code language: HTML, XML (xml)
The second code block removes the previously added action to prevent it from effecting future queries.
<?php
remove_action( 'pre_get_posts', 'b58_set_paged' );
?>
Code language: HTML, XML (xml)
So now the repeater will always show the first page of results.
Take note of this Reusable Part's template ID, you can get from the address bar in the WP Dashboard.
Oxygen Builder Course - Coming Soon!
The Oxygen Builder Mastery course will bring you from beginner to professional - ACF, MetaBox & WooCommerce modules included.
In the Easy Posts style panel, I go to Template PHP. This is where you to edit the template used for every item in the Easy Post container (the template is repeated.)
Under the default template, paste the following php
<?php
$current_index = $this->query->current_post;
// Place this element after the 3rd post item, or
// the last post if the # of posts is less than 3
// this ensure this element is added even if there
// are less than 3 posts on the page.
$other_post_index = $this->query->post_count < 3 ? $this->query->post_count - 1 : 2;
if( $current_index == $other_post_index ) {
echo "<div class='oxy-post'>";
echo do_oxygen_elements( json_decode( get_post_meta( 321, 'ct_builder_json', true ), true ) );
echo "</div>";
}
?>
Code language: HTML, XML (xml)
Let's go over this code.
$current_index
. Behind the scenes, Oxygen Builder is looping over every item returned from the query set in the Easy Posts element, and is executing the code inside the template for every post inside the list. We have a way of accessing the query variable with $this->query
and the index of the currently processing item is current_post
inside the query.do_oxygen_elements
to render the reusable item (template ID 321) and wrap it in a div with class oxy-post
.The Reusable Part is placed as the 4th item in the Easy Posts grid.
The reusable part's list is scrollable inside the list.
The pre_get_posts
hook in the reusable part makes it always load the first page of posts so I can go to the other pages of the Easy Post and the results remain the same.
Using this method, you can replace the do_oxygen_elements
part of the above code and write your own HTML to add whatever you want inside the Easy Posts element.
Remember your added element should conform to whatever sizing rules that have been set in the oxy-post class otherwise it may throw the rest of the list off.
This method builds upon using the the_posts
hook to insert links to terms inside a list of posts sorted by their terms.
So imagine a repeated list like:
Blue A, Blue B, Blue C, See all Blues, Red X, Red Y, Red Z, See all Reds, etc.
Super helpful on an e-commerce site when you want to list a few of featured items then link the term archive.
This method involves a lot of steps but it's pretty straightforward.
the_posts
hookThis is an optional step if you want to use a Featured Image with your Term object.
In my example, I use a Featured image as the background for the item. Terms by default do not have a Featured Image, so I added one to my Custom Taxonomy with Advanced Custom Fields.
The custom field is an Image field that returns the ID.
I add the following helper functions in Code Snippets, do the same with whatever method you prefer.
function b58_create_post_from_term( $term, $post_type="post" ) {
$post_id = -1 * $term->term_id; // negative ID, to avoid clash with a valid post
$post = new stdClass();
$post->ID = $post_id;
$post->post_author = 1;
$post->post_date = current_time( "mysql" );
$post->post_date_gmt = current_time( "mysql", 1 );
$post->post_title = $term->name;
$post->post_content = $term->description;
$post->post_status = "publish";
$post->comment_status = "closed";
$post->ping_status = "closed";
$post->post_name = "regions/" . $term->slug;
$post->post_type = $post_type;
$post->filter = "raw"; // important!
$wp_post = new WP_Post( $post );
wp_cache_add( $post_id, $wp_post, "posts" );
return $wp_post;
}
function b58_get_the_featured_image( $get_url, $size = 'thumbnail' ) {
global $post;
$post_id = $post->ID;
$thumbnail_id = 0;
if( $post_id > 0 ) { // this is a regular post.
$thumbnail_id = get_post_thumbnail_id( $post_id );
} else {
// this is our fake post and it doesn't
// have a thumbnail ID so we have to use the
// custom field we set for this term.
$pos_term_id = -1 * $post_id;
$thumbnail_id = get_field( "term_background", "term_" . $pos_term_id );
}
if( $get_url ) {
return wp_get_attachment_image_url( $thumbnail_id, $size );
}
return $thumbnail_id;
}
function b58_get_the_link() {
global $post;
$post_id = $post->ID;
if( $post_id > 0 ) {
return get_permalink( $post_id );
}
// post id is negative, we use a negative post id in our dummy post object
$pos_term_id = -1 * $post_id;
$term_link = get_term_link( $pos_term_id );
return $term_link;
}
Code language: PHP (php)
Let's review each function in this snippet.
b58_create_post_from_term( $term, $post_type="post" )
This takes a WP Term object ($term
) and creates a fake post of type set in $post_type.
It sets the ID to the negative of its actual ID first as kind of a "flag" to indicate this is a term and not an actual post.
It sets some other variables necessary for the WP_Post object, but the only one of note for our purpose is the post_title.
Then it adds this post to the wp cache, in case something requests this post and since it has a negative ID it will fail if it tries going to the database.
b58_get_the_featured_image( $get_url, $size = 'thumbnail' )
Code language: PHP (php)
If you did not add a custom field with image for the term then skip this function.
This function takes 2 arguments, $get_url
which would be a boolean, this determines if it returns the image's ID or the URL.
The 2nd argument sets the size.
First it checks if the current post's ID is negative, if it is negative then this is a fake post that is a term, otherwise it is a real post.
If it is a fake post then we retrieve the image ID with get_field
for that term's ID.
If it is a real post then we use the built-in get_post_thumbnail_id
function.
Second, if $get_url
is false then only the ID is returned. Otherwise we use wp_get_attachment_image_url
to get the image url and return that.
b58_get_the_link()
This returns the link (permalink or term link) for this post.
If the post ID is negative then it is a fake post we we use get_term_link
to get the link, instead of get_permalink
if it's a real post.
That is it for the helper functions.
I add a repeater to the page, with a query for destinations. I use the same layout as previous examples, centered Post Title with Featured Image background, and the div links to either the post permalink or term link.
For the featured image background, I don't use the usual Featured Image method in Dynamic Data. I use the PHP Function Return Value method because I want to use my helper function to fetch an image rather than the other way because the item might be a "fake post" (term.) So for the div background URL, I use the PHP Function Return Value with function name b58_get_the_featured_image
, and the paramter true
. I could specify a size here too but I don't.
Same idea for the link. I can't use the Permalink dynamic data as I would normally because it would be wrong for the fake post, so I use b58_get_the_link
instead.
the_posts
hookJust like before I add code blocks before and after the repeater, this way we can set a function to be called with the hook and remove that function afterwards so it won't affect other queries.
Here is the code for the code block above the repeater
<?php
function b58_add_tax_links( $posts, $query ) {
// ignore if in admin
if( is_admin() ) {
return $posts;
}
try {
$terms = get_terms([
"taxonomy" => "regions",
"hide_empty" => true,
"orderby"=>"name",
"order" => "ASC"
]);
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
return $posts;
}
if( empty( $terms ) ) {
return $posts;
}
$new_posts = array();
foreach( $terms as $term ) {
for ( $i = 0; $i < count($posts); $i++ ) {
if( has_term( $term->slug, "regions", $posts[$i] ) ) {
$posts[$i]->post_title = $posts[$i]->post_title;
$new_posts[] = $posts[$i];
}
}
// create a post object from this term.
$term_post = b58_create_post_from_term( $term, "destinations" );
$new_posts[] = $term_post;
}
return $new_posts;
}
add_filter( 'the_posts', 'b58_add_tax_links', 10, 2 );
?>
Code language: HTML, XML (xml)
Let's go over this code.
get_terms
query for the taxonomy with the slug regions
, sorted by name in ascending order. The Regions taxonomy is attached to the Destinations custom post type, it is one of the 7 continents where the destination is located, so Nairobi and Cairo are in Africa, Hong Kong is in Asia, and so on.$new_posts
, I will add the posts into this array and return it instead of the actual posts array.$terms
array, and for each term I iterate through the $posts
array and find the ones that have that term and append it to $new_posts
.$posts
array, I create a fake post with the current $term
and give it a post type of destinations (it could be anything, really.) Then I add this fake post to $new_posts
.When the function returns, the $new_posts
array items should look like:
Cairo, Nairobi, Africa, Hong Kong, Asia, London, Europe … so on. Africa, Asia, and Europe, are the fake posts.
Finally, we add the above function to the the_posts
hook.
The Code Block below the repeater is:
<?php
remove_filter( 'the_posts', 'b58_add_tax_links' );
?>
Code language: HTML, XML (xml)
This removes the function from the hook.
At this point the repeater is finished. Items with the same Taxonomy are grouped together, and at the end of each group there is a link to the Term archive. However, the list is a bit jumbled, it all runs together in a large grid. I want each group of items and their archive link to be in a row by themselves, and this is how to do it.
data-post-id
and use dynamic data Post ID for its value._dynamic_list-5-343
.In the top Code Block (either one works, or even a new one), I add the following code to the Javascript section.
jQuery(($) => {
$("#_dynamic_list-5-343 [data-post-id^=\"-\"]").after(
$("<div />")
.css({
height: "0px",
"flex-basis": "100%"
})
)
});
Code language: JavaScript (javascript)
This adds a div after the Terms div (post ID starts with "-", negative) and the div has flex-basis: 100%
and height: 0px
which is like adding a line break in the repeater row.
And that's it.
Each repeated item has their respective post titles and featured image as the background. For the Terms items I added the word "Explore" and set it to be conditionally shown when the post ID is negative (< 0).
Hopefully with these techniques you can spice up your Oxygen Builder repeaters or Easy Posts.
If you have any questions you can DM me on twitter @robchankh or leave a comment on FB where I'll post this.
Very helpful! Thank you Robert 🙂
A great tutorial. Thanks
Great article!
But a quick question, I have a custom taxonomy attached to a custom post type.
I want to use an Oxygen repeater to just show the terms for the custom taxonomy displaying title, description and an image field that I added using a custom field.
So, your code above shows how to add the terms to the posts (a mixture), but I purely want the terms.
How would I go about this?
Thanks in advance for your help.
Rob
Oxygen repeaters only query posts. No terms or users. You can do that with a custom loop.
We just launched an AI code generate that might help you 🙂 You can use code isotropic_alpha for free access: https://codewp.ai/