Développer un plugin, on s'occupe de l'administration, 2e partie

Après avoir vu la partie préférences utilisateurs, voyons maintenant comment appliquer la bibliothèque dans l’administration compte-tenu des réglages effectués.

a11yConfig : accès page d'option, janv. 2020

Petit rappel : il n’y a pas de behaviors permettant d’ajouter du code HTML au moment de la construction générique d’une page de l’administration — ce qui au passage est probablement une bonne chose question sécurité. Par conséquent, il va falloir s’occuper de la mise en place via un script javascript.

Du coup, côté PHP, une petite dose supplémentaire dans le fichier _admin.php, pour ajouter, comme dans la partie publique, le chargement des feuilles de style et des scripts nécessaires :

…
$core->addBehavior('adminPageHTMLHead', ['a11yconfigAdmin', 'adminPageHTMLHead']);
…
class a11yconfigAdmin
{
    public static function adminPageHTMLHead()
    {
        global $core;

        $core->auth->user_prefs->addWorkspace('a11yConfig');
        if ($core->auth->user_prefs->a11yConfig->active) {
            $version = $core->getVersion('a11yConfig');

            $class = '';
            switch ((integer) $core->auth->user_prefs->a11yConfig->icon) {
                case 1:
                    $class = 'a11yc-wc';
                    break;
                case 2:
                    $class = 'a11yc-vd';
                    break;
            }

            $data = [
                // AccessConfig Library data
                'options'  => [
                    'Prefix'           => 'a42-ac',
                    'Modal'            => true,
                    'Font'             => (boolean) $core->auth->user_prefs->a11yConfig->font,
                    'LineSpacing'      => (boolean) $core->auth->user_prefs->a11yConfig->linespacing,
                    'Justification'    => (boolean) $core->auth->user_prefs->a11yConfig->justification,
                    'Contrast'         => (boolean) $core->auth->user_prefs->a11yConfig->contrast,
                    'ImageReplacement' => (boolean) $core->auth->user_prefs->a11yConfig->image
                ],
                // Plugin specific data
                'label'    => $core->auth->user_prefs->a11yConfig->label,
                'class'    => $class,
                'position' => (integer) $core->auth->user_prefs->a11yConfig->position,
                'element'  => (integer) $core->auth->user_prefs->a11yConfig->position === 0 ? 'li' : 'div'
            ];
            echo dcPage::jsJson('a11yc', $data);

            echo
            dcPage::cssLoad(urldecode(dcPage::getPF('a11yConfig/lib/css/accessconfig.min.css')), 'screen', $version) .
            dcPage::cssLoad(urldecode(dcPage::getPF('a11yConfig/css/admin.css')), 'screen', $version) .
            dcPage::jsLoad(urldecode(dcPage::getPF('a11yConfig/js/admin.js')), $version) .
            dcPage::jsLoad(urldecode(dcPage::getPF('a11yConfig/lib/js/accessconfig.min.js')), $version);
        }
    }
…
}

Le principe retenu ici est de fournir tous les réglages au script javascript via un transfert de données de la forme <script type="application/json" id="a11yc-data">…</script>. J’explique de quoi il retourne dans ce billet (en gros ça permet d’éviter d’avoir du javascript inline dans le code de la page). La préparation des données se fait des lignes 14 à 40 et l’écriture proprement dite est faite à la ligne 41.

Ces réglages sont ensuite lus par le script javascript qui en a besoin pour générer le code de la balise qui servira de « support » à la librairie d’Acess42.

Voyons maintenant le fichier admin.js et ce qu’il fait :

/*global getData */
'use strict';

function a11yconfig_option() {
  let data = getData('a11yc');
  let elt = document.createElement(data.element);
  elt.setAttribute('id', 'accessconfig');
  elt.setAttribute('data-accessconfig-buttonname', data.label);
  elt.setAttribute('data-accessconfig-params', JSON.stringify(data.options));
  if (data.class !== '') {
    elt.setAttribute('class', data.class);
  }
  let container = document.querySelector(data.position === 0 ? 'ul#top-info-user' : 'footer');
  container.insertBefore(elt, container.firstChild);
}

document.addEventListener("DOMContentLoaded", function() {
  a11yconfig_option();
});

function a11yconfig_load() {
  const images = document.getElementsByTagName('img');
  for (let i = 0; i < images.length; i++) {
    if (images[i].alt !== '') {
      images[i].classList.add('a42-ac-replace-img');
    }
  }
}

window.addEventListener('load', a11yconfig_load);

Tout d’abord la partie que vous devriez reconnaître, lignes 21 à 30, qui est exactement identique à ce qu’on fait côté public pour ajouter une classe spécifique aux images possédant une alternative textuelle ; je ne vais pas revenir la-dessus.

