Créer un module personnalisé pour Divi – Visual Builder

Il y a deux ans (déjà), j’ai écris un article expliquant comment créer un module personnalisé pour le constructeur de page Divi.

 

En deux ans, le Divi Builder a beaucoup évolué, avec du changement dans son code source, et surtout : l’introduction du « Visual Builder », qui change la façon de développer un module.
En effet, il ne suffit plus de développer son module en PHP : il faut désormais utiliser React si on veut que nos modules soient visibles et éditables dans ce Visual Builder.

Pour ma part j’ai tardé à suivre cette évolution, par manque de temps mais aussi par manque de connaissance de React. Mes modules fonctionnaient toujours sauf dans le Visual Builder.
Il y a quelques mois j’ai commencé à apprendre React : pas le choix si on veut travailler avec Gutenberg. Avec ces nouvelles connaissances, j’ai maintenant de quoi adapter mes modules pour qu’ils soient aussi disponibles dans le Visual Builder de Divi.

Cet article a donc pour but de faire une mise à jour de mon premier article :
aborder les nouvelles techniques pour créer un module qui sera disponible dans le builder « classique » et dans le Visual Builder.

 

Avant d’entrer dans le vif du sujet, comme je l’ai dit, nous allons utiliser React qui est une librairie JavaScript. Il est donc nécessaire que vous connaissiez le JavaScript, et il est préférable d’avoir quelques connaissances de React, au risque d’être vite perdu.

Enfin, cet article n’a pas pour but de vous apprendre React, simplement d’aborder les bases propres au Divi Builder.

 


Sommaire :

1. Composition d’un module Divi
2. Create Divi Extension
3. Paramétrage général du module
4. Les emplacements du module
5. Les champs du module
6. Affichage des champs
7. Le Visual Builder
8. Bonus


 

1. Composition d’un module Divi

 

Un module Divi, c’est du PHP et du HTML, et éventuellement du CSS, du JavaScript.

Tous les modules disponibles dans le builder Divi étendent la classe PHP ET_Builder_Module, à chaque fois que nous créons un module personnalisé, nous faisons de même.

On peut donc simplifier l’architecture de chaque module créé comme la suivante :

NomDuModule // Nom unique
└──NomDuModule.php // notre classe PHP qui étend la classe ET_Builder_Module
└──style.css // styles CSS

Je vous invite à jeter un oeil au fichier class-et-builder-element.php qui se trouve dans le dossier Divi/includes/builder, vous pouvez aussi regarder le code des modules natifs dans Divi/includes/builder/module, c’est toujours utile.

Maintenant si on souhaite rendre notre module disponible dans le Visual Builder, il va falloir ajouter un nouveau fichier JavaScript, il y en a un par module. Ce qui donne :

NomDuModule // Nom unique
└──NomDuModule.php // notre classe PHP qui étend la classe ET_Builder_Module
├── NomDuModule.jsx // fichier JavaScript responsable du rendu dans le visual builder
└──style.css // styles CSS

Bon, avec ça, on ira pas loin, il nous faut créer une extension.

 

2. Create Divi Extension

 

Pour nous simplifier la vie, ElegantThemes a publié un outil nommé « Create Divi Extension », une sorte de « générateur » d’extension pour Divi, qui, en quelques lignes de commande va nous créer une extension toute prête, nous permettant de nous concentrer sur nos modules.
Le plus gros avantage à mes yeux de cet outil, c’est qu’il embarque toutes les dépendances relatives à React, NPM, etc.

Je vous invite à le faire dès maintenant :

Au préalable vous devez avoir Node (6 au minimum) d’installé.
Ouvrez un terminal dans le dossier plugins d’une installation WordPress (de test) et tapez :

npx create-divi-extension my-extension

En remplaçant « my-extension » par le nom de votre extension.
Vous devrez saisir quelques informations à propos de votre extension, elles sont modifiables par la suite.

Une fois votre extension générée (soyez patient), voici l’architecture que vous devez retrouver :

my-extension
├── includes
│ ├── modules // nos modules
│ │ └── HelloWorld // un module
│ │    ├── HelloWorld.jsx // fichier JavaScript responsable du rendu dans le visual builder
│ │    ├── HelloWorld.php // notre classe PHP qui étend la classe ET_Builder_Module
│ │    └── style.css // styles CSS
│ ├── loader.js // charge les fichiers JavaScript
│ ├── loader.php // charge les fichiers PHP
│ └── MyExtension.php // classe PHP 
├── languages // Fichiers de traduction
├── node_modules // dépendances des paquets Node
├── scripts
│ └── frontend.js // JavaScript chargé dans le front-end +  le Visual Builder
├── styles // fichiers CSS minifiés
├── my-extension.php // déclaration de notre extension
├── package.json
└── README.md

