Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough
Jun 08 2021
Jun 08

There's a neat little Drupal module called JSON Field and recently, I had a chance to play around with it. Out of the box, JSON field is a just a plain field where JSON data can be input and output on a web page. On its own, the module does not do much beyond just printing the raw data formatted as JSON. However, I got to thinking it would be ideal to nicely format the data with HTML. In this article, I will show you how I accomplished this with both a preprocess function and some custom code in Twig.

Getting started

First, you'll want a Drupal 8 or 9 instance running. In the root of your project, run:

composer require drupal/json_field

Note, if you get an error, you may need to append a version number, for example:

composer require drupal/json_field:1.0-rc4

Next, enable the module and create a new field on an entity, for example on a page content type. When I created my field, I chose the option, JSON stored as raw JSON in database

Next, input some JSON data, for sample data, I like to use Mockaroo. (At a high level, I could envision using the Drupal Feeds module to import JSON data in bulk and mapping it to a JSON field but I have not tested this.)

An example of the Mockaroo interface showing mock data being generated An example of the Mockaroo interface showing mock data being generated

Create a preprocess function

We are rendering this data in a node so I have a basic node preprocess function setup below with a sub-theme of Olivero called Oliver. Within this, we will leverage Xdebug to examine the data up close. We write this code in our theme's .theme file.

Define and check for an instance of a node

The first thing we want to do is, since we are working within a node context, we will set some node definitions and check to ensure that we are on a node page.

At the top of our file, we will add

use Drupal\node\NodeInterface;

Then, within the function, we will define the node.

  // Define the node.
  $node = \Drupal::routeMatch()->getParameter('node');