La fonction a11yconfig_option() se charge quant à elle de récupérer les réglages (ligne 5) et de créer l’élément HTML qui sert de support à la bibliothèque d’Access42 et l’insère soit en en-tête, soit en pied-de-page. Vous noterez que ce traitement ce fait sur l’événement DOMContentLoaded qui est déclenché avant l’événement load utilisé pour les images et la bibliothèque.

Voilà à quoi ça ressemble une fois placé dans l’entête :

a11yConfig : bouton de réglage dans l'entête de l'administration, janv. 2020

Et dans le pied de page[1] :

a11yConfig : bouton de réglage dans le pied de page de l'administration, janv. 2020

Par ailleurs, il a fallu que j’ajoute un certain nombre de règle CSS, dans le fichier admin.css, afin de « corriger » l’aspect de l’administration et des affichages (bouton et dialogue) de la bibliothèque d’Access42 :

/* Dyslexia font */

/* Plugin */

@font-face {
  font-family: 'opendys';
  src: url('index.php?pf=a11yConfig/lib/css/fonts/opendyslexic-regular-webfont.woff2') format('woff2'), url('index.php?pf=a11yConfig/lib/css/fonts/opendyslexic-regular-webfont.woff') format('woff');
  font-weight: normal;
  font-style: normal;
}

body:not(.a42-ac-inv-contrast):not(.a42-ac-high-contrast):not(.a42-ac-text-img) #accessconfig.a11yc-vd button,
body:not(.a42-ac-inv-contrast):not(.a42-ac-high-contrast):not(.a42-ac-text-img) #accessconfig.a11yc-wc button {
  border: none;
  width: 2em;
  height: 2em;
  background-size: 2em 2em;
  overflow: hidden;
  color: transparent;
  background-image: none;
}

body:not(.a42-ac-inv-contrast):not(.a42-ac-high-contrast):not(.a42-ac-text-img) #accessconfig.a11yc-vd button {
  background-image: url(index.php?pf=a11yConfig/img/visual-deficiency.svg), none;
}
body:not(.a42-ac-inv-contrast):not(.a42-ac-high-contrast):not(.a42-ac-text-img) #accessconfig.a11yc-wc button {
  background-image: url(index.php?pf=a11yConfig/img/wheelchair.svg), none;
}

#accessconfig.a11yc-vd button:hover, #accessconfig.a11yc-wc button:hover,
#accessconfig.a11yc-vd button:active, #accessconfig.a11yc-wc button:active {
  cursor: pointer;
}

div#accessconfig {
  display: inline-block;
  float: left;
  margin: 0 .5em 0 0;
}
div#accessconfig button {
  margin: 0 .5em 0 0;
}

/* AccessConfig dialog */

#a42-ac {
  font-size: 1em;
}
#a42-ac h1 {
  text-indent: initial;
}
@media screen and (max-width: 80em) {
  #a42-ac h1 {
    background: initial;
  }
}
#a42-ac legend {
  border: none;
  background: transparent;
}
#a42-ac-close {
  color: initial;
  background: initial;
  filter: none;
}

/* Dotclear admin */

/* Remove background image on some elements (button, …)
 * An issue has been opened about this topic (https://github.com/access42/AccessConfig/issues/2)
 */

