Développer un plugin pour WordPress
6 – Du côté front-end
18 octobre 2016.
Préliminaires, encore et toujours…
Que voici le moment tant attendu d’en venir enfin à la partie publique de notre extension.
Je l’ai déjà évoqué à plusieurs reprises : pour préserver la structure html, nous nous sommes contraints jusqu’ici à n’ajouter aux éléments qui la composent que des attributs data spécifiques, lesquels n’affectent en rien le rendu final de la page par WordPress. La seule modification concrète que nous nous sommes autorisés a été d’effectuer l’isolation de l’image dans un paragraphe indépendant quand cela était nécessaire.
Pour mettre en place la structure à même de répondre à nos besoins, nous allons mettre à profit le hook the_content de sieur WordPress. Ce filtre nous permet d’opérer des modifications au sein du contenu du post tel qu’il nous est délivré par la base de données avant que de procéder à son affichage.
Comme nous l’avons vu déjà en maintes occasions, l’opération d’accrochage se déroule en deux temps :
- Nous commençons en déclarant une fonction judicieusement nommée content_filtering dans le fichier public/class-text-floating-image-public.php :
57 58 59 60 61 62 63 64 65 66 67 68 69 |
/** * Modification de la structure html du contenu du post * pour y introduire les éléments nécessaires au bon fonctionnement * de notre extension * La fonction content_filtering est appelée sur le hook the_content * depuis la fonction define_public_hooks() de includes/class-text-floating-image.php * @since 1.0.0 */ public function content_filtering($content) { return $content; } |
- Nous nous rendons ensuite sans traîner dans la fonction define_public_hooks() du fichier admin/includes/class-text-floatin-image.php pour y supprimer les lignes d’appel aux fonctions enqueue_styles et enqueue_scripts qui ne nous seront au final d’aucune utilité et y rajouter l’appel à notre fonction content_filtering sur le hook the_content :
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
/** * Register all of the hooks related to the public-facing functionality * of the plugin. * * @since 1.0.0 * @access private */ private function define_public_hooks() { $plugin_public = new Text_Floating_Image_Public( $this->get_plugin_name(), $this->get_version() ); /** * Mise en place de la structure html nécessaire à la mécanique de notre extension * La fonction content_filtering réside dans la classe * public/class-text-floating-image-public.php. */ $this->loader->add_filter('the_content', $plugin_public, 'content_filtering'); } |
Et tiens ! Puisque nous en sommes à faire du ménage, nous pouvons sans plus barguigner supprimer :
- le dossier public/css.
- le dossier public/partials.
- les fonctions enqueue_styles() et enqueue_scripts() du fichier public/class-text-floating-image-public.php.
La classe DOMDocument à la rescousse
Pour traiter le contenu html de notre page alors que nous travaillons en php, il nous faut en passer par la classe DOMDocument dont nous créons derechef une instance. Nous y importons le contenu de la page. Nous y effectuons toutes les aménagements nécessaires et, pour finir, nous en renvoyons le contenu à la machine WordPress.
Voilà pour le principe général.
Et voici pour la mise en pratique :
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
/** * Modification de la structure html du contenu du post * pour y introduire les éléments nécessaires au bon fonctionnement * de notre extension * La fonction content_filtering est appelée sur le hook the_content * depuis la fonction define_public_hooks() de includes/class-text-floating-image.php * @since 1.0.0 */ public function content_filtering($content) { //http://php.net/manual/fr/class.domdocument.php $dom = new DOMDocument(); //http://php.net/manual/fr/domdocument.loadhtml.php //Voir en paticulier la première contribution pour une explication //quant à 'xml encoding="utf-8"' $dom->loadHTML('<?xml encoding="utf-8" ?>'.$content);//Pour conserver le bon encodage //http://php.net/manual/fr/class.domxpath.php $xpathImg = new DOMXPath($dom); //$images est un tableau référençant toutes les images //portant notre attribut 'data-text_floating_image' //et qui doivent donc passer à notre moulinette $images = $xpathImg->query('//img[@data-text_floating_image]'); //Si nous avons des images if ($images->length > 0) { //on veille à inclure notre script wp_enqueue_script( $this->plugin_name, plugin_dir_url( __FILE__ ).'js/text-floating-image-public.js', array('jquery'), $this->version, true ); $options = get_option($this->plugin_name); $pageWidth = $options['pageWidth'];//Largeur de la page en pourcentage $pageMaxWidth = $options['pageMaxWidth'];//max-width de la page en pixels //Vaut 0 si elle n'est pas spécifiée //Pour chacune des images foreach($images as $image) { //Récupération de la valeur de la propriété width //pour définir l'attribut sizes de l'image //ainsi que de la propriété clear pour la transmettre à $elastik //En décomposant la chaine data-text_floating_image-style //à partir des points-virgules (;), //on obtient un array reprenant les différentes propriétés de style //de la forme : // [0] => width:45% // [1] => clear:right // [2] => float:right // [3] => margin-left:20px // [4] => margin-right:0px $tab = explode(';', $image->getAttribute('data-text_floating_image-style')); //On décompose le premier élément de ce tableau //à partir des deux points (:) pour obtenir : // [0] => width // [1] => 45% //On a donc : $width = explode(':', $tab[0])[1];//45% $width = intval($width);//45 //On décompose le deuxième élément de ce tableau //à partir des deux points (:) pour obtenir : // [0] => clear // [1] => right //On a donc : $align = explode(':', $tab[1])[1];//right //Supression des classes de l'image $image->removeAttribute('class'); //Définition de l'attribut sizes de l'image $this->setSizes($image,$width,$pageWidth,$pageMaxWidth); //On force l'image à occuper toute la largeur de son container $image->setAttribute('style','width:100%;height:auto;'); //On recherche tous les éléments pour lesquels //l'attribut 'data-text_floating_image' a la même valeur //que celui de notre image $xpathTexts = new DOMXPath($dom); $texts = $xpathTexts->query('//*[@data-text_floating_image="'.$image->getAttribute('data-text_floating_image').'"]'); //L'élément 0 de ce tableau est l'image elle même //L'élément 1 est le premier bloc de texte qui suit l'image //On force son padding-top à 0. Sa margin-top sera gérée par javascript. $texts->item(1)->setAttribute('style',"padding-top:0;"); //On sélectionne le dernier bloc de texte $last = $texts->item($texts->length - 1); //On force ses padding-bottom et margin-bottom à 0. $last->setAttribute('style', "margin-bottom:0;padding-bottom:0;"); //On stocke une référence au container du post pour les futures insertions $main = $last->parentNode; //On crée un élément $after $after = $dom->createElement('div'); $style = "clear:both;"; //Le cas échéant, on lui donne la hauteur enregistrée dans l'attribut //data-text_floating_image-after de l'image if ($image->getAttribute('data-text_floating_image-after')) { $style .= ("height:".$image->getAttribute('data-text_floating_image-after')."px;"); $image->removeAttribute('data-text_floating_image-after'); } //On passe le style adéquat à $after $after->setAttribute('style', $style); //Et on l'insère avant l'élément qui suit le dernier bloc de texte $main->insertBefore($after,$last->nextSibling); //S'il s'agit d'une prévisualisation //nous n'avons plus rien à faire puisque les éléments //$elastik et $preElastik figurent déjà dans la structure html //Sinon, il nous faut les construire if (!is_preview()) { //On recherche le paragraphe contenant l'image $parent = $image->parentNode; //Si l'image est contenue dans une ancre //$image->parentNode renvoie cette ancre //Il nous faut donc persévérer dans notre quête while ($parent->tagName != 'p') { $parent = $parent->parentNode; } //Attribution du style adéquat au paragraphe contenant l'image $parent->setAttribute('style', $image->getAttribute('data-text_floating_image-style')); //Création et insertion de l'élément elastik $elastik = $dom->createElement('div'); $elastik->setAttribute('style', "float:".$align.";"); $main->insertBefore($elastik,$parent); //Création et insertion de l'élément preElastik $preElastik = $dom->createElement('div'); $preElastik->setAttribute('style', "clear:both"); $main->insertBefore($preElastik,$elastik); } //Nettoyage des attributs de l'image $image->removeAttribute('data-text_floating_image-style'); } //http://php.net/manual/fr/domdocument.savehtml.php //Voir en paticulier la première contribution pour une explication //quant à 'preg_replace' $html = preg_replace('/^<!DOCTYPE.+?>/', '', str_replace( array('<?xml encoding="utf-8" ?>','<html>', '</html>', '<body>', '</body>'), array('', '', '', '', ''), $dom->saveHTML())); return $html; //S'il n'y a pas d'image à traiter, on retourne le contenu originel } else return $content; } /** * Ajoute l'attribut sizes à l'image passée en paramètre * Pour une image dont width est posée à 45% * dans une page dont width vaut 90% et max-width:1000px, * on obtient : * sizes="(max-width: 1111px) 40.5vw, 450px" * Autrement dit : si la largeur du viewport est inférieure à 1111px * (la largeur de la page est alors inférieure à 90% de 1111px = 1000px) * alors la largeur de l'image vaut 90% * 45% = 40,5% de la largeur du viewport; * sinon elle vaut 45% * 1000px = 450px * * Pour une image dont width est posée à 45% * dans une page dont width vaut 90% sans limitation de max-width, * on obtient : * sizes="40.5vw" * (90% * 45% = 40,5% de la largeur du viewport) * * * @since 1.0.0 * @param $image élément HTMl L'image à traiter. * @param $width integer La largeur de l'image en pourcentage * @param $pageWidth integer La largeur de la page en pourcentage * @param $pageMaxWidth integer La largeur max de la page en pixels */ public function setSizes($image,$width,$pageWidth,$pageMaxWidth) { if ($pageMaxWidth > 0) { $maxWidth = round(($pageMaxWidth / $pageWidth * 100))."px"; $theWidth = round(($pageWidth * $width / 100),2)."vw, "; $defaultWidth = round($width * $pageMaxWidth / 100)."px"; $image->setAttribute('sizes','(max-width: '.$maxWidth.') '.$theWidth.$defaultWidth); } else { $image->setAttribute('sizes',($width * $pageWidth / 100)."vw"); } } |
Et du javascript pour mettre de l’huile
Ne reste plus alors qu’à introduire un peu de javascript pour parachever la chose…
Vous noterez cependant que le fichier public/js/text-floating-image-public.js n’est chargé que lorsque la moulinette que nous avons mise en place sur le filtre the_content a détecté des images à traiter.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
;(function($) { 'use strict'; $(window).load(function() { //On recherche toutes les images portant l'attribut 'data-text_floating_image' var $imgs = $('img[data-text_floating_image]'); //Notre plugin jQuery dispatch sur $(window) un événement textFloatingImagePreInit //lorsqu'il a achevé son initialisation. //On pose un écouteur sur cet événement //On initialise un compteur var compteur = 0; function textFloatingImagePreInit() { //On incrémente ce compteur à chaque événement 'textFloatingImagePreInit' compteur ++; //Si toutes les images ont été traitées if (compteur == $imgs.length) { //On enlève notre écouteur $(window).off('textFloatingImagePreInit',textFloatingImagePreInit); //On dispatch un événement 'textFloatingImageInit' sur $(window) $(window).trigger('textFloatingImageInit'); } } $(window).on('textFloatingImagePreInit',textFloatingImagePreInit); //On applique notre plugin jQuery aux images à traiter $imgs.textFloatingImage(); }); })(jQuery); (function($) { 'use strict'; var pluginName = 'textFloatingImage'; function Plugin(image) { var $img = $(image), $elastik, $text, $firstText; function init() { var id = $img.data('text_floating_image'); //Recherche des blocs de texte associés $text = $('[data-text_floating_image=' + id + ']').not('img'); //Initialisation de $firstText qui pointe sur le premier des blocs de texte $firstText = $($text[0]); //Initialisation de l'élément $elastik //L'élément précédent $firstText est le bloc contenant l'image //lequel est llui même précédé par $elastik $elastik = $firstText.prev().prev(); //On appelle la fonction resize resize(); //fonction qu'on pose par ailleurs en réponse à l'événement resize de la fenêtre //et on dispatch l'événement 'textFloatingImagePreInit' $(window) .trigger('textFloatingImagePreInit') .resize(resize); } function resize() { //Reset des éléments $elastik.height(0); $firstText.css('marginTop', 0); //Calcul des la hauteur des blocs de texte var h = getTextHeight(); var imgHeight = $img.height(); //Positionnement des éléments if (imgHeight > h) { $firstText.css('marginTop', (imgHeight - h) / 2); } else { $elastik.height((h - imgHeight) / 2); } } function getTextHeight() { var h = 0; $text.each(function(){ h += $(this).outerHeight(true); }); return h; } init(); } $.fn[pluginName] = function() { return this.each(function() { if (!$.data(this, 'plugin_' + pluginName)) { $.data(this, 'plugin_' + pluginName, new Plugin(this)); } }); }; })(jQuery); |
Conclusion du moment
Nous voilà enfin face à une mécanique parfaitement fonctionnelle. Nous pourrions d’ailleurs nous arrêter là, négligeant les finitions à la manière de ceux qui se sont trop longtemps épuisés à restaurer leur chaumière et repoussent encore et toujours cette foutue dernière couche de peinture.
Mais il me reste une promesse à tenir dont certains se souviennent sans doute : l’internationalisation de l’ensemble. Elle est d’autant plus nécessaire que, dans l’état actuel et pas brillant des choses, nous disposons d’une page d’options rédigée dans la langue de Shakespeare alors que notre pop-up de paramétrage s’exprime lui dans la langue de Molière. Voilà bien un désordre intolérable auquel il nous faut remédier…
Mission que nous tenterons de mener à bien dans le prochain chapitre…
(Si c’est pas du cliffhanger, ça, coco !)