Créer un élément de contenu de type Wrapper

Cas d'utilisation

Les éléments de contenu de type Wrapper sont utilisés pour proposer une mise en forme ou un module avec un élément de début, un élément de fin et éventuellement ou un plusieurs éléments de séparation.

Cette mise en forme se retrouve par exemple dans un module de mise en page avec des colonnes ou encore dans un module de slider. Elle est très pratique car on peut mettre autant d'autres éléments de contenu que l'on souhaite entre l'élément de début et l'élément de fin. Plusieurs Wrapper peuvent mêmes êtres imbriqués.

Développement du module

Contexte

Le module que nous allons développer pour l'exemple de ce tutoriel va être un simple élément div avec son élément de début qui ouvre la balise et son élément de fin qui la ferme.

Configuration

Nous allons commencer par créer le dossier wrapper dans le répertoire /system/modules/ qui va contenir les fichiers de notre module.

Nous allons ensuite, dans ce dossier wrapper, créer un sous dossier config qui va contenir le fichier config.php suivant :

<?php

$GLOBALS['TL_CTE']['wrapper'] = array(
    'wrapperStart' => 'ModuleWrapperStart',
    'wrapperEnd' => 'ModuleWrapperEnd'
);

$GLOBALS['TL_WRAPPERS']['start'][] = 'wrapperStart';
$GLOBALS['TL_WRAPPERS']['stop'][] = 'wrapperEnd';

La première portion de code définit deux nouveaux types d'éléments que nous pourront retrouver dans la liste des types d'éléments lors de la création du contenu d'un article.

La seconde portion de code définit que les deux éléments de contenus que nous venons d'ajouter sont des wrappers. L'élément wrapperStart étant un wrapper de type start (élément de début) et l'élément wrapperEnd étant un wrapper de type stop (élément de fin).

Classes et templates

Nous allons maintenant créer les deux classes ModuleWrapperStart et ModuleWrapperEnd référentes à nos éléments de contenus définis dans le fichier de config.

Nous placerons ces deux classes dans le dossier modules (/system/modules/wrapper/modules/).

Classe ModuleWrapperStart.php :

<?php

class ModuleWrapperStart extends \ContentElement
{
    protected $strTemplate = 'ce_wrapper_start';

    public function generate()
    {
        if (TL_MODE == 'BE')
        {
            return;
        }
        return parent::generate();
    }

    protected function compile()
    {

    }
}

Classe ModuleWrapperEnd.php :

<?php

class ModuleWrapperEnd extends \ContentElement
{
    protected $strTemplate = 'ce_wrapper_end';

    public function generate()
    {
        if (TL_MODE == 'BE')
        {
            return;
        }
        return parent::generate();
    }

    protected function compile()
    {

    }
}

Ces deux classes sont très basiques et sont de simples déclaration de contenu d'élément Contao avec une template référente que nous allons maintenant créer dans le dossier templates (/system/modules/wrapper/templates/).

Template ce_wrapper_start.html5 :

<div>

Template ce_wrapper_end.html5 :

</div>

Comme dit précédemment, le module sera un simple élément div avec l'élément de début qui ouvre la balise et l'élément de fin qui la ferme.

En n'oubliant pas de générer les autoload pour le dossier wrapper depuis le menu Outils de développement - Créateur d'autoload, nous obtenons un module basique et fonctionnel qui permet de créer un élément div sous forme de wrapper et ainsi pouvoir mettre des autres éléments de contenu entre la balise ouvrante et la balise fermante.

Callbacks

Le module est fonctionnel mais présente un petit problème : Si l'élément de début est supprimé, l'élément de fin est toujours présent (et inversement). Cela peut poser des problèmes sur le front-office : une balise div ouverte mais non fermée peut casser la mise en page du site ...

Nous allons donc ajouter deux callbacks au DCA tl_content :

  • un callback à la création de l'élément de début qui va automatiquement créer son élément de fin correspondant
  • un callback à la suppression de l'élément de début ou de l'élément de fin qui va également supprimer l'élément de début ou de fin correspondant

Avant tout chose, nous allons ajouter deux champs au DCA tl_content qui vont avoir pour rôle de lier un élément de début à un élément de fin. Nous allons donc créer le fichier tl_content.php dans le dossier dca (/system/modules/wrapper/dca/).

Ce fichier va contenir les lignes de codes suivantes pour définir nos deux champs :

