Cómo pasar datos desde PHP a JavaScript en WordPress WordPress
Un tema que veo de forma muy recurrente en WPSE, como pregunta o como causa de problemas, es el paso de datos desde PHP para ser utilizados en el JavaScript de un theme. No es que sea algo muy complicado, todos sabemos que si tenemos datos en PHP y queremos incorporarlos al HTML, se imprimen como string y listo:
<h1><?php the_title(); ?></h1>
Y esto no solo en themes, se puede extender a cualquier plugin que utilice hooks del sistema de theming, como wp_head
, wp_footer
, the_content
y tantos otros.
El problema es cuándo se aplica el mismo criterio al código JS. Para pasar datos de PHP a JavaScript, imprimir esos datos directamente en el HTML no siempre es adecuado en WordPress. Casi nunca.
Mi problema de ejemplo favorito es como la minificación, concatenación, o cualquier otra cosa que quieras hacer con un script, se vuelve imposible, cuando menos una tarea molesta que de otro modo podría estar hecha un plis.
Pongamos el caso de que tienes un plugin JavaScript para galerías en el theme y que se añade con wp_enqueue_script()
:
add_action( 'wp_enqueue_scripts', 'cyb_theme_scripts' );
function cyb_theme_scripts() {
wp_enqueue_script(
'gallery-plugin',
get_theme_file_uri( 'assets/js/gallery.min.js' )
);
}
Ahora lo tenemos que inicializar en el documento con algo así:
gallery.run({
'autoplay': 0
});
Ahora imaginemos que la opción autoplay
se puede configurar como una opción en WordPress. La intuición podríamos decirnos que pongamos este código en el header.php o en otro template dónde se necesite (o en un hook que imprima en la parte del theme que nos interese, por ejemplo un shortcode):
<script>
gallery.run({
'autoplay': <?php echo get_option( 'gallery_autoplay', 0 ); ?>
});
</script>
¿Cuál es el problema con esto? Varios. Por ejemplo, en cuanto intentemos concatenar y minificar el JS probablemente romperemos el funcionamiento de la galería y puede que de otros componentes de la página que funcionen con JS. Este código impreso así a pelo en un template queda fuera del sistema de dependencias de WordPress y va a ser muy difícil para un plugin de optimización, no ya encontrarlo, que también, sino saber en que orden va.
Para ilustrarlo con un ejemplo muy sencillo y común: mueves todo el JS al pie de la página y la galería deja de funcionar; el código puesto en el template dará un error fatal en la consola JS. Antes se cargaba el plugin de la galería en el <head>
y luego en el template se echaba a andar; ahora se intenta echar a andar la galería en el template pero el plugin no se ha cargado todavía.
Opciones
Para evitar los problemas de poner código JS en los lugares que nos plazca de forma aleatoria e interferir en el manejo de dependencias de scripts, se pueden considerar numerosas opciones para pasar datos desde PHP. Entre ellas estas tres.
1. Utilizar los atributos data-*
Si el plugin JS funciona con atributos data, podemos considerar imprimir estos atributos en el elemento HTML de la galería directamente. Sencillo, práctico, directo:
<div class="gallery" data-autoplay="<?php echo get_option( 'gallery_autoplay', 0 ); ?>">
</div>
2. Cargar otro archivo .js y «localizar» el script
Otra opción sería llevar el código que inicializa la galería a un archivo js, por ejemplo en el archivo js general del theme, cargar este segundo archivo también con wp_enqueue_script()
declarando la dependencia con el plugin de galerías y localizando el script (wp_localize_script()
).
add_action( 'wp_enqueue_scripts', 'cyb_theme_scripts' );
function cyb_theme_scripts() {
wp_enqueue_script(
'gallery-plugin',
get_theme_file_uri( 'assets/js/gallery.min.js' )
);
wp_enqueue_script(
'theme-js',
get_theme_file_uri( 'assets/js/theme.min.js' ),
[ 'gallery-plugin' ] // array con los identificadores de las dependencias
);
wp_localize_script(
'theme-js' , // script que se va a localizar
'galleryOptions' ,
[ 'autoplay' => get_option( 'gallery_autoplay', 0 ) ]
);
}
Con el código anterior se imprimirá lo siguiente, y en este orden:
<script src="https://example.com/wp-content/themes/my-theme/assets/js/gallery.min.js">
<script>
var galleryOptions = {
"autoplay": 0
};
</script>
<script src="https://example.com/wp-content/themes/my-theme/assets/js/theme.min.js">
El orden en el que se imprimen es el declarado al poner en cola los scripts. Al plugin de la galería no se le puso ninguna dependencia, pero el archivo theme.min.js se ha declarado con dependencia en el plugin de gallerias, así que WordPress lo pondrá siempre después. Además, se ha creado un objeto asociado al script del theme que siempre se va a imprimir antes de ese script, así que podemos utilizar ese objeto dentro de theme.min.js:
(function() {
gallery.run({
'autoplay': galleryOptions.autoplay
});
});
3. Añadir el script inline
Por último, aunque no por ello la opción menos recomendable, todo depende del contexto concreto, podemos imprimir el código que habíamos puesto en el template pero utilizando wp_add_inline_script()
para que no quede fuera del sistema de dependencias:
add_action( 'wp_enqueue_scripts', 'cyb_theme_scripts' );
function cyb_theme_scripts() {
wp_enqueue_script(
'gallery-plugin',
get_theme_file_uri( 'assets/js/gallery.min.js' )
);
$inline_js = "gallery.run({'autoplay': " . get_option( 'autoplay', 0 ) . "});";
wp_add_inline_script(
'gallery-plugin', // Identificador el script del que depende
$inline_js
);
}
Con este código se imprimiría lo siguiente, y en este orden:
<script src="https://example.com/wp-content/themes/my-theme/assets/js/gallery.min.js">
<script>
gallery.run({'autoplay': 0});
</script>
Utilizando cualquiera de estos métodos puedes pasar datos desde PHP a JS sin romper las dependencias de los scripts, y esto permite poder trabajar fácilmente con ellos, por ejemplo en las tareas comunes de concatenar, minificar y mover al pie. Lamentablemente, son muchos los themes que lo hacen …. MAL; luego nos volvemos locos intentando optimizar una web.