Drupal 8 et React : block dynamique ou headless

      

2 tutos en 1, nous allons voir comment coupler Drupal avec react.js en headless et ... avec Drupal lui même (pour rajouter un bloc react temps-réel).

D'abord, dans les deux cas, nous activons les modules RESTful Web Services et Serialization.

Ensuite nous allons créer une vue sur les derniers commentaires postés avec un display REST export (mettez plain text dans le format de display des champs par soucis de simplicité) :

Nous allons maintenant nous appuyer dessus pour faire des appels du webservice JSON via React. Notez pour plus tard le chemin d'appel du webservice : api/v1/comments.

Drupal Headless avec React

En mode headless, Drupal n'est pas utilisé pour le front-office, seulement pour le back-office. Nous avons besoin de charger les bibliothèques react (on peut le faire en ligne) dans un premier temps et de faire appel au webservice dans une second temps :

<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8" />
 <title>App</title>
 <link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
 <link rel="stylesheet" type="text/css" href="stylesheets/style.css">
</head>

<body>

<div id="container">test</div>

<script src="https://npmcdn.com/react@15.3.1/dist/react.js"> </script>
<script src="https://npmcdn.com/react-dom@15.3.1/dist/react-dom.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.24/browser.min.js"></script>
<script type="text/babel" src="js/app.jsx"></script>

</body>
</html>

react.js et react-dom.js sont les libraries react à proprement parler. Mais en plus nous incluons axios qui permet de faire des appels de webservices en JSON. Quand à babel-core c'est le compiler qui transforme le JSX en javascript. Le JSX permet (entre autres) d'avoir des tags HTML directement dans le code JavaScript, ils sont convertis en React.createElement() directement.

Le fichier app.jsx :

class App extends React.Component {

constructor() {
 super();
 // Setting up initial state
 this.state = { 
   data: []
 }
}

// calling the componentDidMount() method after a component is rendered for the first time

componentDidMount() {
 var th = this;
 this.serverRequest = axios.get(this.props.source)
 .then(function(event) {
   th.setState({
     data: event.data
   });
 })
}

// calling the componentWillUnMount() method immediately before a component is unmounted from the DOM

componentWillUnmount() {
 this.serverRequest.abort();
}

render() {
 var titles = []
 this.state.data.forEach(item => {
   titles.push(<h3 className="events">{item.subject}</h3> );
 })
 return (
   <div className="container">
     <div className="row">
       <div className="col-md-6 col-md-offset-5">
         <h1 className="title">All Comments</h1>
         {titles}
       </div>
     </div>
   </div>
 );
 }
}

// rendering into the DOM
ReactDOM.render(
 <App source="http://test.box.local/drupal/web/api/v1/comments" />,
 document.getElementById('container')
);

Voici quelques explications sur le code, mais pour en savoir plus sur react vous pouvez aller ici ou ici.

La base de react c'est essentiellement son DOM virtuel et son moteur de rendu optimisé qui détecte les différences d'un rendu à l'autre : tout se passe via ReactDOM.render et React.CreateElement (ici masqué dans du JSX qui les appelle automatiquement dès qu'il détecte un tag HTML), ainsi que la possibilité de créer ses propres "tags HTML" grâce au système de classe :  <App .../> est compris par JSX comme un React.CreateElement('App', ...); lui même compris comme l'instanciation de la classe App (classe qui doit contenir une méthode render et hériter de React.Component pour être reconnue par React).

C'est une application extrêmement simple, mais elle fonctionne et permet de voir les grands principes de React et comment interagir avec Drupal.

Mais si l'on ne souhaite pas faire tout un front-end en react il est possible de l'utiliser pour améliorer l'ergonomie de Drupal en rajoutant React sur certains blocs par exemple pour les rendre "temps réel". C'est ce que nous allons voir dans la 2ème partie de ce tutoriel.

Drupal real-time avec React

Cette partie est tirée d'un tutoriel portugais qui lui même est la conversion en drupal 8 d'un tutoriel en drupal 7.

Avec la console (uniquement pour drupal 8)  nous créons un module react_comment et un bloc ReactComments :

