官术网_书友最值得收藏!

  • Drupal 9 Module Development
  • Daniel Sipos Antonio De Marco
  • 1677字
  • 2021-06-11 18:36:06

Theming our Hello World module

The HelloWorldController we built in Chapter 2, Creating Your First Module, currently uses a service to retrieve the string to be used as the salutation and then returns a simple markup render array with it. Let's imagine now that we want to output this message but wrap it in our own specific markup. To make an easy thing complicated, we want to break up the salutation string into parts so that they can be styled slightly differently. Additionally, we want to allow others to override our theme using suggestions that depend on whether or not the salutation has been overridden via the configuration form. So, let's see how we can do these things.

To get things started, this is the markup we are after:

<p class="salutation">

  Good morning <span class="salutation—target">world</span>

</p>

The first thing we need to do is to define our own theme hook capable of outputting this. To this end, we implement hook_theme():

/**

* Implements hook_theme().

*/

function hello_world_theme($existing, $type, $theme, $path) {

  return [

    'hello_world_salutation' => [

      'variables' => ['salutation' => NULL, 'target' => NULL,       'overridden' => FALSE],

    ],

  ];

}

For now, we only return one theme hook called hello_world_salutation, which takes the variables you can see. Each of them has a default value in case one is not passed from the client (through the render array). The first two are obvious, but we also want to have a flag on whether or not the salutation has been overridden. This will help with the theme hook suggestions.

By default, if we don't specify a template filename, this theme hook will look for a Twig template with the name hello-world-salutation.html.twig inside the /templates folder of our module. Since this is good enough for us, let's go ahead and create it:

<p {{ attributes }}>

  {{ salutation }}

  {% if target %}

    <span class="salutation—target">{{ target }}</span>

  {% endif %}

</p>

