Sélectionnez votre langue

Comme vous l'avez probablement remarqué, le titre de cet épisode s'inspire de 2 films/livres célèbres :

  1. "One Custom Field to rule them all" est une référence au Seigneur des Anneaux de JRR Tolkien. C'est simplement parce que nous parlons ici du nouveau type de champ personnalisé introduit dans Joomla 4 : "le sous-formulaire"... qui est le plus puissant et peut littéralement gouverner tous les autres :)
  2. "Episode 7 Part 1" est une référence à la célèbre histoire de JK Rowling : tout comme pour Harry Potter, le septième et dernier épisode était un peu trop long pour tenir dans un seul article. La bonne nouvelle est donc la suivante : ce qui aurait dû être le dernier épisode sur les champs personnalisés dans le magazine de la communauté Joomla sera divisé en deux parties ;)

Champs personnalisés de type "Répétable" dans Joomla 3

Comme vous l'avez peut-être remarqué, à un moment donné, Joomla 3 a introduit un nouveau type de champs personnalisés : le champ répétable. Comme son nom l'indique, il permet de répéter un champ ou plus précisément un bloc de plusieurs champs.

Prenons un exemple. Supposons que vous construisez un site Web avec des recettes où vous souhaitez utiliser des champs personnalisés pour répertorier les ingrédients

  • avant d'avoir le champ répétable, le seul moyen était de créer un tas de champs personnalisés comme
    • Nom de l'ingrédient 1
    • Quantité d'ingrédient 1
    • Nom de l'ingrédient 2
    • Quantité d'ingrédient 2
    • Nom de l'ingrédient 3
    • Quantité d'ingrédient 3
    • etc
      En fait, si vous aviez 1 recette avec 20 ingrédients vous deviez créer 20 "Nom de l'ingrédient X" et 20 "Quantité de l'ingrédient"... même si toutes les autres recettes avaient un maximum de 5 ingrédients.
      J'y suis allé, j'ai fait ça :)
  • grâce à ce nouveau type de champ personnalisé appelé "Champ répétable", vous n'avez eu qu'à créer un seul champ personnalisé "Ingrédient". Dans sa configuration vous préciseriez alors qu'il se compose de deux champs : "Nom" et "Quantité".
    Et tu étais prêt à partir ! Peu importe le nombre (maximum) d'ingrédients, vous étiez couvert

Ce champ personnalisé répétable introduit à un moment donné dans Joomla 3 avait

  • une qualité énorme : il a ouvert un nouveau champ de possibilités
  • un inconvénient : seuls les types (natifs) suivants peuvent être utilisés à l'intérieur
    • Éditeur
    • Médias
    • Nombre
    • Texte
    • Zone de texte

Donc en pratique, ce n'était pas complètement "universel" puisqu'on ne pouvait pas avoir

  • tous les autres types de champs personnalisés natifs comme la liste, la radio, la couleur, etc.
  • ni aucun autre Type de Champ Personnalisé provenant d'un tiers
    ( voir l'épisode précédent sur les champs personnalisés pour une très longue liste de champs personnalisés disponibles, gratuits ou payants). Des exemples typiques sont
    • Vidéos
    • Plans
    • Articles Liés)
  • ni aucun type de champ personnalisé conçu spécialement pour votre cas d'utilisation
    (Les Custom Fields sont en effet de minuscules plugins : il est très facile de dupliquer un type de Custom Field existant pour le personnaliser selon vos besoins, même sans être un codeur expérimenté)

Champ répétable

C'est donc la raison pour laquelle différentes personnes ont eu l'idée d'avoir un champ personnalisé « vraiment reproductible », où vous pouvez sélectionner une série de n'importe quel autre champ personnalisé disponible sur le site Web, qu'il soit natif / tiers / personnalisé. .

Il était un peu trop tard pour introduire cette nouvelle fonctionnalité dans Joomla 3 depuis

  • Joomla 4 était (vraiment) à l'horizon à ce moment-là
  • et toute nouvelle fonctionnalité ajoutée dans Joomla 3 aurait nécessité une nouvelle version 3.x et indirectement retardé Joomla 4

