DiggingIntoWordPress

by Chris Coyier & Jeff Starr

Custom Query Shortcode: Run a Loop inside Any Post/Page

Posted by on

I had the occasion yesterday to have a page with a section on it where it would output a very specific set of other pages, which would need to change dynamically. What I could have done is built a special page template for this page, and inside that template run a query_posts() to get these posts. But I wanted this page to remain editable from the admin. Besides, creating a special page template every time you need to do something like this is too cumbersome. WordPress is extensible enough to do better.

Hence, the Custom Query Shortcode!

This is basically how I wanted it to work. You have a shortcode with one single parameter called “query”. (Remember, shortcodes are those things in brackets you can use in the body of Pages/Posts that do special stuff). In the “query” parameter, you literally enter exactly what you would enter for a query when using the query_posts() function. In other words:

[loop query=""]

You can pass whatever query you want to it. For example, let’s say you wanted to query for 20 Pages that were the child of a specific page and list them in Ascending order:

[loop the_query="showposts=20&post_type=page&post_parent=453&ord=ASC"]

What it returns

This is totally customizable in the function itself, but I made the judgement call for myself that it would return list items with linked post titles inside. So you’d go like this:

<ul>
[loop query="posts_per_page=3"]
</ul>

and you’d get:

<ul>
<li><a href="http://digwp.com/post-1/">Post 1</a></li>
<li><a href="http://digwp.com/post-2/">Post 2</a></li>
<li><a href="http://digwp.com/post-3/">Post 3</a></li>
</ul>

And of course those would be real URL’s and real Post titles…

The Code

This would go in your themes functions.php file. Now that I’m thinking more clearly about it, this probably makes more sense to be a plugin since it probably shouldn’t be theme-dependant, but oh well, we can do that another day.

function custom_query_shortcode($atts) {

   // EXAMPLE USAGE:
   // [loop the_query="showposts=100&post_type=page&post_parent=453"]
   
   // Defaults
   extract(shortcode_atts(array(
      "the_query" => ''
   ), $atts));

   // de-funkify query
   $the_query = preg_replace('~&#x0*([0-9a-f]+);~ei', 'chr(hexdec("\\1"))', $the_query);
   $the_query = preg_replace('~&#0*([0-9]+);~e', 'chr(\\1)', $the_query);

   // query is made               
   query_posts($the_query);
   
   // Reset and setup variables
   $output = '';
   $temp_title = '';
   $temp_link = '';
   
   // the loop
   if (have_posts()) : while (have_posts()) : the_post();
   
      $temp_title = get_the_title($post->ID);
      $temp_link = get_permalink($post->ID);
      
      // output all findings - CUSTOMIZE TO YOUR LIKING
      $output .= "<li><a href='$temp_link'>$temp_title</a></li>";
          
   endwhile; else:
   
      $output .= "nothing found.";
      
   endif;
   
   wp_reset_query();
   return $output;
   
}
add_shortcode("loop", "custom_query_shortcode");

Credit

When I first wrote this up to use, it totally didn’t work. I couldn’t figure it out… the query was getting passed over just fine, and if I hard coded the query, the whole thing would work just fine. It turns out that because the shortcode lived inside the content of a Post/Page, the ampersand characters in the query were getting encoded, and thus screwing up the query.

Dan Smith had the genius idea to use html_entity_decode() on the query before using it in query_posts(), but for some reason I couldn’t get it to take.

Roger Stringer coded up the solution with regular expressions you see above. Thanks guys!

10 Responses

  1. personally, i’d have the function output the open and closing list tags as well. if it doesn’t return anything you’ll have unnecessary code in your page (ewwww!).

    i used something similar on a client’s site. the 3 boxes on the page randomly pulls child pages from products.

    • Good point… although in my case I wanted to add ID values to the different unordered lists so I wanted those outside.

      I guess the code above is more generically useful, but in real use should be customized to specific needs. Perhaps adding a parameter or two for specifying what kind of output you want…

  2. You mentioned that you thought this should probably be a plugin instead of a function. I’d love to see an article on creating a plugin. :)

  3. Ok something’s been nagging at me a while and the only way out was to build a page template with query_posts () – but I reckon you can offer a better solution with this example.

    What I need to do is have a link on my main navbar to the latest post in a particular category. A child link on the navbar needs to link to all posts in that category EXCEPT the latest post.

    Could I use your code to make this happen for me?

  4. Here is a solution quite similar to yours but without the need auf the regex … and its easier to modify the output … might be intresting for you – i would remove the category part over there but …

    http://www.wprecipes.com/wordpress-shortcode-display-the-loop

    greeting

  5. very interesting concept … do you know if this can also be done with the shortcode for gallery ..?? … i’d love to loop thru the gallery thumbnails and insert hover-over links/tooltips with the gallery’s medium sized image thus not sure if your loop example would work in such a situation or if plugin path is better direction ..??..

  6. Interesting method, for some writer that didn’t understand PHP this one really useful

  7. Bjarne April 3, 2010

    Hi there,

    Thanks for this super nice shortcode. I wonder why something like this is not part of WP out of the box.

    I have put the code in my functions.php and inserted a shortcode on a page. I am however having some problems excluding certain tags from the query as well as putting my custom field values in the output.

    For excluding tags I have tried using tag__not_in in the shortcode without success. Is this possible? If so, how would it look like in the shortcode?

    To output my custom field values I have tried adding $temp_erf = get_post_meta($post_ID, 'erf', true); in the loop and then a $temp_erf in the output.

    Am I missing something obvious here or is this a limit to the shortcode?

  8. Bjarne April 3, 2010

    By the way…

    When making a query for a particular category using tag it works, but not if you use tag_id

Comments are closed. Contact us with any critical information. Thank you!

Code is poetry