Drupal Planet

LocalGov Drupal: Refactoring Code - Video Paragraph Type

Reformatting Drupal Code: LocalGov Alert Banner

Here's an example of where "less is more" sometimes. I reckon we can make the video paragraph type in LocalGov Drupal simpler by just removing the template.

When looking recently at the template for the video paragraph type in LocalGov Drupal, I thought, "hang on, is this necessary?". As it turns out, we have a template for it, but I'm not sure it's needed.

One of the reasons is must be there however is because we have set a template suggestion for it in the localgov_subsites_paragraphs.module file, so if it's removed we will get a Twig error about an undefined template.

Let's go through this template implementation step-by-step to show how sometimes less is more.

 

I've started streaming some of my work, especially if it's contributions to open source, on twitch if you'd like to follow along/subscribe. As it turns out, Twitch deletes your videos after 14 days, so I have also been uploading them to YouTube. Feel free to subscribe.

Join the "Something nice ..." newsletter

The full title is "Something nice, something quirky, something else".

I send an email once a week with something nice, something quirky, and something else that I think is interesting (all with a web development theme, of course).

Proof of Concept: New Grid System for LocalGov Drupal

News post basic screenshot from LocalGov Drupal

Currently LocalGov Drupal relies on Bootstrap for its grid system. I think we can achieve a really nice grid system with about 30 lines of CSS. Let's see!

When it comes to CSS frameworks, I really don't like using them. I find them bloat and opinionated. I also think they go against basic principles of scaling a web project, because each dependency we include forces that dependency on all users.

If there's something that we can do in a few lines of code that means we don't need to depend on a framework, I think we owe to to ourselves to investigate it. Here's my proof-of-concept of a grid system based on the GOVUK and NHSUK design systems' grid systems. It's only about 30 lines of CSS.

 

 

I've started streaming some of my work, especially if it's contributions to open source, on twitch if you'd like to follow along/subscribe. As it turns out, Twitch deletes your videos after 14 days, so I have also been uploading them to YouTube. Feel free to subscribe.

Join the "Something nice ..." newsletter

The full title is "Something nice, something quirky, something else".

I send an email once a week with something nice, something quirky, and something else that I think is interesting (all with a web development theme, of course).

Refactoring Code: LocalGov Drupal Alert Banner

Reformatting Drupal Code: LocalGov Alert Banner

I've been doing a lot of contribution to LocalGov Drupal. Here's my thought process when refactoring some code for the alert banner module.

LocalGov Drupal is the distribution for local councils in the United Kingdom. When reviewing the alert banner module, there were a few small issues that niggled me with the alert banner HTML and CSS. I wanted to make sure the HTML followed BEM naming conventions, and then the CSS the same. This means the CSS is easier to read, and relies less on nested selectors.

There was also a tiny issue with hover states on some of the buttons.

 

I've started streaming some of my work, especially if it's contributions to open source, on twitch if you'd like to follow along/subscribe. As it turns out, Twitch deletes your videos after 14 days, so I have also been uploading them to YouTube. Feel free to subscribe.

Join the "Something nice ..." newsletter

The full title is "Something nice, something quirky, something else".

I send an email once a week with something nice, something quirky, and something else that I think is interesting (all with a web development theme, of course).

Drupal: Theming Like a Pro

Screenshot of part of the homepage of the demo_umami profile

Ok, maybe the title is a bit ambitious, but here's a little proof-of-concept video of any idea for sub-theming I have been working on.

tl;dr

  1. Create a theme.
  2. Use CSS variables for as many CSS properties as you can - spacing, line-height, font, etc.
  3. Create a sub-theme.
  4. Create a library in that sub-theme which just has a CSS file.
  5. Use that CSS file to set values for the variables in the sub-theme.

E.g. in the base theme, you could have padding on top and bottom of header. This could be a variable called --section-spacing-v (as in, section spacing vertical) and set to 2rem. Then in the sub-theme you could set that variable to 4rem. Continue for any other variables you want to override - fonts, line-height, etc.

For added points, you could take these variables and save them as theme settings, so site builders can set them.

 