Bref, les champs personnalisés de type "Répétable"

  • ont été déposés dans Joomla 4
  • afin d'être remplacé par sa forme plus universelle : les Champs Personnalisés de Type « Sous-formulaire »

Champs personnalisés de type "Sous-formulaire" dans Joomla 4

Cas d'utilisation en général

Quel peut être le cas d'utilisation des champs personnalisés de type « sous-formulaire » ?

Actually the answer is just like for Legos©: you can really build anything you can think of.

Mon cas d'utilisation typique est celui d'un festival de courts métrages. Il existe différents lieux culturels qui organisent des sessions, chaque session consistant en

  • une date/heure
  • une série de courts métrages où nous affichons généralement
    • Le titre
    • La durée
    • Une galerie avec différentes images
    • Une vidéo

Alors comme vous pouvez le deviner :

  • il n'y a pas de nombre prédéfini de films pour une session donnée (cela peut être compris entre 1 et 15)
  • certains des champs liés aux films peuvent être des champs personnalisés natifs (titre et durée par exemple) mais d'autres sont des champs personnalisés tiers plus avancés (galerie et vidéo par exemple)

Le cas d'utilisation de cette démo : un carrousel

Pour les besoins de cette présentation, nous allons construire un Carrousel.

Les raisons de ce choix sont multiples :

  • pour cet exemple, nous n'avons besoin que de champs personnalisés natifs. Cela signifie que vous pouvez immédiatement reproduire la démo
  • il ne nécessite aucune bibliothèque tierce qui n'est pas déjà disponible avec Joomla 4
  • last but not least, ce sera aussi l'occasion de montrer comment bien faire certaines choses avec Joomla 4, comme
    • ajout de CSS dans une mise en page de remplacement / alternative
    • activer uniquement les parties nécessaires du Javascript Boostrap 5 qui est livré par défaut avec Joomla 4

Bien sûr, il se peut que vous détestiez les carrousels ou que vous n'ayez jamais besoin d'un carrousel. Je respecte ça. Pourtant, le carrousel est un bon exemple pour une démo :)

Si vous avez d'autres cas d'utilisation, merci d'être constructif et de poster un commentaire ci-dessous pour expliquer ce que vous réalisez (si possible avec une capture d'écran). Cela pourrait donner encore plus d'idées aux lecteurs.

Il se peut aussi que le code ci-dessous ne soit pas parfait. Je suis ouvert à toutes suggestions d'amélioration bien sûr ;) Mais après tout c'est aussi le point de l'article actuel : même un non-codeur comme moi peut facilement faire un override et construire des choses puissantes grâce aux fonctionnalités et à la flexibilité de Joomla.

Procédure pas à pas

Étape 1 - création des champs personnalisés de base

La première étape sera de créer les champs personnalisés nécessaires à notre carrousel.

Pour chaque diapositive du carrousel, nous voulons :

  • un Titre de Diapositive => un CF (Champ Personnalisé) de Type Texte
  • un Slide Duration => un CF de Type Integer
  • une Slide Image => un CF de Type Media

Puisque je les crée sur un site Web Joomla 4 nouvellement créé, ces champs personnalisés ont respectivement les ID 1, 2 et 3. Si vous aviez déjà créé des champs personnalisés auparavant, ils auront d'autres ID. Ce n'est pas un problème bien sûr. Il vous suffira d'adapter les identifiants respectifs dans le code ci-dessous.