body.a42-ac-inv-contrast button,
body.a42-ac-inv-contrast input[type=button],
body.a42-ac-inv-contrast input.button,
body.a42-ac-inv-contrast input[type=reset],
body.a42-ac-inv-contrast input[type=submit],
body.a42-ac-inv-contrast input[type=submit].reset,
body.a42-ac-inv-contrast input.reset,
body.a42-ac-inv-contrast input[type=submit].delete,
body.a42-ac-inv-contrast input.delete,
body.a42-ac-inv-contrast a.button,
body.a42-ac-inv-contrast a.button.delete,
body.a42-ac-inv-contrast a.button.reset,
body.a42-ac-high-contrast button,
body.a42-ac-high-contrast input[type=button],
body.a42-ac-high-contrast input.button,
body.a42-ac-high-contrast input[type=reset],
body.a42-ac-high-contrast input[type=submit],
body.a42-ac-high-contrast input[type=submit].reset,
body.a42-ac-high-contrast input.reset,
body.a42-ac-high-contrast input[type=submit].delete,
body.a42-ac-high-contrast input.delete,
body.a42-ac-high-contrast a.button,
body.a42-ac-high-contrast a.button.delete,
body.a42-ac-high-contrast a.button.reset {
  background-image: none;
}
body.a42-ac-inv-contrast button:hover,
body.a42-ac-inv-contrast input[type=button]:hover,
body.a42-ac-inv-contrast input.button:hover,
body.a42-ac-inv-contrast input[type=reset]:hover,
body.a42-ac-inv-contrast input[type=submit]:hover,
body.a42-ac-inv-contrast input[type=submit].reset:hover,
body.a42-ac-inv-contrast input.reset:hover,
body.a42-ac-inv-contrast input[type=submit].delete:hover,
body.a42-ac-inv-contrast input.delete:hover,
body.a42-ac-inv-contrast a.button:hover,
body.a42-ac-inv-contrast a.button.delete:hover,
body.a42-ac-inv-contrast a.button.reset:hover,
body.a42-ac-high-contrast button:hover,
body.a42-ac-high-contrast input[type=button]:hover,
body.a42-ac-high-contrast input.button:hover,
body.a42-ac-high-contrast input[type=reset]:hover,
body.a42-ac-high-contrast input[type=submit]:hover,
body.a42-ac-high-contrast input[type=submit].reset:hover,
body.a42-ac-high-contrast input.reset:hover,
body.a42-ac-high-contrast input[type=submit].delete:hover,
body.a42-ac-high-contrast input.delete:hover,
body.a42-ac-high-contrast a.button:hover,
body.a42-ac-high-contrast a.button.delete:hover,
body.a42-ac-high-contrast a.button.reset:hover {
  filter: invert(100%);
}
body.a42-ac-inv-contrast button:focus,
body.a42-ac-inv-contrast input[type=button]:focus,
body.a42-ac-inv-contrast input.button:focus,
body.a42-ac-inv-contrast input[type=reset]:focus,
body.a42-ac-inv-contrast input[type=submit]:focus,
body.a42-ac-inv-contrast input[type=submit].reset:focus,
body.a42-ac-inv-contrast input.reset:focus,
body.a42-ac-inv-contrast input[type=submit].delete:focus,
body.a42-ac-inv-contrast input.delete:focus,
body.a42-ac-inv-contrast a.button:focus,
body.a42-ac-inv-contrast a.button.delete:focus,
body.a42-ac-inv-contrast a.button.reset:focus,
body.a42-ac-high-contrast button:focus,
body.a42-ac-high-contrast input[type=button]:focus,
body.a42-ac-high-contrast input.button:focus,
body.a42-ac-high-contrast input[type=reset]:focus,
body.a42-ac-high-contrast input[type=submit]:focus,
body.a42-ac-high-contrast input[type=submit].reset:focus,
body.a42-ac-high-contrast input.reset:focus,
body.a42-ac-high-contrast input[type=submit].delete:focus,
body.a42-ac-high-contrast input.delete:focus,
body.a42-ac-high-contrast a.button:focus,
body.a42-ac-high-contrast a.button.delete:focus,
body.a42-ac-high-contrast a.button.reset:focus {
  filter: invert(100%);
}

/* Editors */

/* Legacy Editor */
body.a42-ac-inv-contrast .jstElements button,
body.a42-ac-high-contrast .jstElements button {
  width: inherit;
}

body.a42-ac-inv-contrast .jstElements button span,
body.a42-ac-high-contrast .jstElements button span {
  display: inline-block;
}

/* Cancel contrast CSS for CKEditor as it makes it unusable */
body.a42-ac-inv-contrast .cke *,
body.a42-ac-high-contrast .cke * {
  color:  initial !important;
  background-color: initial !important;
}

/* 3rd party plugins */

/* cloneEntry */
body.a42-ac-inv-contrast input[type=submit].clone,
body.a42-ac-high-contrast input[type=submit].clone {
  background-image: none;
}

/* dcRevisions */
body.a42-ac-inv-contrast .button.add,
body.a42-ac-high-contrast .button.add {
  background-image: none;
}

Je sais, c’est assez « copieux », en particulier du côté de la gestion des fonds des boutons utilisés dans l’administration de Dotclear en fonction des états (hover, focus, …) ; et vous noterez que dans le cas où le renforcement ou l’inversion de contraste est actif je fais en sorte de supprimer les images de fond et j’affiche si nécessaire les textes de remplacement. D’autre part, le changement de fond au survol est remplacé par une simple inversion.

Par ailleurs, j’ai fait en sorte que ça ne s’applique pas du tout à l’éditeur CKEditor ; il faudrait probablement une extension particulière pour cet éditeur ou un traitement spécifique ; j’ai aussi prévu le cas de deux des plugins que j’ai développés et/ou que je maintiens et qui ajoutent des boutons particuliers dans l’administration (cloneEntry et dcRevisions), il y en a peut-être d’autres qui nécessiteront des règles dédiées.

D’ailleurs, il est très possible que ce ne soit pas tout à fait complet, côté CSS, d’une part, et d’autre part, il est probable qu’il serait avantageux d’utiliser un pré-processeur CSS pour gérer les règles. Je ferai peut-être un billet sur l’usage de SASS appliqué à ce développement de plugin[2]

On arrête là pour aujourd’hui, avec un plugin fonctionnel à la fois côté public et côté administration, c’est déjà pas mal !

Notes

[1] Pas certain que ce soit une bonne idée ce placement, finalement.

[2] Compass n’est probablement pas nécessaire ici.

Ajouter un commentaire

Comments can be formatted using the Markdown syntax.

Ajouter un rétrolien

URL de rétrolien : https://open-time.net/trackback/14449

Haut de page