Roots Discourse

Passing Data to Blade Template Partial in Custom WP_Query

Hello,

I’m looking for some advice on the best way to pass data to a blade template that is being used for an individual post listing in a WP_Query.

I have a page template (our-team.blade.php) that has a corresponding controller (page-our-team.php). In that controller, there is a function that returns the result of a WP_Query that gets all of the data for the Person CPT. On the Our Team page template, I can use the data from the controller to loop through the query just fine. Within that loop on that template, I am including a template partial to render each individual person list item (list-item-person.blade.php).

My question is, what is the best way to pass the data of each individual Person from the query to the list-item-person.blade.php template? I can add {{ get_the_title() }} etc. and that works just fine, but what if I wanted to add custom fields or the featured image that may require some logic? I don’t want to include that in the template itself, but I’m not sure where I should add that logic and then pass that data to this template.

I tried creating a list-item-person.php controller file which returns the result of get_the_title(), but that results in NULL. I also tried via a filter:

add_filter('sage/template/list-item-person/data', function (array $data) {
    $data['title'] = get_the_title();
    return $data;
});

but that also results in NULL. Any ideas on the best way to handle this? In Sage 8 I would include all of this logic in the list-item-person.php template itself, but I’m obviously trying to move away from that with the new blade controllers.

Thanks for your help!

Filters and controllers are based on the WordPress Template Hierarchy, which does not include list-item-person. More details.

Data can be passed directly to partials when they are included, i.e.:

@include('partials.list-item-person', $data)

The data passed should be an indexed array. The array keys will be converted into variables when the partial is included, i.e.:

// our-team.blade.php
@include('partials.list-item-person', array('people' => $people_query))

// list-item-person.blade.php
@foreach($people as $person)
   {{$person->post_title}}
   // etc...
@endforeach

Using argument-less post-related functions (i.e. the_title()) is a little more complicated in the partials loops, and I tend to avoid it: I don’t like using functions that take no arguments and assume a global value to do their thing. If you really want to, this github issue has a technique I found for making $post available.

3 Likes

I also strongly recommend reading through Laravel’s documentation for Blade. Most of the stuff there is present in Sage, and works essentially the same. Much of Blade is not documented in Sage’s docs because it would be redundant with Laravel’s documentation.

Thank you very much for the response and for clarifying what I was confused about.

So, from what I understand, I cannot create a controller or a filter for a non-WP template blade file (i.e., list-item-person.blade.php). If I want to pass data to this partial, I will need to do it via the @include() directive, by including another argument which is an array of the data to pass.

I guess I’m still confused about where I should transform data/do logic before data gets to the list-item-person.blade.php. For example, let’s say I wanted to check the aspect ratio of the featured image of each post and add a class to the image depending on the result. Where would you suggest I put that logic?

Here is how I currently have my files set up:

// page-our-team.php
<?php
namespace App;

use Sober\Controller\Controller;

class OurTeam extends Controller
{
    public function getPeople()
    {
        $args = array(
        'post_type'              => 'person',
        'posts_per_page'         => -1,
        );
        $people_query = new \WP_Query($args);
        return $people_query;
    }
}
// page-our-team.blade.php
 @extends('layouts.app') 
    @section('content') 
        @while(have_posts()) @php(the_post())
            @include('partials.content-page') 
            @if($get_people)
                <section class="people">
                    @foreach($get_people->posts as $post)  
                        @php(setup_postdata($GLOBALS['post'] = $post)) 
                      
                        @include('partials.list-item-'.get_post_type(), $post)
                        @php(wp_reset_postdata())
                    @endforeach
              </section>
            @endif 
        @endwhile
    @endsection
