$mash@bradmash.com: Technology Enthusiast & Full Stack Web Developer

Five Drupal 8 and Drupal 9 Compatible Contributed Modules You Should Be Using

In this article we will cover some awesome Drupal 8/9 contributed modules you should consider using for your existing, and upcoming Drupal website builds.

Sessionless Bigpipe

Project page: https://www.drupal.org/project/big_pipe_sessionless

What does it do?

The BigPipe module in Drupal 8 only is able to accelerate responses for personalized requests. (Requests that have a session.)

Drupal 8 will cache unpersonalized responses by default. But the first request for such an unpersonalized response is not yet cached, and is slow to render (single flush, so blank screen for a relatively long time).

This module uses BigPipe to accelerate the first unpersonalized response! And after that first response is sent, the response is stored in Page Cache. Which means that any subsequent requests for that unpersonalized page will be answered very quickly by Page Cache!

Description the drupal.org project page.

Are there any caveats?

Have to keep Page Cache module enabled.


Critical CSS

Project page: https://www.drupal.org/project/critical_css

What does it do?

Embeds a critical CSS file into a page’s HTML head, and loads the rest of non-critical CSS asynchronously.

DESCRIPTION FROM DRUPAL.ORG PROJECT PAGE

Are there any caveats?

Can be time consuming to set up and configure for your project. You will need to create a “default-critical.css” file (or implement suggestions hook to name it differently) for your project. This file holds styles that need to load as soon as possible on the page (basically above-the-fold styles).

This can potentially mean a lot of work to adjust how your stylesheet assets are assembled for your project build.


Moderation Sidebar

Project page: https://www.drupal.org/project/moderation_sidebar

What does it do?

Moderation Sidebar provides an off-canvas menu to moderate the current Entity. To use the sidebar, visit any Moderated Entity and click the “Tasks” button in the Toolbar. This will open an off-canvas menu that contains contextual actions related to the Content Moderation module.

Description from drupal.org project page.

Are there any caveats?

Drupal 8.5+ and the Content Moderation module is required to use Moderation Sidebar.


Content Import

Project page: https://www.drupal.org/project/contentimport

What does it do?

This is the simple module which allows an administrator user to import data from a CSV file [for node creation].

Description from drupal.org project page

Are there any caveats?

The project is not covered by Drupal’s security advisory policy.


IMCE

Project page: https://www.drupal.org/project/imce

What does it do?

IMCE is an image/file uploader and browser that supports personal directories and quota.

description From drupal.org project page

Are there any caveats?

None, would be specific to your use case if issues arise.


Conclusion

Do you have any suggestions for global, general type of modules that most site builds could benefit from? Let me know in the comments! Did you learn anything or find these useful? Tell me about your use case!

This article may grow over time with additional module mentions!

Drupal 8 theme: How to develop a custom theme.

Drupal 8 Logo

Whatever your use case is for developing a custom theme in Drupal 8, the PHP Symfony, Twig, and markup goodness Drupal 8 offers developers is great. Let’s cut to the chase and get started.

You can easily scaffold a theme using drupal console with the drupal generate:theme [options] command found at https://hechoendrupal.gitbooks.io/drupal-console/en/commands/generate-theme.html. In this guide, we will be creating a custom Drupal 8, manually generating the files as they are needed. However, do not overlook this command for future use. After you go through this guide and understand what composes a theme, I recommend for future projects using this command.

Create a Drupal 8 theme directory.

The first thing you will need to do is create a directory for your theme. Generally speaking, best practice is to create the directory structure as follows: docroot/themes/custom/YOURTHEMENAME

In this example, we will name our theme directory as docroot/themes/custom/artistCamp

A quick note on yml files.

Anytime you see indentation in yml files, the indentation are important and are only 2 spaces. That is to say, if something is indented twice, it equates to 4 spaces. Improper indentation of yml files will make things not work properly. And generally speaking, improper syntax at all will break things.

*.info.yml

