Published by

Il y a 2 semaines -

Temps de lecture 26 minutes

Des interfaces sur mesure avec le CMS Contentful – épisode 2 : Réaliser sa propre UI Extension Contentful

Cet article est l’épisode 2 d’une série de trois articles traitant de la réalisation d’interface sur mesure avec le CMS Headless Contentful et ses UI Extensions.

Il est la suite de “Réaliser des interfaces sur mesure avec les UI Extensions du CMS Contentful“, un article où nous avons introduit le concept de CMS Headless et où nous nous sommes intéressés au CMS headless Contentful. Nous avons notamment introduit la notion d’UI Extension, une fonctionnalité proposée par Contentful, permettant de développer ses propres interfaces.

Dans cet article, nous allons passer de la théorie à la pratique et réaliser notre propre UI Extension.

Cette extension prendra la forme d’un starter, c’est-à-dire un projet simple, qui pourra servir de base pour réaliser une UI Extension plus complète. Nous implémenterons les fonctionnalités minimum : modifier la valeur et le style d’un field en utilisant l’UI Extension.

Ce ne sont pas les frameworks qui manquent lorsqu’il s’agit de réaliser une petite application. Ici, l’UI Extension sera réalisée avec ReactJS.

L’ensemble du code de ce starter est disponible ici :  https://github.com/paqueDev/contentful-extension-reactjs-starter

Au programme :

  • Mise en place de la structure minimale requise pour réaliser une extension
  • Ajout d’un linter et Babel à notre projet
  • Lancement de notre projet en local
  • Développement de l’extension

Prêt ? C’est parti !

 

Initialisation

Pour commencer nous allons créer un dossier, ma-super-extension (ou tout autre nom avec davantage d’inspiration) dans lequel nous allons réaliser l’extension.

Comme nous l’avons vu, les UI extensions requièrent une structure minimale avec les fichiers index.html et extension.json.

Nous commençons donc par créer le fichier extension.json à la racine du projet. Dans ce dernier, nous allons rapidement décrire notre extension.

extension.json

Ce fichier permet de décrire notre extension.

ma-super-extension/extension.json

{
 "id": "ma-super-extension",
 "name": "Ma Super Extension",
 "srcdoc": "./build/index.html",
 "sidebar": false,
 "fieldTypes": ["Object"]
}

Un rapide coup d’oeil sur les différentes propriétés:

  • id : id utilisé dans le processus de développement
  • name : le nom de l’extension
  • srcdoc : url vers l’index.html de l’extension, ce dernier correspond ici à l’index.html généré au build de notre application
  • sideBar : détermine l’emplacement de l’extension dans l’interface de Contentful, s’il est défini à true, l’extension sera affichée dans la barre latérale de l’interface Contentful
  • fieldTypes : liste des types de champs compatibles, ici un objet

 

index.html

Nous déclarons ensuite notre index.html et l’index.js associé dans un nouveau dossier src :

ma-super-extension/src/index.html

<!DOCTYPE html>
<html>
   <head>
       <meta charset="utf-8"/>
   </head>
   <body>
       <div id="root"></div>
       <script type="text/javascript" src="./index.js"></script>
   </body>
</html>

Nous allons avoir besoin d’ajouter les dépendances nécessaires à notre index.js :

npm install react react-dom contentful-ui-extensions-sdk

C’est dans l’index.js que nous initialisons l’extension. Nous utilisons la méthode init() exposée par l’UI extensions SDK. C’est le point d’entrée de notre application.

ma-super-extension/src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { init } from 'contentful-ui-extensions-sdk';

init(sdk => {
   ReactDOM.render(
           
       , document.getElementById('root')
   );
});

Notre App est quant à elle définie dans un fichier App.js.

ma-super-extension/src/App.js

import React from 'react';

const App = ({ sdk  }) => {
   
   return (
       <div>
           <h1>Hello!</h1>
       </div>
   );
};


