Manipulation des liens extérieurs et les popup pour améliorer l'Accessibilité

Tags

En rédigeant Accessibilité et les liens externes j'ai réalisé qu'il serait mieux que je sépare l’implémentation du raisonnement sur le besoin.

Cet article décrit une méthode programmatique pour transformer tout les liens d'une page qui vont à l'extérieur du site courrant en ajoutant un icône approprié et la note disant qu'une fenêtre s'ouvrira.

Les méthodes généralement utilisés

Actuellement, il existe plusieurs manières de faire. Sauf qu'elles demandent souvent de le faire à la main pour chaque lien et ca peut être lourd.

Surtout pour la rédaction qui se fout un peu de faire du HTML propre

Les méthodes qui suit font bel et bien le travail mais elles ont leurs faiblesses. Aussi elles peuvent devenir rapidement hors de contrôle. Beaucoup d’attributs.

Renoir de : Remise en contexte

Cet article a été écrit en 2009 et à l'époque, la majorité des gens n’utilisaient que Microsoft Internet Explorer et il n’y avait pas des onglets. Il s’agissait d’une fenêtre par site. Nous pouvions avoir une dizaines de fenêtres. Les onglets existaient en 2009, mais il fallait utiliser Mozilla Firefox, ou Opera.

Dans les exemples suivants, imaginons que nous sommes sur une autre page sur un autre nom de domaine que http://example.org, et que nous voulons ouvrir une fenêtre pour les liens extérieurs, comme example.org.

Voici une liste (non exhaustive) de techniques;

Méthode idéale

L'idéal. Simple. Rien a faire.

<a href="http://example.org">
  Texte en exemple
</a>

Méthode target

<a href="http://example.org" target="_blank">
  Texte en exemple
</a>

Mais le problème c'est que l'attribut [target] pour cet usage est obselete. Donc nous de devrions pas l'utiliser.

Méthode avec fonction window.open

<a href="javascript:window.open('http://example.org','mywindow','width=400,height=200')">
  Texte en exemple
</a>

Ceci fonctionnerait bien. Mais du point de vue SEO durant l’indexation crawling, nous compliquons la tâche et ne devrait pas etre utilisé car l'attribut [href] est fait pour avoir le URL, pas une chaîne de caractère a exécuter.

Elle n'est pas recommandée parceque la balise est altérée de son usage natuel: faire un lien et que l'attribut [href] ne devrait servir qu’a déclarer la relation, et séparer l’implémentaiton pour obtenir un comportement.

Méthode onClick

Remise en contexte