This file is required. Once you have your theme directory created, you will need to create the *.info.yml file. In our case, we will name this file artistCamp.info.yml. This file is a configuration file which will register our theme with our Drupal 8 install. In this file there are a few required key/value pairs as well as many optional key/value pairs that we define as metadata to describe our theme.

Required *.info.yml key/value pairs.

The required keys are name, type, and core. For our example we will define a few other useful keys. Below you will see the contents of artistCamp.info.yml.

*.info.yml file used for registering your Drupal 8 theme with the system.

Some of the keys are self-evident such as “description” but let’s elaborate on a few of the other keys.

base theme: Your theme can derive from another theme and according to drupal.org:

“Sub-themes are just like any other theme, with one difference: they inherit the parent theme’s resources. There are no limits on the chaining capabilities connecting sub-themes to their parents. A sub-theme can be a child of another sub-theme, and it can be branched and organized however you see fit. This is what gives sub-themes great potential.”

screenshot: The “Appearance” admin user-interface (/admin/appearance), when you are selecting a theme to use for your website, will show the image you specify via the path provided to the screenshot key.

regions: This is very important metadata that creates reference-able regions that will become the main parts/sections of your theme. Each region renders as its own encapsulated black box. Regions are reference-able via their unique hook. These hooks are essentially IDs that reference your regions for theming and pre-processing. More on this later. The Structure -> Block Layout admin user-interface displays the custom regions you define, so that administrators can add blocks to those regions as needed.

libraries: A reference to a defined Drupal 8 “library” (that is defined in *.libraries.yml more on this later) applied to every node (in Drupal jargin a node is a page) using this theme.

*.theme

Include a theme as it is best practice to have one. If you create it blank, make sure to at least include the <?php at the top of the file, and including a comment like /* Stub */ may be nice as well.

This file allows you to implement a list of life-cycle hook functions associated with your theme such as HOOK_preprocess_page. There are a plethora of contextual hooks you can implement, of which you can check out on the drupal.org website. However, for this file, if we were to implement HOOK_preprocess_page for the artistCamp example, the function name would be, artistCamp_preprocess_page. This function allows us to prepare variables for the page.html.twig file. Drupal inherently knows when to run this function. The equivocation of this function prepares our variables for the page.html.twig template to use. Drupal 8 accomplishes this with its event system runs implemented life-cycle hooks at the proper times.

Preprocessing Drupal 8 theme variables.

As we saw in page.html.twig, our Twig templates can be passed preprocessed variables. That is to say, any Twig template can have custom, defined variables at their disposal if we set them up properly, one way being through preprocess functions. Let’s take a look at the artistCamp_preprocess_page function defined in the artistCamp.theme file.

hook_preprocess_page function used for preparing variables for use in your Drupal 8 theme twig templates.

It is worth noting before commenting on the above function definition that to create custom variables for use in Twig templates via their preprocess function counter-part, that you create them in the following format:

$variables['foo'] = "bar";

So, we create a custom variable called ‘current_route’ to use in the artistCamp page.html.twig template. The route the current user is requesting becomes the value of ‘current_route’. This function only runs on nodes that use the page.html.twig template, which, for most sites, will be used on every page.

Next we will look at the html.html.twig file, however I would urge you quickly scroll past it and look at the page.html.twig file to fully follow this example. To clarify, the ‘current_route’ variable determines the markup generated to the client requesting the web page. I recommend to still read the html.html.twig section.

html.html.twig

This twig template serves as the entry point for ultimately rendering all of your templates. It always runs on page loads that are uncached. Let’s take a look at the artistCamp html.html.twig file.

