The Flexibility of Drupal 8

A presentation at BioRAFT Drupal Nights in January 2016 in Cambridge, MA, USA by Michael Miles

Slide 1

Slide 1

The Flexibility of Drupal drupalnights.org/events/2015/michael-miles-flexibility-drupal BioRAFT Drupal Nights

Slide 2

Slide 2

Michael Miles From: Boston, MA USA Work: Genuine @WeAreGenuine(.com) Exp: Working with Drupal since 2008. Acquia Grand Master. Acquia MVP. Twitter: @mikemiles86 Drupal.org: mikemiles86 All the Places: mikemiles86 mike­miles.com

Slide 3

Slide 3

Flexibility? Many manipulation methods Supports different skillsets Do what works for you

Slide 4

Slide 4

Focus of this Session 8 methods of manipulation Change title, destination & display of menu tabs Menus are universal

Slide 5

Slide 5

Session Environment A standard Drupal 8.0.1 install Bartik sub­theme with Slate color Not live. (blame Murphy)

Slide 6

Slide 6

Environment starting point. A boring gray Drupal 8.0.1 site, with 8 menu tabs.

Slide 7

Slide 7

Environment end point. Colorful menu tabs, each on altered using a different method

Slide 8

Slide 8

#1. Core Admin UI Change data by clicking buttons. “Easy” to use No dev skills needed Capabilities are limited

Slide 9

Slide 9

Login as admin. Go to Structure, Menus and edit the main menu. Then edit the first menu link. Able to change the title and destination using Admin interface.

Slide 10

Slide 10

Have changed the title and destination of first menu link. Was not able to change the display with Admin UI.

Slide 11

Slide 11

#2. Modules Change data by extending Drupal core. Many options available Can build your own Require long term maintence

Slide 12

Slide 12

Want to extend core to change display of second menu link. Find, download and install the Menu Link Attributes module from drupal.org. Use Drupal Admin as in last method to alter menu tab. Module extends the form to allow adding HTML attributes such as inline styles.

Slide 13

Slide 13

Used a module to extend Drupal to change the text and background colors of second menu tab.

Slide 14

Slide 14

#3. Templates Change data by controling rendered HTML. Almost everything is templated Change HTML/Twig code Requires a custom theme

Slide 15

Slide 15

Want to change third menu tab using an overriden template.

Slide 16

Slide 16

Can use development configuration to debug theme and template data for main menu.

Slide 17

Slide 17

<!— THEME DEBUG —> <!— THEME HOOK: ‘menu__main’ —> <!— FILE NAME SUGGESTIONS: * menu—main.html.twig x menu.html.twig —> <!— BEGIN OUTPUT from ‘core/themes/classy/templates/navigation/menu.html.twig’ —> <ul class=”clearfix menu”> <!— // … —> <li class=”menu-item menu-item—active-trail”> <a href=”/node/3” data-drupal-link-system-path=”node/3” class=”is-active”>Templates </li> <!— // … —> </ul> <!— END OUTPUT from ‘core/themes/classy/templates/navigation/menu.html.twig’ —>

Use theme debug info for main menu. Tells the template used (line 5) and where to locate it (line 7). Provides template suggestion on how to override for just the main menu (line 4).

Slide 18

Slide 18

Copy the menu twig template from classy theme. Save as menu­­main.html.twig in templates directory of custom “subBartik” theme.

Slide 19

Slide 19

