Le blog de Vincent Battaglia

Ouvrir un lien dans une nouvelle fenêtre en quelques lignes de jQuery

J’ai toujours trouvé qu’il était du ressort de l’utilisateur de choisir s’il voulait (ou pas) ouvrir un lien dans une nouvelle fenêtre (ou un nouvel onglet). Jakob Nielsen pense d’ailleurs la même chose et l’a même intégré dans un article où il liste 10 erreurs fréquentes sur un site, article qu’il a publié en… 1999 !

Par contre, je peux totalement comprendre quand on me demande de forcer l’ouverture dans une nouvelle fenêtre. En effet, la plupart du temps, l’utilisateur ne sait pas qu’il a le choix… Ce serait trop bête de le perdre juste parce qu’il a ouvert un lien vers un autre site dans la fenêtre courante ! (je sens que ça va générer un débat)

D’un point de vue programmatique, depuis des années, l’usage pour ouvrir un lien dans une nouvelle fenêtre est d’utiliser l’attribut target="_blank" sur la balise <a>. Malheureusement, et nous le savons tous, cet attribut a été déprécié il y a plusieurs années, en même temps que les cadres (ou frames) et provoque donc une erreur à la validation W3C.

Il a donc fallu ruser et faire appel à JavaScript pour s’en sortir. En utilisant l’attribut onclick sur un lien, on a pu arriver à nos fins de façon non-intrusive :

<a href="http://www.google.com" onclick="window.open(this.href);return false;">Go to Google</a>

Ca fonctionne très bien ! Cependant, ajouter les événements directement sur les éléments n’est pas une pratique très recommandée. Mieux vaut tout placer dans un fichier JavaScript séparé.

Dans ce cas, il faut trouver un moyen de repérer les liens qu’on souhaite ouvrir dans une nouvelle fenêtre. Pour cela, la pratique plus ou moins courante dans le monde des développeurs Web est d’utiliser l’attribut rel="external" sur la balise <a>.

De cette façon, le tour est joué en quelques lignes de jQuery :

$('a[rel="external"]').click(function() {
	window.open($(this).attr('href'));
	return false;
});

Parfait. Cependant, cette technique pose problème dans certains cas…

Premièrement, imaginez que vous ayez un site déjà existant avec des milliers de pages. Vous vous voyez placer l’attribut rel="external" sur tous les liens du site ?

Deuxièmement, si votre site est un CMS, vous ne pouvez pas vous permettre d’imposer à la personne responsable du contenu de placer l’attribut rel="external" sur chacun les liens qu’elle introduirait dans le contenu du site…

Pour parer ces cas particuliers, j’ai donc écrit un petit bout de script en jQuery qui ne nécessite aucune intervention sur le code HTML :

$('a').click(function() {
	var href = $(this).attr('href');
	if (href.indexOf('http://') != -1 || href.indexOf('https://') != -1) {
		var host = href.substr(href.indexOf(':')+3);
		if (host.indexOf('/') != -1) {
			host = host.substring(0, host.indexOf('/'));
		}
		if (host != window.location.host) {
			window.open(href);
			return false;
		}
	}
});

Ce script va systématiquement ouvrir tous les liens vers des sites externes dans une nouvelle fenêtre. Dans un premier temps, on regarde si l’attribut href du lien commence par http:// ou https://. Si ce n’est pas le cas, on sait déjà que c’est un lien interne. Ensuite, on regarde si l’hôte du lien (variable host) est identique à l’hôte de la page courante (window.location.host). Si ce n’est pas le cas, on ouvre le lien dans une nouvelle fenêtre.

C’est aussi simple que ça !

innerText ?

Je vous propose aujourd’hui un bon vieux post technique pour briser cette longue période de silence…

Il y a peu, j’ai eu besoin de récupérer le contenu textuel d’un tag HTML. Concrètement, je voulais lire le texte se trouvant dans un fil d’Ariane, sans récupérer le contenu HTML de ce tag. Pour faire simple, à partir de ceci :

<ol id="breadcrumbs">
	<li><a href="index.php">Accueil</a> &raquo;</li>
	<li><a href="news.php">News</a> &raquo;</li>
	<li>Le point sur la crise financière...</li>
</ol>

Je voulais obtenir quelque chose comme :

Accueil &raquo; News &raquo; Le point sur la crise financière...