<?php
{#
/**
* @author Brad Mash, Software Engineer
* @file
* Theme override for the basic structure of a single Drupal page.
*
* Variables:
* - logged_in: A flag indicating if user is logged in.
* - root_path: The root path of the current page (e.g., node, admin, user).
* - node_type: The content type for the current node, if the page is a node.
* - head_title: List of text elements that make up the head_title variable.
*   May contain one or more of the following:
*   - title: The title of the page.
*   - name: The name of the site.
*   - slogan: The slogan of the site.
* - page_top: Initial rendered markup. This should be printed before 'page'.
* - page: The rendered page markup.
* - page_bottom: Closing rendered markup. This variable should be printed after
*   'page'.
* - db_offline: A flag indicating if the database is offline.
* - placeholder_token: The token for generating head, css, js and js-bottom
*   placeholders.
*
* @see template_preprocess_html()
*/
#}
{%
set body_classes = [
logged_in ? 'user-logged-in',
not root_path ? 'path-frontpage' : 'path-' ~ root_path|clean_class,
node_type ? 'page-node-type-' ~ node_type|clean_class,
db_offline ? 'db-offline',
'no-js'
]
%}

{{attach_library('artistCamp/global-header')}}
{{attach_library('artistCamp/global-footer')}}

{% set frontClass = ""  %}
{% if current_route == "view.frontpage.page_1"  %}
{% set frontClass = "frontpage" %}
{% endif %}

<!DOCTYPE html>
<html{{ html_attributes.setAttribute('class',frontClass) }}>
<head>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-XXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());

gtag('config', 'UA-XXXXXXXXX');
</script>