On retrouve l’architecture vue plus haut, avec pas mal de choses autour. Nous verrons en détail chaque fichiers plus tard.
Sachez que le générateur s’est occupé de tout, et que vous devrez vous concentrer essentiellement sur vos modules, dans includes/modules.

 

3. Paramétrage général du module

 

Ouvrez le fichier includes/modules/HelloWorld/HelloWorld.php qui contient la classe PHP de notre module (HelloWorld), laquelle va nous permettre de le configurer.

Nous avons deux paramètres :

* Le slug, obligatoire, qui doit être unique (!) et ne doit plus être modifié, au risque de perdre vos modules déjà créés dans le builder :

public $slug       = 'mcdt_hello_world';

Vous remarquerez le préfixe « mcdt », qui a été ajouté automatiquement lors de la création de l’extension. Il correspond au « prefix » qui est demandé lorsque l’on a généré l’extension avec npx.

* La déclaration (ou non) du support du Visual Builder (« vb ») :

public $vb_support = 'on';

 

Et trois fonctions :

init, qui sert à définir un certain nombre de paramètres du module, ici le minimum : son nom (name)

public function init() {
 $this->name = esc_html__( 'Hello World', 'mcdt-mc-divi-tutorial' );
}

get_fields, qui permet de définir les champs qui seront disponibles dans le module, ici un champ TinyMce (éditeur)

public function get_fields() {
	return array(
		'content' => array(
			'label'           => esc_html__( 'Content', 'mcdt-mc-divi-tutorial' ),
			'type'            => 'tiny_mce',
			'option_category' => 'basic_option',
			'description'     => esc_html__( 'Content entered here will appear inside the module.', 'mcdt-mc-divi-tutorial' ),
			'toggle_slug'     => 'main_content',
		),
	);
}

render, qui se charge d’afficher le contenu du module, avec du html et les valeurs enregistrées en back-office (ici, l’éditeur)

public function render( $attrs, $content = null, $render_slug ) {
	return sprintf( '<h1>%1$s</h1>', $this->props['content'] );
}

 

Avec ça, on a le minimum requis pour avoir un module fonctionnel qui affiche tout simplement du contenu texte.
Essayez donc d’activer votre extension et d’ajouter un module « Hello World » dans une page utilisant le builder Divi : bravo, votre premier module !

On va s’intéresser de plus près à la fonction init, qui en réalité permet de définir un grand nombre de paramètres en plus du name.
On peut par exemple définir l’icône de notre module pour remplacer celui par défaut, définir la classe CSS principale du module…
Modifiez votre fonction init comme ceci :

public function init() {
	$this->name = esc_html__( 'Hello World', 'mcdt-mc-divi-tutorial' );
	$this->icon_path =  plugin_dir_path( __FILE__ ) . 'sun.svg';
	$this->main_css_element = '%%order_class%%.additional_class';
}

– La variable icon_path permet de définir une icône personnalisée, ici un fichier SVG à placer dans le dossier de votre module (fonctionne uniquement dans le visual builder).
– La variable main_css_element permet de définir la classe CSS qui servira de sélecteur principal du module. La variable %%order_class%% sera remplacée automatiquement par le slug du module, et incrémentée par le nombre de modules dans la page. (Le premier aura pour sélecteur votreprefix_hello_world_0, le second votreprefix_hello_world_1, etc).
Cela peut-être utile si on souhaite avoir une classe différente du slug pour notre module.

 

4. Les emplacements du module

 

Avant d’ajouter divers champs dans un module, nous devons de définir leurs emplacements, voici comment s’articule le contenu d’un module :

 

En vert : Les onglets principaux

En jaune : Les sections dans les onglets

En bleu : Les champs dans les sections

 

Nous pouvons ajouter des onglets, en plus des natifs (Contenu, Style, Avancé) – grâce à la fonction get_main_tabs :
Ici j’ajoute l’onglet ‘Custom tab’ qui a pour identifiant custom_tab, on s’en resservira plus bas.