En passant, et comme je vous vois déjà venir, je suis au courant que mon markup n’est pas sémantique… Je devrais virer les &raquo; et les remplacer par un background sur le a (ou mieux : utiliser la pseudo-classe CSS :after mais c’est encore loin d’être supporté sur tous nos navigateurs…)

Bref, ce dont j’avais besoin n’était pas quelque chose comme innerHTML (qui m’aurait donné tout le contenu HTML du ol, ce qui inclut les tags li et a) mais plutôt quelque chose comme innerText.

Est-ce que innerText existe ?

Oui et non… A vrai dire, cela fonctionne partout sauf sur les navigateurs dont le moteur de rendu est Gecko, c’est-à-dire Mozilla Firefox, principalement… Gecko fait le même boulot via la propriété textContent et il a raison car c’est le standard W3C.

Pour pouvoir récupérer le contenu textuel d’un tag, de façon cross-browsers, il faut donc tester si textContent existe, l’utiliser si ce test s’avère positif et utiliser innerText dans tous les autres cas.

Comment tester que innerText (ou textContent) existe ?

Simplement en testant sur un élément qui est toujours présent dans un document, l’élément body, par exemple :

var hasInnerText = !!document.getElementsByTagName('body')[0].innerText;

Maintenant, vous pouvez tester et vérifier la valeur de hasInnerText avant d’utiliser innerText ou textContent.

Mais cela me semble un peu laborieux… Pourquoi ne pas créer une fonction qui renverra toujours le bon résultat, quel que soit le navigateur utilisé ? Le top du top est d’ajouter une méthode directement sur Element via le prototypage :

Element.prototype.text = function() {
	return !!document.getElementsByTagName('body')[0].innerText ? this.innerText : this.textContent;
}

Vous pouvez désormais utiliser cette expression, elle donne le résultat espéré :

document.getElementById('breadcrumbs').text();

Cela résout ce problème dont je suis sans doute un des seuls sur terre à m’être posé (quoique). Pendant que vous êtes en train de paniquer à propos de la crise, de votre argent en banque, de vos investissements et du pouvoir d’achat, j’essaye de m’occuper comme je peux…

Détecter la présence de Firebug

« Il est bien connu que Firebug ralentit Gmail, sauf s’il est configuré correctement. »

Voilà le message que vous avez sans doute tous obtenus en ouvrant Gmail dernièrement. Je trouve que le « Il est bien connu » est un peu exagéré (c’est pas vraiment une vérité absolue depuis la nuit des temps non plus) mais c’est vrai : Firebug ralentit les sites où il y a beaucoup de JavaScript et de XMLHttpRequest (AJAX pour les intimes).

On est bien d’accord, Firebug est un outil fantastique dont n’importe quel développeur Web de cette vieille terre ne pourrait plus se passer. D’ailleurs, à la question : « Comment on faisait du debug JavaScript avant Firebug ? », la première réponse qui me vient généralement à l’esprit est : « Avant, on ne faisait pas de JavaScript ! » (de la même manière qu’on n’envoyait pas de SMS avant l’apparition des téléphones portables).

Mais voilà, surfer avec Firefox et Firebug, c’est un peu comme faire les 20 Km de Bruxelles avec un sac à dos rempli de cailloux ! Une solution loin d’être idiote est de faire deux profils : un pour le développement et un pour le surf.

Mais venons-en au vrai sujet de ce billet : détecter la présence de Firebug à partir du browser. Cela pourrait être utile pour prévenir vos visiteurs que s’ils désactivent Firebug, leur expérience utilisateur sera améliorée dans votre application.

Pour cela, la première chose à faire est de vérifier la présence de l’objet console. Mais comme Firebug n’a pas l’exclusivité sur l’objet console (Companion.JS l’implémente sur IE), ce n’est malheureusement pas suffisant ! Heureusement, Firebug ajoute la propriété firebug à l’objet console et c’est ce qui permet de détecter que notre ami Firebug est là :

if (console && "firebug" in console) {
	// afficher un message de mise en garde (ou de félicitations)
}

ou encore :

if (console && console.firebug) {
	// afficher un message de mise en garde (ou de félicitations)
}

Ce n’est pas plus compliqué que ça mais je suis certain que votre vie a changé depuis le moment où vous avez lu ce billet. Bon amusement !

1MD