9 Dicembre, 2017 | Di

Drupal REACTivated (PARTE 2)

Drupal REACTivated (PARTE 2)

Come creare siti decoupled con Drupal, React e JSON API - PARTE 2

In questo secondo articolo della serie (qui si può trovare il primo) vedremo più approfonditamente come interrogare il backend Drupal, attraverso le JSON API. Il risultato finale sarà quello di un ottenere un semplice blog completamente decoupled che permetterà di visualizzare l'elenco paginato degli Articoli presenti nel backend nonché il dettaglio di ogni Articolo.

Prima di iniziare è bene fare una doverosa premessa. Il mondo dei frontend frameworks è un mondo in continua e veloce evoluzione. In pochi mesi è possibile vedere rilasciate molte nuove release. È per questo che, prima di cominciare ad implementare le nuove funzionalità sul nostro frontend React, è bene effettuare l'aggiornamento delle librerie da noi utilizzate.

Aggiornare React

Iniziamo modificando il file package.json nel modo seguente:

{
  "name": "drupal-reactivated-frontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-scripts": "1.0.17",
    "superagent": "^3.5.2",
    "superagent-jsonapify": "^1.4.5"
  },
  "devDependencies": {
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

Le differenze rispetto alla versione precedente sono:

  • Aggiornamento di react, react-dom e react-script all'ultima versione
  • La libreria react-script è stata spostata dalle devDependencies alle dependencies

Lanciamo quindi 'yarn install' per installare la nuova versione delle librerie. Controlliamo che tutto continui a funzionare avviando il frontend con 'yarn start' (accertiamoci che anche il backend sia avviato).

Installare le librerie aggiuntive

Nella nuova versione del nostro progetto React faremo utilizzo di altre librerie. Per installare le nuove dipendenze utilizziamo il seguente comando:

yarn add reactstrap@next bootstrap@4.0.0-beta.3 react-router-dom html-truncate moment

Vedremo come utilizzare tali librerie nel prosieguo dell'articolo ma spieghiamo adesso molto brevemente per cosa saranno utilizzate:

  • reactstrap e bootstrap: Reactstrap non è altro che una libreria di componenti Bootstrap 4 che fa uso di React e che quindi non utilizza JQuery al suo interno. Per applicare lo stile di Bootstrap 4 abbiamo inoltre incluso tra le nostre dipendenze il pacchetto bootstrap versione 4.0.0-beta.3
  • react-router-dom: la nostra SPA sarà composta da più 'schermate', per definire le rotte di ogni schermata e la navigazione tra le varie schermate faremo utilizzo della libreria react-router e in particolare di quella specifica per le applicazioni web
  • html-truncate e moment: due librerie di utilità per gestire rispettivamente il troncamento di codice html (con corretta chiusura automatica dei tag) e il parsing/formattazione delle date

Come abbiamo appena accennato la nostra applicazione decoupled farà utilizzo di Bootstrap 4 come UI framework. È necessario quindi includere il caricamento del relativo file .css all'interno del nostro codice. Il punto più corretto per includere tale file è sicuramente 'index.js', che ricordiamo essere l'entry-point della nostra applicazione. Senza entrare troppo nel dettaglio la libreria 'css-loader', utilizzata internamente da 'react-script', permette di utilizzare il costrutto 'import' all'interno dei file javascript anche per i fogli di stile. Aggiungiamo quindi tra gli import la seguente riga

import 'bootstrap/dist/css/bootstrap.css';

Altra modifica da apportare al file index.js è quella di eliminare l'import a index.css, questo perché andremo ad utilizzare non più un singolo foglio di di stile per l'intera applicazione ma uno per ogni Componente che ne abbia necessità. Al termine delle modifiche il file index.js sarà quindi:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
 
import 'bootstrap/dist/css/bootstrap.css';
 
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();

Routing e Screens

Passiamo adesso ad analizzare la struttura della nostra SPA. L'applicazione sarà composta da due schermate implementate come vedremo dai due componenti HomeScreen e ArticleScreen. La prima schermata avrà il compito di renderizzare l'elenco paginato degli Articoli presenti nel nostro backend Drupal. La seconda schermata invece permetterà di vedere il dettaglio di un qualsiasi Articolo. Per navigare tra le varie schermate viene utilizzata la libreria react-router-dom. Per chi non conoscesse tale libreria consiglio di leggere la documentazione sul sito ufficiale. Il nostro file App.js, che nella versione precedente conteneva l'intera applicazione, diventa invece il punto in cui vengono dichiarate le 'rotte' e il/i componenti che ogni rotta ha necessità di renderizzare. Ecco la nuova versione del file App.js

import React, { Component } from 'react';
 
import { BrowserRouter, Switch, Route } from 'react-router-dom';
 
import Header from './components/Header';
import HomeScreen from './screens/HomeScreen';
import ArticleScreen from './screens/ArticleScreen';
 
class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <div className="App">
          <Header />
          <Switch>
            <Route exact path="/" component={HomeScreen} />
            <Route path="/articles/:uuid" component={ArticleScreen} />
          </Switch>
        </div>
      </BrowserRouter>
    );
  }
}
 
export default App;

Analizziamone brevemente il contenuto. Per prima cosa oltre al normale import dei componenti della libreria 'react' troviamo l'import di alcuni componenti della libreria 'react-router-dom'. A seguire troviamo l'import dei due screen che compongono l'applicazione ed in più un componente Header che come vedremo non farà altro che renderizzare una Navbar contenente (per adesso) solo il link alla home. Inizia quindi il vero è proprio componente App che implementa il solo metodo render. All'interno di tale metodo vediamo inizializzato il router BrowserRouter il cui unico ruolo è quello di gestire le 'rotte' contenute al suo interno. Al di fuori di qualsiasi rotta (e quindi sempre visualizzato) viene incluso il componente Header. Infine uno Switch (che permette di selezionare solo una rotta da un insieme definito) fa da padre a due componenti Route che rappresentano le due rotte utilizzate dalla nostra applicazione. Vediamo come sono definite. Il primo definisce un rotta dal path '/' in cui verrà renderizzato il componente HomeScreen. L'uso dell'attributo 'exact' consente che la rotta venga presa in considerazione solo quanto il nostro url matcha esattamente quanto definito dall'attributo path. Vi rimando alla documentazione specifica per maggiori informazioni. Il secondo definisce una rotta leggermente più complessa in quanto il path include una parte statica '/articles/' e una parte dinamica ':uuid'. La parte dinamica prende il nome di URL Parameter e consente di effettuare il routing passando quanto matchato da :uuid al componente renderizzato, in questo caso ArticleScreen.