En 2009, utiliser addEventListener était encore pas si bien documenté. L'habitude était d'utiliser la vieille technique qui exploitait la nature XML du HTML. D'utiliser un attribut XML, d'y insérer une chaîne de caractères, et de le faire exécuter. Étant donné que XML et les attributs sont seulement majuscules ou minuscules et qu'il n'y a pas de tiret comme nous pouvons le faire avec les [data] attributes (e.g. [data-open="true"], et l'obtenir via dataset.open) aujourd'hui.

Donc [onclick] était utilisé, il était impossible d'avoir plus d’un [onclick] parce que XML. Pour lire sur l'histoire

<a href="http://example.org/" onclick="window.open('http://example.org/','mywindow','width=400,height=200')">
  Texte en exemple
</a>

Est une bonne méthode mais pas facile a expliquer pour l'équipe de rédaction.


Règles pour les liens extérieurs

  • Une balise html doit être utilisée pour son usage
  • Le markup doit être le plus simple possible pour éviter les erreurs d'entrée de l'édimestre.
  • On ne peut pas utiliser le CSS pour ajouter les icones Note: Parceque les Lecteurs d'écran (aveugles) "lisent" le texte et non pas ce qui a été modifié via CSS.
  • On ne peut pas utiliser de Javascript invalide Note: Si on y va via le DOM, avec les manipulations valide, ca passe bien: bref on évite innerHTML et on se fie aux Spécifications du DOM et de WCAG 1 qui recommande d’éviter de modifier.

D'autres règles pourraient être mentionnées. Mais le message peut bien se résumer ainsi.

En HTML, on doit utiliser les balises pour l'usage qu'elles sont destinés à faire.

... il ne reste pas tant d'options. La méthode que j’ai élaboré répond a toutes ces conditions.

Les autres choses que le lien doit faire

Dans les lignes guides de l'accessibilité du web (et en ergonomie/utilisabilité) on mentionne

Qu'on doit avoir un indice qu'un lien est destiné à ouvrir une page extérieure et/ou un popup

Source: ma mémoire... j'ai pas trouvé de lien référence.

ça veut donc dire qu'en plus de faire le lien en tant que tel, il faut ajouter une image dans le lien qui va annoncer le popup... Ouf!

Ce qu'il faudrait...

En gros, il faudrait, que pour chaque lien... (Renoir de : Il y en a probablement beaucoup aujourd’hui!)

  • Avertir qu'il y a un lien externe dans un icône image ajouté automatiquement
  • Placer l'icone avec la bonne balise et ne pas utiliser le CSS.
  • Avoir un alt="" pour l'image (icône) car sinon c'est du html invalide en plus qu'il servirait a avertir
  • Que le alt="" soit dans la langue utilisée dans le site
  • Que le lien soit utilisable SANS JAVASCRIPT donc, on oublie le href="javascript..." Note: C'est une approche qui veut que l'on puise "dégrader de façon élégante" le code dans la "pire des situations". Ce qu’on surnomme "Graceful Degradation" dans le métier.

la Manière idéale

Il s'agit d'une méthode que j'ai élaborée avec un ancien collègue que l'on surnomme (alias) Reynold Kirby pour être valide avec toutes les règles ci-haut mentionnés.

  • Lister tout les liens externes qui commencent par http://;
  • Éliminer de la liste les domaines qu'on ne veut pas qui se fassent transformer;
  • Créer, via le DOM, une image AVEC le bon attribut [alt];
  • Ajouter un attribut [rel="popup"] pour le handler de popup;
  • Ajouter, finalement, une fonction qui réagit et gère à l’activation (souris)

L’attrape

On va utiliser le [onclick="..."], mais généré automatiquement via Javascript. Car de toute façon il est ridicule de s'interdire d'utiliser JavaScript. Dans ce contexte, ce serait donc acceptable. Et toujours mieux que de faire [target="_blank"] et/ou de demander aux édimestres de faire chaque lien à bras (!!)

Remise en contexte

En 2009, la très grande majorité du HTML et CSS ("FrontEnd") était des chaînes de caractères générés par un langage tel que PHP ou Java, Python ou Perl. C'était l’âge d’or de jQuery, et nous nous passions des morceaux de code JavaScript alignés avec peu de rigeur. Tout a chagé avec Node.js, Chromium, et ECMAScript 6 en 2015 bien sûr.

Les possibilités

Seulement un popup

<a href="http://example.org" rel="popup">...</a>

Popup de 800 par 600 pixels de grand

<a href="http://example.org" rel="popup standard 800 600">...</a>

Popup de 800 par 600 pixels de grand avec une console

<a href="http://example.org" rel="popup console 800 600">...</a>

Popup plein écran

<a href="http://example.org" rel="popup fullscreen">...</a>

Le code

Simplement ajouter ce bout de code Javascript aux autres scripts du site.

var ADDED_ICON_ALT_TEXT = 'Lien sur un site externe qui va ouvrir dans une nouvelle fenetre.'
var ADDED_ICON_IMG = '/icones/extlink.gif'

/* Initialisation */
addImageAtExternalLinks(document)


// Le reste -------
function addImageAtExternalLinks(d) {
  var anchorList = d.getElementsByTagName('a')
  for (var i = 0; i < anchorList.length; i++) {
    /**
     * Remarquez les indexOf()... remplacez renoirboulanger
     * par votre propre nom de domaine... addthis a été ajouté
     * au cas ou vous avez un service du genre.
     *
     * 2024: au lieu d'utiliser "renoirboulanger" dans le grand
     * if imbriqué, le `window.location.href` éviterait complètement
     * ce commentaire ici.
     **/
    if (
      anchorList[i].href !== '' &&
      !anchorList[i].href.includes('renoirboulanger') &&
      !anchorList[i].href.includes('javascript') &&
      !anchorList[i].href.includes('addthis')
    ) {
      var elContainer = anchorList[i]
      var createdEl = d.createElement('img')
      createdEl.setAttribute('alt', ADDED_ICON_ALT_TEXT)
      createdEl.setAtrribute('src', ADDED_ICON_IMG)
      elContainer.appendChild(createdEl)
      elContainer.setAttribute('rel', 'popup')
      // elContainer.onclick = handlePopupOpen
      // AVANT: La méthode ci-haut ^ etait ce que nous faisions
      // MAINTENANT (2015), c’est mieux d’utiliser addEventListener
      elContainer.addEventListener('click', handlePopupOpen)
    }
  }
}

/**
 * Gestionnaire d'evenement pour ouvrir popup.
 *
 * Permet de creer un popup en faisant un lien seulement avec attribut
 * href, et d’adapter chacun de facon configurable.
 *
 * Version révisée en 2024 par Renoir Boulanger lors de la refonte de son site et de voir comment
 * ce code était ravagé durant toutes ces années.
 *
 * @author Renoir Boulanger <hello@renoirboulanger.com>
 * @author Reynold Kirby (alias) <reynold.kirby@gmail.com>
 **/
function handlePopupOpen(e) {
  e.stopPropagation()
  e.preventDefault()
  // Source: http://www.accessify.com/features/tutorials/the-perfect-popup/
  // set defaults - if nothing in rel attrib, these will be used
  var t = 'standard'
  var w = null
  var h = null


  // var that = this
  // AVANT: Lorsqu'on utilise .onlcick, le "this" était disponible.
  // MAINTENANT (2015), c’est mieux d’utiliser addEventListener et currentTarget
  var that = e.currentTarget

  // look for parameters
  var attribs = that.rel.split(' ')
  if (attribs[1] != null) {
    t = attribs[1]
  }
  if (attribs[2] != null) {
    w = attribs[2]
  }
  if (attribs[3] != null) {
    h = attribs[3]
  }

  // call the popup script
  popUpWin(that.href, t, w, h)
}

function popUpWin(url, type, strWidth, strHeight) {
  closeWin()
  // calls function to close pop-up if already open,
  // to ensure it's re-opened every time, retainining focus
  type = type.toLowerCase()
  if (type === 'fullscreen') {
    strWidth = screen.availWidth
    strHeight = screen.availHeight
  }
  var tools = ''
  if (type === 'standard')
    tools =
      'resizable,toolbar=yes,location=yes,scrollbars=yes,menubar=yes,width=' +
      strWidth +
      ',height=' +
      strHeight +
      ',top=0,left=0'
  if (type === 'console' || type === 'fullscreen')
    tools =
      'resizable,toolbar=no,location=no,scrollbars=no,width=' +
      strWidth +
      ',height=' +
      strHeight +
      ',left=0,top=0'
  newWindow = window.open(url, 'newWin', tools)
  newWindow.focus()
}

var newWindow = null

function closeWin() {
  if (newWindow !== null) {
    if (!newWindow.closed) newWindow.close()
  }
}
const strPopUpWarning = 'Lien sur un site externe qui va ouvrir dans une nouvelle fenetre.'

function addImageAtExternalLinks() {
  const anchorList = document.getElementsByTagName('a')
  for (let i = 0; i < anchorList.length; i++) {
    /**
     * Remarquez les indexOf()... remplacez renoirboulanger
     * par votre propre nom de domaine... addthis a été ajouté
     * au cas ou vous avez un service du genre
     **/
    if (
      anchorList[i].href != '' &&
      !anchorList[i].href.includes('renoirboulanger') &&
      !anchorList[i].href.includes('javascript') &&
      !anchorList[i].href.includes('addthis')
    ) {
      const elContainer = anchorList[i]
      const createdEl = document.createElement('img')
      createdEl.alt = strPopUpWarning
      createdEl.src = '/icones/extlink.gif'
      elContainer.setAttribute('rel', 'popup')
      elContainer.appendChild(createdEl)
      elContainer.onclick = dopopup
    }
  }
}

/* Ensuite... l'appel */
addImageAtExternalLinks()

/**
 * Fonctions pour les popup
 *
 * Permet de creer un popup en faisant un lien seulement avec attribut
 * href, et d’adapter chacun de facon configurable.
 *
 * @author Renoir Boulanger <lunique @renoirboulanger.com>
 * @author Reynold Kirby (alias) <reynold .kirby@gmail.com>
 **/
function dopopup(e) {
  // Source: http://www.accessify.com/features/tutorials/the-perfect-popup/
  // set defaults - if nothing in rel attrib, these will be used
  let t = 'standard'
  let w = null
  let h = null

  // look for parameters
  attribs = this.rel.split(' ')
  if (attribs[1] != null) {
    t = attribs[1]
  }
  if (attribs[2] != null) {
    w = attribs[2]
  }
  if (attribs[3] != null) {
    h = attribs[3]
  }

  // call the popup script
  popUpWin(this.href, t, w, h)

  // cancel the default link action if pop-up activated
  if (window.event) {
    window.event.returnValue = false
    window.event.cancelBubble = true
  } else if (e) {
    e.stopPropagation()
    e.preventDefault()
  }
}

function popUpWin(url, type, strWidth, strHeight) {
  closeWin()
  // calls function to close pop-up if already open,
  // to ensure it's re-opened every time, retainining focus
  type = type.toLowerCase()
  if (type == 'fullscreen') {
    strWidth = screen.availWidth
    strHeight = screen.availHeight
  }
  let tools = ''
  if (type == 'standard')
    tools =
      'resizable,toolbar=yes,location=yes,scrollbars=yes,menubar=yes,width=' +
      strWidth +
      ',height=' +
      strHeight +
      ',top=0,left=0'
  if (type == 'console' || type == 'fullscreen')
    tools =
      'resizable,toolbar=no,location=no,scrollbars=no,width=' +
      strWidth +
      ',height=' +
      strHeight +
      ',left=0,top=0'
  newWindow = window.open(url, 'newWin', tools)
  newWindow.focus()
}
var newWindow = null

function closeWin() {
  if (newWindow != null) {
    if (!newWindow.closed) newWindow.close()
  }
}