public function get_main_tabs() {
	$tabs = array(
		'general'    => esc_html__( 'Content', 'et_builder' ),
		'advanced'   => esc_html__( 'Design', 'et_builder' ),
		'custom_css' => esc_html__( 'Advanced', 'et_builder' ),
		'custom_tab' => esc_html__( 'Custom tab', 'mcdt-mc-divi-tutorial' ),
	);
	return $tabs;
}

 

Ensuite, nous pouvons définir quel onglet va afficher quelle section, ainsi que l’intitulé de ces sections :
Placez ceci dans votre fonction init :

// définir les sections et les sous sections (toggles) par onglet
$this->settings_modal_toggles  = array(
	// Natif
	'general'  => array(
		'toggles' => array(
			'main_content' => esc_html__( 'Texte', 'mcdt-mc-divi-tutorial' ),
		),
	),
	// custom
	'custom_tab'  => array(
		'toggles' => array(
			'main_content' => esc_html__( 'Titre', 'mcdt-mc-divi-tutorial' ),
		),
	),
);

Ici nous décidons que :
– l’onglet ‘general’ (Contenu) devra afficher la section main_content, et que cette section s’intitulera « Texte ».
– l’onglet custom_tab (que nous avons défini juste avant) devra afficher la section main_content, et que cette section s’intitulera « Titre ».

Vous pouvez créer autant de section que nécessaire, cependant pensez à ne pas faire une interface trop chargée, elle l’est déjà bien assez avec les options natives de Divi.
D’ailleurs, vous avez peut-être remarqué que dans notre module « Hello World », qui contient pour l’instant qu’un champ « texte », il y a tout un tas d’options (fond, style du texte, bordures, dimensionnement…).
C’est normal, c’est le fonctionnement de Divi : tous les paramètres possibles pour un champ donné s’affichent par défaut, sans aucune action de notre part.
C’est si nous souhaitons les supprimer qu’il faudra faire un peu de customisation, nous le verrons plus tard.

 

5. Les champs du module

 

L’ajout de champs dans le module se fait dans la fonction get_fields, nous pouvons par exemple ajouter un champ « titre » qui sera simplement un champ texte :

/*
* Définir les champs du module
*/
public function get_fields() {
	return array(
		'title' => array(
			'label'           => esc_html__( 'Titre', 'mcdt-mc-divi-tutorial' ),
			'type'            => 'text',
			'option_category' => 'basic_option',
			'description'     => esc_html__( 'Content entered here will appear as title.', 'mcdt-mc-divi-tutorial' ),
			'toggle_slug'     => 'main_content',
			'tab_slug'		  => 'custom_tab', // notre onglet custom
		),
		'content' => array(
			'label'           => esc_html__( 'Content', 'mcdt-mc-divi-tutorial' ),
			'type'            => 'tiny_mce',
			'option_category' => 'basic_option',
			'description'     => esc_html__( 'Content entered here will appear inside the module.', 'mcdt-mc-divi-tutorial' ),
			'toggle_slug'     => 'main_content',
			'tab_slug'		  => 'general',
		),
	);
}

Cette fonction retourne un tableau, qui comprend donc nos champs.
Un champ se paramètre comme suit :

identifiant =>
— label (le nom du champ)
— type (le type de champ)
— option_category (le type d’option, basic, advanced…)
— description (une description facultative)
— toggle_slug (correspond à une section, vues plus haut)
— tab_slug (correspond à un onglet, vus plus haut)

Notre champ « Titre » s’affichera donc dans notre onglet personnalisé « Custom tab » (custom_tab), dans la section « Titre » (main_content), que nous avons définis dans la fonction settings_modal_toggles.

Avec ces informations vous pouvez ajouter autant de champ que vous voulez, en spécifiant précisément leur emplacement.

N’hésitez pas à vous inspirer des modules natifs pour connaitre les champ existants et leurs configurations (Divi/includes/builder/module)

 

6. Affichage des champs

 

Comme vu plus haut, c’est la fonction render qui contrôle l’affichage du contenu des champs dans les pages.
L’extension create divi extension a généré quelque chose de plutôt basique : on affiche notre champs « contenu » (content) défini dans la fonction get_fields, à l’intérieur d’un h1 :

/*
* Affichage du contenu
*/
public function render( $attrs, $content = null, $render_slug ) {
	return sprintf( '<h1>%1$s</h1>', $this->props['content'] );
}

Vous remarquerez l’utilisation de $this->props, qui contient tous les réglages du module, il faut lui passer en paramètre l’identifiant du champ / réglage que l’on souhaite récupérer, ici content.
Je vous invite à faire un petit errror_log( print_r( $this->props, true ) ); et de jeter un oeil au nombre impressionnant de données stockées par le builder!


