May 10 2019
May 10

On several occasions clients have come to us asking for a form with a gated resource. They want the  resource (.pdf, .doc, .xls etc.) to be available to download after the user fills a form. In exchange for the visitor's valuable information, we provide them a file.

This is easily achievable in Drupal 8 with the Webform and Token modules.  At the time of creation of this blog post, I used Drupal version 8.7.0.

gated resource webform gif demo

Install modules:

Install the modules as usual. If you need more information on this topic, you can find it here.

Enable modules:

Enable Webform UI, Webform Node (which comes with webform) and the Token module.

  • Webform UI is not really required if you are creating your form via the yml editor, but provides a nice UI to generate and configure our form elements.
  • Webform Node provides a bridge between webform submissions and nodes. The description of the module reads: "Provides a Webform content type which allows webforms to be integrated into a website as nodes."
    For this example I will not be using the new Webform content type that the Webform Node module provides,I am using it because it allow me to bridge our webform submission with the node field tokens.
  • Token is going to get the gated file URL and name.

Configuring the content type:

For this tutorial I will use the Article content type, add a file field that we will name Gated file.

gated form add file field

I hide the field from the default display or any display you might use.

disable the field from the node display

By default the file will be disabled/not shown, therefore not accessible to site visitors.

Creating a webform:

I am going to create a basic webform with only two form elements: one for name and another one for email with standard validation.

* Important to add the form via: /admin/structure/webform, as a webform entity not as the new webform content type, so you can reference the form from different nodes.

Here is a screenshot of my simple form:

gated resource gated form screenshot

Referenced webform:

In this example I will be using an entity reference field to reference the webform we have just created, via the new field called Gated form (field_gated_form):

create a new entity reference to a webform

Then I create a node of type Article. 

You can see the gated file field and the gated form field highlighted in the following screenshot, also you can notice that I am referencing our previously created webform in the Gated form select

creating a node of type article with the gated resource and the form referenced

Creating a confirmation modal with the file exposure:

Because the file is not visible to the user, we will use the webform confirmation modal to expose a link to the file.

image displaying the settings for the webform

if you browse the available tokens at the bottom of the Confirmation message you'll see the following tokens:

If you need a deeper nesting look at /admin/help/token page, where you'll see all of the token values available per field.

example of token values available per field inside node

For the confirmation message I added the following HTML:

Here is your file:
 <a href="[webform_submission:node:field_gated_file:entity:url]" target="_blank">

We are using tokens to get the name and the URL of the field_gated_field, which is the machine name of our file field. Then we form a simple link (HTML a tag) that allow the user to download the file.

Webform in Drupal 8 is super flexible, easy to use and powerful. This is just a simple example of what can be achieved with a little bit of site building and a bunch of clicks.

This is not a secure way to protect files, anyone with the original link to the document will be able to see it and download it, but for some use cases is good enough.

Nov 06 2018
Nov 06

Pattern Lab (PL), a commonly known pattern library, is an open-source project to generate a design system for your site. In the last two years it has gotten a lot of attention in the Drupal community. It's a great way to implement a design system into your front-end workflow.

The following post describes how our client (the City and County of San Francisco) began to implement a pattern library that will eventually be expanded upon and re-used for other agency websites across the ecosystem.


Using the U.S. Web Design System (USWDS), until their own pattern library was ready for prime time, was a client requirement.

USWDS uses a pattern library system called Fractal. I think Fractal is a great idea, but it lacked support for Twig, Twig is the template engine used by Drupal. Fractal out of the box uses Handlebars (templating engine in JavaScript), and thought the template language in Fractal can be customized I wasn’t able to make it work with Twig macros and iterations even with the use of twig.js

Creating the Pattern Lab

Ultimately, I decided to start from scratch. In addition to the USWDS requirement, the client also needed to be able to reuse this pattern library on other projects. I used the Pattern Lab Standard Edition for Twig, among other things this means that you need PHP in the command line in order to "compile" or generate the pattern library.

I added a gulpfile that was in charge of watching for changes in the PL source folders. Once a Twig, Sass or JavaScript file was changed, the pattern library was re-generated.

Generating the Pattern Library

I also needed Gulp to watch the file changes.

