New ways to display “related content” with Views in Drupal 8 & 9

Summary:
This article demonstrates how, using Drupal Views plus one contributed module, Views Contextual Range Filter, you can make pages in D8/9 that display content that is related to the main content in ways more advanced than previously possible.

An example of a “related content” side bar on besttime2travel.com

In particular we’ll demonstrate how to:

  • display similar products that are cheaper or up to 20% more expensive than the product shown

Blocks of related content items displayed next to the main content make for a richer, more attractive UX and longer dwell times. That’s why online stores and sites to book travel & accommodation have these types of pages in spades:

  • you are booking a flight to Sydney and on the side Sydney’s best and/or cheapest and/or nearest hotels are shown

Drupal 8/9 caters for some of the above use-cases very well, provided the attribute that connects/relates the main content to the side content is a taxonomy term.

So if your content type has a field that is, say, one of various available colours, or a size (S, M, L, XL) or a country name, or some product category (eg. underwear, pants, jackets) and you’ve implemented the allowed values for this field through taxonomy terms, then you’re good… 👍
You can realise this functionality through core Views. You’ll find plenty of tutorials on the Internet. Or if you’re not comfortable with Views — as it can be a bit of a cockpit to operate — then there are modules out there that may suit your specific use-case. Like Similar by Terms perhaps.

However, if you do not have a taxonomy term on your content type, e.g. your field is a text or a number or something else that isn’t enumerable and therefore not implementable as a taxonomy, like a dimension (height, width), a weight, a date/time range, location coordinates (latitude, longitude), a string, or a price, then you’re out of luck with Drupal 8/9 core. 👎

Thanks to a feature recently added to module Views Contextual Range Filter you can now easily realise those more sophisticated “related content” block use-cases.
Let’s talk you through some of those with a few examples.

Views contextual filters
The simplest way to create a block of items that are “related” or “similar” to the main page content is through a View with a contextual filter, aka a Views argument. Or to be more precise through a View that is missing that argument, thus falling back on a Default Contextual Filter.

Let’s say the page in question shows a node of content type Product. Also assume that Product has a Product Category field in the form of a taxonomy term (feel free to interpret Category loosely… think T-shirt size, colour, type of cuisine, season/year, country…).
Yay! The fact that you have a taxonomy term means that even with Views out-of-the-box, you can easily build a block of say 10 products (with or without pager) that share that same Product Category.

To make this yourself, create or edit a View with a Block display. Visit the Advanced section of the Views UI (upper right). Next to Contextual Filters, click Add. In the modal that pops up, type the first few letters of the Product Category field name to bring up a shortlist and tick the check box in front of the field name (do not select the :delta variety of the field).

A new modal opens with a panel titled When the filter value is NOT in the URL. Magic starts to happen after you tick Provide default value and select Taxonomy term ID from URL.

What this means is that after this Views block is configured to display on a content page displaying a product (eg …/node/123), Views will retrieve the Product Category field value of that node and filter the block View by that Product Category. Hence only products that are in the same category as the main product are shown in the side bar (or wherever you decide to show this block).

Because the block is populated by a View, you can add extra filters and sorts to your heart’s content, configure a pager, or show a fixed number, say 5, of the newest products in the category. Or the 10 cheapest ones. Or the 20 most-viewed. Etc. etc.

Tip: exclude the main content item from the related content items
What you achieved above is already pretty useful. One niggly bit. The related product list also contains the main product being viewed. How to suppress that second appearance? Views has had for years a handy exclusion feature that isn’t very well known. This too is invoked by adding a Contextual Filter. However this time Add the Content ID. Again select Provide default value, but now select Content ID from URL from the drop-down. Finally, all the way at the bottom, open the More panel. Check Exclude. And Bob’s your uncle.

All of the above can be done with core Views. No additional modules required. Things get trickier when you move beyond the comfort of taxonomy terms, like the Product Category above.
Attributes like dimension (height, width), weight, date range, proximity to a location, text, or price… none of these are taxonomy terms.

