Contenu principal
enqueue javascript at mid-page

Ajouter un Javascript en milieu de page avec WordPress

WordPress 3.3 est arrivé avec une nouvelle possibilité, celle de pouvoir « enqueue » un fichier javascript en milieu de page pour qu’il soit imprimé en fin de page. En quoi cette possibilité est bonne et pourquoi l’utiliser?

Ha bon ?

Je suis étonné de voir que cette nouvelle possibilité que nous apporte WordPress 3.3 n’ai pas fait plus de bruit que ça. Je l’ai bien vue parmi les changements importants dans le changelog de WP 3.3 mais c’est tout, pas d’article relatif sur un quelconque blog. Peut-être ai-je mal fouillé? Fort possible.

Jusque là nous pouvions « enqueue » les scripts à l’init, et indiquer si le script devait apparaitre dans le head ou le footer. Une fois le head passé, terminé, plus moyen d’ajouter un script proprement, il fallait passer par une technique « un peu bricolage » (en jouant avec la variable globale $wp_scripts) pour pouvoir dire en milieu de page, d’ajouter un script dans le footer.
Là c’est terminé, plus besoin de bricoler, la fonction wp_enqueue_script( ) peut être appelée à n’importe quel moment (enfin, tant que le footer n’est pas passé bien sûr). Comme je le disais en introduction, si l’on appelle wp_enqueue_script( ) après le head, le script sera imprimé dans le footer.

Mais encore

Donc que nous apporte cette possibilité?
Nous avons déjà tous eu affaire avec des plugins qui ajoutent leur(s) script(s) partout sur notre site, sur toutes les pages, même quand il n’est pas nécessaire, voire même dans l’administration.
Donc tous ceux soucieux du temps de chargement de leurs pages on certainement eu à utiliser un code similaire à celui-ci :

12345

add_action( 'wp_print_scripts', 'remove_annoying_scripts' );
remove_annoying_scripts() {
	if ( !is_page('contact') )
		wp_deregister_script( 'contact-form-7' );
}

Mais si demain ma page ne s’appelle plus contact mais contactez-moi? Retour dans le code pour modification. Et encore, si le site est destiné à un client, celui-ci n’est pas forcément au courant que renommer une page (ou son url) peut avoir des conséquences, et ne se rendra même pas compte que le javascript n’est plus disponible (d’ailleurs, sait-il au moins ce qu’est javascript? ;)). Au pays des bisounours il ne devrait même pas s’en soucier (rhôôôô).
A noter que je prend ContactForm7 comme exemple parce qu’il est très employé, je n’ai rien contre lui hein ;)

Bon, et donc, on fait quoi?
En premier c’est aux développeurs de plugins à utiliser cette nouvelle possibilité : ne plus employer wp_enqueue_script( ) sur le hook « init » lorsque ce n’est pas nécessaire, mais l’employer directement dans le code qui sera imprimé sur la page (un formulaire de contact par exemple, ou une galerie d’images, un diaporama, etc). Ainsi, le javascript ne sera imprimé que lorsqu’il est nécessaire.

Et pour ceux qui utilisent ces plugins qui mettent du javascript partout?
Là ça demande un peu de réflexion pour trouver le bon hook. Un exemple concret? J’en ai un avec ContactForm7 justement (ça tombe bien hein? ;))
En premier il faut empêcher le javascript d’être imprimé sur le site, mais sans se préoccuper de la page affichée cette fois.

12345

add_action( 'wp_print_scripts', 'remove_annoying_scripts' );
remove_annoying_scripts() {
	if (function_exists('wpcf7_enqueue_scripts'))
		wp_dequeue_script( 'contact-form-7' );
}

J’ai rajouté un test pour vérifier que le plugin est bien installé, mais ce n’est pas obligatoire. En l’état, aucun script de ce plugin ne sera imprimé.
A noter que j’ai utilisé wp_dequeue_script( ) qui ne va pas supprimer le script (disponible depuis WordPress 3.1), mais juste le laisser dans la file d’attente, en lui disant de rester au chaud pour une utilisation future (ou pas).

Phase deux, lancer wp_enqueue_script( ) seulement quand le formulaire est affiché :
Là je vais détourner un hook disponible dans le plugin, il s’agit du filtre « wpcf7_form_class_attr » qui permet d’ajouter ou de modifier les classes CSS du formulaire. Comme le filtre est utilisé au moment où le formulaire est affiché, c’est notre ouverture!

12345

add_filter('wpcf7_form_class_attr', 'cf7js_needed');
function cf7js_needed($class) {
	wp_enqueue_script( 'contact-form-7' );
	return $class;
}

Comme il s’agit d’un filtre, il y a forcément une valeur qui rentre, et une qui doit sortir. D’où la variable $class en paramètre, que l’on retourne à la fin pour ne pas que le formulaire se retrouve sans classe CSS.

Voilà, c’est tout!
Comme toujours, si on trouve les bons hooks, ça glisse tout seul :)

Hey, je suis pas passé à la version 3.3, c’est mort pour moi ?