Now we check for an instance of a node:

  // If instance of a node.
  if ($node instanceof NodeInterface) {

Field PHP magic

I named my JSON field field_json_raw and we first want to check to see if the field exists and that it is not empty. For this, I like to use a PHP magic method. A magic method is a short cut of sorts to dig into the field data. In Drupal terms, this looks like:

 if ($node->hasField('field_json_raw') &&
   !$node->get('field_json_raw')->isEmpty()) {
	// Custom code here...
  }

The magic methods above are hasField and get.

Start up Xdebug

Next up, we will use Xdebug to examine the data output to see how we might prepare variables for our Twig template. Within the node preprocess function, I set an Xdebug breakpoint and start listening. Once Xdebug is running we can evaluate the field expression using the magic method again but this time adding the value on. For example, $node->get('field_json_raw')->value. That ends up with the plain value of the field:

[{"id": 1, "country": "Cuba", "animal_name": "Cape Barren goose", "description": "Curabitur convallis.", "animal_scientific": "Cereopsis novaehollandiae"}, {"id": 2, "country": "Vietnam", "animal_name": "Horned puffin", "description": "Pellentesque ultrices mattis odio." Etc...
Xdebug showing the plain value of the JSON output Xdebug showing the plain value of the JSON output

Convert the value into an array

What we need to do now is convert that to a usable PHP array. We use the json_decode function:

 // Set a variable for the plain field value.
 $json_raw = $node->get('field_json_raw')->value;
 // Convert the data into an array using json decode.
 $json_array = json_decode($json_raw);

That ends up looking like this:

Xdebug showing the converted JSON array Xdebug showing the converted JSON array

Create a template variable

Now we have a nicely formatted array to loop through once inside a twig template. The final piece is to check for valid JSON and set the template variable. Note, JSON field already check for valid json but it's probably good practice to do this anyway.

 // Check for valid JSON.
 if ($json_array !== NULL) {
  // Create a variable for our template.
  $vars['json_data'] = $json_array;
 }

Finished preprocess function

Putting it all together, our entire preprocess function looks like this:

getParameter('node');
  // If instance of a node.
  if ($node instanceof NodeInterface) {
    // Check for the field and that it is not empty.
    if ($node->hasField('field_json_raw') &&
      !$node->get('field_json_raw')->isEmpty()) {
      // Set a variable for the field value.
      $json_raw = $node->get('field_json_raw')->value;
      // Convert the data into an array.
      $json_array = json_decode($json_raw);
      // Check for valid JSON.
      if ($json_array !== NULL) {
        // Create a variable for our template.
        $vars['json_data'] = $json_array;
      }
    }
  }
}

Render the JSON variable in Twig

Now we'll go into our Twig template and render the variable with a loop. At its very most basic, it will look something like this:


    {% if content.field_json_raw | render %}
      {% for item in json_data %}
      {{ item.animal_name }}
      {{ item.animal_scientific }}
      {{ item.country }}
      {% endfor %}
    {% endif %}

Of course we want to add HTML to this to make it look nicely styled and here is where you can do most anything you want. I opted for a data table:


    {% if content.field_json_raw | render %}
      

{{ 'Animals from around the world'|t }}

{% for item in json_data %} {% endfor %} {{ 'Animal Name'|t }} {{ 'Scientific Name'|t }} {{ 'Country'|t }} {{ item.animal_name }} {{ item.animal_scientific }} {{ item.country }}
{% endif %}

The code above ends up looking like this:

The finished styled output of the JSON data Twig loop The finished styled output of the JSON data Twig loop

Summary

And there you have it, nicely styled JSON data rendered in a Twig template. Using the JSON Field module might not be a common everyday item but it definitely fulfills a specific use case as outlined here.

Resources

Tags

Jun 02 2021
Jun 02

For my local Drupal development environment over the past few years, I’ve been quite happy using Docksal for my projects. Recently, I onboarded to a new project that required me to use Lando, another popular local development server. In addition, I use PHPStorm for coding, an integrated development environment or IDE. When I am coding, I consider Xdebug to be of immense value for debugging and defining variables.

Xdebug is an extension for PHP, and provides a range of features to improve the PHP development experience. [It's] a way to step through your code in your IDE or editor while the script is executing.

Getting started: the basic setup

In this article I will share with you my basic setup to get up and running with Xdebug 3. The core of this tutorial requires Lando and PHPStorm to be running on your machine. You can head over to GitHub to grab the latest stable release of Lando. Installing Lando will also install Docker desktop, a containerized server environment. You'll also need PHPStorm to be installed as well. If you have any issues with getting Lando running, you can check their extensive documentation. You can spin up a Drupal 9 site using Lando but as of yet, there is no option that will set it up as a true composer based workflow. Therefore, you can use Composer itself composer create-project drupal/recommended-project to create a new Drupal 9 project and then initialize it with Lando. In this case, you'd need Composer 2 to be setup globally on your local machine but once Lando is up and running, you can then switch to using lando composer... Thereafter, be sure to install Drupal as well.

Configure the Lando recipe

Lando has the notion of "recipes" for specific server setups and you'll want to set up a basic Drupal 9 site running on Lando. The TL;DR for the one I am using is below. This code would go in your .lando.yml file in the root of your project. Noting that any time your change your recipe, you will need to run lando rebuild -y.

name: d9sandbox
recipe: drupal9
config:
  php: '7.4'
  composer_version: '2.0.7'
  via: apache:2.4
  webroot: web
  database: mysql:5.7
  drush: true
  xdebug: true
  config:
    php: lando/config/php.ini

services:
  node:
    type: node:12.16
  appserver:
    xdebug: true
    config:
      php: lando/config/php.ini
    type: php:7.4
    overrides:
      environment:
        PHP_IDE_CONFIG: "serverName=appserver"

# Add additional tooling
tooling:
  node:
    service: node
  npm:
    service: node

Of note in the above code:

  1. Xdebug set to true
  2. PHP set to 7.4
  3. MySQL set to 5.7
  4. Composer set to 2.x
  5. Pointer to a custom php.ini file where we will need additional configuration for Xdebug.

Configure php.ini

Now, we need to create our php.inifile within lando/config. Create these directories if they do not exist already. You'll want to set these variables for Xdebug:

[PHP]
xdebug.max_nesting_level = 256
xdebug.show_exception_trace = 0
xdebug.collect_params = 0
xdebug.mode = debug
xdebug.client_host = ${LANDO_HOST_IP}
xdebug.client_port = 9003
xdebug.start_with_request = yes
xdebug.log = /tmp/xdebug.log

I also like to add memory_limit = -1 to this file as well to avoid any out of memory issues. Also note that port 9003 is set for Xdebug, that is because Xdebug 3 requires this. If you've used an older version of Xdebug in the past, the port was typically 9000.

Rebuild Lando

Now that we have Lando all set, go ahead and run lando rebuild -y so that these updates take effect. Once that is done, you'll see something like this in terminal:

NAME            d9xdebug
LOCATION        /Users/me/Projects/d9xdebug
SERVICES        appserver, database, node
APPSERVER URLS  https://localhost:55165
                http://localhost:55166
                http://d9sandbox.lndo.site/
                https://d9sandbox.lndo.site/

Once that is done, go to your site and make sure it loads. In my case the url is http://d9sandbox.lndo.site/.

Configure PHPStorm

Now we are ready to configure PHPStorm. Here, we will connect Docker to the IDE. The following below is a series of screen captures that illustrate how to set this up.

Choose a language level and a CLI interpreter

The first thing to do is to go to preferences and search for PHP. Here you will want to set PHP 7.4 as the language level. Next click on the three vertical dots to choose a CLI interpreter.

Choosing a language level and a CLI interpreter in PHPStorm Choosing a language level and a CLI interpreter in PHPStorm

Configure the CLI interpreter

Here you will choose "From Docker…"

Configuring the CLI interpreter in PHPStorm Configuring the CLI interpreter in PHPStorm

Choose the CLI image name

Here you will choose "Docker" and then the image name, in our case devwithlando/php:7.4-apache-2. Now click apply and ok to save all these settings.

Choosing the CLI image name Choosing the CLI image name

Trigger Xdebug

Now for the final step, we are ready to trigger Xdebug with the steps below.

  1. Input a variable to set a breakpoint for. In the example, below, I've set a fake variable, $a =1;within function bartik_preprocess_node(&$variables){...}
  2. Run lando drush cr
  3. Click in the left margin parallel to the variable so that you see a red dot.
  4. Click on the Phone icon at the top of the IDE so that it turns green. This is the "listener" where we set PHPStorm to listen for incoming connections so as to trigger Xdebug.
  5. Now click accept after choosing index.php. This is a standard convention.
  6. If all goes well, you will now see Xdebug get triggered. Click “Accept.”
Triggering Xdebug in the IDE Triggering Xdebug in the IDE

View Xdebug output

Once the above is set, you should now see the actual output from Xdebug as shown below. Thereafter, you can inspect arrays to your heart’s content!

View the Xdebug output View the Xdebug output

Tips and Tricks

  • I have found that occasionally, if other Lando projects are running, Xdebug will sometimes not be triggered so it might be best to only run one project at a time. To do that, run these commands in the root of your project.
lando poweroff
lando start
  • If for some reason the listener does not trigger Xdebug, stop it and clear Drupal cache. Then start listening again.

Summary

Hopefully this article has helped to get up and running with Lando and Xdebug 3 with PHPStorm. I cannot stress enough how useful Xdebug is when working on a Drupal website. I’ve seen folks struggle with using Kint, print_r(), and var_dump(). While these are somewhat useful, to me nothing compares to the speed, preciseness, and usefulness of Xdebug.

Resources

Tags

Jun 03 2013
Jun 03
Theming a Multi-level Responsive Menu in Drupal 7 » Danny Englander

Drupal engineer with a passion for learning new things every day

In my last post, I discussed my new photography site I'm building and theming with Drupal. The site is responsive and I've been searching for an ideal responsive touch friendly multi-level menu. I looked at and tried several jQuery plugins, CSS3 styles and a few Drupal Modules but nothing was quite right.

The Issue

Part of the issue is that I need multi-level menus that are touch friendly, but many of the menus I looked at had severe UX drawbacks. It's confusing to click (touch) on a menu item, have it expand and then make it go to a link all at the same time. FlexNav, a jQuery menu plugin written by Jason Weaver solves this where it separates a linked menu item from navigation through the DOM itself.

Implementation

So FlexNav checks all the boxes on my touch friendly multi-level menu wish list. This is most of the task but the tricky part is dovetailing this into my Drupal theme. Part of the difficulty when digging into the Drupal API is fully understanding what's going on with the menu and how to alter it, of course without hacking core.

Digging into Drupal's API

I checked out all the documentation of menus within the Drupal API and looked at the code in the core file menu.inc but came up empty with adding an ID, class and attributes to the top level

    menu item but not any child
      tags below it. I figured I could do this with jQuery and it's pretty trivial actually but by doing so the menu was just plain buggy. So that left me with the tried and true preprocess function, the bane of any Themer's existence.

      A Solution

      In my research, I found lots of people asking how to do this very same task in forum posts on drupal.org and Drupal Answers on Stack Exchange but as is often the case, there were varied and confusing suggestions. Finally I was looking at how the Drupal Bootstrap Theme implements its main menu and bingo, I found a solution that I could trivially implement into my own custom Unsemantic Framework based Drupal Theme. Note that I didn't use Bootstrap's actual menu as I don't like the way it prevents clicking on a top level menu item if it has sub-menu items.

      Prototype - desktop menu on the left, mobile on the right

      Prototype - desktop menu on the left, mobile on the right

      API Functions

      We'll be leveraging a few bits from the Drupal API here:

      • function menu_tree - To render the full menu tree
      • function drupal_render - To attach classes, attributes only to the main
          wrapper and not to its children.</li>
        • function menu_configure - To build the links and specify the menu we want to theme for FlexNav. (In our case, the Main Menu.)
        • function hook_css_alter - This is just for good measure to unset the nasty system.menu.css file that plagues all themers.

        The Code

        The first bit of code goes into a preprocess_page function in your theme's template.php file.

        function MYTHEME_preprocess_page(&$vars, $hook) {
          // Primary nav.
          $vars['primary_nav'] = FALSE;
          if ($vars['main_menu']) {
          // Build links.
          $vars['primary_nav'] = menu_tree(variable_get('menu_main_links_source', 'main-menu'));
          // Provide default theme wrapper function.
          $vars['primary_nav']['#theme_wrappers'] = array('menu_tree__primary');
         }
        }

        Essentially the above grabs the links tree from the main menu and then creates a wrapper that we can then leverage with function menu_tree as such:

        /**
        * Theme wrapper function for the primary menu links
        */
        function MYTHEME_menu_tree__primary(&$vars) {
          return '
          '</span> . $vars['tree'] . '
        '
        ; }

        Note the custom class and data attribute here, this is the key to the entire exercise. The beauty in this is, it only gets added to the top level

          tag in our menu because we specified #theme_wrappers above. That's crucial for getting this all to work. For good measure, I unset system.menu.css using a hook_css_alter function but that's optional, you just might need to do a few CSS overrides if you don't do this.

          Finally in the theme's page.tpl.php file, we render the menu:

            class="menu-button">Menu
class="menu-navigation">

Finally, be sure to add Flexnav's CSS and JS files - drupal_add_js and drupal_add_css are good methods here - and then call FlexNav:

$(".flexnav").flexNav();

...typically from your theme's custom JS scripts file.

As always, I learned an incredible amount of knowledge on this after working on this specific problem over the course of a few weeks and I'll be contributing back a lot of this code for some feature requests I have for my theme, Bamboo.

Resources

Tags

Mar 14 2012
Mar 14
Theming a Node Post Date With Drupal 7 » Danny Englander

Drupal engineer with a passion for learning new things every day

In this article I will show you how to theme a post date in Drupal 7. The goal is to take a boring date printed inline and theme it so it stands out as a design element. I covered this a while back for Drupal 6 so I thought this would be a nice update for doing this with Drupal 7.

An example of a themed date in parts

An example of a themed date in parts

First Steps

The first step is to enable "Display author and date information" on your content type edit page at:

/admin/structure/types/manage/[your_custom_content_type]

…as shown below:

Get the Data

We can now go retrieve this data in the node.tpl.php file and alter the theming of the post date. Ideally this will be for a custom node template and not the main node.tpl.php file. In my case, I have a content type called blog and I only want the post date to look this way for blog posts. I take node.tpl.php (located in my theme folder) and copy and rename it to node--blog.tpl.php. Note the "double dashes", that's new in Drupal 7.

Find the Code to Replace

Now looking in my new node--blog.tpl.php file, I see the PHP code I am after that I will then alter:

 if ($display_submitted): ?>
   class="submitted"> print $date; ?> --  print $name; ?>
endif; ?>

I want to replace this with some custom PHP code essentially breaking the date up into parts, and getting rid of the author name (though you could easily add this back in and theme it).

Code it

Here is our new code:

 if ($display_submitted): ?>
   class="date-in-parts">
     class="day">  echo date("j", $node->created); ?>
     class="month"> echo date("M", $node->created); ?>
     class="year"> echo date("Y", $node->created); ?>
  
 endif; ?>

Theme it

As you can see, we now have broken the date up into parts which is highly themable. You can make it round, square, or whatever you want with some CSS3 goodness. Note: you will also want to refer to the PHP date manual to get the desired date format.

Resources

Tags

Dec 30 2010
Dec 30
How to add a block region to a node page in Drupal 6 » Danny Englander

Drupal engineer with a passion for learning new things every day

As a Drupal themers / front end developers, we are always asked to push the envelope of what's possible with design and theming. With Drupal 6, custom block regions are usually added in page.tpl.php which is normally outside of the actual page node content / comments. It would be above, below content or in sidebars typically. Occasionally you have the need to add a block region within a node area. This comes in handy especially if you are using a custom themed node page that uses CCK fields, e.g., "node-[custom_content_type].tpl.php".  To accomplish this there are a few steps involved.

How to add a block region to a node page in Drupal 6

  1. Determine and add code to the node type you will add it to in your theme. It might be simply node.tpl.php or a custom named node as mentioned above.
  2. Add a preproccess node function to your theme's template.php
  3. Add the new region your theme's .info file
  4. Flush your site cache
  5. Test it out either using context or the actual block page assigning an existing or new block to the new block region

First we need to determine the name of the new block region that we are going to add. In this tutorial we will call it "node_message". In the node page you want to add code to determine where in the page you want to add it. This part is just like adding a regular block region to page.tpl.php. In our example node.tpl.PHP, we will add the new code after the $content and post meta variables. Note that your node or custom node template file may vary but this will give you an idea of what's possible. Here is code near the bottom of node.tpl.php.

 class="content">
   print $content ?>
if ($links): ?> class="postmeta"> print $links; ?> endif; ?>

We now insert our new "node_message" block code at the end. Note that you could also wrap a custom div class for the new code as well as illustrated in our example. This new block region should not already be in page.tpl.php as you do not want the region to load and process twice so it should be unique to the node.tpl.php level.

  class="content">
     print $content ?>
  

 if ($links): ?>
     class="postmeta"> print $links; ?>
   endif; ?>


  if ($node_message): ?>
      class="node-message"> print $node_message; ?>
 endif; ?>

Now we need to tell Drupal how to process this new code so we will go ahead and add our preprocess function to our theme's template.php file. Note if your theme does not have this file you can go ahead and create it. If it is a new template file with nothing else added you need to add an opening tag but not a closing one. If you are adding to an existing template.php file with existing code, then it's not necessary to add the opening tag.


function my_theme_name_preprocess_node(&$vars, $hook) {
  $vars['node_message'] = theme('blocks', 'node_message');
}

Note above that "my_theme_name" should be the actual name of your theme so you will need to replace that as well as 'node_message' which is the custom name of your block if you decide to change the name.

The next part entails adding the new block region to your theme's .info file. You can do something like this:

regions[node_message] = Node Message

Again take care in the name, it must reflect the actual name of your block, i.e. 'node_message'. Now we are at a point where you can go ahead and clear your site cache and assign or create a new block on the /admin/build/block page. The new custom 'node_message' region should show up on in the regions drop down menu on this page. If you have Drupal's Context module installed, you can also assign your block to the newly created region using that.

Update

An interesting conversation transpired on Twitter after I posted a link to this blog post: Twitter Conversation

Amy Stephen (@AmyStephen) suggested that this tutorial was similar to using {loadmodules positionname} in Joomla & Steve Burge (@alledia) suggested you could use the Drupal module, Insert Block. This method while similar is more of a one off type implementation for single pages.

What this tutorial does is show you programmatically how to add a block within a node that can be leveraged globally. In a way the Insert Block module is more granular where you can literally insert a block between two paragraphs. The method described in this blog post would be ideal for example of having a block on 50 different pages between two specific CCK fields in a custom themed node which you could simply set with one Context instance.

In the screen capture below you can click to enlarge and see an example of a custom block added within a node. I hope this tutorial has been helpful, feel free to comment with any feedback or issues. This tutorial was inspired by a post on drupal.org: http://drupal.org/node/361209

Tags

Apr 01 2010
Apr 01
How to Customize and Theme Post & Date info in Drupal 6 » Danny Englander

Drupal engineer with a passion for learning new things every day

When I was theming my blog in Drupal, I decided I wanted a better way to customize and display post info such as wording used and the way date was displayed. The first step is to have a look around and see where the code is coming from that renders this info. I viewed the files in my custom theme folder and discovered these few lines of code in node.tpl.php

 if ($submitted): ?>
     class="submitted"> print $submitted ?>
 endif; ?>

In HTML, that is rendered as:

Submitted by Danny Englander on 4-01-10

I decided I wanted to have customized date and post info only for my blog so a standard Drupal convention allows you to have node-blog.tpl.php to tailor the display of the blog content type. My theme did not have this file so I simply copied node.tpl.php and renamed it. Now that I had my custom Node Blog template file, I was all set to start customizing date and post info.

Well, the above code seemed boring and hard to theme in Drupal so I discovered a way to be more specific with the way the code that displays post and date info gets output in your Drupal theme.

I simply replaced the code above with this code:

 class="meta post-info">
 if ($submitted): ?>
 class="submitted">Posted by  print theme('username', $node) ?>
   endif; ?>

 class="dateblock">
       class="month"> print $date_month ?>
       class="day"> print $date_day ?>
       class="year"> print $date_year ?>
 

**Note this crucial bit of code below was left out of the original post so if this did not work for you that's why. Place the code below in your theme's template.php file or create one if you don't have one already. (change "mytheme" to the name of your theme):

function mytheme_preprocess_node(&$vars) {
  // Grab the node object.
  $node = $vars['node'];
  // Make individual variables for the parts of the date.
  $vars['date_day'] = format_date($node->created, 'custom', 'j');
  $vars['date_month'] = format_date($node->created, 'custom', 'M');
  $vars['date_year'] = format_date($node->created, 'custom', 'Y');
}

By breaking down and getting more specific with this code, I was now able to use some CSS to customize the date into the nice little square blocks you see to the left of every post title. It also allows to have "Posted by", "submitted by" or whatever other wording you choose for the author part of the code.

Tags

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