Il componente Header è molto semplice:

import React, { Component } from 'react';
 
import { withRouter, Link } from 'react-router-dom';
 
import { Container, Nav, NavItem, NavLink } from 'reactstrap';
 
import './Header.css';
 
class Header extends Component {
  render() {
    const { pathname } = this.props.location;
    console.log('Header::render', pathname);
 
    return (
      <Container className="header">
        <h1>Drupal REACTivated</h1>
        <Nav tabs>
          <NavItem>
            <NavLink tag={Link} to="/" active={pathname === '/'}>
              Home
            </NavLink>
          </NavItem>
        </Nav>
      </Container>
    );
  }
}
 
export default withRouter(Header);

Al suo interno vengono utilizzati dei componenti della libreria reactstrap per definire una Navbar contenente un link alla home dell'applicazione. Unica particolarità che è possibile notare è l'utilizzo del HOC 'withRouter' che consente al nostro componente di avere l'informazione del path attuale al fine di rendere il Link attivo o meno.

HomeScreen

Come detto più volte il componente HomeScreen si occuperà di visualizzare l'elenco paginato degli Articoli presenti nel nostro backend Drupal. Sarà quindi necessario interrogare il modulo JSON API in modo corretto al fine di ottenere il risultato desiderato nonché di utilizzare tale risultato per renderizzare la lista degli Articoli. Il contenuto del nostro componente è il seguente:

import React, { Component } from 'react';
 
import { Container, Row, Col } from 'reactstrap';
 
import { fetchArticles } from '../api';
import ArticleList from '../components/ArticleList';
import SimplePager from '../components/SimplePager';
 
class HomeScreen extends Component {
  constructor(props) {
    super(props);
    this.state = {
      articles: null,
      page: 1,
      more: false
    };
  }
 
  componentDidMount() {
    this.loadArticles();
  }
 
  render() {
    const { page, more, error, errorMessage, articles } = this.state;
 
    return (
      <Container>
        <Row>
          <Col>
            {error && <h2>{errorMessage}</h2>}
            {articles && (
              <div>
                <SimplePager
                  page={page}
                  hasNext={more}
                  onChange={this.handlePagerChange}
                />
                <ArticleList articles={articles} />
                <SimplePager
                  page={page}
                  hasNext={more}
                  onChange={this.handlePagerChange}
                />
              </div>
            )}
          </Col>
        </Row>
      </Container>
    );
  }
 
  loadArticles = () => {
    const { page } = this.state;
    fetchArticles(page)
      .then(response => {
        const body = response.body;
        const articles = body.data;
        const more = body.links.hasOwnProperty('next');
        this.setState({
          error: false,
          articles: articles,
          more: more
        });
        window.scrollTo(0, 0);
      })
      .catch(error => {
        this.setState({
          error: true,
          errorMessage: `Error fetching articles: '${error.message}'`
        });
      });
  };
 
  handlePagerChange = newPage => {
    this.setState(
      {
        page: newPage
      },
      this.loadArticles
    );
  };
}
 
export default HomeScreen;

Nel costruttore viene inizializzato lo stato con tre attributi:

  • articles: inizialmente 'null' che conterrà l'elenco degli articoli restituiti dalla chiamata alle JSON API
  • page: inizialmente a '1' che servirà a memorizzare la pagina correntemente visualizzata (ricordiamo che la lista degli articoli è paginata)
  • more: inizialmente 'false' verrà utilizzato per rappresentare la presenza o meno di altri articoli ne backend e quindi permetterà di decidere se visualizzare il link 'next' del paginatore

Il metodo 'componentDidMount' viene utilizzato invece per avviare la chiamata al backend al fine di ottenere l'elenco (massimo 10 per chiamata) degli Articoli. Analizzeremo tale interrogazione tra breve. Il metodo 'render' si occupa di renderizzare il pager (sia in cima che in fondo alla pagina) ma sopratutto l'elenco degli articoli passando quanto restituito dal backend al componente ArticleList. In caso di errore durante la richiesta al backend viene visualizzato il relativo messaggio. La parte importante di questo componente è sicuramente il metodo 'loadArticles'. Internamente tale metodo va a richiamare 'fetchArticles', contenuto nel file 'api/index.js', passando come parametro l'attributo 'page' memorizzato nello stato. Il metodo fetchArticles è quello che effettivamente va ad interrogare le JSON API. Analizziamone il codice:

import superagent from 'superagent';
import superagentJsonapify from 'superagent-jsonapify';
 
superagentJsonapify(superagent);
 
const BASE_URL = 'http://drupal.docker.localhost:8000';
const CONSUMER_ID = 'fd49afa9-81ef-4550-927c-9c7e5da995b7';
const PAGE_SIZE = 10;
 
export const fetchArticles = page => {
  const pageOffset = PAGE_SIZE * (page - 1);
  return superagent.get(
    `${BASE_URL}/jsonapi/node/article?page[offset]=${pageOffset}&page[limit]=${PAGE_SIZE}&sort=-created`
  );
};
 
export const fetchArticle = uuid => {
  return superagent
    .get(`${BASE_URL}/jsonapi/node/article/${uuid}?include=field_image`)
    .set('X-Consumer-ID', CONSUMER_ID);
};

Dopo l'import delle librerie relative a 'superagent' vengono definite due costanti BASE_URL (che indica l'indirizzo base del nostro backend) e PAGE_SIZE (ovvero il numero massimo di articoli richiesti per ogni interrogazione). Il metodo 'fetchArticle' una volta calcolato il pageOffset effettua una chiamata di tipo get al seguente url `${BASE_URL}/jsonapi/node/article?page[offset]=${pageOffset}&page[limit]=${PAGE_SIZE}&sort=-created`.