{% macro menu_links(items, attributes, menu_level) %} {# … #} {% for item in items %} {# … #} <li{{ item.attributes.addClass(classes) }}> {% if item.title == ‘Templates’ %} <a href=”node/9” style=”color:#F00;background:#0F0”>TEMPLATES ALT</a> {% else %} {{ link(item.title, item.url) }} {% endif %} {% if item.below %} {{ menus.menu_links(item.below, attributes, menu_level + 1) }} {% endif %} </li> {% endfor %} {# … #} {% endmacro %} themes/SubBartik/templates/menu—main.html.twig Alter markup in template override. For menu list item markup (lines 5 ­ 14). If ‘Templates’ is the title of the current menu item (line 6), then use a hardcoded HTML link (line 7).

Slide 20

Slide 20

Clear the Drupal cache to see the changes. When rendering, Drupal uses template override for main menu. Custom logic alters the ‘Templates’ tab to displays a different styled link.

Slide 21

Slide 21

#4. Custom CSS Change data by controlling display. Target specific elements Control inclusion with a library Requires custom theme or module

Slide 22

Slide 22

Can alter a menu tab using custom CSS, by adding css to a library in a custom theme.

Slide 23

Slide 23

menu-alter: version: VERSION css: css/menu_alter.css: {} themes/SubBartik/subbartik.libraries.yml Modules/Themes define libraries in a [name].libraries.yml file. Defining a library named ‘menu­alter’ (line 1) and the css files it includes (lines 2 ­3)..

Slide 24

Slide 24

.menu—main .menu li a[data-drupal-link-system-path=”node/4”] { background: #0000FF; themes/SubBartik/css/menu_alter.css CSS selector chains can become complicated. Targeting a link item in the main menu list, based on data attribute value. Anything that matches will have background color changed.

Slide 25

Slide 25

name: Sub Bartik type: theme base theme: bartik description: ‘A sub-theme of Bartik’ version: VERSION core: 8.x libraries: - subbartik/menu-alter themes/SubBartik/subbartik.info.yml Themes can include libraries globally by adding to *.info.yml file. Including ‘menu­alter’ library globally for sub­theme (lines 7 ­ 8).

Slide 26

Slide 26

Site is using custom theme so it includes the library, which includes the custom css. The selectors target the ‘Custom CSS’ tab and changes the background color.

Slide 27

Slide 27

menu-alter: version: VERSION css: css/menu_alter.css: {} menu-alter-main: version: VERSION css: css/menu_alter_main.css: {} themes/SubBartik/subbartik.libraries.yml A *.libraries.yml file, can include many libraries. Each library has a unique name and included files. Add a new ‘menu­alter­main’ library (line 5 ­ 7).

Slide 28

Slide 28

.menu—main .menu li:nth-child(4) a { color: #FFF; } themes/SubBartik/css/menu_alter_main.css A simplier selector chain for targeting the link of the fourth main menu list item.

Slide 29

Slide 29

{{ attach_library(‘subbartik/menu-alter-main’) }} {% import _self as menus %} {{ menus.menu_links(items, attributes, 0) }} {% macro menu_links(items, attributes, menu_level) %} {% import _self as menus %} {% if items %} {% if menu_level == 0 %} <ul{{ attributes.addClass(‘menu’) }}> {% else %} <ul class=”menu”> {% endif %} {% for item in items %} {# … #} themes/SubBartik/templates/menu—main.html.twig Include libraries for specific templates by using the ‘attach_library’ Twig function. Include the ‘menu­ alter­main’ library will only when rendering the main menu.

Slide 30

Slide 30

Drupal renders main menu using template override. Template includes function to attach library (and css) on the page. Custom CSS causes the text in the tab to display as white.

Slide 31

Slide 31

#5. Custom JavaScript Change data by manipulating DOM elements. Change based on actions Control inclusion with a library Dependent on client browser

Slide 32

Slide 32

Can alter a menu tab using custom JavaScript, by including it in a library in a custom module.

Slide 33

Slide 33

Like themes, modules need to define libraries in a *.libraries.yml file. Will add a library to custom “drupalflex” module to include custom JavaScript.

Slide 34

Slide 34

menu-alter: css: css/menu_alter.css: {} js: js/menu_alter.js: {} dependencies: - core/jquery - core/jquery.once - core/drupal modules/drupalflex/drupalflex.libraries.yml A library can contain many files as well as, depend on other libraries. Library contains css files (lines 2 ­ 3), javascript files (lines 4 ­ 5) and dependencies on other libraries (lines 6 ­ 9).

Slide 35

Slide 35

(function ($, Drupal) { “use strict”; Drupal.behaviors.drupalFlexMenuAlter = { attach: function (context) { $(‘.menu—main ul.menu li a’).each(function(){ if ($(this).attr(‘href’) == ‘/node/5’) { $(this).addClass(‘yellow-menu’); $(this).attr(‘style’, ‘color:#000;’); $(this).attr(‘target’, ‘_blank’); $(this).attr(‘href’, ‘/node/9’); $(this).text($(this).text() + Drupal.t(’ Alt’)); } }); } } })(jQuery, Drupal); modules/drupalflex/js/menu_alter.js Drupal behaviors are like jQuery document ready events. Drupal executes them when DOM loads. Create a custom behavior (line 3). Loop through each link item in the main menu (line 5). If link points to node/5 (line 6) alter display and destination (lines 7 ­ 11).

Slide 36

Slide 36

<?php /** * Implements hook_page_attachments(). */ function drupalflex_page_attachments(array &$attachments) { $attachments[‘#attached’][‘library’][] = ‘drupalflex/menu-alter’; } modules/drupalflex/drupalflex.module Modules need to use PHP code to include libraries onto pages. Use an instance of the page_attachments hook to include the menu­alter on every page.

Slide 37

Slide 37

When Drupal loads the page it will invoke the hook instance in the “drupalflex” module. This will tell Drupal to include the library the contains the custom JavaScript. After DOM loads, JavaScript gets executed and alters the menu tab.

Slide 38

Slide 38

#6. Hooks Change data by interacting with core code. Provided by core and modules Requires custom theme or module Requires PHP skillset

Slide 39

Slide 39

Can alter a menu tab by using a hook in a custom theme or module.

Slide 40

Slide 40

<!— THEME DEBUG —> <!— THEME HOOK: ‘menu__main’ —> <!— FILE NAME SUGGESTIONS: * menu—main.html.twig x menu.html.twig —> <!— BEGIN OUTPUT from ‘core/themes/classy/templates/navigation/menu.html.twig’ —> <ul class=”clearfix menu”> <!— // … —> <li class=”menu-item menu-item—active-trail”> <a href=”/node/6” data-drupal-link-system-path=”node/6” class=”is-active”>Hooks </li> <!— // … —> </ul> <!— END OUTPUT from ‘core/themes/classy/templates/navigation/menu.html.twig’ —>

Theme debug information gives a suggestion of what “theme” hook to implement (line 2).

Slide 41

Slide 41

// Implements hook_preprocess_HOOK(). function subbartik_preprocess_menu__main(&$variables) { // Loop through all menu tabs. foreach ($variables[‘items’] as &$menu_tab) { // Current tab pointing to node/6 ? if ($menu_tab[‘url’]->toString() == ‘/node/6’) { // Change location. $menu_tab[‘url’]->setRouteParameter(‘node’, ‘9’); // Existing attributes? if (!$attributes = $menu_tab[‘url’]->getOption(‘attributes’)) { $attributes = array(‘style’ => ”); } elseif (!isset($attributes[‘style’])) { $attributes[‘style’] = ”; } // Add custom styling. $attributes[‘style’] .= ‘color:#FFF;background:#00F;’; // Add back modified attributes. $menu_tab[‘url’]->setOption(‘attributes’, $attributes); } } } themes/SubBartik/subbartik.theme Themes place hooks in a *.theme file. Creating an instance of the preprocess_menu__main hook (line 4). Looping through all items in the main menu (line 6). If menu item points to node/6 (line 8), then alter the destination and styling (lines 10 ­ 21).

Slide 42

Slide 42

When building main menu, drupal invokes hook implementation. Logic alters menu item and changes text and background color.

Slide 43

Slide 43

<?php // Implements hook_preprocess_HOOK(). function drupalflex_preprocess_menu(&$variables) { if ($variables[‘theme_hook_original’] == ‘menu__main’) { foreach ($variables[‘items’] as &$menu_tab) { if (strtolower($menu_tab[‘title’]) == ‘hooks’) { // Add ‘Alt’ to title. $menu_tab[‘title’] .= ’ Alt’; // Existing attributes? if (!$attributes = $menu_tab[‘url’]->getOption(‘attributes’)) { $attributes = array(‘target’ => ‘_blank’); } else { $attributes[‘target’] .= ‘_blank’; } // Add back modified attributes. $menu_tab[‘url’]->setOption(‘attributes’, $attributes); } } } } modules/drupalflex/drupalflex.module Custom modules invoke hooks in the *.module file. In custom “drupalflex” module, creating an instance of preprocess_menu hook (line 3). Check if processing main menu (line 4), and loop through menu items (line 5). For menu item titled ‘hooks’ (line 6) alter title and target (lines 7 ­ 17).

Slide 44

Slide 44

Building of menus, drupal invokes hook implementation. Logic alters menu item and changes title and target.

Slide 45

Slide 45

#7. Services Change data by replacing core functionality. Control global tasks Requires OOP skillset Requires a custom module

Slide 46

Slide 46

Will change menu tab by overriding the Menu Service in a custom module.

Slide 47

Slide 47

namespace Drupal\drupalflex; use Drupal\Core\DependencyInjection\ServiceProviderBase; use Drupal\Core\DependencyInjection\ContainerBuilder; class DrupalflexServiceProvider extends ServiceProviderBase { /** * {@inheritdoc} */ public function alter(ContainerBuilder $container) { // Override menu_link_tree class with custom. $definition = $container->getDefinition(‘menu.link_tree’); $definition->setClass(‘Drupal\drupalflex\DrupalflexMenuLinkTree’); } } modules/drupalflex/src/DrupalflexServiceProvider.php To override a Service, modules need a *ServiceProvider.php file in an src directory. Service Provider class implements an alter method (line 10). Retrieves definition for menu link tree (line 12) and points it to a custom PHP class (line 13).

Slide 48

Slide 48

namespace Drupal\drupalflex; use Drupal\Core\Menu\MenuLinkTree; class DrupalflexMenuLinkTree extends MenuLinkTree { // Overrides \Drupal\Core\Menu\MenuLinkTree::build(). public function build(array $tree) { $build = parent::build($tree); if (isset($build[‘#items’]) && $build[‘#theme’] == ‘menu__main’) { foreach ($build[‘#items’] as &$item ) { if ($item[‘url’]->toString() == ‘/node/7’) { // Change Title, path and add styling. $item[‘title’] .= ’ Alt’; $item[‘url’]->setRouteParameter(‘node’, ‘9’); $item[‘url’]->setOption(‘attributes’, array( ‘style’ => ‘color:#00F;background:#00CEFD;’, )); } } } return $build; } } modules/drupalflex/src/DrupalflexMenuLinkTree.php Custom MenuLinkTree service class that extends the core base class (line 4). Override build method (line 6), which builds array of menu links. For only the main menu (line 8), loop through each menu item (line 9). If menu item points to node/7 (line 10) alter location and display (lines 11 ­ 16).

Slide 49

Slide 49

Drupal will use custom version of menu service to now build all menus. Will hit logic to change the ‘Services’ menu tab in the main menu.

Slide 50

Slide 50

#8. Combinations Change data by using multiple methods. Real world scenario Use multiple skillsets Most control over Drupal

Slide 51

Slide 51

Will alter a menu item using combination of multiple methods.

Slide 52

Slide 52

//… public function build(array $tree) { $build = parent::build($tree); if (isset($build[‘#items’]) && $build[‘#theme’] == ‘menu__main’) { foreach ($build[‘#items’] as &$item ) { // … if ($item[‘url’]->toString() == ‘/node/8’) { $item[‘title’] .=’ Alt’; } } } return $build; } modules/drupalflex/src/DrupalflexMenuLinkTree.php In custom menu service, Alter title for menu item pointing to node/8 (lines 7 ­ 9).

Slide 53

Slide 53

/** * Implements hook_preprocess_HOOK(). */ function subbartik_preprocess_menu__main(&$variables) { // Loop through all menu tabs. foreach ($variables[‘items’] as &$menu_tab) { // … $menu_tab[‘is_combo’] = ($menu_tab[‘title’] == ‘Combinations Alt’); } } themes/SubBartik/subbartik.theme Preprocess hook in custom theme, add new boolean attribute ‘is_combo’ to menu items (line 8).

Slide 54

Slide 54

<ul class=”menu”> {# … #} {% for item in items %} {# … #}} <li{{ item.attributes.addClass(classes) }}> {% if item.title|lower == ‘templates’|t %} {# … #} {% else %} {{ link(item.is_combo ? item.title| reverse : item.title, item.url) }} {% endif %} {# … #} </li> {% endfor %} </ul> {# … #} themes/SubBartik/templates/menu—main.html.twig In overriden menu template, if is_combo is true reverse the menu item title (line 9).

Slide 55

Slide 55

.menu—main .menu li a.combo { color: #000; font-weight: 800; text-shadow: 0 0 #000 !important; background: red; /* not working, let’s see some red */ background: -moz-linear-gradient( top , rgba(255, 0, 0, 1) 0%, rgba(255, 255, 0, 1) 15%, rgba(0, 255, 0, 1) 30%, rgba(0, 255, 255, 1) 50%, rgba(0, 0, 255, 1) 65%, rgba(255, 0, 255, 1) 80%, rgba(255, 0, 0, 1) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(255, 0, 0, 1)), color-stop(15%, rgba(255, 255, 0, 1)), color-stop(30%, rgba(0, 255, 0, 1)), color-stop(50%, rgba(0, 255, 255, 1)), color-stop(65%, rgba(0, 0, 255, 1)), color-stop(80%, rgba(255, 0, 255, 1)), color-stop(100%, rgba(255, 0, 0, 1))); } themes/SubBartik/css/menu-alter.css Create css rule for links with class “combo” in main menu. Add to css file included in library that is included on all pages by custom theme.

Slide 56

Slide 56

(function ($, Drupal) { “use strict”; Drupal.behaviors.drupalFlexMenuAlter = { attach: function (context) { $(‘.menu—main ul.menu li a’).each(function(){ if ($(this).attr(‘href’) == ‘/node/5’) { // … } else if ($(this).attr(‘href’) == ‘/node/8’){ $(this).addClass(‘combo’); } }); } } })(jQuery, Drupal); modules/drupalflex/js/menu_later.js Add logic into javascript that is part of library defined by custom module. Logic adds ‘combo’ class to link item in main menu that points to node/8 (lines 9 ­11). .

Slide 57

Slide 57

Using a custom service, custom css, custom javascript, template override and hook instance together to alter last menu item.

Slide 58

Slide 58

Drupal is flexible! No “right” way. Only “Right for me” way.

Slide 59

Slide 59

Slides & Notes bit.ly/bioFlex This presentation bit.ly/bioFlexSlides Annotated slides on Slideshare. bit.ly/drupalREAD Blog post on how to select modules.

Slide 60

Slide 60

Thank You! Feedback/Questions? @mikemiles86 @BioRAFT_dev @WeAreGenuine Flexibility of Drupal / @mikemiles86