Algolia y el uso de instantsearch.js desde su script para Shopify
"Los buenos artistas copian, los grandes roban". - Steve Jobs
Inspirado por pimoroni.com Quería implementar una búsqueda similar en nuestra tienda web, buyzero.de buyzero.de está actualmente gestionado por Shopify.
La búsqueda muestra los resultados a medida que se escribe - en la página principal, sustituyendo dinámicamente el contenido previamente mostrado. Muy bueno, muy útil - y muy, muy rápido (gracias a Algolia!)
Como he tenido problemas con la documentación disponible sobre Algolia, y su servicio de incorporación no me ha ayudado mucho, he tenido que averiguar las cosas por mi cuenta, y buscando mucho en Google.
Este es un resumen de mi experiencia en la implementación de Algolia. Lo hice en un solo día, en una sola sesión de unas 12 - 14 horas de codificación.
Preparación sugerida: inyección de código de desarrollo en su página
Sugiero utilizar la extensión de Google Chrome Tampermonkey para inyectar CSS y Javascript desde su disco duro local mientras desarrolla.
Para ello, tiene que habilitar el acceso a los archivos para Tampermonkey. Abrir:
Haga clic en Detalles y active la opción "Permitir el acceso a las URL de los archivos".
Cree el siguiente usercript nuevo en Tampermonkey:
// ==UserScript== // @nombre Algolia-Inject // @namespace https://pi3g.com/ // @versión 0.5 // @description ¡inyectar archivo local para probar Algolia! // @autor Maximilian Batz // @grant GM_getResourceText // @grant GM_addStyle // @noframes // @include https://buyzero.de/* // @recurso YOUR_CSS file://D:\algolia_jr.css // @require file://D:\algolia_jr.js // ==/UserScript== var my_css = GM_getResourceText ("YOUR_CSS"); GM_addStyle(my_css);
Explicación:
- tienes que conceder getResourceText y addStyle para poder acceder e inyectar el archivo CSS
- noframes para no ejecutar una segunda vez en los marcos que aparentemente carga Shopify, y que además coincidiría con el dominio
- include -> coincidirá en buyzero.de. Es posible que desee cambiar esa línea
- recurso utilizado para importar el archivo CSS. Proporcione la ruta ... en Linux podría ser file:///home/whatever/whatever - fíjese en la triple barra diagonal
- require - incluye tu archivo javascript
- userscript: este es el userscript para añadir el archivo CSS
Añadir el código Algolia
Con la parte de "cómo desarrollar" fuera del camino, aquí está mi código de muestra en un pequeño archivo ZIP.
Por favor, no copie y pegue desde este blog, ya que no estoy seguro de si WordPress destruirá el formato del código... use el .js en el archivo ZIP para copiar.
Tenga en cuenta que las dependencias de Algolia / archivos JavaScript ya estaban instalados en nuestro tema de Shopify - mediante la instalación del plugin Algolia Shopify. También nuestro almacén de datos estaba configurado, etc. Algolia ya estaba (y sigue estando) sirviendo resultados de búsqueda en un desplegable mientras escribías en la caja de búsqueda.
Te guiaré a través de las partes importantes del código:
iniciamos aj_setup() una vez que el documento está listo, para inyectar nuestra nueva funcionalidad de búsqueda.
Por favor, tenga en cuenta que utilizo aj para preagregar mis funciones e id's para este script. Puedes usar lo que quieras, no es requerido por Algolia.
El contenedor
en aj_setup() configuré un nuevo contenedor para poner los resultados de la búsqueda (probablemente sería mejor práctica incluir esto directamente en su archivo de tema - para el desarrollo esto fue más fácil).
esta es la parte HTML de la búsqueda
$(".main-content").before('<div id="aj-search-container" class="aj-main-content full-width"><div class="background"><div class="pattern"><div class="container"><div class="row"><div class="col-md-12"><div class="row"><div class="col-md-12 center-column content-without-background">'+
""+
"<h1>Talresultado für: <span id='sc-query'></span></h1>" +
"" +" +
"" +
"" +
"" +
"" +
"
"" +" +
"" +
"" +
"" +
"</DIV" +
"
'</div></div></div></div></div></div></div></div>'
);
- Le doy la identificación aj-search-container, poder mostrar/ocultar todos los resultados del contenedor de búsqueda
- la mayoría de las divisiones, incluyendo contenedor son necesarios para que el estilo sea compatible con nuestro tema actual - utilícelos según sea necesario
- sc-query se utiliza para reflejar la búsqueda que el usuario ha introducido en ese momento, para mostrar que el sistema actualiza los resultados dinámicamente en su nombre (hay código para hacer esto - siga leyendo)
- aj-right-container contiene filtros para llegar a las "facetas" de la búsqueda. El usuario puede elegir una o varias marcas para mostrar, categorías de productos, e incluimos un filtro de precios
- aj-clear-all contendrá un enlace para borrar las facetas - para devolver todos los resultados que coincidan con la consulta del usuario
- aj-left-container contiene los resultados de la búsqueda. aj-stats contendrá las estadísticas sobre la búsqueda (resultados de la búsqueda + tiempo de búsqueda), aj-search-results contendrá los resultados, y aj-search-pagination contendrá la paginación
La plantilla de éxitos
de nuevo, esto probablemente iría directamente en su página en lugar de ser inyectado a través de Javascript.
$(".main-content").before(''+
''+
' <div class="aj-hit">'+
' <div class="aj-hit-image">'+
' <a href="/es/{{product_link}}/"><img src="{{image}}" alt="{{título}}"></a>'+
' </div>'+
' <div class="aj-hit-content">'+
' <div class="aj-hit-title"><a href="/es/{{product_link}}/">{{{highlightResult.title.value}}{{variant_title}}</a></div>'+
'<p class="aj-hit-description">{{{_highlightResult.body_html_safe.value}} <a href="/es/{{product_link}}/">" mehr</a></p>'+
'<p class="aj-hit-sku"><a href="/es/{{product_link}}/">{{sku}}</a></p>'+
' <div class="aj-hit-bottominfos">' +
' <div class="aj-hit-price">{{precio}} €</div>' +
' <div class="aj-hit-stock">{{inventory_quantity}} auf Lager </div>'+
' <div class="aj-hit-link"><a href="/es/{{product_link}}/">para la producción</a></div>'+
' </div>'+
'</div>'+
'
' +
'</div>'+
''+
'');Esta plantilla se inserta como "script". Esta es la forma recomendada por los documentos de Algolia.
Se utiliza para obtener un resultado de búsqueda, o un acierto.
Aquí, en el interior aj-hit que tenemos:
- aj-hit-imageUtilizando {{product_link}} y {{image}} como marcadores de posición para insertar posteriormente los valores correctos
- aj-hit-titleTambién enlazo en el {{variant_title}} para proporcionar un título completo para las variaciones
- aj-hit-descriptionpara mostrar la descripción del producto - {{{_highlightResult.body_html_safe.value}}
- aj-hit-price para el {{precio}} - no olvides añadir tu moneda (o añadirla modificando la variable pasada a la plantilla)
- aj-hit-stockpara mostrar la cantidad de {{inventory_quantity}} que tenemos
- aj-hit-link - por último, un enlace a la página del producto. {{enlace_del_producto}}
Todo esto está muy bien (IMHO) estilizado con mi archivo CSS. El estilo está fuera del alcance de este blogpost, consulte buyzero.de para inspirarse y luego codificar el suyo propio.
Más sobre {{{highlightResult.title.value}}
Las variables que se muestren con tres corchetes no serán escapadas por Algolia for HTML. Esto se utiliza aquí, para permitir el resaltado.
Algolia pondrá automáticamente las etiquetas alrededor de las partes del título, etc., que hayan coincidido en el Resultado destacado variable. Puede utilizar la en la salida, para mostrar al usuario por qué se cree que el resultado de la búsqueda es relevante para su búsqueda.
Más adelante te mostraré cómo modificar los valores que se pasan a la plantilla, ya que no querrás renderizar todo el cuerpo de la descripción de tu producto, por ejemplo.
aj_setup_search
var pb = aj_setup_search();
Para manejar la búsqueda más tarde, devolvemos una variable desde la función de configuración (llamada var punto_bueno dentro de la función setup_search. pb es su abreviatura).
var search = instantsearch({ appId: 'YOUR_APP_ID', apiKey: 'YOUR_API_KEY', // search only API key, no ADMIN key indexName: 'shopify_buyzero_de_products', urlSync: true, searchParameters: { hitsPerPage: 10 }, searchFunction(my_helper){ point_both.ihelper = my_helper; //search.helper.setQuery('Pi 3'); //sic! //my_helper.search(); //sic! if(my_helper.state.query === '') { return; } my_helper.search(); } //end searchFunction });//end instantsearch setupAquí configuramos la búsqueda (como ya hemos dicho, instantsearch ya está incluido y disponible en la página a través de otro archivo .js).
Tienes que pasar:
- su appId
- apiKey
- indexName
obtendrá estos de su backend Algolia. Allí también puede examinar sus estructuras de datos, y ver qué parámetros puede utilizar para el facetado, por ejemplo.
La parte realmente importante es la función searchFunction (my_helper).
El ayudante le permite dirigir la búsqueda a través de su propio script, en lugar de activarla desde un widget de caja de búsqueda que se configura con el instantsearch de Algolia.
Es importante crear una referencia a este ayudante, en esta función particular.
point_both.ihelper = my_helper;
El acceso al ayudante desde la variable de búsqueda más tarde no trabajo.
De hecho, necesitas ambos. ¿Ves este código de ejemplo que he comentado?
¡//search.helper.setQuery('Pi 3'); //sic!
¡//my_helper.search(); //sic!
sic es la palabra latina que significa "así". Lo que significa que, efectivamente, hay que acceder de dos maneras diferentes para establecer la consulta y luego ejecutar la búsqueda.
Además, usted Necesito para añadir my_helper.search() aquí. Esto permitirá -según recuerdo- que tus widgets actualicen la búsqueda correctamente... si ves que tus resultados no se actualizan, ¡comprueba si lo tienes ahí!
Después de añadir los widgets, de los que hablaremos más adelante, concluimos la configuración de la búsqueda con el siguiente código:
search.start();
point_both.search = search;
devolver el punto_ambos;Aquí también añadimos una referencia al propio objeto de búsqueda, y lo devolvemos a la función aj_setup.
Observe que point_both tiene ahora acceso al objeto "search" y al objeto especial "ihelper" que obtuvimos dentro de la función searchFunction.
Añadir el primer widget
añadiremos el resultado como primer widget:
search.addWidget(
instantsearch.widgets.hits({
contenedor: '#aj-search-results',
plantillas: {
item: document.getElementById('aj-hit-template').innerHTML,
vacío: "Es konnten leider keine Resultate für die Suchanfrage "{{query}}" gefunden."
},
transformData: {
item: function (my_result) {
//consola.log(mi_resultado);
//console.log(mi_resultado._highlightResult.body_html_safe.value);
if (my_result["variant_title"] == 'Default Title'){
my_result["variant_title"] = ";
}
si no
{
mi_resultado["título_variante"] = ' :: ' + mi_resultado["título_variante"];
}
//mi_resultado["body_html_safe"] = mi_resultado["body_html_safe"].substring(0,175) + '...';
//NO queremos multilínea, queremos que coincida con el final REAL de la cadena. /m coincidiría con el final de la línea ..
var patrón_a = /(.*?)(]*)?$/i;
var patrón_b = /[^<]+$/i;
//toma el primer grupo coincidente - que excluye la segunda coincidencia.
var body_string = my_result._highlightResult.body_html_safe.value.substring(0,175).replace(pattern_a, "$1″);
if (pattern_b.test(body_string)){
body_string = body_string + ""
}
//búsqueda de pi 3 b+
//Identificación del producto https://buyzero.de/products/budget-kit-raspberry-pi-3-model-b?variant=698066665499
mi_resultado._resaltoResultado.body_html_safe.value = body_string + '...';
mi_resultado["enlace_producto"] = "https://buyzero.de/products/” + my_result["handle"] + "?variant=" + my_result["id"];
devolver mi_resultado;
}
}
})
);El contenedor para poner la salida se selecciona como '#aj-search-results', la plantilla para renderizar los resultados se selecciona aquí: item: document.getElementById('aj-hit-template').innerHTML.
Supongo que lo ponen en una etiqueta , porque el contenido no se renderiza como HTML por el navegador.
Para los vacíos sólo pasamos una cadena de vuelta como plantilla.
Muy sencillo hasta ahora, ¿verdad?
A continuación utilizamos transformData para preparar una versión abreviada del cuerpo de texto de su producto y otras variables pasadas a la plantilla de aciertos comentada anteriormente.
Como hemos introducido en la salida de la búsqueda -utilizando el resaltado automático de Algolia- necesitamos acortar el texto de forma segura. He preparado mis propias expresiones regulares para esto, en mi código de ejemplo.
Si hay una declaración incompleta que comienza con ), se corta de la salida.
Si hay una etiqueta sin la correspondiente etiqueta para cerrarla, la añadimos.
El enlace del producto se construye obteniendo el mango, y estableciendo el id como la variante.
Añadir paginación
añadir widgets adicionales es fácil:
// Añade esto después de las otras llamadas a search.addWidget()
search.addWidget(
instantsearch.widgets.pagination({
contenedor: '#aj-search-pagination',
render: function(mi_obj){
console.log("render");
console.log(mi_obj);
},
getConfiguration: function(my_obj){
console.log("getConfiguration");
console.log(mi_obj);
}
})
);
no quieres la salida del registro de la consola ... Estaba usando esto para depurar, y aparentemente olvidé quitarlo. Lo siento. Soy humano.
Una vez más, se establece el contenedor de salida del widget. #aj-búsqueda-paginación'
Realmente no necesitas nada más para que la paginación funcione - configura el widget, y dale estilo - estarás bien.
Facetado
Con las facetas, el usuario puede afinar su búsqueda:
// Facetado
search.addWidget(
instantsearch.widgets.refinementList({
contenedor: '#aj-facet-brand',
attributeName: 'vendedor',
operador: "o",
límite: 5,
showMore: true,
cssClasses: {
header: 'aj-refine-header',
contar: 'aj-refine-count'
},
plantillas: {
encabezado: "Marke / Marca
}
})
);
- debe buscar los posibles attributeNames en su backend Algolia - le mostrará las facetas disponibles para sus datos (ver captura de pantalla abajo).
- El límite de 5 no se refiere a los resultados de su búsqueda, sino al límite de entradas que muestra Algolia para su refinementList
- Los refinamientos disponibles se actualizarán automáticamente si se modifican los demás parámetros de búsqueda (por ejemplo, si no hay coincidencias para una marca específica, ésta no se mostrará)
- showMore: mostrará un enlace para mostrar más en la parte inferior del widget
- contar: 'aj-refine-count' - puedes estilizar un poco los resultados de la búsqueda. Probablemente más de lo que necesitaba aquí - consulte la documentación oficial
Widget de facetas claras
search.addWidget(
instantsearch.widgets.clearAll({
contenedor: '#aj-clear-all',
plantillas: {
enlace: 'Filter zurücksetzen / Reset'
},
autoHideContainer: false,
clearsQuery: true,
})
);utilice esto para permitir que el cliente borre las facetas (es decir, se le mostrarán de nuevo todos los resultados que coincidan con su consulta).
No hay necesidad de configurar además de esto.
No voy a hablar de los otros widgets - echa un vistazo a mi código si estás interesado.
Vinculación con el cuadro de búsqueda
No utilizamos el widget de búsqueda de Algolia, sino que nos vinculamos a un evento de keyup en la entrada de nuestro formulario de búsqueda, así:
var pb = aj_setup_search();
//#search_query es la entrada que queremos
$("#search_query").keyup(function(search) {
updateSearch(pb);
});
}(acabamos de hablar de aj_setup_search, se añade aquí para mostrar de dónde viene pb).
Hacemos esto para poder hacer un procesamiento adicional cuando se dispara el evento keyup - ocultando el contenido que estábamos mostrando previamente en la página, y añadiendo los resultados de la búsqueda, etc.
función updateSearch(pb){
var mi_consulta = $("#search_query").val();
//$(".main-content").html(my_query);
si (mi_consulta) {
$(".main-content").hide();
$("#megamenu-header-menu2").hide();
$("#ajr_na").show();
$("#sc-query").html(my_query);
$("#aj-search-container").show();
pb.search.helper.setQuery(my_query);
//console.log(search_handle.helper.state);
pb.ihelper.search();
}
si no {
$("#aj-search-container").hide();
$(".main-content").show();
$("#ajr_na").hide();
$("#megamenu-header-menu2").show();
}}
Utilizamos jQuery para obtener el valor del campo de búsqueda. Si no está vacío, ocultamos el contenido principal que estábamos mostrando antes, y mostramos el contenedor de búsqueda.
Ponemos sc-query para mostrar al usuario que estamos respondiendo a su entrada: $("#sc-query").html(my_query);
Por último, las partes importantes son éstas:
pb.search.helper.setQuery(my_query);
pb.ihelper.search();
como ves, tenemos que utilizar dos ayudantes diferentes para establecer la consulta y ejecutar la búsqueda real. Sí, así es como hay que hacerlo - por suerte encontré a alguien que se quejaba de este comportamiento.
Finalmente, si la cadena de búsqueda está vacía (el usuario ha pulsado Escape, o la ha borrado, o la hemos borrado nosotros, o lo que sea...) ocultamos la búsqueda, y mostramos el contenido principal.
Gracias por leer
Gracias por su atención, espero que esto ayude a alguien que quiera implementar instantsearch.js sin usar el widget de búsqueda de Algolia.
Referencias
Tampermonkey
Algolia
- Documentación de los widgets de búsqueda instantánea de Algolia
- https://community.algolia.com/shopify/instant_search.html
- https://www.algolia.com/doc/tutorials/search-ui/instant-search/build-an-instant-search-results-page/instantsearchjs/
- https://www.algolia.com/doc/guides/searching/highlighting-snippeting
- https://www.algolia.com/doc/guides/search-ui/building-a-search-ui/
- https://community.algolia.com/instantsearch.js/v2/widgets/hits.html#struct-HitsTransforms
- https://stackoverflow.com/questions/42672838/algolia-instantsearch-add-a-callback-after-results-are-displayed
- https://community.algolia.com/algoliasearch-helper-js/gettingstarted.html
- https://github.com/algolia/instantsearch.js/issues/1924
- https://jsfiddle.net/bobylito/bsjdch4m/1/
- https://stackoverflow.com/questions/49638646/algolia-instantsearch-with-multiple-indices-and-multiple-pagination-widgets/49796164?noredirect=1#comment86826994_49796164
- https://github.com/algolia/instantsearch.js/issues/2887
Bono
- https://discourse.algolia.com/t/how-to-create-facet-widget-from-shopify-metafield/1710
- https://github.com/algolia/awesome-algolia