// list-item-person.blade.php
@php
$image = wp_get_attachment_image_src(get_post_thumbnail_id(), 'medium');
$w     = $image[1];
$h     = $image[2];
$class = ( ($h / $w) > 0.5 ? 'portrait' : '';
$featured_image = get_the_post_thumbnail(get_the_id(), 'medium', array('class' => $class));
@endphp

<article>
    <div class="img-circle">
        @if(has_post_thumbnail())
            {!! $featured_image !!}
        @endif()
    </div>
    <h2>{{ get_the_title() }} </h2>
</article>

As you can see, I have a @php where I have logic in the list-item-person.blade.php template which doesn’t seem like the right place to have that.

Thanks again for your help!

“Best practice” here is, to some extent, whatever works for you, but the approach I tend toward is to set up and run all necessary logic (apart from simple conditionals) at the controller level, and then pass only what the Blade needs to the Blade. This means I never really use WordPress functions in my Blades—I just echo variables passed from my controller.

Usually this comes down to doing some more advanced data construction/manipulation at the controller level, instead of just passing queries on through. You can see a more in-depth example of this here: The right way to run multiple WordPress loops

In your case, you could accomplish that with an array_map on your initial query that processes the data for partials.list-item-person (In fact, you could event construct the partial name in the controller, and avoid the concatenation in your template, which would allow you to just do: @include($post['partial_name'], $post['data'])).

Thanks for the additional resource and info on how you normally do it. Much appreciated!

Hopefully I’m not causing any fuss by reopening this, but I’m just interested if you have any example of taking this approach that also allows for updating the post data via an AJAX call (or another way, I’m open to suggestions), where in my case the user is selecting some taxonomy terms on the page that determine which posts should then be displayed. I currently have a very similar setup to what @travis describes, where I’m initially doing the query to get all the posts (for initially displaying on the page) in controller, and then looping over them in my blade template.

I have a filter on the page rendered by the blade template, where the user can pick from some taxonomies which triggers an AJAX request. With AJAX I’m at a point where I can successfully get the new list of posts each time the user makes a selection. Where I’m a little lost is how exactly specific parts of either controller or the view itself (or perhaps a partial that gets data from the controller) can use this new data. Is there a way to update/refresh just a partial, passing it in new data based on the data I’m getting after each AJAX call?

Right now I’m just getting the new post data in functions php as I’m not sure how to set up my action in the AJAX differently.

Controller

public function case_studies_loop()
{
$case_study_items = get_posts([
    'post_type' => 'case-study',
    'posts_per_page'=>'10',
]);


return array_map(function ($post) {
    return [
        'heading' => apply_filters('the_title', $post->post_title),
        'excerpt' => get_field('post_excerpt', $post),
        'permalink' => get_permalink($post),
        'thumbnail' => get_field('posts_additional_fields_thumbnail', $post),
    ];
}, $case_study_items);
}

Blade Template/View

@include('partials.our-work-gallery');

Partial (our-work-gallery.blade.php)

<div class="container taxonomy-filter-response">
  <div class="row">
    @foreach($case_studies as $case_study_item)
      <a class="col-md-6 d-flex feed-col cpt-list-item" href="{!! $case_study_item['permalink'] !!}">
        <div class="text-left blog-feed-post-entry-wrapper" style="background-image: url({!! 
              $case_study_item['thumbnail'] !!})">
          <div class="post-title-wrap">
            <p class="lead white feed-entry-title post-title">{!! $case_study_item['heading']!!}</p>
          </div>
        </div>
      </a>
    @endforeach
  </div>
</div>

AJAX

$('.ajax-tax-select').change(function(e) {

  var industryID = $('#industry-term').val();
  var serviceID = $('#service-term').val();
  var industryName = $('#industry-term').text()
  var serviceName = $('#service-term').text();

  $.ajax({
    url: ajax_object.ajax_url,
    type: 'POST',
    data:{
     'action':'do_ajax',
     'fn':'update_case_studies',
     'serviceID': serviceID,
     'serviceName': serviceName,
     'industryID': industryID,
     'industryName': industryName,
    },
    dataType: 'json',
    success:function(resp){
      if(resp.success) {
         console.log('success');
         console.log('data: ', resp);
      } else {
          console.log('error');
      }
    },
    error: printError,
  });

  e.preventDefault();

});

console.log(ajax_object);

var printError = function( req, status, err ) {
  console.log( 'Hmm... something went wrong with the request: ', req, 'status: ', status, 'error: ', err );
  alert( req.responseText);
};
},

The 'action':'do_ajax', and 'fn':'update_case_studies', work with a function added in functions.php that successfully runs on each AJAX call and gets the new list of posts:

functions.php

