If you are dealing with blog or news portal, article excerpts may be used in multiple places - for example: on blog category page, in "see also" section of article, on homepage as "featured article", in sidebar as "latest articles", on author page as "articles written by author".
Now we are running into a problem. Excerpts in various places should display different properties of the article. Let's think of few examples - featured article on the homepage should display title, thumbnail and category. Excerpt on category page should not show category (we know what category we browse), just title and thumbnail. And sidebar with latest articles should contain only title to save place.
Each of these times, article excerpt should be handled by the same Twig component. Reusability and modularity of templates are important. In this article, I will describe how to create a reusable article excerpt Twig component.
Article excerpt component #
Here's an example of the article excerpt component. Depending on options passed to it, it will display different properties of the article - like title, thumbnail or text intro. These are not set in stone and you can add yours or modify existing HTML markup used for outputting article properties. What's important is overall structure of the component.
{% import '_system/macros' as m %}
{% if article is defined %}
{% set settingsDefault = {
attributes: ['title'],
truncateTitle: 60,
truncateIntro: 20,
transform: {
width: 300,
height: 200,
format: 'jpg'
}
} %}
{% set settings = settings|default(settingsDefault) %}
{% set settings = settingsDefault|merge(settings) %}
<div class="article-excerpt {{settings.class ?? null}}">
{% for attribute in settings.attributes %}
{% switch attribute %}
{# title #}
{% case 'title' %}
<a href="{{article.url}}" class="article-excerpt__title">
{{m.truncateChars(article.title, settings.truncateTitle)}}
</a>
{# date #}
{% case 'date' %}
{% if article['postDate'] is defined%}
<time class="article-excerpt__date" datetime="{{date|date('yy-m-d')}}">
{{article.postDate|time('full')}}
</time>
{% endif %}
{# intro #}
{% case 'intro' %}
{% if article['intro'] is defined and article['intro'] is not empty %}
<div class="article-excerpt__intro">
{{m.truncateChars(article['intro'], settings.truncateIntro)}}
</div>
{% endif %}
{# categories #}
{% case 'category' %}
{% if article['category'] is defined and article.category.exists() %}
<a href="{{category.one().url}}" class="post-single__category">{{category.one().title}}</a>
{% endif %}
{# read more link #}
{% case 'more' %}
<a href="{{post.url}}" class="article-excerpt__more">
{{'read_more'|t}}
</a>
{# thumbnail #}
{% case 'thumbnail' %}
{% if article['thumbnail'] is defined and article.thumbnail.exists() %}
<a href="{{article.url}}">
<img src="{{article.thumbnail.getUrl(settings.transform)}}" class="article-excerpt__thumbnail">
</a>
{% endif %}
{% endswitch %}
{% endfor %}
</div>
{% endif %}
Here is how the article excerpt component should be used - it needs to be included with options passed to it:
{% for singleEntry in entries.all() %}
{% include 'article-excerpt' with {
article: singleEntry,
settings: {
attributes: ['title', 'intro', 'more'],
class: 'featured',
}
} only %}
{% endfor %}
Passing parameters to excerpt component #
Article excerpt requires element object passed as an article
to the component in order to display anything - usually, it would be single entry
or category
. It also needs options - to decide which article properties should be displayed and in what order.
If no options are provided, default ones defined inside the component are used. If you provide your options, they will be merged with default options - and your values will overwrite specific values in defaults. Therefore there is no need to always define all options - just these which you want to change from their default values.
Note the only keyword used in include
tag - thanks to it we can isolate the component from outside variable context and prevent any unforeseen complications.
Here is the options list:
attributes
- an array of article attributes to display. Order is taken into account. All possible attributes are listed withinswitch
tag inside article excerpt component, with their names ascase
values.truncateTitle
- number of characters to which title of the element (article) should be truncated (if we want to display it). The component uses truncating macro described in this article, but you can switch to any other method. It's important to truncate text values - excessively long text might break our layout and we never know what content authors might type in.truncateIntro
- number of characters by which intro of the element should be truncated (if we want to display it).transform
- image transform settings for thumbnail - will be used if we want to display a thumbnail.class
- by using this option you can set an additional class on article excerpt to tweak it looks.
Component structure #
How does it work exactly? Let's take a closer look at Twig code.
First, we check if element
is defined. If it is not, there is nothing to display. Then, there are default options and code that merges them with options provided when including component. Lastly, there is {% switch %}
and {% for %}
tag - thanks to it we can loop through elements and display them in the correct order.
Note that each element has if
tag checking if such field/attribute of the article exists. Thanks to that, our component will be robust and won't throw errors in case someone uses it with an element that does not have a specific field/attribute. In the case of text values, we also check if text not empty. In case of element objects (like category or image) we use .exists()
method to check if such an element exists.
One more thing worth noting is the usage of BEM CSS class notation. It's perfect for modular webpages.
Positioning of attributes #
The attributes order is decided by their order in elements
array. What about the situation when we want to have two columns? For example, one column for thumbnail and second for other attributes like title, intro, and category.
Popular WordPress themes usually use position: absolute
for that. This solution however, has disadvantages - you need to make sure that all attributes have proper positioning and don't overlap.
I recommend the usage of display: grid
. Here is example setup:
.article-excerpt{
display: grid;
grid-template-columns: [left] auto [right] auto;
grid-template-rows: auto;
grid-column-gap: 1rem;
}
.article-excerpt__thumbnail{
grid-column: left;
grid-row: 1/100;
}
.article-excerpt__title, .article-excerpt__intro, .article-excerpt__date{
grid-column: right;
}
There are two columns - left and right. Thanks to grid-template-rows: auto
we don't need to explicitly set up a number of rows - this CSS is supposed to be universal and work properly regardless of how many attributes of the article we plan to display.
Thumbnail is on left column - and it stretches from row 1 to 100. 100 is just some large number that we are sure is bigger than the maximum amount of article properties that can be displayed. Thanks to that, properties on the right column will be properly displayed, stacking on top of each other - and their position will not depend on how big thumbnail is. I guess it can be called a bit of hack.