Implementé un cuadro de búsqueda de autocompletar en mi sitio asp.net mvc4. Actualmente puedo hacer que el cuadro devuelva resultados que se actualizan a medida que escribo en el cuadro de búsqueda. También estoy generando dinámicamente botones de "categoría" en función de los "identificadores de tipo" de resultados y los inserto en un encabezado que aparece cuando la función de autocompletar produce resultados.
Quiero presentar una funcionalidad que es así: cuando el usuario hace clic en el botón de categoría, los resultados de autocompletar existentes se filtran aún más, por lo que solo se muestran los resultados de ese "tipo de ID". Después de eso, si el usuario desea volver a ver todos los resultados que coinciden con la cadena de búsqueda, puede hacer clic en el botón "Todos".
Para ver una versión funcional de esto, consulte el cuadro de búsqueda en Discogs.com. También he pegado una captura de pantalla de este widget a continuación, como referencia.
¿Cómo puedo implementar esto? No puedo encontrar ninguna publicación de stackoverflow sobre esto porque no sé cómo formular mi pregunta.
Mi código está abajo. En él, ya tengo un autocompletado en funcionamiento y tengo la parte que genera dinámicamente los botones de categoría. Ahora, necesito ayuda para encontrar un patrón de diseño para filtrar aún más los resultados de autocompletar cuando hago clic en los botones de categoría que se generaron dinámicamente.
@model myproject.Models.Search_Term @Scripts.Render("~/bundles/jquery") <script type="text/javascript"> //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // autopopulate input boxes //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //detect the browser resize and close the Autocomplete box when that event is triggered $(window).resize(function() { $("#searchBox").autocomplete("close"); }); //helper method for autopopulate. //https://stackoverflow.com/questions/2435964/how-can-i-custom-format-the-autocomplete-plug-in-results //this helps in creating a autocomplete menu with custom HTML formatting function monkeyPatchAutocomplete() { $.ui.autocomplete.prototype._renderItem = function( ul, item) { var inner_html = '<img src="' + item.imgPathSmall + '">'; return $("<li>") .data("ui-autocomplete-item", item) .append(inner_html) .appendTo(ul); }; } // look up search term $(document).ready(function () { //call this to enable the autocomplete menu with custom HTML formatting monkeyPatchAutocomplete(); //trigger autocomplete $("#searchBox").autocomplete({ source: function (request, response) { $.ajax({ url: "/Explore/SearchAutocomplete", type: "POST", dataType: "json", data: { search: request.term }, success: function (data) { response($.map(data, function (item) { return { objectName: item.ObjectName, detail1: item.Detail1, detail2: item.Detail2, detail3: item.Detail3, imgPathSmall: item.Image_Data_SmallPad_string, objectType: item.ObjectType, objectID: item.ObjectID, image_Data_SmallPad: item.Image_Data_SmallPad, image_MimeType_SmallPad: item.Image_MimeType_SmallPad }; })) } }) }, select: function (event, ui) { event.preventDefault(); //redirect to result page var url; switch (ui.item.objectType) { case 1: url = '@Url.Action("Category1", "Explore")?i=' + ui.item.objectID; break; case 2: url = '@Url.Action("Category2", "Explore")?i=' + ui.item.objectID; break; case 3: url = '@Url.Action("Category3", "Explore")?i=' + ui.item.objectID; break; case 4: url = '@Url.Action("Category4", "Explore")?i=' + ui.item.objectID; break; case 5: url = '@Url.Action("Category5", "Explore")?i=' + ui.item.objectID; break; case 6: url = '@Url.Action("Category6", "Explore")?i=' + ui.item.objectID; break; case 7: url = '@Url.Action("Category7", "Explore")?i=' + ui.item.objectID; } window.location.href = url; } }).data("ui-autocomplete")._renderMenu = function (ul, items) { //------------------------------------------------------------------------------------ //Append the header //------------------------------------------------------------------------------------ var header = ` <li> <div class='acmenu_header'> <div class="btn-group special" role="group" aria-label="..."> <button type="button" class="btn btn-default btn-xs">All</button> `; //helps determine the category buttons to generate var categories = []; $.each(items, function (index, item) { if (item.objectType) { switch (item.objectType) { case 1: categories.push(1); break; case 2: categories.push(2); break; case 3: categories.push(3); break; case 4: categories.push(4); break; case 5: categories.push(5); break; case 6: categories.push(6); break; case 7: categories.push(7); } } }); //helps determine the category buttons to generate var uniqueCategories = [...new Set(categories)]; var arrayLength = uniqueCategories.length; //generate the category buttons within the header for (var i = 0; i < arrayLength; i++) { switch (uniqueCategories[i]) { case 1: header = header + '<button type="button" class="btn btn-default btn-xs">Category1</button>' break; case 2: header = header + '<button type="button" class="btn btn-default btn-xs">Category2</button>' break; case 3: header = header + '<button type="button" class="btn btn-default btn-xs">Category3</button>' break; case 4: header = header + '<button type="button" class="btn btn-default btn-xs">Category4</button>' break; case 5: header = header + '<button type="button" class="btn btn-default btn-xs">Category5</button>' break; case 6: header = header + '<button type="button" class="btn btn-default btn-xs">Category6</button>' break; case 7: header = header + '<button type="button" class="btn btn-default btn-xs">Category7</button>' } } header = header + ` </div> </div> </li> `; $(ul).append(header); //------------------------------------------------------------------------------------ //append the autocomplete results var that = this; var currentCategory = ""; var currentCategoryLabel = ""; $.each(items, function (index, item) { if (item.objectType != currentCategory) { if (item.objectType) { switch (item.objectType) { case 1: currentCategoryLabel = "Category1"; break; case 2: currentCategoryLabel = "Category2"; break; case 3: currentCategoryLabel = "Category3"; break; case 4: currentCategoryLabel = "Category4"; break; case 5: currentCategoryLabel = "Category5"; break; case 6: currentCategoryLabel = "Category6"; break; case 7: currentCategoryLabel = "Category7"; } ul.append("<li class='ui-autocomplete-category'>" + currentCategoryLabel + "</li>"); } currentCategory = item.objectType; } that._renderItem(ul, item); }); //append the footer var footer = ` <li> <mark><span class="glyphicon glyphicon-cog" aria-hidden="true"></span> Advanced search</mark> </li> `; $(ul).append(footer); }; }) </script> @using (Html.BeginForm("Search", "Explore", FormMethod.Post, new { id = "searchFormNavbar", @class = "nav navbar-form navbar-left", enctype = "multipart/form-data" })) { @Html.AntiForgeryToken() <div class="input-group" id="searchDiv"> @Html.EditorFor(m => Model.SearchTerm, new { htmlAttributes = new { @class = "form-control", @id = "searchBox", placeholder = "Search x, y, z, and more...", style = "width:100%; min-width: 380px;" } }) <div class="input-group-btn"> <button id="searchBtn" class="btn btn-default" type="submit" style="color:steelblue"> <span class="glyphicon glyphicon-search" aria-hidden="true"></span> </button> </div> </div> }
Como usted mismo sugirió, el mejor método es, de hecho, mantener los resultados de búsqueda de autocompletar en caché y filtrar más localmente. No tengo experiencia con AJAX o Javascript, pero puedo ejemplificar algunos conceptos simples en C# que podrían ayudarlo.
Para empezar, veo que en su código tiene un método para realizar la búsqueda de autocompletar. Parece que usa mucho las expresiones lambda, sin embargo, es posible que desee extraer métodos con nombre de ellas. Lo que haría es hacer que el método de búsqueda de autocompletar devuelva una matriz de objetos que contengan los resultados coincidentes. Como dije, no estoy familiarizado con AXAJ, pero en ASP.NET hay algunas cosas que debe tener en cuenta al guardar datos temporales locales. Uno de ellos es el ciclo de la página, el otro es la persistencia variable. Supongo que está familiarizado con su entorno y sabe cómo almacenar correctamente los datos para que persistan correctamente durante la vida útil de la página. Usaría una variable de sesión para esto.
Podría tener una función de filtrado que sería algo como esto:
// For the sake of comprehensability, let's assume your result object type is called ACResult, and ACResult.ObjectType is an integer as a type ID. internal ACResult[] FilterResults(int objType) { return cachedResults.Where((result) => { return result.ObjectType == objType; }).ToArray(); }
En el ejemplo anterior, se supone que cachedResults es una matriz con sus elementos de resultados de búsqueda. Utiliza el método de extensión de LINQ Where<T>(Func<T, bool> predicate)
El ToArray<T>()
está ahí porque el método Where
devolverá un IEnumerable
que debe volver a convertirse en una matriz para cumplir con los métodos tipo de retorno. LINQ o algún equivalente podría no estar disponible. La alternativa más simple es iterar sobre ellos y seleccionar los que coincidan con el tipo deseado:
internal ACResult[] FilterResults(object objType) { List<ACResult> ret = new List<ACResult>(); foreach (ACResult result in cachedResults) { if (result.ObjectType == objType) ret.Add(result); } return ret.ToArray(); }
Cada botón de categoría llamaría al mismo controlador de eventos con el ID de tipo como parámetro, que llamará a este método para filtrar los resultados almacenados en caché y volver a representar la lista de sugerencias de autocompletar con los resultados devueltos. Algo como:
void CategoryButton_OnClicked(int objType) { ACResult[] matchingCategory = FilterResults(objType); if (objType != -1) // Assuming -1 means it's your "All" button. { //Pseudo function encapsulating your suggestion list rendering: ReRenderSuggestionList(matchingCategory); } else { //Your "All" button clears the cat filtering without reloading the results, which is why the FilterResults method doesn't alter the original "cachedResults" array. ReRenderSuggestionList(cachedResults); } }
Puede que no sea de mucha ayuda, pero realmente estoy tratando de ayudarte aquí. Me intriga cómo algo que puede ser trivial en algunos entornos puede convertirse en un verdadero problema en otros, especialmente en las aplicaciones web, por lo que probablemente me mantengo alejado de ellas. Mi enfoque es WinForms, Class Libraries (.NET (Framework | Core | Standard)) y aplicaciones UWP.
Estás pidiendo un patrón de diseño, así que aquí hay uno:
almacene la categoría como atributo de datos personalizado dentro de los botones del encabezado:
//generate the category buttons within the header for (var i = 0; i < arrayLength; i++) { header += '<button type="button" data-category="' + uniqueCategories[i] + '" '; header += 'class="btn btn-default btn-xs">Category' + uniqueCategories[i] + '</button>'
Para el botón mostrarTodo simplemente establezca un atributo vacío:
<button type="button" class="btn btn-default btn-xs" data-category>All</button>
dentro de la función _renderItem almacena la categoría para cada elemento de la lista:
return $('<li data-category="' + item.objectType + '">')
Después de eso, puede usar la delegación de eventos para filtrar los elementos de la lista:
Si establece los estilos en un bucle estricto, solo habrá un redibujado* en el navegador:
// this function can be further optimized function applyFilter(c) { $("li[data-category]").show().each(function() { if(c && c != $(this).data("category")) { $(this).hide(); } }); } $(document).on("click", ".btn[data-category]", function(e){ // category shall be undefined to show all items, otherwise 1,2, and so on applyFilter($(this).data("category")); })
Siéntase libre de pedir más detalles, pero creo que obtendrá la idea.
Llame a una API de back-end, obtenga resultados de autocompletar y colóquelos en una matriz. Supongo que no es necesario escribir aquí esa funcionalidad (si necesita ayuda con eso, hágamelo saber). Una vez que tenga una matriz con sus resultados, puede usar https://www.w3schools.com/jsref/jsref_filter.asp
Básicamente, puede filtrar su matriz con una lógica que puede implementar y personalizar según sus necesidades. Como un ejemplo simple:
var ages = [32, 33, 12, 40]; function checkAdult(age) { return age >= document.getElementById("ageToCheck").value; } function myFunction() { document.getElementById("demo").innerHTML = ages.filter(checkAdult); }
.filter usa la función checkAdult para devolver solo elementos en la matriz que son más grandes que ageToCheck. Aquí puede implementar fácilmente la lógica para filtrar sus resultados localmente, para evitar llamadas repetitivas al backend.
¡Espero que esto ayude! Si necesitas más detalles, házmelo saber. :)
Teniendo en cuenta tu comentario:
Más o menos, sí. Tengo funcionando la funcionalidad de autocompletar, y también tengo la creación dinámica de los botones de filtro dentro del funcionamiento de autocompletar. Lo que necesito averiguar ahora es cómo ocultar/eliminar los resultados de autocompletar que no coinciden con el ui.item.objectType asociado con el botón de filtro correspondiente. por cierto, esta pregunta no tiene nada que ver con java. el script que ves arriba es javascript/jquery/ajax
esto es exactamente eso.