Example 1: Filtering by price range relative to the main content
Building on the “similar products” block created above, how would you enhance the View to display products that are a) similar (in the same category) and b) cheaper or up to 20% more expensive than the product shown?

Point a) you already realised. For point b) you need a way to:
1) relate to prices and price ranges, which are neither taxonomy terms nor fixed values
2) deal with mathematical expressions (“no more than +20%”).

The module Views Contextual Range Filter provides both 1) and 2).

First, in the Advanced section of your Views UI (upper right), add the Price field as a Contextual Filter. We’ll get back to finalising its configuration later. For now save the View.
In order to contextually filter by price range (e.g. anything up to $50), as opposed to a specific price (exactly $49.95), we employ Views Contextual Range Filters.

Install (use composer if you wish) and enable the Views Contextual Range Filter. Visit its configuration page at ../admin/config/content/contextual-range-filter. You’ll find your contextual filter named Price in the list.
Check the box in front of it and save.
Go back to the Views UI, Advanced section and click the Price contextual filter.
Under Provide default value, you’ll find a new option: PHP Code.
[Aside: this is actually an old option… It was available in Views D7 but was dropped from Views in D8]

In the text area enter:

if (isset($entity['node'])) {
$value = $entity['node']->field_price->getString();
$value *= 1.20; // add 20%
return "--$value";
// double hyphen indicates range, i.e. anything up to $value
}

Hit Apply. Save the View.

Note 1) check quotes, they should be plain apostrophes and double quotes — but they may come out differently when copied from this text.
Note 2) if the machine name of your field isn’t “price”, but say, “service_fee” adjust the code above accordingly: change “field_price” to “field_service_fee”.

With this PHP code snippet we have now created a filter that retrieves the price of the main product being viewed, adds 20% and uses that as the upper limit to filter related products by price range: products in the same category that are cheaper, or up to 20% more expensive.

Example 2: Filtering by proximity relative to the main content
The same PHP Code default filter serves us well when filtering by proximity. If your content type has a Geofield field on it to hold the location of your content items, such as points of interest, hotels, bars etc., then the following code snippet will ensure the related content block only shows items that are within 10 kilometres of the main content item on the screen:

if (isset($entity['node'])) {
$geofield = $entity['node']->field_location->getValue()[0];
return $geofield['lat'] . ',' . $geofield['lon'] . '<10km';
// use '<10mi' for ten miles radius
}

To see this code snippet in action visit Best Time 2 Travel (albeit with a different proximity radius). Pick any place and you’ll see nearby locations in the right side bar.

Note that the range operator ‘ — ‘ wasn’t used in the above code. This is because Geofield has its own format for expressing proximity to a location and this format is what is returned above.
Note also that if your Geofield is called something different from ‘location’ you need to adjust the name field_location in the snippet accordingly.
Because Geofield has its own format, different from the contextual range operator format, do not enable (i.e. untick) the range version of the contextual filter at …/admin/config/content/contextual-range-filter

Example 3: Filtering by content creation date relative to the main content
Using the Authored On field of a node, like a blog post or article, you can show in a block on the same page any related content that was published within, say, +/- 5 days of the date that the post being viewed was authored.
Here’s the code snippet for that:

if (isset($entity['node'])) {
// Get the node creation timestamp in seconds
$created = $entity['node']->getCreatedTime();
$five_days = 5 * 24 * 60 * 60; // in seconds
$range = date('Ymd', $created — $five_days) . '--' .
date('Ymd', $created + $five_days);
return $range;
}

Conclusion
Using Drupal core Views and one contributed module you can easily create “related” or “similar” side bar content blocks in Drupal 8/9 that will make your web pages more appealing, excite your visitors and increase dwell time.

🏁

Click here to sign up with Medium and I’ll receive a portion of the membership fees.

Software developer, entrepreneur, happy mapper.