I've started streaming some of my work, especially if it's contributions to open source, on twitch if you'd like to follow along/subscribe. As it turns out, Twitch deletes your videos after 14 days, so I have also been uploading them to YouTube. Feel free to subscribe.

Join the "Something nice ..." newsletter

The full title is "Something nice, something quirky, something else".

I send an email once a week with something nice, something quirky, and something else that I think is interesting (all with a web development theme, of course).

Create View Mode Component and Views List in PatternLab and Drupal

Mark.ie Loves Patternlab

Here are two short, related videos demoing how I create a view mode component, use that view mode in a views list in Patternlab, and then integrate both with Drupal.

In this first video, we will look at creating the view mode pattern and then integrating that with Drupal.

 

In the second video, we will see how to create a views listing in Patternlab and integrate that with Drupal - in essence, we just clone the views templates into Patternlab, so the heavy lifting is done for us, no need to recreate the wheel.

 

Let me know in the comments what other videos you might like to see.

Join the "Something nice ..." newsletter

The full title is "Something nice, something quirky, something else".

I send an email once a week with something nice, something quirky, and something else that I think is interesting (all with a web development theme, of course).

Add highlight.js Syntax Highlighting to a Gatsby-rendered Drupal Website

When every tutorial for syntax highlighting in Gatsby talks about highlight.js markdown but you have a Drupal website, you need to write your own for posterity. Here's mine.

This website uses the very helpful CKEditor CodeSnippet module to provide syntax highlighting. That in turn uses highlight.js. However, while investigating using Gatsby for the frontend of this website, I came across the headache of adding code highlighting to the site, when my code is just a string in the body field. Here's my solution ... if you have a better one, let me know in the comments.

