Defining contextual links

Contextual links are a bit more complicated than the other types of links we've seen before, but nothing is too challenging for us. Let's take a look at how we can add contextual links to our salutation component so that users can navigate to the configuration form via a contextual link.

First, we will need to create the .links.contextual.yml file and define the link:

hello_world.override:
title: 'Override'
route_name: hello_world.greeting_form
group: hello_world

Nothing too complicated here. Again, we have a title link and a route_name. Additionally, we have a group key, which indicates the group name that this link will be a part of. We will reference this later.

Next, we will need to alter our theme hook template file because the contextual links are printed in a title_suffix variable that is available in all theme hooks and is used by various modules to add miscellaneous data to templates. The Contextual module is one such example. So, we will need to get that printed. This is what it will look like now:

<div {{ attributes }}>
{{ title_prefix }}
{{ salutation }}
{% if target %}
<span class="salutation--target">{{ target }}</span>
{% endif %}
{{ title_suffix }}
</div>

You'll note that we included the title_prefix variable to keep things nice and consistent. Usually, these will be empty, so no need to worry.

Finally, comes the more complex part—one that may even change in the future, but, for now, this is how we have to proceed.

Our hello_world_salutation theme hook defines individual variables rather than a render element. In such cases, inside a general preprocessor, the Contextual module looks at the first defined variable to check whether there are any contextual links defined. In the case of theme hooks that use render elements, it checks that element instead.

This is what the contextual links definition looks like inside a render array and also what we need to add for our use case:

'#contextual_links' => [
'hello_world' => [
'route_parameters' => []
],
]

Here, we defined that the hello_world group of contextual links should be rendered here. Also, we specified an array of route parameters, which, in our case, is empty. This is because, typically, the contextual links are just that—contextual, meaning that they usually work with an entity or something that has an ID, and its route requires a parameter. So, here is where we can supply that because as we've seen, the *.links.contextual.yml definition is static and generic.

The #contextual_links property is, in fact, a render element itself that gets replaced with another render element (contextual_links_placeholder). The latter outputs a simple text placeholder in the HTML, which gets replaced with the correct links via JavaScript.

So, now that we know how to make use of the contextual links, let's alter our Hello World salutation component to make use of this. This is what it looks like now:

public function getSalutationComponent() {
$render = [
'#theme' => 'hello_world_salutation',
'#salutation' => [
'#contextual_links' => [
'hello_world' => [
'route_parameters' => []
],
]
]
];

$config = $this->configFactory->get('hello_world.custom_salutation');
$salutation = $config->get('salutation');

if ($salutation != "") {
$render['#salutation']['#markup'] = $salutation;
$render['#overridden'] = TRUE;
return $render;
}

$time = new \DateTime();
$render['#target'] = $this->t('world');

if ((int) $time->format('G') >= 00 && (int) $time->format('G') < 12) {
$render['#salutation']['#markup'] = $this->t('Good morning');
return $render;
}

if ((int) $time->format('G') >= 12 && (int) $time->format('G') < 18) {
$render['#salutation']['#markup'] = $this->t('Good afternoon');
return $render;
}

if ((int) $time->format('G') >= 18) {
$render['#salutation']['#markup'] = $this->t('Good evening');
return $render;
}
}

The main changes are as follows. First, we have already defined the #salutation variable at the top and made it into a render array. As you remember, these are highly nestable. In this render array, we added our #contextual_links render element. Second, every time we need to set the value for the salutation string below, we do so in a #markup element this time, because, as we saw in the previous chapter, we need a property that defines how the render array gets rendered.

So now if you clear the cache and navigate to the /hello page, you should be able to hover over the salutation and see the contextual links icon pop up and contain our Override link. You should land on the salutation configuration form when you click on the link and also note a destination query parameter in the URL:

The destination query parameter is used by Drupal to return the user to the page they previously were on after they submitted a form on that page. This is a handy trick to keep in mind, as it is a very popular UX technique.