The following is a SIMPLE example of the Gulp task that generates the PL watching the folders. the following code snippet shows the config object containing an array of the folder directories.

  "css": {
    "file" : "src/sass/_all.scss",
    "src": [
    "pattern_lab_destination": "pattern-lab/public/css",
    "dist_folder": "dist/css"
  "js": {
    "src": [

And the following one is a common watcher in gulp:

gulp.task('watch', function () {, ['legacy:js']);, ['pl:css']);, ['generate:pl']);, ['generate:pl']);

The following task is in charge of generating the pattern library with PHP:

gulp.task('pl:php', shell.task('php pattern-lab/core/console --generate'));

Please NOTE that this is an oversimplified example.


Having generated the Pattern Library, I figured out that in order to use this Pattern Lab into my Drupal theme, I needed to generate a single CSS file and single JavaScript (JS) file.
The main Sass file imports all Sass code from USWDS by using the `@import` statement.
I imported the source Sass code from USWDS which I required with npm and imported  the source file directly from the node_modules folder:


// Styles basic HTML elements
@import '../../../node_modules/uswds/src/stylesheets/elements/buttons';
@import '../../../node_modules/uswds/src/stylesheets/elements/embed';

Then I imported the scss files that were inside my pattern elements:

// Styles inside patterns.
@import "../_patterns/00-protons/*.scss";
@import "../_patterns/01-atoms/**/*.scss";
@import "../_patterns/02-molecules/**/*.scss";
@import "../_patterns/03-organisms/**/*.scss";
@import "../_patterns/04-templates/**/*.scss";
@import "../_patterns/05-pages/**/*.scss";

All the styles were dumped into a single file called components.css

Having this single CSS file created I was able to use USWDS CSS classes along with the new ones.
I had to add a /dist folder where the transpiled Sass would live and be committed for later use in the Drupal theme.


I did something similar for JavaScript. The biggest challenge was to compile the USWDS JavaScript files exactly as they were. I resorted to copying all the source for the JavaScript into the src folder of the pattern library and set a watcher specifically for the USWDS JavaScript, and added another watcher for the new Pattern Lab JavaScript:


In the following example I compile all the JS that lives inside the components into a single file.

Then the resulting file is copied  to: ./pattern-lab/public/js which is the folder that reads the Pattern Lab when working on Pattern Lab only.
The other copy of the file goes to the distribution folder ./dist/pl/js which is the one I use in my Drupal theme.

// Component JS.
// -------------------------------------------------------------------- //
// The following task concatenates all the JavaScript files inside the
// _patterns folder, if new patterns need to be added the config.json array
// needs to be edited to watch for more folders.

gulp.task('pl:js', () => {
    return gulp.src(config.pattern_lab.javascript.src)
            presets: ['es2015']

The resulting files:


Were included the HEAD of my Pattern Lab by editing pattern-lab/source/_meta/_00-head.twig 

I included the following lines:

<link rel="stylesheet" href="" media="all">
<script src=""></script>
<script src=""></script>

Please refer to the repo if you need the details of the integration: GitHub - SFDigitalServices/sfgov-pattern-lab: SFGOV Pattern Lab

Integrating the Pattern Lab with Drupal.

Composer and libraries:

I used the following plugin:

composer require oomphinc/composer-installers-extender

This plugin allowed me to put the pattern library in a folder different than vendor
Then I added some configuration to the composer.json

Under extra I specified where composer should install the repository of type github:

"extra": {
        "installer-paths": {
            "web/libraries/{$name}": ["type:github"],

Then under repositories I set the type:github

"repositories": {
        "github": {
            "type": "package",
            "package": {
                "name": "sf-digital-services/sfgov-pattern-lab",
                "version": "master",
                "type": "drupal-library",
                "source": {
                    "url": "",
                    "type": "git",
                    "reference": "master"

and required the package under require: As you can see the name matches the name in the previously declared github repo:

"require": {
   "sf-digital-services/sfgov-pattern-lab": "dev-master",

A composer update should clone the github repo and place the Pattern Lab inside relative to the Drupal web folder:


Components Libraries

The Component Libraries module was especially important because it allowed me to map the Pattern Lab components easily into my theme.

Then I had to map my Pattern Lab components with the Drupal theme:

The Drupal Theme:

I created a standard Drupal theme:

The file:

In the following part of the file I connected the Pattern Lab Twig files to Drupal:

      - ../../../libraries/sfgov-pattern-lab/pattern-lab/source/_patterns/00-protons
      - ../../../libraries/sfgov-pattern-lab/pattern-lab/source/_patterns/01-atoms
      - ../../../libraries/sfgov-pattern-lab/pattern-lab/source/_patterns/02-molecules
      - ../../../libraries/sfgov-pattern-lab/pattern-lab/source/_patterns/03-organisms
      - ../../../libraries/sfgov-pattern-lab/pattern-lab/source/_patterns/04-templates
      - ../../../libraries/sfgov-pattern-lab/pattern-lab/source/_patterns/05-pages

  - sfgovpl/sfgov-pattern-lab

The sfgovpl.libraries.yml file:

In the last line of the previous code example, you can see that I required sfgov-pattern-lab library,  which will include the files compiled by Gulp into my Pattern Lab.

      '/libraries/sfgov-pattern-lab/dist/css/components.css': {}
    '/libraries/sfgov-pattern-lab/dist/pl/js/components.js': {}

Using the Twig templates in our theme:

The following is an example of how to use a molecule from the pattern library into the Drupal theme:

You can include the @molecules/08-search-results/03-topic-search-result.twig twig like this:

Pattern Lab twig:


<div class="topic-search-result">

  <div class="topic-search-result--container">

    <div class="content-type"><i class="sfgov-icon-filefilled"></i><span>{{ content_type }}</span></div>

    <a class="title-url" href="{{ url }}"><h4>{{ title }}</h4></a>

    <p class="body">{{ body|striptags('<a>')|raw }}</p>



Drupal template:

The following example calls the Pattern lab molecule originally located at: web/libraries/sfgov-pattern-lab/pattern-lab/source/_patterns/02-molecules/08-search-results/03-topic-search-result.twig but thanks to the Components module we just call it as: @molecules/08-search-results/03-topic-search-result.twig

{# Set variables to use in the component. #}
{% set url = path('entity.node.canonical', {'node': elements['#node'].id()  }) %}
{% set description = node.get('field_description').getValue()[0]['value'] %}
{% set type = node.type.entity.label %} {# content type #}
{# Icluding the molecule in our Pattern Lab.#}

{% include "@molecules/08-search-results/03-topic-search-result.twig" with {
  "content_type": type,
  "url": url,
  "title": elements['#node'].get('title').getString(),
  "body": description
} %}


SFGOV Pattern Lab, Initial development was made in large part for Chapter Three and this post is intended to show the core concepts of decoupling your Pattern Lab from your Drupal theme.

You can find the full code implementation for the Pattern library and Drupal in the following Urls:

SFDigitalServices/sfgov and the Pattern Lab here: SFDigitalServices/sfgov-pattern-lab

You should try the value module, it is great for extracting values in the Drupal twig templates and connect them with your Pattern Lab twig templates.

Give a try to the UI Patterns module, looks promising and a great solution for decoupled Pattern Libraries.

Aug 07 2016
Aug 07

(This module was tested on drupal 8.1.8)

Twig Extensions, Views and Drupal 8

Problem to solve

Sometimes working with Views can be a challenge, in some cases you want to modify the values or process the content of the field.

Many developers use the preprocess functions, or alter the query of the view, however there is another option: twig filters.

The Goals:

Being able to calculate the reading time of any given node (body field).

The field needs to output: "5 min read", "9 min read", depending on the word count of the node.

Twig Filters:

Twig filters give us the ability to modify variables on twig template.

{% set salutevariable = "Hello Friend" %}"

If we output the variable {{ salutevariable }} is going to show: Hello Friend

If we want to make the variable lowercase, we use a filter.

{{ salutevariable | lower }} the "|" indicates that we are going to use a filter in our case "lower", so the value "Hello Friend" is going to be parsed by the filter lower.

The output will be: hello friend

Official Documentation:

Creating A Module:

Create a new module in: modules/custom/

Our module is going to have the following structure:

|-- src/TwigExtension/ReadingTimeExtension.php contains:

name: readingtime
type: module
description: Provides a Twig filter that calculates the time of reading for any given string.
core: 8.x
package: Custom

Create a services file:

    class: Drupal\readingtime\TwigExtension\ReadingTimeExtension
      - { name: twig.extension }

The ReadingTimeExtension.php will contain the following:


namespace Drupal\readingtime\TwigExtension;

use Drupal\Core\Render\Markup;

class ReadingTimeExtension extends \Twig_Extension {
   * Here is where we declare our new filter.
   * @return array
  public function getFilters() {
    return array(
      'readingtime' => new \Twig_Filter_Function(
        array('Drupal\readingtime\TwigExtension\ReadingTimeExtension', 'readingTimeFilter') // Here we are self referencing the function we use to filter the string value.

   * This is the same name we used on the services.yml file
   * @return string
  public function getName() {
    return "readingtime.twig_extension";

   * @param $string
   * @return float
  public static function readingTimeFilter($string) {

    if($string instanceof Markup) { // we check if the $string is an instance of Markup Object.

      // strip_tags help us to remove all the HTML markup.

      // if $string is an instance of Markup we use the method __toString() to convert it to string.

      $striped_markup = strip_tags($string->__toString()); 

      // On avarage we read 130 words per minute.

      // the PHP function str_word_count counts the number of words of any given string.

      $wpm = str_word_count($striped_markup)/130; // $wpm = Words Per Minute.

      $reading_time = ceil($wpm); // Round the float number to an integer.

      return $reading_time; // we return an integer with the number of minutes we need to read something.

    } else {

      return $string;




Testing our brand new filter:

Now we install our new module in admin/modules.

It's also helpful to generate dummy content with the devel module.

The View:

Then I proceed to create a simple view of articles:

View configuration:


show: fields


Content: Title
Content: Body ( this is the one we are going to apply our new twig filter )
Content: Body

We access the configuration of the second Content: Body

and under the REWRITE RESULTS option we are going to override the output of the field: 

overriding field views drupal 8

and the result is the following:

twig filters views result

Dec 14 2015
Dec 14

My installation settings:

  • Drupal 7
  • WYSIWYG Version 7.x-2.2+73-dev
  • ckeditor 4.5.1

Client requirements.

I created a simple CKeditor plugin that allows non-technical writers to change the text of a URL only by selecting (partially) the link and editing the content in a prompt.

This small plugin allows the end users to avoid typos or deleting the URL by mistake.

In order to make this plugin work in Drupal, I needed to create a plugin for the WYSIWYG Drupal module (the Drupal way).

Plugin demo:

The Drupal 7 Module:

Drupal 7 module structure:

 - ckeditor_linkchange/ <- Drupal 7 Module folder.
    - plugins/
        - linkchange/ <- Actual ckeditor plugin.
    - <- Drupal 7 info file.
    - ckeditor_linkchange.module <- Drupal 7 module file.

This file declares our module and its dependencies:

name = "Ckeditor linkchange plugin"
description = "Adds the ability to change the text or a url with the linkchange button (CKEditor)"
package = Custom
dependencies[] = wysiwyg
core = 7.x


The `hook_wysiwyg_plugin()` lets the WYSIWYG module know that we have a new plugin available.

 * Implements hook_wysiwyg_plugin().
function ckeditor_linkchange_wysiwyg_plugin($editor, $version) {
    $plugins = array();
    switch ($editor) {
        case 'ckeditor':
            if ($version > 4) {
                $plugins['linkchange'] = array(
                    'path' => drupal_get_path('module', 'ckeditor_linkchange') . '/plugins/linkchange',
                    'filename' => 'plugin.js',
                    'load' => TRUE,
                    'buttons' => array(
                        'linkchange' => t('Linkchange'),
    return $plugins;

In my case I checked that the ckeditor version is superior to version number 4.

the array $plugins['linkchange'] contains the path to the plugin and the buttons parameter which allows this plugin to be enabled from the WYSIWYG UI (admin/config/content/wysiwyg/).

The CKeditor plugin:

 - linkchange/
    - dialogs/
        - linkchange.js
    - icons/
        - linkchange.png    
    - plugin.js


 (function($) {
    CKEDITOR.plugins.add( 'linkchange', {
        icons: 'linkchange',
        init: function( editor ) {
            editor.addCommand( 'linkchange', new CKEDITOR.dialogCommand( 'linkchangeDialog' ) );
            editor.ui.addButton( 'linkchange', {
                label: 'Link text change',
                command: 'linkchange',
                toolbar: 'linkchange'
            if ( editor.contextMenu ) {
                editor.addMenuGroup( 'linkchangeGroup' );
                editor.addMenuItem( 'linkchangeItem', {
                    label: 'Change link',
                    icon: this.path + 'icons/linkchange.png',
                    command: 'linkchange',
                    group: 'linkchangeGroup'
                editor.contextMenu.addListener( function( element ) {
                    if ( element.getAscendant( 'linkchange', true ) ) {
                        return { abbrItem: CKEDITOR.TRISTATE_OFF };
            CKEDITOR.dialog.add( 'linkchangeDialog', this.path + 'dialogs/linkchange.js' );


 CKEDITOR.dialog.add( 'linkchangeDialog', function( editor ) {
    return {
        title: 'Text link change',
        minWidth: 400,
        minHeight: 200,
        contents: [
                id: 'tab-basic',
                label: 'Basic Settings',
                elements: [
                        type: 'text',
                        id: 'txtchng',
                        label: 'Text update',
                        setup: function( element ) {
                            this.setValue( element.getText() );
                        commit: function( element ) {
                            element.setText( this.getValue() );
        onShow: function() {
            var selection = editor.getSelection();
            var element = selection.getStartElement();
            this.element = element;
            if(element.$.nodeName === "A") { // Only if it's and anchor.
                this.setupContent( this.element );
            } else {
        onOk: function() {
            var txt = this.element;
            this.commitContent( txt );
            if ( this.insertMode )
                editor.insertElement( txt );

The complete module can be found here you can take it as reference to implement yours.

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web