Première réaction : Quoi!? Qu’est-ce que t’attends !?
Deuxième réaction : Bien sûr que non, je me suis penché sur la question.
Dans la même veine que mon article La nouvelle aide contextuelle de WordPress 3.3, j’ai développé une fonction (2 en fait) qui marchera à la fois pour WP 3.3+ (en utilisant tout simplement wp_enqueue_script( ) normalement) et pour les versions antérieures. Cet outil sera pratique à la fois pour un utilisateur de WP < 3.3 et pour des développeurs.

Je ne vais pas détailler tout ceci mais voici en gros ce qui va se passer pour WP < 3.3 :
En début de billet je vous parlais d'une méthode un peu bricolage qui consistait à jouer avec la variable $wp_scripts, qui contient toutes les références des scripts enregistrés, qu'ils soient imprimés ou non. Le défaut dont elle souffrait c'est la disparition des dépendances, c'est à dire imprimer tel script seulement si tel autre est imprimé, ou après tel autre script. Bref, tenir compte de la présence ou l'ordre de tout le monde.

Là, je vais utiliser la même méthode mais plus poussée. Ma fonction va mettre les scripts en file d'attente jusqu'au footer, et vont s'accumuler les uns après les autres. Arrivé le moment de les imprimer dans le footer, une autre fonction va se charger de réorganiser les scripts (et d'en ajouter si besoin) en fonction des dépendances respectives).

0001020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546

/* Can enqueue scripts at mid-page for WP 3.3+ and < 3.3 */
if (!function_exists('sf_enqueue_script')) {
	function sf_enqueue_script($handle = '', $src = false, $deps = array(), $ver = false, $in_footer = false) {
		if ( $handle == '' )
			return;

		if ( function_exists('wp_trim_words') || !did_action('wp_print_scripts') ) {	// WP 3.3+

			wp_enqueue_script($handle, $src, $deps, $ver, $in_footer);

		} else {																		// WP < 3.3
			global $wp_scripts;
			if ( ! is_a( $wp_scripts, 'WP_Scripts' ) ) {
				if ( ! did_action( 'init' ) )
					_doing_it_wrong( __FUNCTION__, sprintf( __( 'Scripts and styles should not be registered or enqueued until the %1$s, %2$s, or %3$s hooks.' ),
						'<code>wp_enqueue_scripts</code>', '<code>admin_enqueue_scripts</code>', '<code>init</code>' ), '3.3' );
				$wp_scripts = new WP_Scripts();
			}
			if ( $src ) {
				$_handle = explode('?', $handle);
				$handle  = $_handle[0];
				$wp_scripts->add( $handle, $src, $deps, $ver );
			}
			$wp_scripts->enqueue( $handle );
			$wp_scripts->groups[$handle] = 1;
			// Now we really add the script in the list of scripts to print in footer
			$wp_scripts->in_footer[] = $handle;
			// Many things could happen, we'll deal with dependencies just before print the scripts
			add_action('wp_print_footer_scripts', 'sf_handle_scripts_deps');
		}
	}
}

/* Handle scripts dependencies just before print them (for WP all_deps($wp_scripts->in_footer);
		$to_dos = array();
		foreach ($wp_scripts->to_do as $key => $to_do) {
			$to_dos[] = $to_do;
			if ( !in_array($to_do, $wp_scripts->in_footer) && !in_array($to_do, $wp_scripts->done) ) {
				$wp_scripts->enqueue( $to_do );
				$wp_scripts->add_data( $to_do[0], 'group', 1 );
				$wp_scripts->groups[$to_do] = 1;
			}
			unset($wp_scripts->to_do[$key]);
		}
		$wp_scripts->in_footer = $to_dos;
	}
}

Voilà, la fonction sf_enqueue_script( ) est à utiliser de la même manière que wp_enqueue_script( ), même paramètres toussa.

Le poney sur le gâteau

Tant que j’y suis, il y a aussi une autre fonction qui a été modifiée avec WP 3.3 et que j’ai adaptée pour les versions antérieures : wp_localize_script.
Depuis WP 3.3, wp_localize_script( ) accepte (enfin) le passage de tableaux multidimensionnels comme paramètre. J’ai donc crée une fonction qui aura le même comportement pour WP < 3.3, en utilisant json_encode() :

01020304050607080910111213141516

if ( !function_exists( 'sf_localize_script' ) ) {
	function sf_localize_script( $handle, $object_name, $l10n ) {
		if ( ( is_array($l10n) && count($l10n) ) && !function_exists('wp_trim_words') ) {		// $l10n is an array and WP < 3.3
			if (isset($l10n['l10n_print_after']) && $l10n['l10n_print_after']) {
				$new_l10n = array( 'l10n_print_after' => $l10n['l10n_print_after'] );
				unset($l10n['l10n_print_after']);
				$new_l10n['l10n_print_after'] = 'var '.$object_name.' = '.json_encode($l10n).';'."\n".$new_l10n['l10n_print_after'];
			} else {
				$new_l10n = array();
				$new_l10n['l10n_print_after'] = 'var '.$object_name.' = '.json_encode($l10n).';';
			}
			wp_localize_script( $handle, $object_name.'0', $new_l10n );
		} else
			wp_localize_script( $handle, $object_name, $l10n );
	}
}

Utilisation strictement identique à sa version originale.

Avec tout ceci, plus de problème de compatibilité par rapport aux versions de WordPress.

See ya!