Create custom utility function for highlight.js

  • Add highlight.js to your repo npm install highlight.js
  • Create a file at /src/utils/highlightCode.js.
  • Import the library and the CSS style you want to use
  • Set the languages you want to load (we don't need to load all of them for performance reasons)
  • Create a function in this file that finds every instance of your code element and loop through it to let highlight.js work on each instance. That file will look something like this:
import hljs from 'highlight.js'
import 'highlight.js/styles/monokai-sublime.css'

hljs.configure({
  languages: [
    'php',
    'javascript',
    'json',
    'bash',
    'scss',
    'css',
    'yml',
    'twig',
    'html',
  ],
})

export default function highlightCode() {
  const codeBlocks = document.querySelectorAll('pre > code')
  codeBlocks.forEach(codeBlock => {
    if (typeof codeBlock === 'object') {
      hljs.highlightBlock(codeBlock)
    }
  })
}

Use the useEffect() hook to apply this to our template

In my case, I wanted to apply this to the article template, which means attaching it to the

<div dangerouslySetInnerHTML={{ __html: nodeArticle.body.value }} />

but by the time Gatsby has grabbed and rendered that, it's too late. Instead, we add our useEffect() before we render our article - this is basically the hook_preprocess_node() of React! That's looks like this:

// import React, etc, etc

import highlightCode from '../../utils/highlightCode'

export default function SingleArticlePageTemplate({ data }) {
  useEffect(() => {
    highlightCode()
  })
  return (
    <>
      <Layout>

        // Stuff before body field (title, image, etc)

            <div dangerouslySetInnerHTML={{ __html: articleBody }} />

        // Stuff after body field (tags, etc)

      </Layout>
    </>
  )
}

It's pretty simple, hopefully it will help you/save you some time Googling.

Join the "Something nice ..." newsletter

The full title is "Something nice, something quirky, something else".

I send an email once a week with something nice, something quirky, and something else that I think is interesting (all with a web development theme, of course).

Drupal key_value_expire Table is very large because of Honeypot Module

key_value_expire db_size before/after Drupal Honeypot

I had a very large key_value_expire table, even though Honeypot module had been uninstalled.

When investigating why I had such a large database on mark.ie (about 50MB - not huge, but a lot for a personal website) this evening, I noticed that the key_value_expire table in the database was about 40MB (after clearing cache). And that most of the entries in it (over 160,000) were for honeypot_time_restriction.

As it turns out, honeypot had been putting a time to expire of Jan 2038, so the table was just getting bigger and bigger. Then I did some Googling and found this: Honeypot key value entries never expire (effectively). That issue has been fixed for newer versions of honeypot, but all the old entries before that update are still in the DB.

There's another issue - Write update hook to clean up old honeypot_time_restriction values - to clean up all the old entries, but it's only at RTBC at the moment, and when it gets into the Honeypot module, it could take a long time for the update hook to run depending on how many entries you have in the table.

For my set up, I just ran DELETE FROM key_value_expire WHERE collection = "honeypot_time_restriction"; directly on the DB. It only took about 10 seconds to delete the 160,000+ rows, and reduced that table from about 40MB to 3MB.

It might be worth checking this on your site to try reduce your DB size.

Join the "Something nice ..." newsletter

The full title is "Something nice, something quirky, something else".

I send an email once a week with something nice, something quirky, and something else that I think is interesting (all with a web development theme, of course).

Printing Values of a Parent Node from a Drupal Paragraphs Field

Someone asked in Slack today how to print the URL of the node that a paragraph is on. I was up to the challenge.

First off, you can do this with php in your .theme file quite easily, but I like to keep my template items in my templates.

Here's the code I used to first get the node id, then the node title, and then create a link from these two pieces of information.

{% set parent = paragraph._referringItem.parent.parent.entity %}

 

What this does is:

 

  1. Set a variable called parent - note is uses parent twice and then entity

    You won't see parent or entity in your kint/dpm/dd output, which is a pity because entity is great - load the entity you want to get information from.

  2. Use parent to then get the node id value and title value parent.nid.value and parent.title.value.
  3. Create a link using this variables.

 

It's quite simple really. You can now use this approach to get other fields/data from your host node.

Join the "Something nice ..." newsletter

The full title is "Something nice, something quirky, something else".

I send an email once a week with something nice, something quirky, and something else that I think is interesting (all with a web development theme, of course).

Writing View Mode Templates in PatternLab

Wanna know my rules when I am writing templates in PatternLab for Drupal view modes such as teasers, cards, search results, etc? Read on, my friend...

BEM Classes for Streamlined Classes

Classes used in the card view mode should be prefixed with card__ not node__

  • We need to make sure that CSS for cards does not leak out into other components
  • We need to make sure we have as little selector specificity as possible - so we have .card__content instead of .card .node__content in our CSS.

Card Variations

If you want to have slight variations for cards, please add that as an option in the classes array. Examples of this might be for a card for a specific content type, or a card when there is no image present, or a card that has the image on the left/right.


{% set classes = [ 
  "card", content_type ? 
  "card--" ~ content_type, image ? 
  "card--has-image" : "card--no-image", 
  alignment ? "card--" ~ alignment, ] 
%} 

This will print:

<article class="card card--event card--no-image card--image-left"></article>

We can then apply overrides using these variations. For example, if the date field on a event is bold but but on a blog is not, we can have code like so:

.card__date { 
  color: $c-grey--darkest; 
} 

.card--event .card__date { 
  font-weight: $fw--bold; 
} 

Avoid Twig Blocks

Try not to use Twig blocks in view modes - {% block image %} If we use Twig blocks, then in the Drupal template anything can be put in the {% block %} to override it. If we change the order of things in the {% block %} in PL, this will have no effect in Drupal, since the Drupal version will be overriding it.

For example, the Drupal template could have something like this:

{% block image %} 
  {{ content.field_date }} 
{% endblock %} 

Avoid Drupal Templates

Try to avoid having templates based on specific content types for view modes. This is usually necessary for Full view mode, but for Teaser, Card, etc let's try to keep them more generic.

If you need to use a content-type template, that is fine; but it should be the exception, not the rule.

In general, since each content type Card should be similar, then each content type should be able to use the same node--card.html.twig template file.

Avoid {% if not page %}

{% if not page %} Should not be needed in view modes. A Card or Teaser will never be a full page. For the same reason, we can usualy leave this out in the content type full view mode templates, since the full view mode will always be a page.

Do Not Hardcode Heading Levels

Unless you know that the Card view mode is never going to have a h2 item above it, do not set h2 or h3 etc as the heading element.

We do not want to have a HTML structure like this:

<h1>Page Title</h1>
  <h2>Views Card Block Title</h2>
  <h2>Card Title</h2>
  <h2>Card Title</h2>
  <h2>Card Title</h2>

 

Instead, we would like this:

<h1>Page Title</h1>
  <h2>Views Card Block Title</h2>
    <h3>Card Title</h3>
    <h3>Card Title</h3>
    <h3>Card Title</h3>

 

In general, view modes will take a h2, so let's place that as our default.

{% set card_title_element = card_title_element|default('h2) %}

<{{ card_title_element }}>{{ card_tite }}</{{ card_title_element }}>

Then, in our Drupal template, we can set the element depending on whether it has a parent h2 or not; for example, if it is in a 'Related Content' building block and that building block has a title such as:

<h2 class="related-content__title">Read More Like This</h2>

 

We can do so in the Drupal template with a variation of this:

 

{% if node._referringItem.parent.parent.entity.field_p_rc_title.value %} 
  {% set teaser_title_element = 'h3' %} 
{% endif %} 

If the above returns as false, our default h2 from the PL pattern will kick in.

Use Prefixes for Variables

Let's use prefixes for variables, using {{ card_content }} instead of {{ content }}. This will help avoid global variables being used by accident in our components (unless we want them to, such as the image_alt variable from data.json). The {{ content }} if used, will not necessarily print the content of your component when that component is used outside of its own context: for example, if you print a card in a views list in a sample page.

Variables for Specific Content Types

If there's a variable for a specific content type, such as the location field for an event card, we can just wrap that in an {% if %} statement, like so:

{% if content_type == 'event' %}
  {% if event_location %}

    {{ card_location}}

  {% endif %} 
{% endif %} 

Then that variable will not print in any other content type's template.

Join the "Something nice ..." newsletter

The full title is "Something nice, something quirky, something else".

I send an email once a week with something nice, something quirky, and something else that I think is interesting (all with a web development theme, of course).

A Very Simple PoC of Using Voice to Admin a Drupal Website

Speak "voice admin new page" to to to the node create form for basic page

I was playing around with the SpeechRecognition API last night and thought, "wouldn't it be cool if we could use voice to administer a website?". Then, I put together this tiny proof of concept module for use with Drupal.

Here's a short video of it in action.

Ok, that looks pretty cool. Show me the code.

window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; 
const recognition = new SpeechRecognition(); 

recognition.interimResults = true; 
recognition.addEventListener('result', e => { 
  transcript = Array.from(e.results).map(result => result[0]).map(result => result.transcript).join(''); 
  const statement = e.results[0][0].transcript; 
  console.log(statement); 
  if (statement === "voice admin new page") { 
    window.location.href = "/node/add/page"; 
  } else if (statement === "voice admin new article") { 
      window.location.href = "/node/add/article"; 
    } else if (statement === "voice admin log out") { 
      window.location.href = "/user/logout"; 
    } else if (statement === "voice admin go home") { 
      window.location.href = "/en"; 
    } 
  }); 

// When we stop talking, start the process again, so it'll record when we start 
// talking again. recognition.addEventListener('end', recognition.start); recognition.start(); 

WTF? That code is crap. You have hard-coded what you want the site do to. That's not going to work for all sites, only for your specific use case.

 

Yep, that's true at the moment. Like I say, it's just a proof-of-concept. We'd need to create a settings page for users to decide if they want it to be available or not. And we'd have to create a better parsing system that listens for "voice admin" and then starts recording. Then we'd need to make sure that it patches together the fragments of speech after this to construct the action that you want to perform. Following that, it would be really cool if on the settings page users could type in commands and responses that SpeechRecognition will listen for and then perform.

 

I think all this is very possible, and probably not too much work. It would be great to see more progress on this.

 

If you'd like to create a pull request, the code is available on GitHub.

 

Join the "Something nice ..." newsletter

The full title is "Something nice, something quirky, something else".

I send an email once a week with something nice, something quirky, and something else that I think is interesting (all with a web development theme, of course).