$GLOBALS['TL_DCA']['tl_content']['fields']['section_parent'] = [
    'sql'       => "int(10) unsigned NOT NULL default '0'"
];

$GLOBALS['TL_DCA']['tl_content']['fields']['section_child'] = [
    'sql'       => "int(10) unsigned NOT NULL default '0'"
];

Le champ section_parent sera utilisé pour un élément de fin et désignera son élément de début correspondant. De la même manière, le champ section_child sera utilisé pour un élément de début et désignera son élément de fin correspondant.

Nous allons maintenant ajouter, toujours dans le fichier tl_content.php, les lignes de codes définissant nos deux callbacks :

$GLOBALS['TL_DCA']['tl_content']['config']['onsubmit_callback'][] = array('tl_content_wrapper', 'sectionUpdate');
$GLOBALS['TL_DCA']['tl_content']['config']['ondelete_callback'][] = array('tl_content_wrapper', 'sectionDelete');

Puis nous allons créer la classe correspondante avec les deux méthodes appelées ce dessus. Toujouts dans le fichier tl_content.php :

class tl_content_wrapper extends tl_content
{
    public function sectionUpdate(DataContainer $dc)
    {
        if ($dc->activeRecord->type != 'sectionStart') return '';
        if (!$dc->activeRecord->section_child || $dc->activeRecord->section_child == 0)
        {
            $arrSet = array('pid' => $dc->activeRecord->pid,
                'ptable' => $dc->activeRecord->ptable,
                'tstamp' => time(),
                'type' => 'sectionEnd',
                'sorting' => $dc->activeRecord->sorting + 64,
                'section_parent' => $dc->activeRecord->id
            );
            $insertElement = $this->Database->prepare("INSERT INTO tl_content %s")->set($arrSet)->execute()->insertId;
            $child = $insertElement;
            $insertElement = $this->Database->prepare("UPDATE tl_content %s WHERE id=?")->set(array('section_child'=>$child))->execute($dc->activeRecord->id);
        }
    }

    public function sectionDelete(DataContainer $dc)
    {
        if ($dc->activeRecord->type != 'sectionStart' && $dc->activeRecord->type != 'sectionEnd') return '';
        if ($dc->activeRecord->type == 'sectionStart')
        {
            if ($dc->activeRecord->section_child && $dc->activeRecord->section_child != 0)
            {
                $this->Database->prepare("DELETE FROM tl_content WHERE id=?")->execute($dc->activeRecord->section_child);
            }
        }
        if ($dc->activeRecord->type == 'sectionEnd')
        {
            if ($dc->activeRecord->section_parent && $dc->activeRecord->section_parent != 0)
            {
                $this->Database->prepare("DELETE FROM tl_content WHERE id=?")->execute($dc->activeRecord->section_parent);
            }
        }
    }
}

Ainsi, lors de la création d'un élément de début (méthode sectionUpdate), un élément de fin sera automatiquement créé avec les bons paramètres de façon à ce qu'il soit créé pour le bon article (ptable et pid), juste après l'élément de début (sorting), avec le bon type et son parent de renseigné. En même temps, nous avons pris soin d'ajouté l'id de l'élément de fin créé au champ section_child de l'élément de début correspondant. Ainsi, les deux éléments sont bien liés.

Cette liaison est utile lors de la suppression d'un élément (méthode sectionDelete). En effet, si l'élément est de type début alors on supprime son élément child correspondant ; si l'élément est de type fin alors on supprime son élément parent correspondant.

Aller plus loin

Ce tutoriel touche à sa fin mais il est possible d'améliorer sur module sur plusieurs points.

Part element

Il est envisageable de définir des éléments de séparation (part element). Peu utile pour notre exemple mais peut être utilisé dans le cas d'une mise en forme en colonnes.

Pour se faire, il faudra rajouter, dans le fichier config, un Wrapper $GLOBALS['TL_WRAPPERS']['separator'][] qui fonctionne comme les deux autres. De plus, il faudra faire attention au niveau des callbacks à créer un tableau d'enfants de façon à supprimer tous les éléments de séparation ainsi que l'élément de fin lors de la suppression d'un élément de début.

Ajouter des champs à notre élément de début

Il est envisageable d'ajouter des champs à notre élément de début pour personnaliser la balise div créée.

Nous pouvons par exemple ajouter un champ classe pour ajouter une classe CSS à notre balise ...

Retour

comments powered by Disqus