Drupal 8 Custom Block Module Dev
Tags: drupal, jquery, css, php
Published: 2019-09-17 22:08:53

For those ready to move beyond a simple "Hello World" module, this post documents building a Drupal 8 module with a Giphy search form in a custom block. The form uses jQuery to request data from the Giphy API and display the results.

Drupal 8 module to demonstrate custom block creation with the following features:

*   configuration data that is passed into the modules js library
*   public API search using core jQuery functions
*   twig template for a front end Giphy search form

<h5><i class="icon-github"></i> <a href="https://github.com/jimfrenette/drupal-recipes/tree/master/giphys">Source Code</a></h5>

### Module Folder

To get started, create a folder for the module in either `/modules/custom/` or `/sites/all/modules/`. The name of the module folder is typically the same as the name given to the module. In the case of this particular module, `giphys` is an appropriate name. For example:

    # context is drupal project root
    
    cd modules
    mkdir custom
    mkdir custom/giphys
    

### Module Info File

An info.yml file is needed to store module metadata. Since the module machine name is `giphys`, the file will be named `giphys.info.yml`

    # create the giphys.info.yml file
    
    cd custom/giphys
    touch giphys.info.yml
    

Here are the metadata contents of this file. Included is data to inform Drupal of the core compatibility, module dependencies and a description for the administration portal interface.

##### giphys.info.yml

    name: Giphys
    type: module
    description: 'Giphys is a Giphy search block'
    core: 8.x
    package: Other
    dependencies:
      - block
    

After saving `giphys.info.yml`, in Drupal, select the Extend menu, e.g., http://drupal.docker.localhost:8000/admin/modules. Giphys should be listed under Other:

![Extend | Giphys](https://thepracticaldev.s3.amazonaws.com/i/wqduouuiayyf1fy9gk7b.png)

*   For information on how to setup a Drupal Docker development environment, read my post on the subject at [jimfrenette.com/2017/05/docker-drupal-dev-environment](https://jimfrenette.com/2017/05/docker-drupal-dev-environment/).

### Module File

The `giphys.module` file is the entrypoint used to define help and theme hook functions that return respective data and paths. .module files should only contain functions that implement hooks.

    # create the giphys.module file
    
    touch giphys.module
    

##### giphys.module

```php
<?php

/**
 * @file
 * Module file for giphys_module.
 */

use Drupal\Core\Routing\RouteMatchInterface;

/**
 * Implements hook_help().
 *
 * @see https://www.drupal.org/documentation/help-text-standards
 *
 * @see hook_help()
 */
function giphys_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.giphys':
      // Help text for the admin section, using the module name in the path.
      return t("This is help text created in giphys implementation of hook_help().");
  }
}

function giphys_theme($existing, $type, $theme, $path) {
  return [
    'giphys' => [
      'variables' => [
        'url' => 'http://example.com',
        'secret' => NULL
      ],
    ],
  ];
}
```

### Twig Template

Note that Drupal 8 currently uses Twig version 1.x, and Drupal 8.4 is slated to use Twig version 2.x. Twig 2 also requires PHP 7.

    # create the templates/giphys.html.twig file
    
    mkdir templates
    
    touch templates/giphys.html.twig
    

##### giphys.html.twig

```html
<div>
  <form id="giphys-search" accept-charset="UTF-8">
    <label for="giphys-search-text" class="visually-hidden">Search</label>
    <input id="giphys-search-text" class="required" title="Enter the terms you wish to search for." type="search" value="" size="15" maxlength="128" required="required" aria-required="true" />
    <input class="search-form__submit button js-form-submit form-submit" type="submit" value="Search" />
  </form>
  <ul class="giphys-list"></ul>
</div>
```

### Module Libraries

CSS and JavaScript for the module is defined in a libraries YAML data file. The CSS and JavaScript assets are loaded in the order they are listed. Additionally, core jQuery is required by the module and is listed as a module dependency to ensure that it gets loaded.

Create the `giphys.libraries.yml` in the root of the giphys folder together with the other data files.

    # create the giphys.libraries.yml file
    
    touch giphys.libraries.yml
    

##### giphys.libraries.yml

    giphys:
      version: 1.x
      css:
        theme:
          css/giphys.css: {}
      js:
        js/giphys.js: {}
      dependencies:
        - core/jquery
    

#### CSS

The CSS for our module is very simple with only a few tweaks to display the giphy API search results list as tiles. Create a new `css` folder and the `giphys.css` file as shown.

    # create the css/giphys.css file
    
    mkdir css
    
    touch css/giphys.css
    

##### giphys.css

```css
ul.giphys-list {
    list-style: none;
    padding: 0;
    margin: 0;
}
ul.giphys-list li {
    margin-right: 5px;
    display: inline-block;
}
``` 

#### JavaScript

This file uses the `drupalSettings` object to get the Giphys module block configuration data. This data is used to make the Giphy API request when a user enters a search term. The results are parsed into html elements and injected into the unordered list element output by the twig template.

    # create the js/giphys.js file
    
    mkdir js
    
    touch js/giphys.js
    

##### giphys.js

```javascript
console.log(drupalSettings.giphys);

(function ($) {
  var $giphysList,
      giphysEndpoint,
      giphysSearchTerm;

  giphysEndpoint = drupalSettings.giphys.url + '?api_key=' + drupalSettings.giphys.secret;

  $giphysList = $('ul.giphys-list');

  $('#giphys-search').submit( function(e) {
    e.preventDefault();

    $giphysList.empty();

    giphysSearchTerm = $('#giphys-search-text').val();

    $.getJSON(giphysEndpoint + '&q=' + giphysSearchTerm).done(function(data) {
      if (data) {

        var $giphysListItem,
            giphysData = data.data,
            len = giphysData.length;

        for(var i = 0; i < len; i++) {
          $giphysListItem = '<li><img src="'+ giphysData[i].images.fixed_height_small.url +'" /></li>';
          $giphysList.append($giphysListItem);
        }
      }
    });

  });

})(jQuery);
```

### Module PHP

The `GiphysBlock.php` uses an instance of the block plugin to define a custom block. Also contained in the GiphysBlock class are functions that add a form to the block configuration for setting the API secret, URL and default search term.

The build function attaches the block configuration values to the drupalSettings object for the front end code to use.

    # create the src/Plugin/Block/GiphysBlock.php file
    
    mkdir src
    mkdir src/Plugin
    mkdir src/Plugin/Block
    
    touch src/Plugin/Block/GiphysBlock.php
    

##### GiphysBlock.php

```php
<?php

namespace Drupal\giphys\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Defines a Giphys block block type.
 *
 * @Block(
 *   id = "giphys_block",
 *   admin_label = @Translation("Giphys block"),
 *   category = @Translation("Giphys"),
 * )
 */
class GiphysBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function blockForm($form, FormStateInterface $form_state) {

    $config = $this->getConfiguration();

    $form['url'] = [
      '#type' => 'textfield',
      '#title' => $this->t('url'),
      '#default_value' => 'http://api.giphy.com/v1/gifs/search',
      '#required' => TRUE,
      '#description' => $this->t('api url'),
    ];

    $form['secret'] = [
      '#type' => 'textfield',
      '#title' => $this->t('secret'),
      '#default_value' => 'dc6zaTOxFJmzC',
      '#required' => TRUE,
      '#description' => $this->t('api key'),
    ];

    $form['term'] = [
      '#type' => 'textfield',
      '#title' => $this->t('term'),
      '#default_value' => 'trump frog',
      '#required' => FALSE,
      '#description' => $this->t('default search query term or phrase'),
    ];

    return $form;

  }

  /**
   * {@inheritdoc}
   */
  public function blockSubmit($form, FormStateInterface $form_state) {
    $this->setConfigurationValue('url', $form_state->getValue('url'));
    $this->setConfigurationValue('secret', $form_state->getValue('secret'));
    $this->setConfigurationValue('term', $form_state->getValue('term'));
  }

  /**
   * {@inheritdoc}
   */
  public function build() {

    $config = $this->getConfiguration();

    return array(
      '#theme' => 'giphys',
      '#attached' => array(
        'drupalSettings' => array(
            'giphys' => array(
                'url' => $config['url'],
                'secret' => $config['secret'],
                'term' => $config['term']
            )
        ),
        'library' => array(
          'giphys/giphys',
        ),
      ),
    );

  }
}
```

Here is what the Giphys block looks like placed in the content area with search results shown.

![Giphys block placed](https://thepracticaldev.s3.amazonaws.com/i/hs2x1tnrticjkrvabkp0.png)

<h5><i class="icon-github"></i> <a href="https://github.com/jimfrenette/drupal-recipes/tree/master/giphys">Source Code</a></h5>

Originally published at [jimfrenette.com/2017/08/drupal-8-custom-block-module-dev](https://jimfrenette.com/2017/08/drupal-8-custom-block-module-dev/)

* * *

#### Resources

*   [Acquia Knowledge Base: Lesson 9.3 - Adding JavaScript / CSS / Libraries to components](https://kbv2.acquia.com/article/lesson-93-adding-javascript-css-libraries-components)
*   [Creating custom modules | Drupal 8](https://www.drupal.org/docs/8/creating-custom-modules)
*   [Adding stylesheets (CSS) and JavaScript (JS) to a Drupal 8 module](https://www.drupal.org/docs/8/creating-custom-modules/adding-stylesheets-css-and-javascript-js-to-a-drupal-8-module)
*   [Twig in Drupal 8](https://www.drupal.org/docs/8/theming/twig)
*   [Giphy API Doumentation](https://developers.giphy.com/docs/)