Twig notation is easy to understand. {{ }} means that we are printing a variable with that name (which can even be a render array) and {% %} refers to control structures, such as if statements or loops. Do check out the Twig documentation (https://twig.symfony.com/) for more information if you are unsure.

Note

There are some great ways to debug what values end up being printed in the Twig template. You can use the native Twig dump() function, which will output things using the PHP var_dump(), or you can install the Devel module and use the kint() function, which will format things in a more readable way.

We wrapped the target variable in an if statement so that if by any chance it's missing, we don't print an empty span tag. It's best practice to have your template mirror the possibilities of the theme hook being called with the defaults.

Finally, we also have an attributes array, which we are printing on the wrapper. We did not define this, but each theme hook comes with it. The variable is an Attribute object, as we discussed earlier, which gets printed into a string of the inpidual attributes.

Now, instead of printing the class we want directly in the template, we will use the preprocessor to make things more dynamic.

So let's implement the preprocessor next:

/**

* Default preprocessor function for the hello_world_salutation theme hook.

*/

function template_preprocess_hello_world_salutation(&$variables) {

  $variables['attributes'] = [

    'class' => ['salutation'],

  ];

}

As I mentioned earlier, at this stage we are still working with an array of attributes. The theme system will turn it into the Attribute object before rendering the template, which in turn will know how to handle that.

Other modules or themes can now implement this preprocessor themselves and change the classes (or any other wrapper attributes) as they need. Had we hardcoded the class in the template file, they would have had to override the entire template—which, although still a viable option, is overkill if you just need to add a class.

Now, let's allow themers to have different implementations for our salutation message depending on whether or not it is overridden by an admin. I know this particular example is quite a stretch in terms of usefulness, but it allows us to demonstrate the approach, which is very useful.

So, as we discussed, we can define a suggestion for our theme hook:

/**

* Implements hook_theme_suggestions_HOOK().

*/

function hello_world_theme_suggestions_hello_world_salutation($variables) {

  $suggestions = [];

  if ($variables['overridden'] === TRUE) {

    $suggestions[] = 'hello_world_salutation__overridden';

  }

  return $suggestions;

}

If you remember, our theme hook had the overridden variable, which can be used for this flag. So, in our theme hook suggestion implementation, we check for it, and if it's true, we add our suggestion. This function gets called on the fly at the time of rendering and the most specific suggestion encountered is used if, of course, the salutation is overridden. If that is the case, it will try hello_world_salutation__overridden, and if not found, it will fall back to hello_world_salutation, which exists.

Themes can now have two different templates that render the salutation in two different ways, depending on whether or not the message has been overridden:

  • hello-world-salutation.html.twig
  • hello-world-salutation--overridden.html.twig

Okay, our theme hook is now ready for use. Let's use it.

Since our theme template breaks our salutation message up into pieces, and can even receive the overridden flag, it will not be enough to just use this theme hook in the HelloWorldController. Instead, we will need to go back to our service and have it return the render array responsible for outputting the salutation. After all, business logic knows the structural aspects of how a certain component needs to be rendered. Theming just needs to style and alter that based on the flexibility offered by a good functional implementation.

However, let's not override the getSalutation() method on the service, but instead create a new one called getSalutationComponent(). This will then return the render array, which can output the whole thing:

/**

* Returns the Salutation render array.

*/

public function getSalutationComponent() {

  $render = [

    '#theme' => 'hello_world_salutation',

  ];

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

  $salutation = $config->get('salutation');

  if ($salutation !== "" && $salutation) {

    $event = new SalutationEvent();

    $event->setValue($salutation);

    $this->eventDispatcher->dispatch(SalutationEvent::EVENT,     $event);

    $render['#salutation'] = $event->getValue();

    $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'] = $this->t('Good morning');

    return $render;

  }

  if ((int) $time->format('G') >= 12 && (int) $time-  >format('G') < 18) {

    $render['#salutation'] = $this->t('Good afternoon');

    return $render;

  }

  if ((int) $time->format('G') >= 18) {

    $render['#salutation'] = $this->t('Good evening');

    return $render;

  }

}

This is how it will look. We start by creating the render array that uses our new theme hook. Then, we look in the configuration object and if there is a message stored in there, we use that, set the overridden flag to true, and return the render array. You'll note that we didn't set a target, which means that it won't get printed in the template file (as expected). If, however, it is not overridden, we proceed with our previous logic and set the message dynamically while keeping the target the same. You can easily see how this now maps to what the theme hook and template expect for the different cases.

A couple of points to be made before going forward. First, I want to reiterate the warning that due to things such as caching, the dynamic salutation message won't actually work as expected. We'd need to set some cache metadata to prevent this render array from being cached in order for it to work. However, we will see more on that in Chapter 11, Caching. Second, you will have noted that the variables we defined in the theme hook show up preceded by a # sign, as if they were properties known to the render system. As I said earlier, they are in fact not properties, but they are known by the theme system as variables because we defined them as such. So, it's important to be able to distinguish these kinds of things when reading code that you didn't write yourself. There are, of course, many properties you don't know off the top of your head, but with experience, you'll be able to read the code, figure out the source, and understand what it means. In this, the difference between a good developer and a great one is the ability of the latter to figure things out by reading the source code rather than relying on documentation. And third, you can see quite some code duplication here between the two methods. This is not great but I'll leave you to come up with more creative ways of refactoring this service to avoid this.

Now, we have a service that can return a string representation of our message, and a fully-fledged renderable component. It follows that we edit our Controller and have it return this component instead of its own render array:

/**

* Hello World.

*

* @return array

*/

public function helloWorld() {

  return $this->salutation->getSalutationComponent();

}

You'll note that we don't need the #markup property anymore, as we have our own render array. For the salutation token and the block we created, let's not use this component but rely on the string version. This way we keep both options in the code for you to see.

主站蜘蛛池模板: 三河市| 碌曲县| 伊宁市| 凤翔县| 汾阳市| 邢台县| 区。| 彭州市| 灵丘县| 南充市| 清水河县| 芦山县| 通化市| 衡山县| 孟州市| 宝坻区| 南漳县| 吉木萨尔县| 海安县| 科技| 靖江市| 鄂州市| 神农架林区| 皮山县| 象山县| 平邑县| 商城县| 宜昌市| 北海市| 沙洋县| 安溪县| 朔州市| 新乐市| 枣庄市| 溧水县| 武川县| 特克斯县| 湖北省| 蒙阴县| 长海县| 吐鲁番市|