Comme d'habitude, pour chaque champ personnalisé, vous pouvez spécifier

  • Groupe de terrain (si vous avez envie d'en créer un pour garder votre site organisé)
  • Accès (Public, Invité, Super Utilisateurs, …)
  • Langue

Mais sur l'onglet "Général", notez le nouveau commutateur appelé "Utiliser uniquement dans le sous-formulaire". Dans cet exemple, je souhaite activer ce commutateur car ces trois CF ne seront utilisés que dans le contexte du CF de Type Subform. Notez que l'activation de ce commutateur masque le champ d'affectation de catégorie, ce qui est assez logique : de toute façon, vous ne souhaitez pas afficher ces CF directement sur le formulaire d'édition d'article car tout ce que vous souhaitez afficher est le CF "parent" du sous-formulaire de type.

Champ personnalisé natif

Étape 2 - création du sous-formulaire Champ personnalisé de type

Maintenant, la partie passionnante : créons le sous-formulaire Champ personnalisé de type. Dans la zone nommée "Champs" en bas de l'écran, ajoutez les différents CF de base créés ci-dessus.

Selon votre préférence, vous pouvez affecter ce CF à toutes les catégories ou seulement à une sélection de catégories.

Champ personnalisé de sous-formulaire de type

Vous finirez donc par vous retrouver avec une telle liste de champs personnalisés (remarque : dans la partie 1, nous n'utilisons pas le champ personnalisé "Slide Free Text (éditeur)". Veuillez donc l'ignorer jusqu'à la partie 2) :

Tous les champs personnalisés pour un carrousel

Étape 3 - créez quelques articles

Créez une nouvelle catégorie et créez-y quelques articles. Pour chacun d'eux remplissez au moins

  • un Titre (et un Alias)
  • le CF de Type Sous-formulaire avec plusieurs valeurs pour Titre / Durée / Image

Création d'article avec champ personnalisé de type Sous-formulaire

Étape 4 - créer un élément de menu

Afin d'afficher nos articles dans le front-end, créons un élément de menu de type Blog pointant vers la catégorie souhaitée.

Étape 5 - créer une disposition alternative pour le champ personnalisé

Il y a deux façons d'y parvenir

  1. soit manuellement : consultez la section « Créer une mise en page alternative pour le champ personnalisé » dans l'article suivant du Joomla Community Magazine publié en mai 2021 : Explorez le noyau ! Jouez avec les champs personnalisés pour enrichir votre contenu ou votre design
  2. soit de la manière habituelle via l'interface de Joomla
    1. allez dans Système > Modèles de site > Détails et fichiers Cassiopeia > Créer des remplacements > Dispositions > com_fields
    2. cliquez sur "champ"
    3. vous obtenez le message de confirmation suivant : "Remplacement créé dans /templates/cassiopeia/html/layouts/com_fields/field"

Quelle que soit la façon dont vous choisissez, il existe alors deux méthodes pour renommer / éditer ce fichier nouvellement créé nommé par défaut "render.php":

  1. soit en restant dans l'interface de Joomla > Tab Editor > html > layouts > com_fields > field
  2. soit via votre (s) client FTP ou IDE (ce qui est ma méthode préférée car il est plus facile de faire et d'annuler des modifications)

Si vous ne renommez pas ce fichier "render.php", toute modification que vous y apporterez s'appliquera à tous les champs personnalisés en toutes circonstances. C'est ce qu'on appelle un « Override ».

Ce que nous voulons est similaire mais légèrement différent : nous voulons que nos modifications s'appliquent non pas par défaut mais seulement quand nous le voulons. En d'autres termes, nous voulons pouvoir attribuer notre fichier dans certains cas. C'est ce qu'on appelle une « mise en page alternative » (ou « mise en page alternative »).

Il s'agit d'un processus en deux étapes :

  1. renommez d'abord ce fichier "render.php" en quelque chose d'explicite. Exemples : marc.php, carousel.php ou dans ce cas rawvalue.php. Notez que les tirets sont autorisés dans le nom de fichier (mais pas les traits de soulignement)
  2. puis modifiez le champ personnalisé choisi et accédez à Options d'onglet > Options de rendu > Mise en page. Vous y trouverez une liste déroulante qui répertorie toutes les mises en page alternatives disponibles. Sélectionnez le fichier nouvellement créé et renommé

Voir les captures d'écran correspondantes ci-après :

Disposition alternative

Affectation d'une disposition alternative au champ personnalisé

Étape 6 - Mise en page alternative - version 1 - la valeur brute

Il est maintenant temps de modifier notre mise en page alternative comme suit :

<?php
/**
 * @package     Joomla.Site
 * @subpackage  com_fields
 *
 * @copyright   (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('_JEXEC') or die;

use Joomla\CMS\Language\Text;

if (!array_key_exists('field', $displayData))
{
	return;
}

$field = $displayData['field'];
$label = Text::_($field->label);
$value = $field->value;
$showLabel = $field->params->get('showlabel');
$prefix = Text::plural($field->params->get('prefix'), $value);
$suffix = Text::plural($field->params->get('suffix'), $value);
$labelClass = $field->params->get('label_render_class');
$renderClass = $field->params->get('render_class');

if ($value == '')
{
	return;
}

?>
<?php

// the block ABOVE comes from the original render.php file. The block BELOW is custom-made and replaces the rest of the original render.php file.

// Get the rawvalue and json_decode it
$rawvalue = $field->rawvalue;
$items = json_decode($rawvalue, true);

// this would display the value of the Custom Field taking into account the potential render class of the Custom Field
echo '<h2>Displaying the <strong>value</strong> of the Custom Field</h2>';
echo '<span class="field-value ' . $renderClass . '">' . $value . '</span>';

// this would display the rawvalue (json) of the Custom Field
echo '<h2>Displaying the <strong>rawvalue (json)</strong> of the Custom Field</h2>';
echo '<small><pre>'.print_r($items, true).'</pre></small>';

// this would display the rawvalue (json) as unordered list
echo '<h2>Displaying the <strong>rawvalue (json)</strong> as unordered list</h2>';
echo '<ul>';
foreach ($items as $item) {
    echo
      '<li>Slide<ul>'.
         '<li>' . ( $item['field1'] ?? '' ) . '</li>' .
         '<li>' . ( $item['field2'] ?? '' ) . '</li>' .
         '<li>' . ( $item['field3']['imagefile'] ?? '' ) . '</li>' .
      '</ul></li>';
}
echo '</ul>';

// this would display all what $displayData contains and that we could potentially use in an override / alternate layout
echo '<h2>Displaying the <strong>$displayData</strong> variable</h2>';
echo '<small><pre>'.print_r($displayData, true).'</pre></small>';
?>

Comme vous le remarquerez, nous avons laissé intact ici le premier bloc du fichier render.php d'origine mais nous avons adapté le reste afin de récupérer la "valeur brute" du CF (c'est-à-dire ce qui est techniquement écrit dans la base de données) et non son "valeur" (qui signifie la "valeur rendue" dans le contexte Joomla).

Accédez au front-end de votre site et voyez le résultat. Il affichera respectivement

  1. la valeur du champ personnalisé
  2. la valeur brute (json) du champ personnalisé
  3. la valeur brute (json) en tant que liste non ordonnée
  4. tout ce que contient $ displayData et que nous pourrions potentiellement utiliser dans une mise en page de remplacement ou de remplacement

Voir les captures d'écran correspondantes ci-après :

Valeur du champ personnalisé

Valeur brute du champ personnalisé

Valeur brute du champ personnalisé en tant que liste non ordonnée

variable d'affichage des données

Étape 7 - Mise en page alternative - version 2 - le carrousel sur la vue d'article uniquement

Créez une nouvelle disposition alternative en suivant la procédure expliquée ci-dessus.

Renommez ce fichier my-carousel-article-view-only.php (par exemple) et copiez-collez le code suivant à l'intérieur.

<?php

defined('_JEXEC') or die;

if (!array_key_exists('field', $displayData))
{
	return;
}

$field = $displayData['field'];
// Get the rawvalue
$rawvalue = $field->rawvalue ?? '';
$carouselContainerId = 'carousel-field'. $field->id . '-'. md5($rawvalue, false); // giving a unique ID to the carousel container. NB: the ID cannot start with a digit
// getting he article ID by making an override of components/com_fields/layouts/fields/render.php
echo $displayData['itemid'];

if ($rawvalue == '')
{
	return;
}

?>

<?php
use Joomla\CMS\Factory; // necessary bc we use herafter Factory::getApplication() and Factory::getDocument()
$app = Factory::getApplication(); // JFactory is indeed deprecated in J!4
$view = $app->input->getCMD('view', ''); // "view" would output "article" or "category" for example
// $id = $app->input->getCMD('id', ''); // "id" gives the id of the category if blog view, the id of the article if article view. So not useful here
if ('article' !== $view) {
//    return; // comment this line if you want the carousel to also display on the blog view for example
}
?>

<?php // https://docs.joomla.org/J4.x:Using_Bootstrap_Components_in_Joomla_4
\Joomla\CMS\HTML\HTMLHelper::_('bootstrap.carousel', '#' . $carouselContainerId, ['interval' => 3000, 'pause' => 'false']); // selector is necessary for the potentials Options to work ?>

<?php $items = json_decode($rawvalue, true); ?>

<div id="<?php echo $carouselContainerId; ?>" class="carousel slide carousel-fade" data-bs-ride="carousel">
	<div class="carousel-indicators">
		<?php $first=true; $i=0; ?>
			<?php foreach($items as $item): ?>
				<button <?php if ($first) {echo "class=\"active\""; $first=false;} ?> type="button" data-bs-target="#<?php echo $carouselContainerId; ?>" data-bs-slide-to="<?php echo $i; ?>" aria-current="true" aria-label="<?php echo "Slide " . $i+1; ?>"></button>
			<?php $i=$i+1; ?>
		<?php endforeach; ?>
	</div>
	<div class="carousel-inner">
		<?php $first=true; ?>
		<?php foreach($items as $item): ?>
			<?php
				// Note: field1 in $item['field1'] refers to the Custom Field having ID 1 - nothing to do with the Order of fields within the Custom Field of Type SubForm
				// If necessary adapt the next three lines according to the ID of the Custom Fields on *your* site
				$slideTitle = $item['field1'] ?? '';
				$slideDuration = $item['field2'] ?? '1';
				$slideImage = $item['field3']['imagefile'] ?? '';
			?>
			<div class="carousel-item <?php if ($first) {echo "active"; $first=false;} ?>" data-bs-interval="<?php echo 1000*$slideDuration; ?>">
				<img class="d-block w-100" src="/<?php echo $slideImage; ?>" />
				<div class="carousel-caption d-none d-md-block">
					<h5><?php echo $slideTitle; ?></h5>
				</div>
			</div>
		<?php endforeach; ?>
	</div>
	<button class="carousel-control-prev" type="button" data-bs-target="#<?php echo $carouselContainerId; ?>" data-bs-slide="prev">
		<span class="carousel-control-prev-icon" aria-hidden="true"></span> <span class="visually-hidden">Previous</span>
	</button>
	<button class="carousel-control-next" type="button" data-bs-target="#<?php echo $carouselContainerId; ?>" data-bs-slide="next">
		<span class="carousel-control-next-icon" aria-hidden="true"></span> <span class="visually-hidden">Next</span>
	</button>
</div>

<?php
$carouselCSS = <<<MYCSS
/* example of CSS for bootstrap.carousel - adding a background to the carousel-caption - see https://ui.glass/generator/ for glassmorphism CSS */
.carousel-caption {
bottom: 40%; /* otherwise with our background too close from Indicators with the default 1.25rem */
left: 25%; /* to make it narrower than with the default 15% */
right: 25%; /* to make it narrower than with the default 15% */
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
background-color: rgba(0,0,0,0.5);
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.125);
}
/* by default the custom fields appear in a Unordered List. To make the carousel start on the left and hide the bullet point we use a negative margin as a first approach */
li.field-entry.mycarousel {list-style-type: none; margin-left: -2rem}
MYCSS;

// use Joomla\CMS\Factory; // is already called above so commented here bc can only be put once in the file
$doc = Factory::getDocument();
$doc->addStyleDeclaration($carouselCSS); // CSS will be injected only once even if this layout is called multiple times on a page

// Ununeeded stuff, just needed during testing/development. Uncomment the 'return' to execute the code hereafter
return;

echo 'test';
?>

Modifiez votre sous-formulaire CF de type et attribuez cette nouvelle disposition alternative comme expliqué ci-dessus.

Accédez au front-end de votre site et voyez le résultat.

  • Dans la vue Blog, le champ personnalisé "Carrousel" n'apparaît plus.
    C'est signifié :)
    Nous verrons pourquoi dans la partie 2 de l'épisode 7 actuel sur les champs personnalisés. Et bien sûr, nous fournirons également une belle solution pour cela ;)
  • Ouvrez l'un des articles de la catégorie. Le voici : notre Carrousel entièrement fonctionnel !

Voici un gif animé du résultat (la qualité de l'image est volontairement médiocre afin de garder le fichier le plus léger possible)

GIF animé du carrousel

Explications sur le code

Dans l'ordre d'apparition, voici quelques commentaires sur le code.

Qu'est-ce que c'est ?? ''

Sur la ligne suivante, nous voyons ?? ''

$rawvalue = $field->rawvalue ?? '';

C'est une notation courte -appelée Null Coalescing Operator- permettant d'attribuer une valeur si la variable est NULL. Plus d'informations sur https://www.php.net/manual/en/language.operators.comparison.php  

Usine

use Joomla\CMS\Factory;

Cette ligne est nécessaire car plus loin dans le code nous utilisons Factory::getApplication() et Factory::getDocument()

JFactory

JFactory est obsolète dans J!4. Le code correct est maintenant le suivant :

$app = Factory::getApplication();

getCMD('vue', '')

Dans ce premier exemple, nous ne voulons pas afficher le CF sur la vue Blog.
Alors comment savoir si on est dans le cas d'une Vue « article » ou « catégorie » par exemple ?

Grâce à cette variable :

$view = $app->input->getCMD('view', ''); 

Afficher uniquement sur une vue d'article (et non sur une vue de blog)

Grâce à la ligne suivante, le reste du code sera ignoré.

Notez la « condition Yoda » qui est une bonne pratique pour éviter les erreurs ou les résultats inattendus dans votre code si vous faites des fautes de frappe : https://en.wikipedia.org/wiki/Yoda_conditions  

if ('article' !== $view) { return;}

Appeler uniquement le javascript nécessaire de Boostrap 5 livré avec J!4

Joomla 4 est livré avec Bootstrap 5. Mais pour rendre vos sites Web plus performants, le javascript BS5 n'est pas chargé par défaut. Au lieu de cela, vous pouvez décider vous-même dans vos dérogations / mises en page alternatives pour activer uniquement ce dont vous avez besoin et quand et où vous en avez besoin.

C'est exactement ce que fait la ligne suivante, appelant ce qui est nécessaire pour qu'un carrousel fonctionne :

\Joomla\CMS\HTML\HTMLHelper::_('bootstrap.carousel', '#' . $carouselContainerId, ['interval' => 3000, 'pause' => 'false']);

Pour plus d'informations, consultez la documentation officielle : https://docs.joomla.org/J4.x:Using_Bootstrap_Components_in_Joomla_4  

Le code HTML du carrousel

Pour le code Carousel, j'ai simplement pris un exemple sur le site officiel de BS et remplacé bien sûr dans leur code HTML tous les Titres / Images / etc donnés par les valeurs brutes respectives récupérées depuis notre CF de Type Sous-formulaire.

Voir différents exemples de Bootstrap Carousels sur https://getbootstrap.com/docs/5.1/components/carousel/ .

Remarque : field1 dans $item['field1'] fait référence au champ personnalisé ayant l'ID 1. Donc rien à voir avec l'ordre des champs dans le champ personnalisé de type sous-formulaire. Si nécessaire adaptez le code en fonction de l'ID des Champs Personnalisés sur *votre* site.

Ajout de CSS personnalisé

Les deux lignes suivantes permettent d'injecter notre CSS dans le Head of de la Page :

$doc = Factory::getDocument();
$doc->addStyleDeclaration($carouselCSS); 

Remarque : CSS ne sera injecté qu'une seule fois même si cette mise en page est appelée plusieurs fois sur une page

Utiliser une variable pour le CSS

Écrire du CSS sur plusieurs lignes peut parfois être fastidieux, selon la notation que vous utilisez. Ici nous avons opté pour l'utilisation d'une variable $carouselCSS avec la "syntaxe heredoc". Très utile!

Plus d'informations sur https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc .

Revenir

Un peu avant la fin du fichier, il y a une commande return. Cela peut être pratique dans le sens où tout code ci-dessous sera ignoré. Ainsi, lorsque vous vous souciez de faire des tests, vous pouvez simplement commenter cette ligne afin d'exécuter n'importe quel code de test qui se trouve ci-dessous.

return;

Aucun commentaire