add_action('wp_ajax_nopriv_do_ajax', 'do_ajax_function');
add_action('wp_ajax_do_ajax', 'do_ajax_function');
function do_ajax_function() { //...do stuff to get new post data

Right now this is how I"m getting the data. The ... above is where I run another query giving me the new list of posts to display. So now I have this new lists of posts in functions.php. But is there a way I can pass this data to udpate the partial, so the list of posts is displayed without a page reload?

I’m really not sure if I’m taking the right approach, and have some other issues with my AJAX call probably, but since my controller/view setup is very similar to what’s being discussed here, and I want to do it more dynamically, I wonder if anyone has any thoughts about the best way to do this. I made a post outliing some of my struggles here (sorry it is a bit messy, jumping directly to my latest reply might summarize it best).

Also feel free to close this/move it if somehow re-opening/replying is not the right thing to do here in the forums. I’m a bit new here and just wondering if anyone here on this thread might be able to point me toward a good resource/example or give me a hint on how to approach this.

They can’t.

Composers/controllers and views are PHP, which means they are run server-side. A jQuery AJAX request is executed client side. Once your AJAX request runs, the composer/controller and view is all done. If you want to change things on the page without reloading it, your only option is to manipulate the DOM with JavaScript and insert whatever HTML content you want or need. You could configure your AJAX request to return a string containing HTML instead a collection of data, and you could generate that string with the use of your blade, but that’s not an especially efficient way of doing that (remove and adding tons of stuff to the DOM in that way will trigger reflow/repaint actions, which are expensive).

In my experience when folks want to update on-page content these days they usually turn to a library that optimizes that form them, like Vue or React (although there are lots and lots of others as well).

In general though, it’s unclear to me why you need this data to be loaded and refresh via JavaScript. A page reload to change filters for a list of posts seems like a minimal compromise (if it’s a compromise at all), is much more straightforward to implement, takes advantage of a lot of tools WordPress is already giving you, and is much more accessible (since it doesn’t have to rely on JavaScript).

Thanks for explaining that. I see. I figured this might be the case. Okay, so if I go the route of a page reload when the filter changes, is it possible to use both options selected from multiple select lists to trigger the appropriate page (and data) to load? I suppose I started down the path of using Javascript since these filters are updating values in the DOM, and that seemed the way to access those values.

To move forward, I guess my first issue now is that I still don’t really know how to dynamically get the correct data to my controller (even if reloading the page altogether) or what this looks like. I’ve only ever set this up in such a way that the controller gets data once, and that data is accessed in the blade template once.

What exactly should happen when the user clicks on the taxonomy filter in order to reload the page with new data? Instead of triggering AJAX, what exactly is happening? Are you suggesting the page loads the same URL with query parameters added to represent the taxonomy, and the data is accessed via those parameters? What is triggered, and how, if not using JS? How is data passed to the controller or the blade view?

Also, can this be done in a way that utilizes an established convention for routing and URL/permalink structure so my archive template view updates along with the URL in a reliable/useful way (maybe this is one of tools already available in WP to which your’e alluding)?

Many thanks again. If you an example or advice on first steps to setting this up more the WordPress way and getting it to work with a multiple taxonomy filter, any additional help is greatly appreciated. But in any case I’ll start exploring this and trying to learn as best I can how to go about it.

For a single taxonomy, my strategy is to generate a <ul> of links, with each link pointing to whatever I want the filter to be, i.e. /category/news. You can use a little bit of JavaScript to make it behave like a drop-down menu (or if you really wanted to be clever, you could use <details> to get the behavior “for free”). Here is an example on a site I just launched: https://www.tecequipment.com/blog/

If you need to do multiple taxonomies at once, you would probably have to resort to query parameters on your URLs (AFAIK WordPress’s permalink structure can’t really handle multiple taxonomies at once out of the box). In this case you would probably want to use <select>s instead of styled <ul>. The most straightforward approach is to wrap them in a form and add a “filter” button:

<form method="get">
  <select name="tax1">
    <!-- some options -->
  </select>
  <select name="tax2">
    <!-- some options -->
  </select>
  <button type="submit">Filter</button>
</form>

Then in your controller you can just check for $_GET['tax1'] or whatever and use it if it’s been submitted.

If you really don’t like having to hit a button to filter, you could use the onchange event to submit the form for you, with a little JS:

<form id="filter-form">
  <select onchange="document.getElementByID('filter-form').submit()">
  <!-- etc... -->

(I didn’t test that, but something like that should work. I’d probably also just put the JS in it’s own JS file rather than those inline bits, and hook onto the even itself: https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onchange)

Thanks so much for the explanation @alwaysblank! I figured this might be the case, and I suppose I got pretty lost looking at lots of tutorials involving AJAX and really overcomplicated this one. I ended up scrapping my previous approach pretty much entirely and using a form with submit button for selecting the multiple taxonomies, like you described. The action on the form is set to same URL and query params get added based on select values, which is working great.

In my controller, I’m getting the query vars from the URL and passing them to my args for my custom WP_Query that runs on each submit, giving me the posts which I loop through in my blade. Not sure every part of my solution is the most elegant, but I’ve managed to get it working quite nicely with some custom numbered pagination as well. Thanks again!