Nous pouvons modifier un peu cette fonction pour afficher notre champ « titre », qui logiquement s’affichera dans le h1, et le champ « contenu » à la suite :

public function render( $attrs, $content = null, $render_slug ) {

	$title = $this->props['title'];
	$content = $this->props['content'];

	if ( '' !== $title ) {
		$title = sprintf( '<h1>%1$s</h1>', $title );
	}

	$output = sprintf(
		'%1$s
		%2$s',
		$title,
		$content
	);

	return $output;
}

Ajoutez un module « Hello World » à une page depuis le builder, saissez un titre et un contenu : consultez votre page, votre contenu s’affichera bien.

Notez que nous n’avons pas besoin de définir de wrapper pour notre module car Divi s’en occupe, si vous observez le code source vous verrez quelque chose du genre :

<div class="et_pb_module votreprefix_hello_world votreprefix_hello_world_0">
	<div class="et_pb_module_inner">
		<h1>Mon titre</h1>
		<p>Mon contenu</p>
	</div>
</div>

 

7. Le Visual Builder

… et React !

 

Actuellement notre module est disponible dans le builder « classique », et aussi dans le Visual Builder. Si vous passez sur le VB, vous pouvez ajouter le module Hello World :
On retrouve bien notre champ « contenu » WYSIWYG dans l’onglet « Contenu »,  et notre champs « titre » dans l’onglet « Custom tab », cependant notre champs « Titre » ne s’affiche pas et c’est normal.
Comme pour le rendu PHP, il faut modifier le rendu JavaScript, ça va se passer dans le fichier HelloWorld.jsx.

Tout d’abord ouvrez un terminal dans le dossier de votre extension, et lancez la commande npm start, cela va mettre en route un « watcher », qui compilera votre code à chaque modification (tout ceci étant fourni par l’extension « create-divi-extension »).

Ouvrez le fichier HelloWorld.jsx

En premier lieu on trouve un import de React lui même, mais aussi de Component : React fonctionne avec des « composants », chaque module est un composant.
Nous importons également un fichier de style qui sera chargé à la fois dans le Visual Builder, et le front.

// External Dependencies
import React, { Component } from 'react';
// Internal Dependencies
import './style.css';

Ensuite nous avons notre classe HelloWorld qui étend la classe Component :
Comme dans la classe PHP du module, on définit le slug, puis vient la fonction render, qui a exactement le même rôle que sa comparse vue tout à l’heure : afficher notre contenu.
Les réglages et contenus des modules sont accessibles via les props qui sont propres à React et s’actualiseront dès qu’une action utilisateur sera réalisée (modifier le texte par exemple). Aussi pour accéder à notre champ « Contenu », nous écrivons this.props.content, qui peut être comparable à $this->props['content'] côté PHP.

class HelloWorld extends Component {

  static slug = 'mcdt_hello_world';

  render() {
    const Content = this.props.content;

    return (
      <h1>
        <Content/>
      </h1>
    );
  }
}

export default HelloWorld;

Nous allons modifier cette fonction pour afficher notre titre et notre contenu, comme nous l’avons fait dans la classe PHP :

render() {
	
const Content = this.props.content;

return (
	<Fragment>
    	{ !! this.props.title && (
    	  <h1>{this.props.title}</h1>
    	) }
        <Content/>
    </Fragment>
);
}

Vous remarquez l’utilisation de Fragment, en effet React n’autorise pas d’afficher plusieurs éléments s’ils ne sont pas contenus dans un élément parent. Comme je ne souhaite pas ajouter une div supplémentaire autour de mon titre et de mon contenu, j’utilise un Fragment.
Vous devez modifier la ligne d’import comme ceci : import React, { Component, Fragment } from 'react';

Et enfin on n’oublie pas d’exporter notre module : export default HelloWorld;

A partir de maintenant votre module est utilisable à 100% dans le visual builder !

Gardez à l’esprit que :
– C’est la fonction render côté PHP qui est responsable de l’affichage en front.
– Les deux fonctions render doivent en général avoir le même markup HTML, surtout lorsque vous commencerez à styler vos éléments.
– Divi se charge de créer un wrapper pour votre module autour de vos éléments, pas la peine d’y ajouter une couche.

Pour aller plus loin :

