How to display featured products on the homepage? (Sage 9 / WooCommerce)

Thanks man, I was able to find a solution with that train of thought.

For anybody else who might stumble here… here’s what I used:

<div class="featured-products row">
  @php
      $args = array(
        'post_type' => 'product',
        'posts_per_page' => 12,
        'tax_query' => array(
          array(
            'taxonomy' => 'product_visibility',
            'field'    => 'name',
            'terms'    => 'featured',
          ),
        ),
      );
      $loop = new WP_Query($args);
      if ($loop->have_posts()) {
        while ($loop->have_posts()) : $loop->the_post();
          wc_get_template_part('content', 'product');
        endwhile;
      } else {
        echo __('No products found');
      }
      wp_reset_postdata();
  @endphp
</div>

@shaneparsons if you want to clean things up, that would be a great candidate to move to your controller or a data filter.

I do, but controllers and filters are unfamiliar territory for me… Any advice on where to start?

@shaneparsons yep, I would just take a quick look at the docs and see if that gets you pointed in the right direction. If you still have questions after that go ahead and follow up here. Data filters will be the simplest place to start.

Using Sage’s data filters https://roots.io/sage/docs/blade-templates/
Using Controller https://github.com/soberwp/controller

2 Likes

Thank you @mmirus, I appreciate the guidance.

I’m having trouble understanding how to implement either of those concepts though, so I’ll likely just move along for the time being so I can finish the website on time.

In the meantime, if anybody has time and wishes to help me understand filters and controllers more, It’d definitely be good to know in the near future. This link helped me a bit, but I still don’t fully understand how I’d convert my code above into it.

This isn’t tested, but you’d do something roughly like this with the filter approach.

First, in filters.php, fetch your products and add them to the data that’s passed to your template:

add_filter('sage/template/front-page/data', function (array $data) {
    $args = array(
        'post_type' => 'product',
        'posts_per_page' => 12,
        'tax_query' => array(
            array(
                'taxonomy' => 'product_visibility',
                'field'    => 'name',
                'terms'    => 'featured',
            ),
        ),
    );
    $data['products'] = new WP_Query($args);
    return $data;
});

If your page doesn’t have the front-page class on the body element, replace front-page with the appropriate class.

Then in your Blade template:

@if (!$products->have_posts())
  {{ __('No products found') }}
@endif

@while ($products->have_posts()) @php $products->the_post() @endphp
  @php wc_get_template_part('content', 'product') @endphp
@endwhile
@php wp_reset_postdata() @endphp

@alwaysblank is a wizard when it comes to doing this stuff cleanly

4 Likes

The use of WC here means I don’t have a lot of suggestions: I don’t really use WC unless I absolutely have to because it makes me grumpy. :frowning:

In this case I probably would have used a partial that expects certain data instead of WC’s templating system, so i could so something like this:

// app/filters.php
add_filter('sage/template/front-page/data', function (array $data) {
    $data['products'] = array_map( function ($product) {
        $featured_image_id = get_post_thumbnail_id($product->ID);
        return array(
            'name' => $product->post_title,
            'link' => get_permalink($product),
            'image' => $featured_image_id ? wp_get_attachment_image_src($featured_image_id, 'large') : false,
        );
    }, get_posts(array(
        'post_type' => 'product',
        'posts_per_page' => 12,
        'tax_query' => array(
            array(
                'taxonomy' => 'product_visibility',
                'field' => 'name',
                'terms' => 'featured',
            ),
        ),
    )));
    return $data;
});
// resources/views/front-page.blade.php
<div class="featured-products row">
    @foreach($products as $product)
        @include('products.featured', $product)
    @endforeach
</div>

// resources/views/products/featured.blade.php
<div class="featured-product">
    <h3>{{ $product['name'] }}</h3>
    <img src="{{ $product['image'][0] }}">
    <a href="{{ $product['link'] }}"></a>
</div>
4 Likes

Wow, thanks for the elaborate examples @mmirus and @alwaysblank… Just looking through them helps clear up a lot of the confusion I had with filters.

I ran out of time today, but I’ll be testing these first thing tomorrow morning!

1 Like

Hmm, I’m getting the following errors:

Example #1:
Call to a member function have_posts() on null in ...

Example #2:
Notice: Undefined variable: products in ...
Warning: Invalid argument supplied for foreach() in ...

It feels like filters.php and front-page.blade.php aren’t communicating with each other properly and products ends up being null/undefined… Any ideas why?

Edit: I tried deleting Controller as per this discussion, but that didn’t seem to fix it.

Disregard the previous message, I figured it out… Here’s what I did:

  • implemented this example
  • changed the filter path from sage/template/front-page/data to sage/template/home/data to match my body class
  • changed WP_Query($args) to \WP_Query($args)
  • updated to soberwp/controller: "dev-master#403c46b4b31706e42a767fca618b0829b852d26c"
    • note: "2.1.0" will replace this once it is released
1 Like

It appears I spoke too soon… While deleting soberwp/controller fixed my filter issues, it appears to have broken my posts page (i.e. Class 'App' not found)… Is there a way to get filters and controllers to work in harmony? How should I go about fixing this?

Which version of Controller are you using? The latest tagged version should fix this issue.

I’m using the latest (2.0.1)

I was able to convert the filter into a controller to overcome the issue in the meantime… but the conflict with filters is still a cause for concern. What does this mean for the default filters in filters.php? Are they working as intended?

It looks like it hasn’t been tagged yet, but this commit should be 2.1.0 which I think fixes that problem: https://github.com/soberwp/controller/commit/2ccaf4fb25c31cf0e5a70593a6a51433b2c784fc

@withjacoby probably has better input re: Controller than I do though.

Perfect! Until 2.1.0 is released, it seems like using dev-master fixes the issues.

I’d recommend against defining dev-master in your composer.json, since it means that every time you run composer update it’ll pull whatever’s at the tip of the master branch—which may break your site. Instead, if the head of master is working for you I recommend locking it to that commit, i.e.:

"soberwp/controller": "dev-master#403c46b4b31706e42a767fca618b0829b852d26c"

Normally you can leave this kind of behavior to composer.lock, but when dealing w/ non-tagged stuff, this makes me feel a lot safer.

2 Likes

Tagging 2.1.0 this coming Sunday, but everything @alwaysblank said above is exactly right.

3 Likes

2.1.0 is tagged. Changelog

4 Likes

Probably it’s better to use wc_get_products() instead of get_posts(). It’s the right way according to Woo and also here: https://cfxdesign.com/create-a-custom-woocommerce-product-loop-the-right-way/

1 Like