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 :
Au programme : Prêt ? C’est parti ! 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 Un rapide coup d’oeil sur les différentes propriétés: 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 Nous allons avoir besoin d’ajouter les dépendances nécessaires à notre index.js : C’est dans l’index.js que nous initialisons l’extension. Nous utilisons la méthode ma-super-extension/src/index.js Notre App est quant à elle définie dans un fichier App.js. ma-super-extension/src/App.js Nous avons maintenant la base minimale de notre extension. 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 :
Les dépendances à installer : Le fichier de configuration à ajouter : ma-super-extension/.babelrc 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 : La configuration du linter est déclarée dans le fichier .eslintrc.js. ma-super-extension/.eslintrc.js Nous ajoutons un fichier .eslintignore pour éviter que le linter ne prenne en compte le dossier de build. ma-super-extension/.eslintignore et enfin nous ajoutons un script ma-super-extension/package.json 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. Avant de poursuivre, il y a quelques prérequis : Si ce n’est pas déjà fait, rendez-vous sur
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 : 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 A ce stade, lorsque nous lançons la commande Contentful nous demande de nous authentifier. Il existe plusieurs façon de se logger : ma-super-extension/.contentfulrc.json 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 Ç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 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”) : 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. 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 : 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 : 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 :
Nous installons les dépendances : puis déclarons et initialisons le store : ma-super-extension/src/index.js 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 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 Dans ce dernier, nous utilisons Pour ajouter prop-type aux dépendances : 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 Nous créons ensuite un ma-super-extension/src/components/EditMessage/index.js Nous importons notre nouveau composant dans App. ma-super-extension/src/App.js 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 : 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 et utilisons le styled-component Container dans le composant EditMessage : ma-super-extension/src/components/EditMessage/index.js Nous obtenons : Penchons-nous maintenant sur la modification du store au changement de valeur dans notre À l’évènement ma-super-extension/src/components/EditMessage/index.js 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 Nous avons donc dans notre dossiers ./src/actions deux fichiers : ma-super-extension/src/actions/fieldValue.js et ma-super-extension/src/actions/index.js 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 ma-super-extension/src/reducers/fieldValue.js Dans le cas de ‘UPDATE_MESSAGE’, nous modifions le state, attribuant à message sa nouvelle valeur. Désormais, lorsque nous écrivons dans notre La dernière étape est d’attribuer cette nouvelle valeur au field Contentful. Pour cela nous allons utiliser la fonction 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 A l’aide d’un hook useEffect, nous allons surveiller la valeur de fieldValue. ma-super-extension/src/App.js Si la valeur de fieldValue change, alors nous attribuons sa nouvelle valeur au field Contentful. Pour cela nous définissons une méthode ma-super-extension/src/App.js Ça y est ! Lorsque nous modifions la valeur de notre 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 ma-super-extension/src/container/App.js Nous ajoutons une nouvelle action ma-super-extension/src/actions/fieldValue.js et le reducer associé ma-super-extension/src/reducers/fieldValue.js 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 ma-super-extension/src/App.js 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.
Initialisation
true
, l’extension sera affichée dans la barre latérale de l’interface Contentful
init()
exposée par l’UI extensions SDK. C’est le point d’entrée de notre application.Vite fait bien fait, linter + Babel
linter-fix
dans le package.json. La commande npm run linter-fix
permettra de corriger les problèmes qui peuvent l’être.Lancer le projet en local
npm run start
, un message d’erreur s’affiche dans notre terminal.
contentful login
: vous trouverez la documentation de cette méthode d’authentification ici.npm run start
, nous avons maintenant dans notre terminal :start
.
Développement de l’application
<Provider>
pour tous les composants imbriqués qui seront encapsulés dans la fonction connect()
.prop-types
pour documenter les types des propriétés passées au composant.textarea
. Celui-ci affichera par défaut la valeur de message récupéré depuis le store.textarea
.onChange
, nous allons envoyer au store l’action dispatch
à laquelle nous passons en paramètre la valeur courante du textarea
.textarea
.textarea
, le store est modifié et nous voyons apparaître nos actions dans la console.setValue()
fournie par l’UI extensions SDK.setFieldValue
que nous appelons dans le hook.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”.initFieldValue
. Sinon nous attribuons au field la valeur fieldValue du state initial de notre store.initFieldValue
:INIT_FIELD_VALUE
:window.startAutoResizer
fournie par le SDK.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 :Ressources :