È il momento di parlare un po' di come le risorse possano essere interrogate all'interno del modulo e più in generale della specifica JSON API. I dati relativi ad una specifica risorsa possono essere prelevati effettuando una richiesta di tipo GET ad uno specifico endpoint. Nel caso del modulo JSON API, le varie Entity (e quindi anche i Nodi e gli Articoli) utilizzano come endpoint il path '/jsonapi//' nel nostro caso quindi otteniamo '/jsonapi/node/article'. A tale path possono essere passati dei parametri aggiuntivi ad esempio per applicare la paginazione e il sorting. Nel caso della paginazione i parametri da noi utilizzati sono 'page[offset]' e 'page[limit]' mentre per quanto riguarda il sorting l'unico parametro passato è 'sort' che come valore assume '-created' ad indicare l'ordinamento per data di creazione discendente (è il simbolo '-' a definire l'ordinamento discendente anziché ascendente). La chiamata restituisce un json object contenente due attributi:

  • data: contenente i dati di risposta, in questo caso un array di Articoli
  • links: contenente un insieme di link che puntano ad esempio nel caso di paginazione agli endpoint della pagina corrente e (se disponibile) di quella successiva

Analizziamo invece un degli elementi contenuti all'interno dell'attributo data:

{
  "type": null,
  "id": "6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4",
  "attributes": {
    "nid": 34,
    "uuid": "6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4",
    "vid": 34,
    "langcode": "en",
    "status": true,
    "title": "Quidem",
    "created": 1514971754,
    "changed": 1514982134,
    "promote": true,
    "sticky": false,
    "revision_timestamp": 1514982134,
    "revision_log": null,
    "revision_translation_affected": true,
    "default_langcode": true,
    "path": null,
    "body": {
      "value": "Esca huic in jugis laoreet lobortis mos sudo ulciscor. Blandit comis saepius sino verto. Fere ille magna modo ullamcorper usitas veniam ymo. Causa facilisi inhibeo ratis usitas. Eligo genitus illum incassum iustum neo pertineo tego torqueo tum. At causa illum macto premo tum valde. Augue damnum inhibeo jus lenis nimis pagus roto vereor verto. Ad esse genitus jumentum laoreet luptatum nisl nobis ulciscor vulpes.\n\nFere humo suscipit ulciscor. Genitus hos oppeto sit uxor. Camur esse luctus pertineo. Adipiscing aptent metuo sino turpis usitas ymo. Causa cui genitus molior nulla nutus similis vereor. Esca humo nutus oppeto refero similis veniam. Dolore lobortis obruo os patria sino vero. Eu mauris proprius tincidunt zelus. Augue damnum enim hendrerit huic populus vel.\n\nEx gravis letalis nobis plaga. Aptent caecus pneum praesent probo ulciscor. Accumsan capto molior sagaciter valetudo virtus. Conventio facilisis laoreet neo nulla odio quis te ullamcorper valde. Abluo fere lenis refoveo volutpat. Antehabeo elit eum humo loquor pneum premo tego.\n\nAutem diam erat eu molior quibus. Abbas lucidus wisi. In nostrud quae secundum similis singularis tego tum valde vulputate. Acsi ad blandit duis euismod ille metuo nobis sudo usitas. Accumsan acsi caecus genitus ideo in quadrum ratis veniam venio. Causa dolore eu jugis odio paratus. Abdo hendrerit molior pecus persto populus quidem.\n\nAptent comis luctus nobis turpis vel. Accumsan bene paulatim typicus validus. Commoveo gilvus immitto mauris utrum venio. Exerci genitus lenis os vero vicis voco. Eligo laoreet praesent. Ad bene ibidem letalis pertineo. Aliquam camur ex haero nulla si ulciscor uxor. Aliquam caecus commoveo ex jugis odio refoveo saepius tamen.\n\nConsectetuer gemino meus tincidunt utinam. Aliquip conventio diam distineo hos mauris nostrud singularis turpis usitas. Interdico luctus molior nibh nisl obruo occuro uxor valde. Abdo antehabeo camur decet mauris premo ullamcorper. Abdo appellatio diam eligo ibidem nibh nunc utrum volutpat. Iusto praemitto si. At modo saepius saluto si similis veniam voco.\n\nAbdo amet eligo et nulla proprius sed. Abico bene similis. Nibh oppeto pertineo. Dolus eum jumentum quadrum quidne ulciscor ut valde. Nisl obruo quae.\n\nCausa eum quibus quidne singularis tum. Gravis ideo mos. Adipiscing amet comis enim facilisi iustum neo singularis voco. Jus macto obruo patria te vero. Esse facilisi sed typicus. Blandit dignissim eros ulciscor vicis. Damnum eros immitto imputo paratus persto.\n\nInhibeo lucidus persto quidem. Accumsan et gravis humo ideo pagus quibus suscipere ymo. Blandit eu exerci in torqueo volutpat. Abbas aptent exputo ille immitto nimis nobis olim ut. Amet antehabeo decet facilisis laoreet quibus. Aliquam ex loquor probo proprius sit. Camur dignissim odio praemitto. Dolor duis esse fere feugiat loquor ut.\n\nAliquam aliquip bene commoveo erat lenis sit valde vindico. Oppeto scisco tincidunt. Distineo nobis odio os si sudo suscipere. Consectetuer exerci exputo macto quidne sagaciter suscipere uxor virtus. Camur luptatum minim veniam. Dolus erat olim premo qui tego veniam. Et melior oppeto proprius quibus veniam.\n\nDuis jumentum molior suscipit ulciscor ut uxor. Ibidem mauris nulla plaga sed ulciscor volutpat. Interdico pecus saluto suscipit uxor. Caecus dolor hendrerit interdico paulatim praemitto. Fere modo zelus. Appellatio comis consequat damnum ex gemino lenis olim pala.\n\nAbico esse facilisis mauris minim te vereor vindico. Cui fere ideo obruo quis tum ut verto vulputate. Decet dignissim distineo haero iustum jugis mos saluto. Aptent eum nulla obruo pneum vulpes. Commoveo iriure luctus neque nimis vicis. Abbas dignissim fere lobortis nibh pagus paulatim probo velit. Aptent dignissim quia sed turpis.\n\n",
      "format": "plain_text",
      "summary": "Esca huic in jugis laoreet lobortis mos sudo ulciscor. Blandit comis saepius sino verto. Fere ille magna modo ullamcorper usitas veniam ymo. Causa facilisi inhibeo ratis usitas. Eligo genitus illum incassum iustum neo pertineo tego torqueo tum. At causa illum macto premo tum valde. Augue damnum inhibeo jus lenis nimis pagus roto vereor verto. Ad esse genitus jumentum laoreet luptatum nisl nobis ulciscor vulpes.\n\nFere humo suscipit ulciscor. Genitus hos oppeto sit uxor. Camur esse luctus pertineo. Adipiscing aptent metuo sino turpis usitas ymo. Causa cui genitus molior nulla nutus similis vereor. Esca humo nutus oppeto refero similis veniam. Dolore lobortis obruo os patria sino vero. Eu mauris proprius tincidunt zelus. Augue damnum enim hendrerit huic populus vel.\n\nEx gravis letalis nobis plaga. Aptent caecus pneum praesent probo ulciscor. Accumsan capto molior sagaciter valetudo virtus. Conventio facilisis laoreet neo nulla odio quis te ullamcorper valde. Abluo fere lenis refoveo volutpat. Antehabeo elit eum humo loquor pneum premo tego.\n\nAutem diam erat eu molior quibus. Abbas lucidus wisi. In nostrud quae secundum similis singularis tego tum valde vulputate. Acsi ad blandit duis euismod ille metuo nobis sudo usitas. Accumsan acsi caecus genitus ideo in quadrum ratis veniam venio. Causa dolore eu jugis odio paratus. Abdo hendrerit molior pecus persto populus quidem.\n\nAptent comis luctus nobis turpis vel. Accumsan bene paulatim typicus validus. Commoveo gilvus immitto mauris utrum venio. Exerci genitus lenis os vero vicis voco. Eligo laoreet praesent. Ad bene ibidem letalis pertineo. Aliquam camur ex haero nulla si ulciscor uxor. Aliquam caecus commoveo ex jugis odio refoveo saepius tamen.\n\nConsectetuer gemino meus tincidunt utinam. Aliquip conventio diam distineo hos mauris nostrud singularis turpis usitas. Interdico luctus molior nibh nisl obruo occuro uxor valde. Abdo antehabeo camur decet mauris premo ullamcorper. Abdo appellatio diam eligo ibidem nibh nunc utrum volutpat. Iusto praemitto si. At modo saepius saluto si similis veniam voco.\n\nAbdo amet eligo et nulla proprius sed. Abico bene similis. Nibh oppeto pertineo. Dolus eum jumentum quadrum quidne ulciscor ut valde. Nisl obruo quae.\n\nCausa eum quibus quidne singularis tum. Gravis ideo mos. Adipiscing amet comis enim facilisi iustum neo singularis voco. Jus macto obruo patria te vero. Esse facilisi sed typicus. Blandit dignissim eros ulciscor vicis. Damnum eros immitto imputo paratus persto.\n\nInhibeo lucidus persto quidem. Accumsan et gravis humo ideo pagus quibus suscipere ymo. Blandit eu exerci in torqueo volutpat. Abbas aptent exputo ille immitto nimis nobis olim ut. Amet antehabeo decet facilisis laoreet quibus. Aliquam ex loquor probo proprius sit. Camur dignissim odio praemitto. Dolor duis esse fere feugiat loquor ut.\n\nAliquam aliquip bene commoveo erat lenis sit valde vindico. Oppeto scisco tincidunt. Distineo nobis odio os si sudo suscipere. Consectetuer exerci exputo macto quidne sagaciter suscipere uxor virtus. Camur luptatum minim veniam. Dolus erat olim premo qui tego veniam. Et melior oppeto proprius quibus veniam.\n\nDuis jumentum molior suscipit ulciscor ut uxor. Ibidem mauris nulla plaga sed ulciscor volutpat. Interdico pecus saluto suscipit uxor. Caecus dolor hendrerit interdico paulatim praemitto. Fere modo zelus. Appellatio comis consequat damnum ex gemino lenis olim pala.\n\nAbico esse facilisis mauris minim te vereor vindico. Cui fere ideo obruo quis tum ut verto vulputate. Decet dignissim distineo haero iustum jugis mos saluto. Aptent eum nulla obruo pneum vulpes. Commoveo iriure luctus neque nimis vicis. Abbas dignissim fere lobortis nibh pagus paulatim probo velit. Aptent dignissim quia sed turpis.\n\n"
    },
    "comment": {
      "status": 2,
      "cid": 0,
      "last_comment_timestamp": 1514982134,
      "last_comment_name": null,
      "last_comment_uid": 0,
      "comment_count": 0
    }
  },
  "relationships": {
    "type": {
      "data": {
        "type": "node_type--node_type",
        "id": "b1126548-d96d-419d-be7a-c494e3be4e90"
      },
      "links": {
        "self": "http://drupal.docker.localhost:8000/jsonapi/node/article/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4/relationships/type",
        "related": "http://drupal.docker.localhost:8000/jsonapi/node/article/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4/type"
      }
    },
    "uid": {
      "data": {
        "type": "user--user",
        "id": "e61d8466-8759-49f5-8b8c-6c6af52bc080"
      },
      "links": {
        "self": "http://drupal.docker.localhost:8000/jsonapi/node/article/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4/relationships/uid",
        "related": "http://drupal.docker.localhost:8000/jsonapi/node/article/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4/uid"
      }
    },
    "revision_uid": {
      "data": {
        "type": "user--user",
        "id": "e61d8466-8759-49f5-8b8c-6c6af52bc080"
      },
      "links": {
        "self": "http://drupal.docker.localhost:8000/jsonapi/node/article/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4/relationships/revision_uid",
        "related": "http://drupal.docker.localhost:8000/jsonapi/node/article/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4/revision_uid"
      }
    },
    "field_image": {
      "data": {
        "type": "file--file",
        "id": "ff50dc33-175e-4b19-ab2a-82220fa7f022",
        "meta": {
          "alt": "Aptent at blandit humo ille interdico macto saepius sino vereor.",
          "title": "Appellatio humo singularis. Comis dignissim jus luctus nostrud odio premo voco.",
          "width": "521",
          "height": "420"
        }
      },
      "links": {
        "self": "http://drupal.docker.localhost:8000/jsonapi/node/article/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4/relationships/field_image",
        "related": "http://drupal.docker.localhost:8000/jsonapi/node/article/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4/field_image"
      }
    },
    "field_tags": {
      "data": [
        {
          "type": "taxonomy_term--tags",
          "id": "389aebcc-1508-407e-b4c5-85444ed572d5"
        }
      ],
      "links": {
        "self": "http://drupal.docker.localhost:8000/jsonapi/node/article/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4/relationships/field_tags",
        "related": "http://drupal.docker.localhost:8000/jsonapi/node/article/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4/field_tags"
      }
    }
  },
  "links": {
    "self": "http://drupal.docker.localhost:8000/jsonapi/node/article/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4"
  },
  "nid": 34,
  "uuid": "6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4",
  "vid": 34,
  "langcode": "en",
  "status": true,
  "title": "Quidem",
  "created": 1514971754,
  "changed": 1514982134,
  "promote": true,
  "sticky": false,
  "revisionTimestamp": 1514982134,
  "revisionLog": null,
  "revisionTranslationAffected": true,
  "defaultLangcode": true,
  "path": null,
  "body": {
    "value": "Esca huic in jugis laoreet lobortis mos sudo ulciscor. Blandit comis saepius sino verto. Fere ille magna modo ullamcorper usitas veniam ymo. Causa facilisi inhibeo ratis usitas. Eligo genitus illum incassum iustum neo pertineo tego torqueo tum. At causa illum macto premo tum valde. Augue damnum inhibeo jus lenis nimis pagus roto vereor verto. Ad esse genitus jumentum laoreet luptatum nisl nobis ulciscor vulpes.\n\nFere humo suscipit ulciscor. Genitus hos oppeto sit uxor. Camur esse luctus pertineo. Adipiscing aptent metuo sino turpis usitas ymo. Causa cui genitus molior nulla nutus similis vereor. Esca humo nutus oppeto refero similis veniam. Dolore lobortis obruo os patria sino vero. Eu mauris proprius tincidunt zelus. Augue damnum enim hendrerit huic populus vel.\n\nEx gravis letalis nobis plaga. Aptent caecus pneum praesent probo ulciscor. Accumsan capto molior sagaciter valetudo virtus. Conventio facilisis laoreet neo nulla odio quis te ullamcorper valde. Abluo fere lenis refoveo volutpat. Antehabeo elit eum humo loquor pneum premo tego.\n\nAutem diam erat eu molior quibus. Abbas lucidus wisi. In nostrud quae secundum similis singularis tego tum valde vulputate. Acsi ad blandit duis euismod ille metuo nobis sudo usitas. Accumsan acsi caecus genitus ideo in quadrum ratis veniam venio. Causa dolore eu jugis odio paratus. Abdo hendrerit molior pecus persto populus quidem.\n\nAptent comis luctus nobis turpis vel. Accumsan bene paulatim typicus validus. Commoveo gilvus immitto mauris utrum venio. Exerci genitus lenis os vero vicis voco. Eligo laoreet praesent. Ad bene ibidem letalis pertineo. Aliquam camur ex haero nulla si ulciscor uxor. Aliquam caecus commoveo ex jugis odio refoveo saepius tamen.\n\nConsectetuer gemino meus tincidunt utinam. Aliquip conventio diam distineo hos mauris nostrud singularis turpis usitas. Interdico luctus molior nibh nisl obruo occuro uxor valde. Abdo antehabeo camur decet mauris premo ullamcorper. Abdo appellatio diam eligo ibidem nibh nunc utrum volutpat. Iusto praemitto si. At modo saepius saluto si similis veniam voco.\n\nAbdo amet eligo et nulla proprius sed. Abico bene similis. Nibh oppeto pertineo. Dolus eum jumentum quadrum quidne ulciscor ut valde. Nisl obruo quae.\n\nCausa eum quibus quidne singularis tum. Gravis ideo mos. Adipiscing amet comis enim facilisi iustum neo singularis voco. Jus macto obruo patria te vero. Esse facilisi sed typicus. Blandit dignissim eros ulciscor vicis. Damnum eros immitto imputo paratus persto.\n\nInhibeo lucidus persto quidem. Accumsan et gravis humo ideo pagus quibus suscipere ymo. Blandit eu exerci in torqueo volutpat. Abbas aptent exputo ille immitto nimis nobis olim ut. Amet antehabeo decet facilisis laoreet quibus. Aliquam ex loquor probo proprius sit. Camur dignissim odio praemitto. Dolor duis esse fere feugiat loquor ut.\n\nAliquam aliquip bene commoveo erat lenis sit valde vindico. Oppeto scisco tincidunt. Distineo nobis odio os si sudo suscipere. Consectetuer exerci exputo macto quidne sagaciter suscipere uxor virtus. Camur luptatum minim veniam. Dolus erat olim premo qui tego veniam. Et melior oppeto proprius quibus veniam.\n\nDuis jumentum molior suscipit ulciscor ut uxor. Ibidem mauris nulla plaga sed ulciscor volutpat. Interdico pecus saluto suscipit uxor. Caecus dolor hendrerit interdico paulatim praemitto. Fere modo zelus. Appellatio comis consequat damnum ex gemino lenis olim pala.\n\nAbico esse facilisis mauris minim te vereor vindico. Cui fere ideo obruo quis tum ut verto vulputate. Decet dignissim distineo haero iustum jugis mos saluto. Aptent eum nulla obruo pneum vulpes. Commoveo iriure luctus neque nimis vicis. Abbas dignissim fere lobortis nibh pagus paulatim probo velit. Aptent dignissim quia sed turpis.\n\n",
    "format": "plain_text",
    "summary": "Esca huic in jugis laoreet lobortis mos sudo ulciscor. Blandit comis saepius sino verto. Fere ille magna modo ullamcorper usitas veniam ymo. Causa facilisi inhibeo ratis usitas. Eligo genitus illum incassum iustum neo pertineo tego torqueo tum. At causa illum macto premo tum valde. Augue damnum inhibeo jus lenis nimis pagus roto vereor verto. Ad esse genitus jumentum laoreet luptatum nisl nobis ulciscor vulpes.\n\nFere humo suscipit ulciscor. Genitus hos oppeto sit uxor. Camur esse luctus pertineo. Adipiscing aptent metuo sino turpis usitas ymo. Causa cui genitus molior nulla nutus similis vereor. Esca humo nutus oppeto refero similis veniam. Dolore lobortis obruo os patria sino vero. Eu mauris proprius tincidunt zelus. Augue damnum enim hendrerit huic populus vel.\n\nEx gravis letalis nobis plaga. Aptent caecus pneum praesent probo ulciscor. Accumsan capto molior sagaciter valetudo virtus. Conventio facilisis laoreet neo nulla odio quis te ullamcorper valde. Abluo fere lenis refoveo volutpat. Antehabeo elit eum humo loquor pneum premo tego.\n\nAutem diam erat eu molior quibus. Abbas lucidus wisi. In nostrud quae secundum similis singularis tego tum valde vulputate. Acsi ad blandit duis euismod ille metuo nobis sudo usitas. Accumsan acsi caecus genitus ideo in quadrum ratis veniam venio. Causa dolore eu jugis odio paratus. Abdo hendrerit molior pecus persto populus quidem.\n\nAptent comis luctus nobis turpis vel. Accumsan bene paulatim typicus validus. Commoveo gilvus immitto mauris utrum venio. Exerci genitus lenis os vero vicis voco. Eligo laoreet praesent. Ad bene ibidem letalis pertineo. Aliquam camur ex haero nulla si ulciscor uxor. Aliquam caecus commoveo ex jugis odio refoveo saepius tamen.\n\nConsectetuer gemino meus tincidunt utinam. Aliquip conventio diam distineo hos mauris nostrud singularis turpis usitas. Interdico luctus molior nibh nisl obruo occuro uxor valde. Abdo antehabeo camur decet mauris premo ullamcorper. Abdo appellatio diam eligo ibidem nibh nunc utrum volutpat. Iusto praemitto si. At modo saepius saluto si similis veniam voco.\n\nAbdo amet eligo et nulla proprius sed. Abico bene similis. Nibh oppeto pertineo. Dolus eum jumentum quadrum quidne ulciscor ut valde. Nisl obruo quae.\n\nCausa eum quibus quidne singularis tum. Gravis ideo mos. Adipiscing amet comis enim facilisi iustum neo singularis voco. Jus macto obruo patria te vero. Esse facilisi sed typicus. Blandit dignissim eros ulciscor vicis. Damnum eros immitto imputo paratus persto.\n\nInhibeo lucidus persto quidem. Accumsan et gravis humo ideo pagus quibus suscipere ymo. Blandit eu exerci in torqueo volutpat. Abbas aptent exputo ille immitto nimis nobis olim ut. Amet antehabeo decet facilisis laoreet quibus. Aliquam ex loquor probo proprius sit. Camur dignissim odio praemitto. Dolor duis esse fere feugiat loquor ut.\n\nAliquam aliquip bene commoveo erat lenis sit valde vindico. Oppeto scisco tincidunt. Distineo nobis odio os si sudo suscipere. Consectetuer exerci exputo macto quidne sagaciter suscipere uxor virtus. Camur luptatum minim veniam. Dolus erat olim premo qui tego veniam. Et melior oppeto proprius quibus veniam.\n\nDuis jumentum molior suscipit ulciscor ut uxor. Ibidem mauris nulla plaga sed ulciscor volutpat. Interdico pecus saluto suscipit uxor. Caecus dolor hendrerit interdico paulatim praemitto. Fere modo zelus. Appellatio comis consequat damnum ex gemino lenis olim pala.\n\nAbico esse facilisis mauris minim te vereor vindico. Cui fere ideo obruo quis tum ut verto vulputate. Decet dignissim distineo haero iustum jugis mos saluto. Aptent eum nulla obruo pneum vulpes. Commoveo iriure luctus neque nimis vicis. Abbas dignissim fere lobortis nibh pagus paulatim probo velit. Aptent dignissim quia sed turpis.\n\n"
  },
  "comment": {
    "status": 2,
    "cid": 0,
    "last_comment_timestamp": 1514982134,
    "last_comment_name": null,
    "last_comment_uid": 0,
    "comment_count": 0
  },
  "uid": null,
  "revisionUid": null,
  "fieldImage": null,
  "fieldTags": []
}

Gli attributi più importanti sono sicuramente:

  • id: contenente l'uuid del nostro nodo Articolo (utilizzeremo questo dato per effettuare la seconda interrogazione alle JSON API ovvero quella per visualizzare i dettagli dell'Articolo)
  • attributes: un object contenente appunto gli attributi del nostro articolo come lo stato di pubblicazione, il nid, il titolo e molto altro
  • relationships: contenente una serie di object che rappresentano relazioni ad entità esterne (si prendano come esempio l'attributo 'uid', che fornisce informazioni sull'entità User collegata al nostro Articolo secondo la relazione 'autore del nodo', o anche 'field_image', contenente i dati dovuti all'omonimo campo di tipo Image associato al bundle)

Una volta ottenuti i dati dal backend, la HomeScreen non fa altro che passare quanto contenuto nell'attribute 'data' al componente ArticleList. A sua volta tale classe non fa altro che renderizzare un componente ArticleTeaser per ogni articolo contenuto nell'array di dati.

ArticleTeaser

Questa classe rappresenta una delle due possibili visualizzazioni di un Articolo. Vedremo nell'altro screen dell'applicazione l'uso della visualizzazione alternativa ovvero di ArticleFull. Come suggerisce il nome ArticleTeaser si occuperà di visualizzare, come succede generalmente in Drupal, solo i dati essenziali del nostro Nodo. Vediamone il codice:

// Simple component that render an Article
 
import React, { Component } from 'react';
 
import { Link } from 'react-router-dom';
 
import moment from 'moment';
import truncate from 'html-truncate';
 
import './Article.css';
 
class ArticleTeaser extends Component {
  createBodyMarkup(markup) {
    return { __html: truncate(markup, 600) };
  }
 
  render() {
    const { article } = this.props;
    const momentDate = moment(article.created * 1000).format('DD MMM YYYY');
    return (
      <div className="article article--teaser">
        <h2 className="article__title">
          <Link to={`/articles/${article.uuid}`}>{article.title}</Link>
        </h2>
        <div className="article__date">{momentDate}</div>
        <div
          className="article__body"
          dangerouslySetInnerHTML={this.createBodyMarkup(article.body.value)}
        />
        <div className="article__links">
          <Link to={`/articles/${article.uuid}`}>Leggi</Link>
        </div>
      </div>
    );
  }
}
 
export default ArticleTeaser;

Il codice è facilmente interpretabile. Segnaliamo solo:

  • L'uso delle librerie 'html-truncate' e 'moment' per la gestione del body e della data di creazione
  • La creazione di un link avente come text il titolo dell'articolo e come href il path '/articles/:uuid' che come già spiegato rappresenta la rotta verso la screen di dettaglio

La lista degli Articoli viene quindi renderizzata nel seguente modo:

Drupal REACTivated (PARTE 2)

ArticleScreen

Seconda e (per adesso) ultima screen della nostra applicazione utilizzata per renderizzare i dettagli di ogni Articolo. Visualizziamo il codice per poi commentarlo:

import React, { Component } from 'react';
 
import { Container } from 'reactstrap';
 
import { fetchArticle } from '../api';
import ArticleFull from '../components/ArticleFull';
 
class ArticleScreen extends Component {
  constructor(props) {
    super(props);
    this.state = {
      article: null
    };
  }
  componentDidMount() {
    const { uuid } = this.props.match.params;
    this.loadArticle(uuid);
  }
 
  render() {
    const { error, errorMessage, article } = this.state;
 
    return (
      <Container>
        {error && <h2>{errorMessage}</h2>}
        {article && <ArticleFull article={article} />}
      </Container>
    );
  }
 
  loadArticle = uuid => {
    fetchArticle(uuid)
      .then(response => {
        const body = response.body;
        const article = body.data;
        this.setState({
          error: false,
          article: article
        });
      })
      .catch(error => {
        this.setState({
          error: true,
          errorMessage: `Error fetching article: '${error.message}'`
        });
      });
  };
}
 
export default ArticleScreen;

Nel costruttore vediamo inizializzato lo stato contente un singolo attributo 'article' inizialmente vuoto che servirà a memorizzare i dati ricevuti dall'interrogazione del backend. Il metodo componentDidMount invece ha un duplice ruolo. Da una parte si occupa di estrapolare l'URL Parameter uuid, dall'altra utilizzando proprio quest'ultimo dato va ad interrogare il backend richiamando loadArticle. Il metodo render non fa altro che controllare la presenza nello stato di un articolo (this.state.article != null) e in caso positivo di utilizzare il componente ArticleFull per renderizzare i dettagli dell'articolo. Il metodo loadArticle come succedeva anche per la HomeScreen non fa altro che richiamare fetchArticle sempre presente nel file 'api/index.js'. Andiamo quindi a riprendere il codice di tale file per commentare quest'ultimo metodo.

import superagent from 'superagent';
import superagentJsonapify from 'superagent-jsonapify';
 
superagentJsonapify(superagent);
 
const BASE_URL = 'http://drupal.docker.localhost:8000';
const CONSUMER_ID = 'fd49afa9-81ef-4550-927c-9c7e5da995b7';
const PAGE_SIZE = 10;
 
export const fetchArticles = page => {
  const pageOffset = PAGE_SIZE * (page - 1);
  return superagent.get(
    `${BASE_URL}/jsonapi/node/article?page[offset]=${pageOffset}&page[limit]=${PAGE_SIZE}&sort=-created`
  );
};
 
export const fetchArticle = uuid => {
  return superagent
    .get(`${BASE_URL}/jsonapi/node/article/${uuid}?include=field_image`)
    .set('X-Consumer-ID', CONSUMER_ID);
};

Il metodo non fa altro che effettuare una richiesta di tipo GET verso l'endpoint`${BASE_URL}/jsonapi/node/article/${uuid}?include=field_image`. Il path utilizzato è simile al precedente con due eccezioni:

  • A '/jsonapi/node/article' viene concatenato '/${uuid} in modo da ricevere le informazioni di un singolo articolo identificato proprio da quell'uuid.
  • Viene passato come parametro un campo 'include' con valore 'field_image'
  • Nell'header della richiesta viene settato il parametro 'X-Consumer-ID' che analizzeremo a breve

Spendiamo due parole aggiuntive per quest'ultimo parametro. Di default la specifica JSON API definisce che l'interrogazione verso una risorsa debba restituire solo i dati specifici di quella risorsa e i 'riferimenti' alle risorse collegate. Cerchiamo di capire con un esempio questo per adesso astruso concetto. Provando ad interrogare ad esempio le JSON API utilizzando l'endpoint '/jsonapi/node/article/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4' (senza il parametro include) il risultato ottenuto (privato delle parti non utili) è

{
	"data": {
    ...
		"relationships": {
      ...
			"field_image": {
				"data": {
					"type": "file--file",
					"id": "ff50dc33-175e-4b19-ab2a-82220fa7f022",
					"meta": {
						"alt": "Aptent at blandit humo ille interdico macto saepius sino vereor.",
						"title": "Appellatio humo singularis. Comis dignissim jus luctus nostrud odio premo voco.",
						"width": "521",
						"height": "420"
					}
				},
				"links": {
					"self": "http:\/\/drupal.docker.localhost:8000\/jsonapi\/node\/article\/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4\/relationships\/field_image",
					"related": "http:\/\/drupal.docker.localhost:8000\/jsonapi\/node\/article\/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4\/field_image"
				}
			},
      ...
		},
		"links": {
			"self": "http:\/\/drupal.docker.localhost:8000\/jsonapi\/node\/article\/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4"
		}
	},
	"jsonapi": {
    ...
	},
	"links": {
    ...
	}
}

In particolare notiamo i dati dell'oggetto 'field_image' contenuto tra le relazioni. Ripetiamo ora la stessa interrogazione ma utilizzando il parametro include. L'endpoint diventa quindi '/jsonapi/node/article/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4?include=field_image' e il risultato:

{
	"data": {
    ...
		"relationships": {
      ...
			"field_image": {
				"data": {
					"type": "file--file",
					"id": "ff50dc33-175e-4b19-ab2a-82220fa7f022",
					"meta": {
						"alt": "Aptent at blandit humo ille interdico macto saepius sino vereor.",
						"title": "Appellatio humo singularis. Comis dignissim jus luctus nostrud odio premo voco.",
						"width": "521",
						"height": "420"
					}
				},
				"links": {
					"self": "http:\/\/drupal.docker.localhost:8000\/jsonapi\/node\/article\/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4\/relationships\/field_image",
					"related": "http:\/\/drupal.docker.localhost:8000\/jsonapi\/node\/article\/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4\/field_image"
				}
			},
      ...
		},
		"links": {
			"self": "http:\/\/drupal.docker.localhost:8000\/jsonapi\/node\/article\/6c50cb76-fcd1-4e71-b987-2de6d4cdcbc4"
		}
	},
	"jsonapi": {
    ...
	},
	"links": {
    ...
	},
	"included": [
		{
			"type": "file--file",
			"id": "ff50dc33-175e-4b19-ab2a-82220fa7f022",
			"attributes": {
				"fid": 28,
				"uuid": "ff50dc33-175e-4b19-ab2a-82220fa7f022",
				"langcode": "en",
				"filename": "generateImage_BPmlnl.png",
				"uri": "public:\/\/2018-01\/generateImage_BPmlnl.png",
				"filemime": "image\/png",
				"filesize": 2456,
				"status": true,
				"created": 1514982134,
				"changed": 1514982134,
				"url": "\/sites\/default\/files\/2018-01\/generateImage_BPmlnl.png"
			},
			"relationships": {
				"uid": {
					"data": {
						"type": "user--user",
						"id": "a2602c02-a84b-4e34-b34e-69b82214c377"
					},
					"links": {
						"self": "http:\/\/drupal.docker.localhost:8000\/jsonapi\/file\/file\/ff50dc33-175e-4b19-ab2a-82220fa7f022\/relationships\/uid",
						"related": "http:\/\/drupal.docker.localhost:8000\/jsonapi\/file\/file\/ff50dc33-175e-4b19-ab2a-82220fa7f022\/uid"
					}
				}
			},
			"links": {
				"self": "http:\/\/drupal.docker.localhost:8000\/jsonapi\/file\/file\/ff50dc33-175e-4b19-ab2a-82220fa7f022"
			}
		}
	]
}

Cosa evidente è la presenza di un nuovo attributo chiamato 'included' di tipo array. L'array conterrà i dettagli di ogni entità collegata alla risorsa principale attraverso le relazioni specificate nel parametro 'include'. Nel nostro caso vediamo infatti essere presenti tutti i dettagli dell'entità File collegata all'articolo tramite la relazione 'field_image' come ad esempio l'uri, il mime-type, la filesize e altro. Avremmo potuto ottenere lo stesso risultato effettuando una query verso l'endpoint '/jsonapi/file/file/ff50dc33-175e-4b19-ab2a-82220fa7f022' ma ovviamente una richiesta http aggiuntiva avrebbe generato un overhead che come abbiamo visto in questo caso è possibile evitare. Il risultato di questa query verso il backend ricordiamo ancora una volta viene utilizzato dalla screen ArticleScreen per renderizzare il componente ArticleFull.

Consumers e Consumer Image Styles

Il modulo JSON API fornisce molte informazioni circa le entità presenti nel nostro backend. Un dato a cui però siamo interessati per visualizzare i dettagli dei nostri articoli non è presente nella risposta anche in caso venga utilizzato il parametro 'include'. Stiamo parlando dell'url generato dal modulo Image per ogni Image Style disponibile nel CMS. Quello che vorremmo infatti è utilizzare l'immagine generata dal backend secondo uno specifico Image Style all'interno del nostro componente ArticleFull. Per nostra fortuna sono disponibili i due moduli Consumers e Consumer Image Styles. Il primo permette di definire nuovi 'Consumer' all'interno del backend in modo da poter generare risposte differenti in base al Consumer richiedente. Il secondo permette di associare ad ogni Consumer uno o più Image Style. La risposta ad uno specifico Consumer sarà quindi 'decorata' aggiungendo ai dati quelli relativi agli Image Style correlati. Affinché i due moduli citati compiano il loro dovere è necessario passare come parametro della richiesta un Consumer UUID che viene creato tramite il backend. Il parametro viene specificato utilizzando il campo 'X-Consumer-ID' dell'header. Vediamo quindi come installare e utilizzare Consumers e Consumer Image Styles. Per prima cosa è necessario aggiungere i moduli al nostro progetto drupal utilizzando il seguente comando:

composer require drupal/consumer drupal/consumer_image_styles

Dopo aver abilitato i due moduli andiamo in Configuration -> Web services -> Consumers (admin/config/services/consumer) e aggiungiamo un nuovo consumer. Assicuriamoci di inserire una Label per il nostro nuovo Consumer e di selezionare gli Image Style che vogliamo rendere disponibili, esattamente come in figura:

Drupal REACTivated (PARTE 2)

Una volta cliccato su 'Save' si viene rediretti sulla schermata dove è possibile reperire il Consumer ID:

Drupal REACTivated (PARTE 2)

Non ci resta che modificare la costante 'CONSUMER_ID' presente nel file 'api/index.js' con il valore generato dal backend. All'interno della risposta delle JSON API troviamo adesso una nuova sezione chiamata 'derivatives' che contiene esattamente quello che potete immaginare:

"meta": {
  "derivatives": {
    "large": "http:\/\/drupal.docker.localhost:8000\/sites\/default\/files\/styles\/large\/public\/2018-01\/generateImage_BPmlnl.png?itok=nZStEnMo",
    "medium": "http:\/\/drupal.docker.localhost:8000\/sites\/default\/files\/styles\/medium\/public\/2018-01\/generateImage_BPmlnl.png?itok=WgGvC65q",
    "thumbnail": "http:\/\/drupal.docker.localhost:8000\/sites\/default\/files\/styles\/thumbnail\/public\/2018-01\/generateImage_BPmlnl.png?itok=XoCEVX0q"
  }
}

ArticleFull

Il componente ArticleFull non fa altro che renderizzare, secondo un layout differente rispetto a ArticleTeaser, i dati ottenuti dal backend.

// Simple component that render an Article in Full-Mode
 
import React, { Component } from 'react';
 
import moment from 'moment';
 
import ImageDerivative from './ImageDerivative';
 
import './Article.css';
import './ArticleFull.css';
 
class ArticleFull extends Component {
  createBodyMarkup(markup) {
    return { __html: markup };
  }
 
  render() {
    const { article } = this.props;
    const momentDate = moment(article.created * 1000).format('DD MMM YYYY');
 
    return (
      <div className="article article--full">
        <h2 className="article__title">{article.title}</h2>
        <div className="article__date">{momentDate}</div>
        <div className="article__image">
          <ImageDerivative image={article.fieldImage} derivative="large" />
        </div>
        <div
          className="article__body"
          dangerouslySetInnerHTML={this.createBodyMarkup(article.body.value)}
        />
      </div>
    );
  }
}
 
export default ArticleFull;
view raw

Per quanto riguarda il campo immagine, è stato creato, nell'ottica della riusabilità, un componente separato, dal nome ImageDerivative, che in base al parametro 'derivative' passato visualizza l'immagine secondo un preciso Image Style. Per completezza riporto di seguito il codice:

import React from 'react';
 
class ImageDerivative extends React.PureComponent {
  render() {
    const { image, derivative } = this.props;
    const url = image.meta.derivatives[derivative];
 
    return <img src={url} alt={image.filename} />;
  }
}
 
export default ImageDerivative;

Il risultato finale è quello rappresentato in figura:

Drupal REACTivated (PARTE 2)

Evoluzioni

In questo articolo abbiamo trattato principalmente delle interazioni frontend-backend per il recupero dei dati. Abbiamo fatto uso sempre di chiamate di tipo GET. Molte sono le migliorie e le nuove funzionalità che possono essere introdotte all'interno della nostra applicazione. Quanto implementato è stato ridotto all'essenziale al fine di rendere il tutto facilmente comprensibile anche a chi ha un'esperienza e una conoscenza limitata del mondo React. Come possibile improvement ed esercizio consiglio di cercare ad esempio di visualizzare i dettagli sull'Autore o i Tags associati ad ogni Articolo (TIP: include=?).

Nel prossimo articolo di questa serie vedremo come effettuare delle interrogazioni per manipolare/aggiornare i dati presenti nel nostro backend Drupal. Vedremo quindi l'utilizzo dei metodi POST, PATCH e DELETE secondo le specifiche JSON API.