export default App;

Nous avons maintenant la base minimale de notre extension.

Vite fait bien fait, linter + Babel

Nous allons également ajouter rapidement Babel et lb et un linter à notre projet.

Babel

Babel est un compilateur JavaScript permettant de convertir du code ECMAScript 2015 ou plus récent vers une version qui sera compatible avec des navigateurs plus ancien.

Il sert également à convertir la syntaxe JSX utilisée par React.

Nombre de plugins sont mis à disposition. Pour en savoir plus : https://babeljs.io/docs/

Les dépendances à installer :

npm install @babel/core @babel/plugin-proposal-class-properties @babel/preset-env @babel/preset-react --save-dev

Le fichier de configuration à ajouter :

ma-super-extension/.babelrc

{
  "presets": [ "@babel/preset-env","@babel/preset-react"],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

Linter 

Afin d’assurer la lisibilité et la maintenabilité du code au fil du développement, nous mettons en place un linter: ESLint.

Les dépendances à installer :

 npm install eslint eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-react eslint-plugin-standard @babel/eslint-parser  @babel/eslint-plugin --save-dev

La configuration du linter est déclarée dans le fichier .eslintrc.js.

ma-super-extension/.eslintrc.js

module.exports = {
    extends: ["standard"],
    plugins: ["standard", "react", "@babel"],
    rules: {
        "eqeqeq" : "warn",
        "no-var": "error", // optional, recommended when using es6+
        "no-unused-vars": 1, // recommended
        "arrow-spacing": ["error", { before: true, after: true }], // recommended
        indent: ["warn", 4],
        "comma-dangle": [
            "error",
            {
                objects: "only-multiline",
                arrays: "only-multiline",
                imports: "never",
                exports: "never",
                functions: "never",
            },
        ],

        // options to emulate prettier setup
        semi: ["error", "always"],
        "template-curly-spacing": ["error", "always"],
        "arrow-parens": ["error", "as-needed"],

        // standard.js
        "space-before-function-paren": [
            "error",
            {
                named: "always",
                anonymous: "always",
                asyncArrow: "always",
            },
        ],

        // standard plugin - options
        "standard/object-curly-even-spacing": ["error", "either"],
        "standard/array-bracket-even-spacing": ["error", "either"],
        "standard/computed-property-even-spacing": ["error", "even"],
        "standard/no-callback-literal": ["error", ["cb", "callback"]],

        // react plugin - options
        "react/jsx-uses-react": "error",
        "react/jsx-uses-vars": "error",
        "no-tabs": 0

    },
    parser: "@babel/eslint-parser",
    parserOptions: {
        babelOptions: {
            plugins: [
                "@babel/plugin-proposal-class-properties",
            ],
        },
    }
};

Nous ajoutons un fichier .eslintignore pour éviter que le linter ne prenne en compte le dossier de build.

ma-super-extension/.eslintignore

build/*.js

et enfin nous ajoutons un script linter-fix dans le package.json. La commande npm run linter-fix permettra de corriger les problèmes qui peuvent l’être.

ma-super-extension/package.json

 "scripts": {
   "linter-fix": "eslint --fix . --ext .js,.jsx src",
 }

Il est temps de voir enfin le résultat s’afficher dans notre navigateur, non ?

Encore faut-il pouvoir lancer un build et faire tourner l’extension en local.

Et c’est justement la suite.

Lancer le projet en local

Avant de poursuivre, il y a quelques prérequis :

  • avoir créé un compte Contentful
  • avoir créé une organization et un space
  • avoir déclaré au moins un Content Model

Si ce n’est pas déjà fait, rendez-vous sur https://www.contentful.com/help/contentful-101/

En lançant notre projet en local dans Contentful, nous allons pouvoir suivre le développement de notre application au fur et à mesure, en hot reload.

Installons les dépendances nécessaires :

npm install contentful-cli @contentful/contentful-extension-scripts @contentful/forma-36-tokens

Nous ajoutons dans notre package.json les différents scripts qui permettront de lancer, build, et déployer notre application, obtenant ainsi :

ma-super-extension/package.json

{
 "name": "ma-super-extension",
 "version": "1.0.0",
 "private": false,
 "description": "Une UI Extension en ReactJS",
 "devDependencies": {
   "@babel/core": "7.3.4",
   "@babel/plugin-proposal-class-properties": "7.3.4",
   "@babel/plugin-transform-runtime": "7.3.4",
   "@babel/preset-env": "7.3.4",
   "@babel/preset-react": "7.0.0",
   "@contentful/contentful-extension-scripts": "0.3.0",
   "babel-eslint": "10.0.1",
   "babel-plugin-transform-es2015-modules-commonjs": "6.26.2",
   "babel-plugin-syntax-dynamic-import": "6.18.0",
   "contentful-cli": "0.20.0",
   "eslint": "5.16.0",
   "eslint-config-standard": "12.0.0",
   "eslint-plugin-import": "2.17.2",
   "eslint-plugin-node": "8.0.1",
   "eslint-plugin-promise": "4.1.1",
   "eslint-plugin-react": "7.12.4",
   "eslint-plugin-standard": "4.0.0"
 },
 "dependencies": {
   "@contentful/forma-36-tokens": "0.2.0",
   "contentful-ui-extensions-sdk": "3.5.0",
   "react": "16.11.0",
   "react-dom": "16.11.0"
 },
 "scripts": {
   "prestart": "contentful space use && contentful extension update --src http://localhost:1234 --force",
   "start": "contentful-extension-scripts start",
   "build": "contentful-extension-scripts build",
   "deploy": "npm run build && contentful space use && contentful extension update --force",
inter-fix": "eslint --fix . --ext .js,.jsx src",
   "l   "linter-fix": "eslint --fix . --ext .js,.jsx src",
   "login": "contentful login",
   "logout": "contentful logout",
   "help": "contentful-extension-scripts help"
   
 },
 "browserslist": [
   "last 5 Chrome version",
   "> 1%",
   "not ie <= 11"
 ]
}

A ce stade, lorsque nous lançons la commande npm run start, un message d’erreur s’affiche dans notre terminal.

Contentful nous demande de nous authentifier.

Il existe plusieurs façon de se logger :

  • Soit en utilisant contentful-cli via la commande contentful login : vous trouverez la documentation de cette méthode d’authentification ici.
  • Soit en ajoutant à la racine de notre projet un fichier .contentfulrc.json

ma-super-extension/.contentfulrc.json

{
 "cmaToken": "<your-content-management-api-token>",
 "activeSpaceId": "<you-space-id>",
 "activeEnvironmentId": "master"
}

Pour obtenir votre content management token,  allez dans Settings > API Keys. Cliquez sur Content management tokens, puis Generate Personal Token.

Pour obtenir votre space ID,  aller dans Settings > API Keys, sélectionnez une API Key dans laquelle vous trouverez votre space ID.

Attention à bien spécifier .contentfulrc dans votre .gitignore afin de ne pas l’héberger dans votre repository et rendre alors accessibles vos clés d’API. 

Nous sommes désormais connectés !

Lorsque nous lançons la commande npm run start, nous avons maintenant dans notre terminal :

Ça tourne !

Par contre lorsque nous ouvrons notre navigateur pour voir notre localhost:1234, le résultat n’est pas très satisfaisant.

Et oui, rien n’apparaît. Notre extension est faite pour tourner dans une iframe.

Nous allons donc visualiser notre application directement depuis Contentful.

Oui, nous y sommes presque !

 

Allez dans Settings > Extensions.

C’est ici que sont listées les extensions et, après avoir lancé notre extension en local, celle-ci apparaît déjà. Elle a été ajoutée automatiquement dans la liste après le lancement du script start.

Si elle n’apparaît pas, nous pouvons également l’ajouter manuellement via le bouton Add extension.

Il ne reste plus qu’à utiliser cette extension dans le field d’un content model (Ici le nom du content model sera simplement “Test”) :

  • Ajoutez un field de type JSON Object,
  • Nommez-le (ici, nous le nommerons “custom” ) et aller dans Appearance
  • Sélectionnez l’extension

Une fois un content créé, vous pourrez voir les différents champs définis depuis le content model et… notre extension!

Nous pouvons désormais visualiser notre extension en local et la voir évoluer lors du développement.

Développement de l’application

L’objectif  de cette extension est de pouvoir attribuer une valeur au field via une interface que nous concevons nous même.

Dans cette partie nous allons  donc :

  • modifier la valeur du field
  • récupérer la valeur du field lorsque l’on ouvre le content et initialiser l’extension avec cette valeur
  • styliser un minimum notre interface

Bien que pour cet exemple nous ne travaillons que sur une valeur du type object très simple, nous mettrons en place Redux, en prévision d’une évolution de l’extension en une application plus complexe.

Commençons par modifier la valeur de notre field.

Nous partirons sur le principe que nous souhaitons enregistrer un objet simple, comprenant une key “message” qui contiendra une chaîne de caractères. Par exemple :

{
   message: 'Vous ne pouvez pas comprendre la récursivité sans avoir d’abord compris la récursivité'
}

Comme dit précédemment, nous allons utiliser Redux.

Nous ne décrirons pas ici le fonctionnement de Redux. Si vous souhaitez en savoir davantage sur ce dernier, rendez-vous sur la documentation officielle : https://redux.js.org/.

Nous installons les dépendances :

npm install redux react-redux redux-logger

puis déclarons et initialisons le store :

ma-super-extension/src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './reducers';
import logger from 'redux-logger';

import { init } from 'contentful-ui-extensions-sdk';

import App from './App';

const initialState = {
   fieldValue: {
       message: ''
   }
};

const store = createStore(rootReducer, initialState, applyMiddleware(logger));

init(sdk => {
   ReactDOM.render(
       <Provider store={store}>
           <App sdk={sdk}/>
       </Provider>,
       document.getElementById('root')
   );
});

Nous initialisons le store avec un initialState dont la structure correspond à celle que nous souhaitons attribuer à notre field.

Nous rendons le store disponible via le <Provider> pour tous les composants imbriqués qui seront encapsulés dans la fonction connect().

Afin de pouvoir tracer les actions dans la console, nous utilisons redux-logger.

Nous allons maintenant créer le champ par lequel nous modifierons la valeur du store.

Nous créons un nouveau composant EditMessage.

ma-super-extension/src/components/EditMessage/index.js

import React from 'react';
import PropTypes from 'prop-types';

const EditMessage = ({ }) => {
   return (
       <div>
           <label>Update Message</label>
       </div>
   );
};

EditMessage.propTypes = {};

export default EditMessage;

Dans ce dernier, nous utilisons prop-types pour documenter les types des propriétés passées au composant.

Pour ajouter prop-type aux dépendances :

npm install prop-types

Nous connectons notre composant au store et récupérons la valeur message de celui-ci  que nous passons en propriété à notre composant.

ma-super-extension/src/components/EditMessage/index.js

import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

const EditMessage = ({ storedMessage }) => {
   return (
       <div>
           <label>Update Message</label>
       </div>
   );
};

EditMessage.propTypes = {
   storedMessage: PropTypes.string
};

const mapStateToProps = ({ fieldValue }) => ({
   storedMessage: fieldValue.message || null
});

export default connect(mapStateToProps)(EditMessage);

 

Nous créons ensuite un textarea. Celui-ci affichera par défaut la valeur de message récupéré depuis le store.

ma-super-extension/src/components/EditMessage/index.js

import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

const EditMessage = ({ storedMessage }) => {
   return (
       <div>
           <label>Update Message</label>
           <textarea type={'text'}  defaultValue={storedMessage || ''} />
       </div>
   );
};

EditMessage.propTypes = {
   storedMessage: PropTypes.string
};

const mapStateToProps = ({ fieldValue }) => ({
   storedMessage: fieldValue.message || null
});

export default connect(mapStateToProps)(EditMessage);

 

Nous importons notre nouveau composant dans App.

ma-super-extension/src/App.js

import React  from 'react';
import EditMessage from './components/EditMessage';

const App = ({ sdk }) => {
  
   return (
       <div>
           <h1>Hello</h1>
           <EditMessage/>
       </div>
   );
};

export default App;

Voyons le résultat :

Pour rendre le résultat un peu plus propre visuellement, nous allons styliser l’ensemble à l’aide de styled-components.

Si vous ne connaissez pas les styled-components et/ou souhaitez vous documenter, le lien vers la documentation est ici.

Nous ajoutons les dépendances nécessaires :

npm install styled-components

Nous créons au même niveau que l’index.js de notre composant un fichier styled.js, puis y définissons un styled-component Container

ma-super-extension/src/components/EditMessage/styled.js

import styled from 'styled-components';

export const Container = styled.div`
 display : flex;
 flex-direction : column; 

 & label {
   margin : 10px 0;
   line-height : 15px;
   font-size:13px;
   font-weight : 400;
 }

 & textarea{
   min-height : 100px;
 }
`;

et utilisons le styled-component Container dans le composant EditMessage :

ma-super-extension/src/components/EditMessage/index.js

import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Container } from './styled';
import { updateMessage } from '../../actions/index';

const EditMessage = ({ storedMessage }) => {
   return (
       <Container>
           <label>Update Message</label>
           <textarea type={'text'}  defaultValue={storedMessage || ''} />
       </Container>
   );
};

EditMessage.propTypes = {
   storedMessage: PropTypes.string
};

const mapStateToProps = ({ fieldValue }) => ({
   storedMessage: fieldValue.message || null
});

export default connect(mapStateToProps)(EditMessage);

Nous obtenons :

Penchons-nous maintenant sur la modification du store au changement de valeur dans notre textarea.

À l’évènement onChange, nous allons envoyer au store l’action dispatch à laquelle nous passons en paramètre la valeur courante du textarea.

ma-super-extension/src/components/EditMessage/index.js

import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Container } from './styled';
import { updateMessage } from '../../actions/index';

const EditMessage = ({ dispatch, storedMessage }) => {
   return (
       <Container>
           <label>Update Message</label>
           <textarea type={'text'}
               defaultValue={storedMessage || ''}
               onChange={e => {
                   dispatch(updateMessage(e.target.value));
               }}/>
       </Container>
   );
};

EditMessage.propTypes = {
   storedMessage: PropTypes.string
};

const mapStateToProps = ({ fieldValue }) => ({
   storedMessage: fieldValue.message || null
});

export default connect(mapStateToProps)(EditMessage);

 

Nous décrivons notre action dans un dossier ./src/actions.

Considérant que notre store sera amené à évoluer et devenir plus complexe, nous créons un fichier dédié aux actions qui modifieront fieldValue dans notre store : .src/actions/fieldValue.js.

Nous y déclarons notre action updateMessage, avec un payload contenant la valeur courante du textarea.

Nous avons donc dans notre dossiers ./src/actions deux fichiers :

ma-super-extension/src/actions/fieldValue.js

export const updateMessage = value => ({
   type: 'UPDATE_MESSAGE',
   payload: {
       value: value
   }
});

et

ma-super-extension/src/actions/index.js

export * from './fieldValue';

Intéressons nous maintenant au reducer correspondant.

En suivant la même structure que pour les actions, nous avons ainsi un dossier ./src/reducers dans lequel se trouve deux fichiers : index.js et fieldValue.js.

ma-super-extension/src/reducers/index.js

import { combineReducers } from 'redux';
import fieldValue from './fieldValue';

export default combineReducers({
   fieldValue
});

ma-super-extension/src/reducers/fieldValue.js

const fieldValue = (state = [], action) => {
   switch (action.type) {

    case 'UPDATE_MESSAGE' :
        return {
            ...state,
            message: action.payload.value
        };
  
    default:
        return state;
    }
  };

export default fieldValue;

Dans le cas de ‘UPDATE_MESSAGE’, nous modifions le state, attribuant à message sa nouvelle valeur.

Désormais, lorsque nous écrivons dans notre textarea, le store est modifié et nous voyons apparaître nos actions dans la console.

La dernière étape est d’attribuer cette nouvelle valeur au field Contentful. Pour cela nous allons utiliser la fonction setValue() fournie par l’UI extensions SDK.

Pour en savoir plus, vous pouvez aller voit la documentation du SDK.

Nous allons réaliser cette étape dans le fichier App.js.

Nous connectons App au store afin de récupérer la valeur de fieldValue.

ma-super-extension/src/App.js

import React  from 'react';
import { connect } from 'react-redux';
import EditMessage from './components/EditMessage';

const App = ({ sdk, fieldValue }) => {
   return (
       <div>
           <h1>Hello</h1>
           <EditMessage/>
       </div>
   );
};

const mapStateToProps = ({ fieldValue }) => ({
   fieldValue: fieldValue,
});

export default connect(mapStateToProps)(App);

 

A l’aide d’un hook useEffect, nous allons surveiller la valeur de fieldValue.

ma-super-extension/src/App.js

import React  from 'react';
import { connect } from 'react-redux';
import EditMessage from './components/EditMessage';

const App = ({ sdk, fieldValue }) => {
   return (
       <div>
           <h1>Hello</h1>
           <EditMessage/>
       </div>
   );
};

const mapStateToProps = ({ fieldValue }) => ({
   fieldValue: fieldValue,
});

export default connect(mapStateToProps)(App);

 

Si la valeur de fieldValue change, alors nous attribuons sa nouvelle valeur au field Contentful. Pour cela nous définissons une méthode setFieldValue que nous appelons dans le hook.

ma-super-extension/src/App.js

import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import isEqual from 'lodash/isEqual';
import { initFieldValue } from '../actions';
import EditMessage from './components/EditMessage';

const App = ({ fieldValue, sdk, dispatch }) => {

   useEffect(() => {
           setFieldValue();
   }, [fieldValue]);

   const setFieldValue = () => sdk.field.setValue(fieldValue);

   return (
       <div>
           <h1>Hello</h1>
           <EditMessage/>
       </div>
   );
};

const mapStateToProps = ({ fieldValue }) => ({
   fieldValue: fieldValue,
});
export default connect(mapStateToProps)(App);

 

Ça y est ! Lorsque nous modifions la valeur de notre textarea, la valeur du field Contentful est modifiée et nous voyons le bouton “current status” de notre content passer en “publish changes”. Notre modification a bien été prise en compte,  elle est en “draft”.

Il reste encore quelque chose à régler : lorsque nous rafraîchissons notre page, ou que nous quittons notre content pour ensuite y revenir, nous ne voyons plus notre nouveau message.

Il faut en effet initialiser notre store avec la valeur du field Contentful.

Pour cela nous ajoutons un nouveau hook qui se déclenchera une fois au démarrage.

Dans ce hook, si le field Contentful possède déjà une valeur message alors nous appelons l’action initFieldValue. Sinon nous attribuons au field la valeur fieldValue du state initial de notre store.

ma-super-extension/src/container/App.js

import React, {useEffect} from 'react';
import {connect} from 'react-redux';
import {initFieldValue} from '../actions';
import EditMessage from './components/EditMessage';

const App = ({fieldValue, sdk, dispatch}) => {
   useEffect(() => {
       if (sdk.field.getValue() && sdk.field.getValue().message !== undefined) {
           dispatch(initFieldValue(sdk.field.getValue()));
       } else {
           setFieldValue();
       }
   }, []);

   useEffect(() => {
       setFieldValue();
   }, [fieldValue]);

   const setFieldValue = () => sdk.field.setValue(fieldValue);

   return (
       <div>
           <h1>Hello</h1>
           <EditMessage/>
       </div>
   );
};

const mapStateToProps = ({fieldValue}) => ({
   fieldValue: fieldValue,
});
export default connect(mapStateToProps)(App);

Nous ajoutons une nouvelle action initFieldValue :

ma-super-extension/src/actions/fieldValue.js

export const initFieldValue = object => ({
   type: 'INIT_FIELD_VALUE',
   payload: {
       value: object
   }
});

export const updateMessage = value => ({
   type: 'UPDATE_MESSAGE',
   payload: {
       value: value
   }
});

et le reducer associé INIT_FIELD_VALUE :

ma-super-extension/src/reducers/fieldValue.js

const fieldValue = (state = [], action) => {
   switch (action.type) {
   case 'INIT_FIELD_VALUE' :
       return action.payload.value;

   case 'UPDATE_MESSAGE' :
       return {
           ...state,
           message: action.payload.value
       };

   default:
       return state;
   }
};

export default fieldValue;

Et voilà !

Un dernier petit détail supplémentaire pour le confort :

Nous utilisons dans le hook appelé au rendu de notre App la méthode window.startAutoResizer fournie par le SDK.

ma-super-extension/src/App.js

import React, {useEffect} from 'react';
import {connect} from 'react-redux';
import isEqual from 'lodash/isEqual';
import {initFieldValue} from '../actions';
import EditMessage from './components/EditMessage';

const App = ({fieldValue, sdk, dispatch}) => {
   useEffect(() => {
       if (sdk.field.getValue() && sdk.field.getValue() !== {}) {
           dispatch(initFieldValue(sdk.field.getValue()));
       } else {
           setFieldValue();
       }

       sdk.window.startAutoResizer();

       return () => {
           sdk.window.stopAutoResizer();
       };
   }, []);

   useEffect(() => {
       setFieldValue();
   }, [fieldValue]);

   const setFieldValue = () => sdk.field.setValue(fieldValue);

   return (
       <div>
           <h1>Hello</h1>
           <EditMessage/>
       </div>
   );
};

const mapStateToProps = ({fieldValue}) => ({
   fieldValue: fieldValue,
});
export default connect(mapStateToProps)(App);

sdk.window.startAutoResizer() permet de mettre à jour automatiquement la hauteur de l’extension. Sans cela, si la height du DOM de l’application devient plus grande, l’iframe sera visuellement coupé et il faudra scroller pour voir le reste de l’application dépassant de la hauteur initiale :

Avec l’autoResizer, l’iframe s’adapte automatiquement.

Ça y est !

Vous avez une UI Extension en React !

Tips  : 

Contentful met également à disposition un design System, Forma 36, utilisable dans les  extensions. Dans la continuité de l’application en ReactJS que nous avons réalisée précédemment, nous pourrions utiliser ces composants avec la bibliothèque de composants React @contentful/forma-36-react-components.

 

Si nous avons la satisfaction d’avoir désormais notre propre UI Extension, cela nous amène naturellement une autre question : jusqu’où peut-on aller ?

C’est le sujet du 3ème et dernier article de cette série : “Une interface sur mesure avec les UI Extensions du CMS Contentful, REX“, où nous verrons jusqu’où peuvent nous mener ces UI Extensions à travers un retour d’expérience : une UI Extension dédiée à notre chargé de SEO, avec une interface adaptée à ses besoins.

 

Ressources :

Published by

Commentaire

Laisser un commentaire

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

Nous recrutons

Être un Sapient, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.