-
Using Lazy Builders and Auto Placeholders in Drupal 8
Introduction
I’ve been working on a site that features a lot of user-specific customization and output. The site offers workshops (courses) using the Opigno LMS for Drupal 8. The workshops are rendered in a few different ways throughout the site. For the most part, the rendered workshops appear the same for all users. Here’s an example of a “card” view mode for a workshop:
If a user has successfully completed a workshop, a special badge will appear on the card view mode. Also, the card will be highlighted:
There are two parts of the workshop card template that have to change based on the user viewing the card:
-
NightwatchJS Command to Repeat a Keystroke
Here’s a simple custom command that lets you repeat a specific key (single or combo) N number of times.
In the Drupal world you want to place this in your “Commands” directory (as defined in DRUPAL_NIGHTWATCH_SEARCH_DIRECTORY in web/core/.env).
You’d want to change “sitename” to be your site’s name (so it feels namespaced).
/tests/Nightwatch/Commands/sitenameRepeatKeystroke.js
123456789101112131415161718192021222324252627282930313233/*** Repeats a keystroke (can be multiple keys) a specific number of times.** @param {array} keys* Array of keys to send.* Example 1: [browser.Keys.TAB]* Example 2: [browser.Keys.SHIFT, browser.Keys.TAB, browser.Keys.NULL]);* (it's important to terminate modifier keys with NULL)* @param {integer} times* Number of times to repeat the key send.* @param {integer} pauseBetween* Number of milliseconds to pause between each key send (100 ms default).* @param {function} callback* A callback which will be called.* @return {object}* The 'browser' object.*/exports.command = function myCommand(keys, times, pauseBetween = 100, callback) {const self = this;for (let i = 0; i < times; i++) {this.keys(keys);if (pauseBetween) {this.pause(pauseBetween);}}if (typeof callback === 'function') {callback.call(self);}return this;};Using this in /tests/Nightwatch/Tests/misc.js with a 1000ms pause between each keystroke:
12345678910111213/* eslint-disable no-unused-expressions */module.exports = {'@tags': ['mysite', 'learn-page', 'accessibility'],'When I hit shift-tab 9 times on the Learn page my logo becomes the active element': browser => {browser.drupalRelativeURL('/learn');browser.sitenameRepeatKeystroke([browser.Keys.SHIFT, browser.Keys.TAB, browser.Keys.NULL],9,1000,);},}; -
Monitoring Which Element Has Focus in a Web Browser
While building accessibility-minded websites, it’s useful to be able to monitor which element has focus in your browser.
Using a screen reader works well, but there may be an easier solution for those who aren’t used to screen readers.
Chrome makes this pretty simple. See https://developers.google.com/web/tools/chrome-devtools/accessibility/focus.
In Firefox, Safari, Chrome, and other browsers I’m able to achieve similar functionality (maybe even better) by running a simple snippet in the web developer tools console.
New lines appear as you move through the elements with your keyboard (or mouse).
You may want to turn this into a one-liner for easier pasting.
1234567var lastElement = document.activeElement;setInterval(function() {if (lastElement != document.activeElement) {lastElement = document.activeElement;console.log(document.activeElement);}}, 100);Output in Firefox:
-
Faster Debugging with Lando and xdebug
I’ve been struggling with the speed (or lack thereof) of Lando for Drupal sites. I have suspected xdebug was part of the issue. Sure enough, disabling xdebug speeds up the lando instance considerably. Re-enabling it slows it right back down again. I use xdebug often, so having to lando rebuild to turn xdebug on and off is not an option. I wondered if there’s a way to leave the extension enabled but only use it when needed.
While researching config directives like xdebug.remote_enable and xdebug.remote_autostart, I came across this issue thread on the github Lando project: Allow faster xdebug on/off toggling
The title sounds promising, right?
-
Quickly Protecting a Few Nodes from Deletion in Drupal 8
I’m working on a Drupal 8 site with some critical pages for which we do not want to allow deletion. The homepage, for example, is a basic page; we do not want to allow anyone (even UID #1) to delete this page. If there comes a time where UID #1 decides they need to, they’ll have to update this simple code to support that. We need not make it any more complicated than that for this particular project.
Here’s a solution that:
- shows nothing but an error on the “Delete” form for specific nodes (works for all users)
- prevents deletion by any means (works for all users except UID #1)
- why except UID #1? because hook_node_access doesn’t run for this user
-
Testing Tokens with Entity Ref Traversal via Drush
I am working on a project that has some complicated depth. I needed a way to quickly test token replacement. It’s pretty simple when you understand a few things.
Example Architecture
Here’s the Drupal architecture for this example:
- Discussion Group (content type)
- Title
- Intro
- Body
- “Associated Experts” paragraph field (allows one “Associated Experts” paragraph item)
- Associated Experts (paragraph type, used by many content types like Discussion Group above)
- Heading
- Intro
- Experts (allows unlimited number of “Expert” nodes)
- Expert (content type)
- Title
- Photo
- Discussion Group (content type)
-
Rendering a Drupal 8 Link with Anchor and Destination
I’m working on a site that contains several complicated views (many exposed filters). The views render entities using a couple different view modes. These view modes use Display Suite. Within these view modes I’ve leveraged DS custom fields for several needs.I needed to render a node “Edit” link within some of these view modes. The user would click the edit link, edit the node, then click “Save”. The system would then bring the user to the exact position of the page they were on before, filters and settings intact.This type of solution would work without Display Suite (leveraging other methods). You’re getting a DS-based example because that’s what the site called for.In my particular case I needed to separate the anchor from the edit link. If you’re okay having them in the same place you could skip Step 1 and just include the anchor within the markup for the edit link. -
Getting a Field from an Entity in Drupal 8
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455// @see https://drupal.stackexchange.com/a/99711/28091// Using magic methods we can check if a specific property of the first item// in the field has a value. $entity->field_name is equivalent to// $entity->__get('field_name')). This may not be as safe as using// ->get('field_name') but is useful. Read on...// For simple text fields, booleans, etc. we can ask for ->value, for an// entity reference we'd look at ->target_id. ->uri and ->title for a link.// ->format and ->value for a long text field. Etc.// Due to how __get('field_name') works, if the field doesn't exist on the// entity, an InvalidArgumentException will not be thrown. If instead you// use get('field_name') an exception would be thrown if field_name didn't// exist on the entity. You would want to, in this case, check if the// entity has a field with $entity->hasField('field_name');// So, to see if a field exists and has a value, here are a few examples:// Example 1 (top level entity already loaded):if (isset($entity->field_name->target_id)) {do_something();}// Example 2 (in a DSField plugin, load an entity then traverse it):if (!isset($this->entity()->field_fullcalendar_subtitle->value)) {do_something();}// If we need to loop through multiple items/values in a field (even if// there is only one), you can either ask for an array using ->getValue(),// or you can foreach the field using// foreach ($entity->field_name as $field_item) {}. If you use this approach// the items are objects. This is convenient for field types that have more// methods. For example, you can get to the referenced taxonomy term via// $field_item->entity->name->value, $field_item->entity->tid->value, etc.// Example 3:foreach ($entity->field_location as $field_item) {$location_name = $field_item->entity->name->value;}// Here are some additional examples:// Example 4:$myentity->field_myref->entity->title->value// Example 5:$myentity->field_myref->referencedEntities()[0]->title->value// Example 6:foreach ($myentity->field_myref->referencedEntities() as $entity) {$body_format = $entity->field_body->format;$body_value = $entity->field_body->value;} -
Display-only Pseudo Fields in Drupal 8
In the past I’ve been happy to rely on Display Suite to create what I call “Frontend Only” (DSField) fields (which I created via php). These fields appeared in the Manage Displays screens so that site admins can easily drag them around in the display as needed. As it turns out, you can achieve nearly the same result using a few built-in hooks in Drupal 8. The Display Suite DSField plugin has a few handy helpers like the ability to only show the field on specific display mode forms (e.g., show the field on Full content but not on Teaser). You can still control whether or not the display-only field renders if you use the core hooks, so we’ll be okay.
[UPDATE January 30, 2020] After some discussions with a colleague I decided the world needs a Plugin-based approach to the solution below. We talked through how it’d work before going about our day. After writing the info file, .module file, and prepping the folder structure I realized I never checked to see if this already existed.
I was pleasantly surprised to see that Sutharsan had already created a module that fits the bill! Extra Field is nearly identical in structure and implementation as what I’d planned to build. I just finished writing my first ExtraField\Display plugin and it works beautifully! I will leave my post below in tact, but I highly recommend stopping here and simply using Extra Field. Note that the README.txt file is worth reading, and there is an extra_field_example sub-module that explains things quite well.
-
Tideways and Xhgui using Lando
While I prefer using Valet+ for my Drupal development, I have been asked a few times to share my Lando + Tideways setup. I can’t go into too many details at this point, because it’s been a while since I’ve used this, but here’s my setup. I’ve included some documentation within some of the files (especially at the bottom of .lando.yml).
You can see some explanation and screenshots of Tideways and Xhgui in my Tideways and Xhgui using Dev Desktop post if you need a bit of an introduction. Also, you may be interested in seeing how I got Tideways running with Valet+.
This example is for Drupal 7. You can rework it easily to work with Drupal 8. The .conf may not be required in either case.