drupal generate:module --module="react_comment" --machine-name="react_comment" --module-path="/modules/custom" --description="React real time comments" --core="8.x" --package="Custom" --composer --learning --uri="http://default" --no-interaction

drupal generate:plugin:block --module="react_comment" --class="ReactComments" --label="React comments" --plugin-id="react_comments" --learning --uri="http://default" --no-interaction

Voici le fichier react_comment.libraries.yml qui permet d'inclure le javascript :

recent.comments:
 version: VERSION
 js:
   js/react-comments.js: {}
 dependencies: 
   - react_comment/reactjs

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

Note: il faut inclure une version de react.min.js dans le répertoire js en le téléchargeant à la main (vous pouvez en trouver une version ici).

Voici le fichier src/Plugin/Block/ReactComments.php qui sert à inclure l'application react et à créer un div par défaut dans un block que l'application react pourra modifier à son gré :

<?php

namespace Drupal\react_comment\Plugin\Block, il n'y a que 2 lignes qui changent :

use Drupal\Core\Block\BlockBase;

/**
  * Provides a 'ReactComments' block.
  *
  * @Block(
  * id = "react_comments",
  * admin_label = @Translation("React comments"),
  * )
  */
class ReactComments extends BlockBase {
 
  /** 
    * {@inheritdoc}
    */
  public function build() {
    $build = [];
    $build['react_comments']['#markup'] = '<div id="recent-comments"></div>';
    $build['#attached']['library'][] = 'react_comment/recent.comments';
    return $build;
  }

}

Enfin la pièce maîtresse : le fichier js/react-comments.js :

/**
 * @file
 * Main JS file for react functionality.
 *
 */
 
(function ($) {
 
  Drupal.behaviors.react_blocks = {
    attach: function (context) {
 
      // A div with some text in it
      var CommentBox = React.createClass({displayName: 'CommentBox',
 
      loadCommentsFromServer: function() {
        $.ajax({
          url: this.props.url,
          dataType: 'json',
          success: function(data) {
            this.setState({data: data});
          }.bind(this),
          error: function(xhr, status, err) {
            console.error(this.props.url, status, err.toString());
          }.bind(this)
        });
      },
 
      getInitialState: function() {
        return {data: []};
      },
 
      componentDidMount: function() {
        this.loadCommentsFromServer();
        setInterval(this.loadCommentsFromServer, this.props.pollInterval);
      },
 
      render: function() {
          return (
            React.createElement("div", {className: "commentBox"},
              React.createElement("h3", null, React.createElement("b", null, "Check them out!")),
              React.createElement(CommentList, {data: this.state.data})
            )
          );
        }
      });
 
      var CommentList = React.createClass({displayName: 'CommentList',
        render: function() {
          var commentNodes = this.props.data.map(function (comment) {
            return (
              React.createElement(Comment, {name: comment.name, subject: comment.subject},
                comment.subject
              )
            );
          });
          return (
            React.createElement("div", {className: "commentList"},
              commentNodes
            )
          );
        }
      });
 
      var Comment = React.createClass({displayName: 'Comment',
        render: function() {
          return (
            React.createElement("div", {className: "comment"},
              React.createElement("h2", {className: "commentAuthor"},
                this.props.name
              ),
              this.props.subject
            )
          );
        }
      });
 
      // Render our reactComponent
      React.render(
        React.createElement(CommentBox, {url: "api/v1/comments", pollInterval: 2000}),
        document.getElementById('recent-comments')
      );
 
    }
  }
 
})(jQuery);

Ce fichier est écrit en JS et non en JSX, il est donc un peu plus lourd à lire. On retrouve les React.createElement qu'on aurait pu écrire sous forme de tag HTML. L'appel au webservice est fait via jQuery. setInterval sert à rafraîchir le contenu toutes les 2 secondes. On pourrait imaginer des mécanismes de rafraîchissement plus complexes en se basant sur l'API de cache de drupal 8. Le reste est assez facile à comprendre quand on connaît React. Nous avons 3 classes : CommentBox, CommentList et Comment qui sont imbriquées.

Et voila le résultat :

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.