-> Pour générer des fichiers pour la production, lancez simplement npm run build.
-> Pour ajouter des modules, créez un nouveau dossier dans /includes/modules et adaptez le fichier /includes/modules/index.js.
-> Respectez les conventions de nommage : nom de dossier = nom de fichiers = nom de classes.
-> Le fichier /scripts/frontend.js est chargé dans le visual builder et le front-end.

 

Je vous conseille vivement ce repo GitHub (par l’équipe ET), qui propose pas mal d’exemples de champs et de comment les utiliser.

Et retrouvez l’intégralité du code utilisé pour cet article sur mon GitHub.

 

 8. Bonus 

 

Voici quelques petites astuces que j’ai découvertes au cours de mes péripéties avec le Divi Builder :

  • Supprimer le cache du builder lorsqu’on développe (en effet quoi de plus énervant de ne pas voir nos modifications apparaître ? ) /!\ A retirer en production !
class MCDT_HelloWorld extends ET_Builder_Module {

	[...]
	public $debug_module = true;
					
	public function remove_from_local_storage() {
		global $debug_module; 
		echo "<script>localStorage.removeItem('et_pb_templates_".esc_attr($this->slug)."');</script>";
	}

	public function init() {

		// à retirer en prod
		$debug_module = true;

		if (is_admin()) {
			// Clear module from cache if necessary
			if ($debug_module) { 
				add_action('admin_head', array( $this, 'remove_from_local_storage' ) );
			}
		}
		// à retirer en prod
    }
[...]
}
  • – La fonction get_advanced_fields_config qui permet d’activer et/ou de désactiver les très nombreuses options de style présentes par défaut sur tous les champs, pratique quand on veut éviter les carnages à base de sapin de Noël :
function get_advanced_fields_config() {
	$advanced_fields = array(
		// paramètres spécifiques au fonts (Onglet "style")
		'fonts'  => array(
			// notre champs titre
			'title' => array(
				'label'    => esc_html__( 'Titre', 'mc_divi_custom_modules' ),
				// sélecteur CSS
				'css'      => array(
					'main' => "{$this->main_css_element} h1",
					'font' => "{$this->main_css_element} h1",
					'color' => "{$this->main_css_element} h1",
					'plugin_main' => "{$this->main_css_element} h1, {$this->main_css_element} h1",
					'text_align' => "{$this->main_css_element} h1",
				),
				// option d'alignement
				'use_alignment' => true,
				// désactiver l'option d'ombre portée
				'hide_text_shadow' => true,
			),
			// notre champs contenu
			'content'   => array(
				'label'    => esc_html__( 'Contenu', 'mc_divi_custom_modules' ),
				'css'      => array(
					'main'        => "{$this->main_css_element} p",
					'color'       => "{$this->main_css_element}, {$this->main_css_element} *",
					'line_height' => "{$this->main_css_element} p",
					'plugin_main' => "{$this->main_css_element}, %%order_class%% p",
				),
				// désactiver l'option d'ombre portée
				'hide_text_shadow' => true,
			),
		),
		// désactiver la première partie de style du texte
		'text' => false,
		// autoriser uniquement les background de couleur
		'background' => array(
			'use_background_color_gradient' => false, // default
			'use_background_image'          => false, // default
			'use_background_video'          => false, // default
		),
		// désactiver l'ombre porter du module
		'box_shadow' => false,
		// désactiver les filtres
		'filters'			=> false,
		// désactiver les animations
		'animation'			=> false,
	);

	return $advanced_fields;
}

C’est tout pour aujourd’hui, je compléterai cette liste au fur et à mesure ! Bon développement et n’hésitez pas à partager en commentaires vos propres modules 🙂

 


 

Ressources utiles et importantes :

Vous avez des questions ?
Contactez-moi :
Me contacter

3 thoughts on “Créer un module personnalisé pour Divi – Visual Builder

  1. Hello Marie. Bravo pour cet excellent tutoriel. J’ai quand même une question. Si j’ai déjà développé un plugin en php, comment pourrai-je faire pour le transformer en module divi sans tout re-coder ?

    1. Bonjour Amadou,
      contente que mon tutoriel t’ai été utile !
      Si tu as développé un module uniquement pour le « back-end » builder et que tu souhaites l’adapter pour le Visual Builder :
      tu peux créer un nouveau plugin en utilisant l’outil « Create Divi extension » dans lequel tu crées un nouveau module et y importe ton code PHP existant. Il te restera à créer la partie JavaScript pour le Visual Builder.

Laisser un commentaire

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

Nom *

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

ut fringilla non dolor. quis vel, dictum