{#
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXXX');</script>
<!-- End Google Tag Manager -->


<link rel="preload" as="script" href="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js">
<script>
(adsbygoogle = window.adsbygoogle || []).push({
google_ad_client: "ca-pub-XXXXXXXXXXXXX",
enable_page_level_ads: true
});
</script>
#}
<head-placeholder token="{{ placeholder_token }}">
<title>{{ head_title|safe_join(' | ')|trim('| ') }}</title>
<css-placeholder token="{{ placeholder_token }}">
<js-placeholder token="{{ placeholder_token }}">
</head>
<body{{ attributes.addClass(body_classes) }}>
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->

{#
<section aria-label="Skip to main content.">
#}
{# Using the open source implementation found at: https://accessibility.oit.ncsu.edu/it-accessibility-at-nc-state/developers/accessibility-handbook/mouse-and-keyboard-events/skip-to-main-content/ #}
{#
<a href="#page-instance-fluid" class="skip-main">
{{ 'Skip to main content'|t }}
</a>
</section>
#}
{# Do not remove these three: page_top; page; page_bottom; ever! #}
{{ page_top }}
{{ page }}
{{ page_bottom }}
<js-bottom-placeholder token="{{ placeholder_token }}">
</body>
</html>

You can see that the main HTML elements are here, html, head, body.

Rather than go into great detail about everything going on in this file, we will comment on some high-level things. First notice that we can attach libraries directly to our template with a single twig function:

{{attach_library(‘artistCamp/global-header’)}}

Libraries will be discussed in the *.libraries.yml section below. However, what this is doing is taking all of the resources listed in the ‘artistCamp/global-header’ library (mainly CSS and JavaScript references), packaging them up, and applying them to the final rendered page markup this template produces.

Finally, towards the bottom of the file you will notice the following:

{{ page_top }}
{{ page }}
{{ page_bottom }}

These are variables which reference global parent regions (not the regions we defined, these regions, particularly the page region, hold our custom defined regions we defined in our *.info.yml file). If you’re curious why page_top and page_bottom are not accessible in Block Layout you can see the following link, https://api.drupal.org/api/drupal/core!modules!system!system.module/function/system_system_info_alter/8.2.x. But in short, these are system administered regions.

page.html.twig

If you’re following, you should see that whatever is output via this file will be between the <body>…</body> tag since {{ page}} was output there as we saw when we looked at the html.html.twig file.

This is the default template that all themes use if no other twig template overrides their precedence (more on twig hook theme precedence later). To clarify, if your theme includes all of the files this tutorial has outlined up to this point, as well as this file, this is the file that is basically going to generate all of the markup for your website no matter what page you’re on.

Let’s take a look at the artistCamp page.html.twig file, and keep in mind that we preprocessed this twig template via out *.theme file’s artistCamp_preprocess_page function which made the ‘current_route’ variable available.

<?php
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
{#
    /**
* @author Brad Mash, Software Engineer
* @file
* Artist Camp Theme File
* page.html.twig
* Implements Bootstrap 4.1.x
*/
    #}
    <!-- Artist Camp Developed by Brad Mash. -->
    <div id="global-container" class="global-container">
{% include directory ~ '/templates/includes/header/header.html.twig' %}
<div id="global-content-container">
<div id="content" class="content">
<div class="top-fluid-container">
<div id="top-fluid" class="top-fluid">
{{ page.top_fluid }}
</div>
</div>

{% set containerized_routes = ['user.login', 'user.register', 'node.add','node.add_page','entity.user.edit_form','comment.admin','view.files.page_1','view.message.page_1']  %}
<div id="page-instance-fluid" class="page-instance-fluid">
<main id="main">
{% if current_route in containerized_routes %}
<div class="container">
{{ page.content }}
</div>
{% else %}
{{ page.content }}
{% endif %}
</main>
</div>

<div class="bottom-fluid-container">
<div id="bottom-fluid" class="bottom-fluid">
{{ page.bottom_fluid }}
</div>
</div>
{% include directory ~ '/templates/includes/footer/footer.html.twig' %}
</div>
</div>

{# {% include directory ~ '/templates/includes/footer/footer.html.twig' %} #}
    </div>

Again, rather than comment on every part of the file, let’s consider the more interesting parts. The first thing we want to point out is the use of our custom defined regions such as {{ page.top_fluid }}

If needed can theme the top_fluid region with a twig template. We will not spend time doing this in this tutorial. However, Drupal will provide theme hooks for this region which can be themed and preprocessed as needed.

**Note: This may be a good point in time to say that twig templating in Drupal 8 is very granular in scope. For example, a form on a page can be twig templated and preprocessed. But then, each input of the form themselves can be as well! Twig templating is a game of inception.

Drupal 8 Twig Logic

Next consider this snippet of twig markup found in the artistCamp.page.twig file:

{% set containerized_routes = ['user.login', 'user.register', 'node.add','node.add_page','entity.user.edit_form','comment.admin','view.files.page_1','view.message.page_1']  %}
<div id="page-instance-fluid" class="page-instance-fluid">
<main id="main">
{% if current_route in containerized_routes %}
<div class="container">
{{ page.content }}
</div>
{% else %}
{{ page.content }}
{% endif %}

It’s easy to see we have a small piece of logic that checks our custom defined and available ‘current_route’ variable, and depending on its value either outputs the {{ page.content }} region in a container or not.

Finally it’s worth mentioning that you can include twig templates inside of other twig templates using the following syntax as we see in this file including the footer twig template:

{% include directory ~ '/templates/includes/footer/footer.html.twig' %}

*.libraries.yml

This file contains library definitions which can reference internal and external resources such as CSS and Javascript, and houses those defined resources under a custom name/ID that you choose. Let’s take a look at the artistCamp.libraries.yml file:

#Global for all pages
global-header:
  version: 1.0
  header: true
  css:
    templates/includes/css/global/temp.css: {}
    base:
templates/includes/css/global/base.min.css: { minified: true }
    layout:
templates/includes/css/global/layout.css: { minified: true }
templates/includes/css/global/layout.min.css: { minified: true }
templates/includes/css/bootstrap/bootstrap-grid.min.css: { minified: true }
    component:
templates/includes/css/custom-dropdown-menu.css: {}
https://cdnjs.cloudflare.com/ajax/libs/flickity/2.1.2/flickity.min.css: { minified: true }
https://unpkg.com/ionicons@4.2.2/dist/css/ionicons.min.css: { minified: true }
https://cdnjs.cloudflare.com/ajax/libs/plyr/3.4.6/plyr.css: {}
templates/includes/css/global/component.min.css: { minified: true }
templates/includes/css/global/header.min.css: { minified: true }
templates/includes/css/global/footer.min.css: { minified: true }
https://use.typekit.net/hkv5mea.css: {}
https://fonts/googleapis.com/css?family=Encode+Sans+Condensed: {}
    #theme:
 #templates/includes/css/global/print.css: { media: print }
  js:
    templates/includes/js/custom-dropdown-menu.js: {}
    templates/includes/js/temp.js: { weight: -5, minified: true }
    templates/includes/js/global.min.js: { weight: -5, minified: true }
    https://cdnjs.cloudflare.com/ajax/libs/plyr/3.4.6/plyr.polyfilled.min.js: {minified: true}
    https://cdnjs.cloudflare.com/ajax/libs/flickity/2.1.2/flickity.pkgd.min.js: {minified: true}
  dependencies:
    - core/jquery
    - core/jquery.once

global-footer:
  js:
    templates/includes/js/custom-plyr-implementation.js: {}

404:
  version: 2.1
  header: true
  css:
    component:
templates/includes/css/global/404.min.css: {}

If you recall, in the html.html.twig file we had:

{{attach_library('artistCamp/global-header')}}
{{attach_library('artistCamp/global-footer')}}

These two lines applied the resources defined in the libraries file to our template, and ultimately applies them to all rendered pages.

Advanced Theming

As mentioned in this tutorial, theming in Drupal 8 is very granular. That is to say, virtually any component on your webpage not only is likely theme-able, but will have dedicated theme hook names (suggestions) associated with it so that you can implement the proper files to target that component. If you have twig debugging turned on for you site, you can go to any page in your site, open developer tools, and in the markup HTML comments will be present that show you the twig theme suggestions. To clarify, this means that if you need to theme or preprocess a particular component the HTML comment will basically tell you what file you need to create so that you can target the component in question. Read more on this topic at https://www.drupal.org/docs/8/theming/twig/debugging-twig-templates.

Conclusion

Once you have implemented these files, you can go to yoursite.com/admin/appearance and install and set your theme as active.

SoundCloud 2020 Review: A Love/Hate Relationship

SoundCloud logo

In this article I will analyze SoundCloud as of January 28th, 2020 as a longtime user of the platform. I’m not some huge artist, so I can only give the perspective of the features I use as well as from the vantage point of what is probably your typical user. So without any further introduction, let’s analyze!

What We Love

Free

Who doesn’t like free things? The fact that you can sign up for SoundCloud and within a few minutes broadcast your music to the world is insane. This fundamental aspect of the platform is a huge reason why it is successful in retaining and adding new users. For musicians, bands, and artists of any audio medium, SoundCloud is a great choice for hosting your content for consumption.

Some would complain that under SoundCloud’s free tier they only get 180 minutes (3 hours) of upload time. But we can’t fault SoundCloud for limiting free tier users to this allocation limit. It’s free! And they’re hosting millions of audio files for free! They have to draw a line somewhere. Let’s be thankful this is even still an option. We love this about SoundCloud.

Pro & Pro Unlimited

Want more upload time? Try a pro account and get double the upload time of that of a free tier account (6 hours total). Oh, you want no cap? Get a Pro Unlimited account and have no cap at all!

Now, I won’t bore you with all of the details on the differences between a pro and pro unlimited account, after all you can just compare here – https://help.soundcloud.com/hc/en-us/articles/115003568408-Learn-about-our-Pro-plans.

Nonetheless, there are some great features packed into the Pro tier plans.

Statistics

For example, I have a Pro Unlimited account. I’ll show you my (low, but growing) statistics so that you can see the insight SoundCloud provides users with this plan.

SoundCloud Pro Unlimited Statistics User Interface [part 1]
Notice that this image, and the following are for “Plays” … the relevant stats change depending on your selector (Plays, Likes, Reposts, Comments, or Downloads; this is modified by the date range you select as seen in the top right of the interface).

Scrolling down I see more stats:

SoundCloud Pro Unlimited Statistics User Interface [part 2]
This one seems self-explanatory, Top Played Tracks and Audience/Location.

And yet more statistics as I scroll down:

SoundCloud Pro Unlimited Statistics User Interface [part 3]
How did the users find me via SoundCloud and via playlists or albums?

Finally the last section as I scroll down a bit more:

SoundCloud Pro Unlimited Statistics User Interface [part 4]
This section is especially interesting if you’re interested in knowing where your listeners found your SoundCloud profile from.

The main thing that has been helpful for me with the stats is being able to see what people are listening too. If you have listeners who do not like or comment, but do indeed listen to your music, you can start to glean what people are interested in and cater your content to your stats.

Discovery

The ability for you to be discovered, and the ability for you to discover new music on SoundCloud really is limitless.

Lil Nas X was basically discovered over night via his release of “Old Town Road” on SoundCloud.

Connections

The ability to make meaningful connections on SoundCloud is awesome. Whether it’s just making a new friend by leaving a comment on a track, making a connection to a music label, or finding a new podcast to listen to the tools to communicate on SoundCloud are there to support you making connections.

The ability to private message, leave comments, like uploads, and follow creators you care about allows you to make those connections.

For example, as of recent I discovered a new artist on SoundCloud – Veridium. I genuinely like this guy’s music and mixes. We’ve since followed each other and have sent each other a few messages back and forth. You should check his music out – https://soundcloud.com/jerri-mperdempes

Here’s my favorite mix of his:

Collaboration

Want to collaborate with someone but don’t know anyone to work with? Start asking artists on SoundCloud! Again, you can find all skill levels, all genres, and types of people to work with on the platform if you really try.

For example, I recently finished a side project up for another website I co-founded called devjams.com where I collaborated with 5 other SoundCloud artists.

Here are some of the conversations I had with some of the artists from the project I just mentioned:

SoundCloud Private Message User-Interface [example 1]

And another:

SoundCloud Private Message User-Interface [example 2]

What We Hate

Broken Comment Statistics

To clarify, I need to explain where the system for comments “breaks”. If a user comments on one of your uploads, and that user is deleted or you block them (and you indicate to remove their likes/comments upon blocking them) the comment counter stat is not reduced for that upload on your public profile. What do I mean? Let me show you an example on my profile.

Example of broken comment counter on public SoundCloud profile.
The public stats for comments is reporting 1 comment. Do you see any comments on the track? Nope!

Tags Seem Useless

Not entirely, but under certain circumstances tags do seem useless. This is more the case when you create your own user defined tag. Again let me you an example.

I created a user-defined tag of “OriginalWorks” as seen below:

Example of SoundCloud user-defined tags.
Notice the red arrow pointing to my user-defined tag called “OriginalWorks”. What happens when I click on that tag? No results, that’s what happens.

So when I click on that tag I get the following:

Example of SoundCloud user-defined tags not working [image 1].
No tracks with “OriginalWorks” tag.
Example of SoundCloud user-defined tags not working [image 2].
No playlists with “OriginalWorks” tag.

Bots

I’ll be honest I don’t even know how this one could be fixed… but it is super annoying nonetheless.

If you haven’t seen this specific bot network before, then I doubt you’re a regular user of SoundCloud. Which bot service am I talking about? Yeap, you guessed it – soundviral.com

soundviral.com logo - I do not endorse this company/service - in fact, I loathe them.
These annoying bastards will flood you with any upload telling you how much you rock… and how much more you could rock if you bought fake followers through their bot service.

Compression

I must say SoundCloud within the past year or so seems to be getting better at not compressing, or introducing artifacts into your uploads. With a pro unlimited account I no longer worry too much about this as I can upload HD files such as wavs with the upper tier account type. But free users do report this fairly often. I only mention this in passing – they;re getting better. I guess I mention it just to say, hey, SoundCloud, make sure you’re always putting a priority on this topic.

Conclusion

Are there other things I could talk about? Oh for sure, but this is just a subjective view of the SoundCloud platform. These are the big things that, to me, are what make me have a love/hate relationship with SoundCloud as a whole.