WordPress

Creando un Buscador en Ajax para WordPress

Programando un Live Search en WordPress con Ajax

El CMS del momento tiene muchas virtudes, pero entre ellas no está, todavía, la elegancia en la ejecución de sus funciones más básicas. Y es que sigue abusando demasiado del clásico modelo de refrescar la pantalla para mostrar los cambios, ya sea en el guardado, en el cajón de búsqueda, en el acceso a los post, etc

Si lo que pretendemos es crear sistemas que atenúen este efecto, haciendo que el buscador de WordPress no refresque la pantalla, o la paginación no nos tenga unos segundos de espera en el innecesario proceso de recargarlo todo de nuevo, disponemos de multitud de plugins que nos ayudarán a ir realizando estos cambios poco a poco.

Pero este post no va sobre ellos, este post va sobre crear nuestro propio buscador.

¿Y por qué haríamos algo así? Primero, porque la satisfacción de hacerlo uno mismo y aprender en el proceso, siempre es muy gratificante. Y segundo y más importante, porque aplicar un plugin nos puede servir para un caso muy concreto pero… ¿Y si somos nosotros los que estamos desarrollando un plugin? ¿Y si el buscador no busca en la taxonomía personalizada que he creado? ¿Y si los resultados de búsqueda no tienen el formato que desearía?

En definitiva, crearlo nosotros nos da el control total, y eso en programación, marca siempre la diferencia.

Pasa, disfruta, aprende y comparte.


¿Tienes prisa?

 

BUSCADOR SENCILLO EN AJAX PARA WORDPRESS:

Muestra los títulos y enlaces de los post coincidentes:

ajax.js

jQuery(document).ready(function ($) 
{
//Buscador Ajax
$('.keyword').keyup(function () {
var textobuscar = $('.keyword').val();
    var data = {
        'action': 'get_post_information',
'textobuscar': textobuscar
    };
    $.post(ajaxurl, data, function(response) {
$('#datafetch').html('<p>'+ response +'</p>');
    });
});
})

Función PHP para el functions.php:

function ajax_get_post_information() 
{

$textobuscar = $_POST['textobuscar'];

$the_query = new WP_Query( array( 'posts_per_page' => -1, 's' => esc_attr( $_POST['textobuscar'] ), 'post_type' => 'post' ) );
    if( $the_query->have_posts() ) :
        while( $the_query->have_posts() ): $the_query->the_post(); ?>

            <h2><a href="<?php echo esc_url( post_permalink() ); ?>"><?php the_title();?></a></h2>

        <?php endwhile;
        wp_reset_postdata();  
    endif;
}
    die();
}

Hooks para el functions.php:

function add_ajax_javascript_file()
{
    wp_enqueue_script( 'ajax_custom_script', plugin_dir_url( __FILE__ ) . 'ajax.js', array('jquery') );
}

add_action( 'wp_ajax_get_post_information', 'ajax_get_post_information' );

 

BUSCADOR DE CAMPOS Y TAXONOMÍAS PERSONALIZADAS EN AJAX PARA WORDPRESS:

Mantenemos los hooks y ajax.js y sustituímos la función PHP por la siguiente:

function ajax_get_post_information() { 
 
     $textobuscar = $_POST['textobuscar'];
 
     if ($textobuscar != ""){
 
     $the_query = new WP_Query( array( 'posts_per_page' => -1, 's' => esc_attr( $_POST['textobuscar'] ), 'post_type' => 'alimentos' ) );
    if( $the_query->have_posts() ) :
 
 $numio = 99;
 
        while( $the_query->have_posts() ): $the_query->the_post(); 
 
 if ($the_query ==""){
 echo "Lo sentimos, no se han encontrado coincidencias";
 }else{ 
 
 $array_objetivo[] = get_the_title();
 $array_observaciones[] = get_post_meta( get_the_ID(), 'event_observaciones', true);
 $event_hidratos[] = get_post_meta( get_the_ID(), 'event_hidratos', true);
 $event_proteina[] = get_post_meta( get_the_ID(), 'event_proteina', true);
 $event_grasa[] = get_post_meta( get_the_ID(), 'event_grasa', true);
 $event_aporte[] = get_post_meta( get_the_ID(), 'event_aporte', true);
 $event_ids[] = get_the_ID();
 $event_porcion[] = get_post_meta( get_the_ID(), 'event_porcion', true);
 }
 $numio++;
 endwhile;
        wp_reset_postdata();  
 
 foreach ($array_objetivo as $numio => $objetivo) { 
 
 //Extraccion de la taxonomía a partir del id del post
 $product_terms = wp_get_object_terms( $event_ids[$numio],  'tipos_alimentos', $args = array('parent'        => 0) );
 foreach( $product_terms as $term ) {
 $todo[] = $term->name; 
 }
 //Fin extracción de la taxonomia
 echo '
'.number_format($event_porcion[$numio], 0).'
'.$event_ids[$numio].'
'.number_format($event_hidratos[$numio], 0).'
'.number_format($event_proteina[$numio], 0).'
'.number_format($event_grasa[$numio], 0).'
'.$event_aporte[$numio].'
'.$todo[$numio].'
'; ?>

En el desarrollo del post se explica línea por línea cómo personalizar el código 😉

 

Nuestro objetivo

Al finalizar este paso a paso habremos creado un buscador en tiempo real como el que puedes ver a continuación:

A lo largo del proceso aprenderemos a realizar búsquedas en Ajax en las entradas tradicionales de WordPress, y/o en post y taxonomías personalizadas propias o de algún plugin de terceros.

También habremos aprendido a escoger qué campos deseamos mostrar al público y darles formato con HTML.

De paso repasaremos algunos conceptos básicos como cuál es el funcionamiento de Ajax, qué es el enqueue de funciones y aspectos de seguridad para evitar ataques de inyección de código.

 

Archivo ajax.js

Lo primero que debemos hacer es crear un archivo con tecnología Ajax.

Esta forma de trabajar permite combinar los lenguajes JavaScriptXML, por lo que podemos hacer llegar peticiones a una base de datos (por ejemplo mediante PHP), y que los resultados nos sean devueltos sin necesidad de actualizar la página.

El código a incluir en ese archivo será:

jQuery(document).ready(function ($) 
{
//Buscador Ajax
$('.keyword').keyup(function () {
var textobuscar = $('.keyword').val();
    var data = {
        'action': 'get_post_information',
        'textobuscar': textobuscar
    };
    $.post(ajaxurl, data, function(response) {
    $('#datafetch').html('<p>'+ response +'</p>');
    });
});
})

 

Explicado línea a línea:

jQuery(document).ready(function ($)

Esta parte es la declaración de la función. Indica al sistema que cuando se haya cargado (ready), debe ser accesible su contenido.

Si hemos programado previamente en Jquery, veremos que la declaración es un poco diferente a la habitual:

$( document ).ready(function()

Esto es así porque WordPress da error si no se "escapan" los $ de los selectores. Aplicando la función jQuery() a la función de Jquery, hacemos que los selectores escapen sus caracteres problemáticos y pueda ser ejecutada en este CMS.

Lo siguiente que nos encontramos:

$('.keyword').keyup(function () {

Indica que al levantar la tecla (keyup) del campo cuyo class sea keyword, el contenido de la función se ejecutará.

Esto quiere decir que, como veremos más adelante, nuestro campo donde escribiremos el contenido a buscar, debe ser algo tipo:

<input type="text" name="texto_busca" class="keyword"></input>

Pero no tiene porqué ser un único campo. En realidad podemos aplicar class="keyword" a tantos campos como queramos, y el contenido de todos ellos se lanzará al buscador cada vez que escribamos algo en ellos.

Por otro lado, es importante el evento que active el buscador. Nosotros hemos elegido keyup, pues de esa manera, cada vez que escribas una letra, el buscador analizará todo el contenido que hay escrito en el cajón de búsqueda hasta ese momento.

Si yo escribo "manzana":
- Al escribir "m" el buscador empezará a seleccionar en la base de datos las palabras que contengan "m".
- Al escribir "ma" cambiará la búsqueda y ahora irá a por las que tengan "ma" en su interior.
Y así sucesivamente.

Si lo deseo, puedo usar cualqueir otro evento, como por ejemplo un botón llamado "¡Encuentalo!":

$('.encuentralo').click(function () {

cuyo html sería algo como:

<input type="text" name="texto_busca" class="keyword"></input>
<button class="encuentralo">¡Encuéntalo!</button>

 

Tras ello nos encontramos con:

var textobuscar = $('.keyword').val();

Que lo que hace es declarar una variable llamada textobuscar (var  textobuscar), cuyo valor va a ser lo que se haya escrito (.val()) en el campo que contenga la clase keyword.

 

Tras ello comenzamos a asignar los valores que se van a pasar a la siguiente página, y que serán aquellos con los que podremos trabajar:

 var data = {
        'action': 'get_post_information',
        'textobuscar': textobuscar
    };

Funciona de manera similar a un formulario HTML, donde action  señala la función que va a recepcionar el contenido,  'textobuscar' es el nombre del campo que será enviado y en nuestro caso, también el valor que contendrá ese campo.

 

La parte final del código únicamente es la encargada de recepcionar cualquier salida de la función a la que vamos a enviar las variables (return, echo, console, print..), y mostrarla donde le digamos:

$.post(ajaxurl, data, function(response) {
    $('#datafetch').html('<p>'+ response +'</p>');
    });

En nuestro caso, en el Div cuyo id es datafetch

 

Creando el archivo PHP

En nuestro function.php o dentro de nuestro plugin, en la parte que vayamos a usarlo, pondremos el siguiente código:

function ajax_get_post_information() 
{

$textobuscar = $_POST['textobuscar'];

$the_query = new WP_Query( array( 'posts_per_page' => -1, 's' => esc_attr( $_POST['textobuscar'] ), 'post_type' => 'post' ) );
    if( $the_query->have_posts() ) :
        while( $the_query->have_posts() ): $the_query->the_post(); ?>

            <h2><a href="<?php echo esc_url( post_permalink() ); ?>"><?php the_title();?></a></h2>

        <?php endwhile;
        wp_reset_postdata();  
    endif;
}
    die();
}

Si ajax.js iba a ser el archivo que recogiese los datos que introdujésemos, este código va a ser el encargado de procesar la búsqueda y devolverle los resultados para que los muestre.

Recordemos que en el ajax enviábamos un campo llamado textobuscar a la función get_post_information.

Las primeras líneas del php son, precisamente, el nombre de la función y la recepción por POST de la variable:

function ajax_get_post_information() 
{

     $textobuscar = $_POST['textobuscar'];

Ese contenido queda asignado a otra variable del mismo nombre, pero esta vez, como ahora hemos cambiado a php, bajo su sintaxis $textobuscar

 

Tras ello tenemos el código del buscador propiamente dicho:

$the_query = new WP_Query( array( ', 's' => esc_attr( $_POST['textobuscar'] ), 'post_type' => 'post' ) );
    if( $the_query->have_posts() ) :
        while( $the_query->have_posts() ): $the_query->the_post();

Lo que hace es realizar una consulta a la base de datos de WordPress (new WP_Query), mostrando todos los resultados sin paginar ('posts_per_page' => -1), y buscando coincidencias dentro de los títulos de las entradas de WordPress ( 'post_type' => 'post' ).

Si quisiéramos paginar los resultados porque son muchísimos, podemos emplear cualquier valor para posts_per_page. Ej: posts_per_page' => 20 mostrará 20 resultados en cada página.

Por otro lado, si disponemos de post_type personalizados por nosotros o por un plugin, basta con cambiar el valor de búsqueda. Por ejemplo:  'post_type' => 'alimentos' 

¿Y si no he creado yo el post_type, cómo se su nombre? Basta con acceder al listado de entradas personalizadas, que suelen estar bajo la apariencia de inmuebles, coches, alimentos o cualquier otra funcionalidad otorgada por el plugin, y mirar en la url:

En cuanto a la última parte, es donde se especifican los resultados de salida y su formato:

     <h2><a href="<?php echo esc_url( post_permalink() ); ?>"><?php the_title();?></a></h2>

En nuestro ejemplo mostrará el título de los post (the_title()), con un link a dicha entrada (post_permalink()).

Pero podemos añadir la complejidad y formato que queramos. Veamoslo en el siguiente apartado.

 

Buscador ajax que muestre campos y taxonomías personalizadas

En la mayor parte de los casos, si estamos realizando un buscador para un post_type personalizado, nos interesarán muchos más campos que el título. Y la mayor parte de esos campos no serán los típicos de WordPress.

En el siguiente código veremos una versión de buscador bastante más completa que la anterior:

function ajax_get_post_information() { 

     $textobuscar = $_POST['textobuscar'];

     if ($textobuscar != ""){

     $the_query = new WP_Query( array( 'posts_per_page' => -1, 's' => esc_attr( $_POST['textobuscar'] ), 'post_type' => 'alimentos' ) );
    if( $the_query->have_posts() ) :
	
	$numio = 99;
	
        while( $the_query->have_posts() ): $the_query->the_post(); 
		
		if ($the_query ==""){
			echo "Lo sentimos, no se han encontrado coincidencias";
		}else{ 
		
		$array_objetivo[] = get_the_title();
			$array_observaciones[] = get_post_meta( get_the_ID(), 'event_observaciones', true);
			$event_hidratos[] = get_post_meta( get_the_ID(), 'event_hidratos', true);
			$event_proteina[] = get_post_meta( get_the_ID(), 'event_proteina', true);
			$event_grasa[] = get_post_meta( get_the_ID(), 'event_grasa', true);
			$event_aporte[] = get_post_meta( get_the_ID(), 'event_aporte', true);
			$event_ids[] = get_the_ID();
			$event_porcion[] = get_post_meta( get_the_ID(), 'event_porcion', true);
		}
		$numio++;
		endwhile;
        wp_reset_postdata();  
		
		foreach ($array_objetivo as $numio => $objetivo) { 
		
		//Extraccion de la taxonomía a partir del id del post
			$product_terms = wp_get_object_terms( $event_ids[$numio],  'tipos_alimentos', $args = array('parent'        => 0) );
			foreach( $product_terms as $term ) {
				$todo[] = $term->name; 
			}
			//Fin extracción de la taxonomia
			echo '<div class="fila_nutri"><div class="nutrientes"><div class="oculta_alimento id_comis event_porcion'.$numio.'">'.number_format($event_porcion[$numio], 0).'</div><div class="oculta_alimento id_comis id_si'.$numio.'">'.$event_ids[$numio].'</div><div class="oculta_alimento ch_al_color color_hidratos_blo hidratos_si'.$numio.'">'.number_format($event_hidratos[$numio], 0).'</div><div class="oculta_alimento pr_al_color color_proteinas_blo proteina_alimento'.$numio.'">'.number_format($event_proteina[$numio], 0).'</div><div class="oculta_alimento color_grasas_blo gr_al_color grasas_alimento'.$numio.'">'.number_format($event_grasa[$numio], 0).'</div><div class="oculta_alimento id_comis aporte_alimento'.$numio.'">'.$event_aporte[$numio].'</div><div class="oculta_alimento id_comis tipo_alimento'.$numio.'">'.$todo[$numio].'</div></div>';
		?>
			<div name="<?php echo $numio ?>" class="alimento_listado cuadro_buscador alimento_si<?php echo $numio ?>" onclick="replica_nombre('<?php echo $numio ?>')"><?php echo $objetivo; ?></div><div class="cuadro_buscador2"><?php if ($array_observaciones[$numio] != ""){ echo " ||| "; } ?></div><div class="oculta_alimento cuadro_buscador2 observacion_si<?php echo $numio; ?>"> <?php if ($array_observaciones[$numio] != ""){ echo $array_observaciones[$numio]; }?></div>
        </div>
		<?php } 
		
    endif;
	unset($cat);
	}
die();
}

 

La primera diferencia que encontramos es que la función únicamente se ejecutará cuando haya texto que buscar:

if ($textobuscar != ""){

Lo que quiere decir, que si la variable textobuscar no está vacía, que continúe con la ejecución. Si lo está, finalizamos.

Tras ello hemos añadido un mensaje que indica que no se han encontrado resultados, en el caso de que la búsqueda no arroje nada:

if ($the_query ==""){
     echo "Lo sentimos, no se han encontrado coincidencias";
}

A continuación, aprovechando que se está accediendo a la base de datos, extraemos todos los campos que nos interesen de cada post que encuentre. En este caso, son todo campos personalizados que habíamos creado en el post_type al declararlo:

$array_objetivo[] = get_the_title();
     $array_observaciones[] = get_post_meta( get_the_ID(), 'event_observaciones', true);
     $event_hidratos[] = get_post_meta( get_the_ID(), 'event_hidratos', true);
     $event_proteina[] = get_post_meta( get_the_ID(), 'event_proteina', true);
     $event_grasa[] = get_post_meta( get_the_ID(), 'event_grasa', true);
     $event_aporte[] = get_post_meta( get_the_ID(), 'event_aporte', true);
     $event_ids[] = get_the_ID();
     $event_porcion[] = get_post_meta( get_the_ID(), 'event_porcion', true);
}

Lo que dice cada línea es:

$array_observaciones[] = get_post_meta( get_the_ID(), 'event_observaciones', true);

Guarda en este array (Cadena de valores - $array_observaciones[]) lo que extraigas bajo el nombre 'event_observaciones' (es el nombre del campo de texto desde el que se introdujo este valor. El input name) correspondiente a id del post en curso (get_the_ID()).

Siguiendo esa lógica, podemos extraer los valores de cualquier entrada simplemente cambiando get_the_ID() por la id del post correspondiente.

 

Y en la última parte, en el foreach, recorremos las cadenas de valores creadas para darle formato:

foreach ($array_objetivo as $numio => $objetivo) { 
		
		//Extraccion de la taxonomía a partir del id del post
		$product_terms = wp_get_object_terms( $event_ids[$numio],  'tipos_alimentos', $args = array('parent'        => 0) );
			foreach( $product_terms as $term ) {
				$todo[] = $term->name; 
			}
			//Fin extracción de la taxonomia
			echo '<div class="fila_nutri"><div class="nutrientes"><div class="oculta_alimento id_comis event_porcion'.$numio.'">'.number_format($event_porcion[$numio], 0).'</div><div class="oculta_alimento id_comis id_si'.$numio.'">'.$event_ids[$numio].'</div><div class="oculta_alimento ch_al_color color_hidratos_blo hidratos_si'.$numio.'">'.number_format($event_hidratos[$numio], 0).'</div><div class="oculta_alimento pr_al_color color_proteinas_blo proteina_alimento'.$numio.'">'.number_format($event_proteina[$numio], 0).'</div><div class="oculta_alimento color_grasas_blo gr_al_color grasas_alimento'.$numio.'">'.number_format($event_grasa[$numio], 0).'</div><div class="oculta_alimento id_comis aporte_alimento'.$numio.'">'.$event_aporte[$numio].'</div><div class="oculta_alimento id_comis tipo_alimento'.$numio.'">'.$todo[$numio].'</div></div>';
		?>
			<div name="<?php echo $numio ?>" class="alimento_listado cuadro_buscador alimento_si<?php echo $numio ?>" onclick="replica_nombre('<?php echo $numio ?>')"><?php echo $objetivo; ?></div><div class="cuadro_buscador2"><?php if ($array_observaciones[$numio] != ""){ echo " ||| "; } ?></div><div class="oculta_alimento cuadro_buscador2 observacion_si<?php echo $numio; ?>"> <?php if ($array_observaciones[$numio] != ""){ echo $array_observaciones[$numio]; }?></div>
        </div>
<?php }

Extrayendo de paso el nombre de la taxonomía a la cual pertenecen esas entradas:

$product_terms = wp_get_object_terms( $event_ids[$numio],  'tipos_alimentos', $args = array('parent'        => 0) );
     foreach( $product_terms as $term ) {
     $todo[] = $term->name; 
}

Siendo tipos_alimentos el nombre de la custom taxonomy que haya creado previamente.

 

Paso final: enqueue en WordPress

WordPress tiene un método que permite añadir código personalizado sin necesidad de tocar los archivos de instalación.

Ese método son los hooks: pequeñas líneas de código que unen nuestras funciones a WordPress, permitiendo ejecutarlas y ser accesibles desde donde deseemos: el administrador, la parte pública o ambos.

O dicho de otra manera, nuestro buscador no funcionará si no enlazamos los dos bloques de contenido a través de dos hooks.

Los hooks necesarios serán estas líneas de código:

Este para el archivo ajax.js:

add_action( 'admin_enqueue_scripts', 'add_ajax_javascript_file' );
function add_ajax_javascript_file()
{
    wp_enqueue_script( 'ajax_custom_script', plugin_dir_url( __FILE__ ) . 'ajax.js', array('jquery') );
}

Y este otro para la función php:

add_action( 'wp_ajax_get_post_information', 'ajax_get_post_information' );

Ambos hooks deben añadirse al function.php de nuestro plugin o tema activo.

 

 

Esperamos que esta entrada te haya sido de utilidad y, recuerda que si tienes dudas, desde Loopeando.com estaremos encantados de ayudarte en los comentarios.

 

Cristian Sarabia Martínez

Desde que a principios de los 90 mi padre desempolvó su Spectrum, no he dejado de probar y experimentar con la tecnología.

Enamorado del mundo web, Full Stack Developer de profesión y diseñador por devoción.

Ahora hago mis pinitos en esto del blogging para compartir con vosotros un poquito de todo lo que la comunidad me ha dado.

1 Comentario

Haz clic aquí para dejar tu comentario

  • Hola. Buenas tardes. ¿Cómo está? Muy bueno el material, antes de empezar a hacer mi versión me asalta una duda:
    ¿Dónde irían los archivos JS y PHP?