How to load JS and CSS libraries in a Drupal 8 controller

Thu, 08/11/2016 - 08:03 | 519 Views

In the blog entry http://www.anexusit.com/blog/how-to-add-reactjs-drupal-8-composer, I show you how to add into Drupal 8 any library globally using composer.

But, that library is just there, because the library isn't used for any module or theme. For that reason, I am going to show you how to use a library in a controller.

In my example, I will include ReactJS inside a controller to be able to create react elements in that controller, to show how to use I will create a simple ReactJS form.

0. Setting your environment

I strongly recommend to set your environment in developer mode; that means to enable all debugs possible and disable all caches, the easy way to do that is using the Drupal Console project with the following command:

$ drupal site:mode dev

1. Create a module

Using the Drupal Console we could create a module using the following instruction:

$ drupal generate:module  --module="MySearch" --machine-name="mysearch" --module-path="/modules/custom" --description="My custom search using react" --core="8.x" --package="Anexus" --module-file --composer -n

2. Create a controller

With our module generated, now we need to create a controller to, later on, add the ReactJS library, again using the Drupal Console commands we will make the controller.

$ drupal generate:controller  --module="mysearch" --class="ReactJSFormController" --routes="title:MySearch name:mysearch.react_j_s_form_controller_form method:form path:/mysearch" 

3. Including library in our module


To enable libraries in our module, we need to create a file named mysearch.libraries.yml, below could you find an example

react.min:
  version: VERSION
  js:
    /libraries/reactjs/build/react.min.js: {}

react:
  version: VERSION
  js:
    /libraries/reactjs/build/react.js: {}

react.dom.min:
  version: VERSION
  js:
    /libraries/reactjs/build/react-dom.min.js: {}

The keys react.min, react and react.dom.min are arbitrary strings; you could change for any identifier do you prefer. 

Maybe you are wondering about the empty curly brackets {}, the idea is like each module could load the same library with different properties, like CSS could have media info, or js could be declared to be minified or report that library is an external file.

I included the minify version of React to being used when you push this module in prod and min version could be utilized in development to facilitate the debugging.

The property version is supposed to be the library version, but if you are not sure about the proper value you could use the "constant" VERSION, this constant contains the Drupal version and is used to generate the URL to include the library resource as you can see below.

<script src="/libraries/reactjs/build/react.min.js?v=8.1.9-dev"></script>

In my case, I was using Drupal 8.1.9-dev.

4. Install your module


Using the Drupal Console we could install our module

$ drupal module:install mysearch

5. Updating our controller

The controller that was generated its pretty simple as you could see in the following snippet of code:

<?php

namespace Drupal\mysearch\Controller;

use Drupal\Core\Controller\ControllerBase;

/**
 * Class ReactJSFormController.
 *
 * @package Drupal\mysearch\Controller
 */
class ReactJSFormController extends ControllerBase {

  /**
   * Form.
   *
   * @return string
   *   Return Hello string.
   */
  public function form() {
    return [
      '#type' => 'markup',
      '#markup' => $this->t('Implement method: form')
    ];
  }

}

 

If you use the module Webprofiler you can validate how many JS were loaded in your controller, in my case 69 JS files as you could appreciate in the following image.

Webprofiler no reactjs

Let me change a little bit, to include our ReactJS library.

/**
   * Form.
   *
   * @return string
   *   Return Hello string.
   */
  public function form() {
    return [
        '#markup' => $this->t('Implement method: index'),
        '#attached' => array(
            'library' =>  array(
                'mysearch/react.min',
               'mysearch/react.dom.min', 
            ),
        ),
    ];
  }

If you remember, the library was registered as react.min, but we need to include the module name first, to avoid conflict with other modules that use the same key.

Sometimes you need to clear your cache to see the changes; you can do that with Drupal Console

$ drupal cache:rebuild all

After that, you could verify that you have one extra JS file, only for this particular controller.

Webprofiler with reactjs

6. Including ReactJS form

The objective of this example was to demonstrate how to render a ReactJS form in Drupal 8

The first thing I need to do is register a local library to load JS and CSS for our form, as you can check in the following example:

react.form:
  version: VERSION
  js:
    js/mysearch.form.js: {}
  css:
    theme:
      css/mysearch.form.css: {}

As you could notice the path is a little different, because doesn't start with "/" that means is a relative module path instead of Drupal Root relative.

When this new library has loaded both JS and CSS components are loaded.

The content of mysearch.form.css is listed below:

.SearchForm {
    padding: 8px 24px;
}
.SearchForm > input,
.SearchForm > textarea,
.SearchForm > select {
    display: block;
    width: 240px;
    padding: 4px 8px;
    margin-bottom: 8px;
    border-radius: 3px;
    border: 1px solid #888;
    font-size: 14px;
}

.ContactForm > input.ContactForm-error {
    border-color: #b30e2f;
}

And the JS for our ReactJS form 

var Search = React.createClass({

    render: function(){
        return (
            React.createElement('form', {onSubmit: this.onSubmit, className: 'SearchForm', noValidate: true},
                React.createElement('input', {
                    type: 'text',
                    placeholder: 'Search'
                }),
                React.createElement("select", { placeholder: 'Category', value: '', onChange: this.changeHandler },
                    React.createElement("option", { value: 1 }, "Software"),
                    React.createElement("option", { value: 2 }, "Movie")
                ),
                React.createElement('button', {type: 'submit'}, "Go")
            )
        );
    },
});

ReactDOM.render(React.createElement(Search),  document.getElementById("content"));

The result will be a simple form, but with a lot of possibilities to implemented.

Drupal ReactJS form

This form doesn't process anything, handle submits and request results via an API will be explaining in a further blog entry.

You can download a full and functional copy of this example from http://github.com/enzolutions/drupal-8-reactjs-form