Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use button within autocomplete dropdown to further filter the already-displayed results

I implemented an autocomplete search box on my asp.net mvc4 site. I am currently able to have the box return results that update as i type in the search box. I also am dynamically generating "category" buttons based on the result "type IDs" and inserting them in a header that appears when the autocomplete produces results.

I want to introduce functionality that goes like this: when the user clicks the category button, the existing autocomplete results get filtered further so only results of that "type ID" are shown. After that, if the user wants to see all of the results matching the search string again, they can click the "All" button.

To see a working version of this, please check out the search box on Discogs.com. I have also pasted a screenshot of this widget below, for reference.

How can I implement this? I can't find any stackoverflow posts about this because I don't know how to phrase my question.

enter image description here

My code is below. In it, I already have a functioning autocomplete, and I have the portion that dynamically generates the category buttons. Now what I need help with is finding a design pattern to further filter the autocomplete results when I click the category buttons that were dynamically generated.

@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>
}
like image 835
sion_corn Avatar asked Jan 17 '20 14:01

sion_corn


2 Answers

You are asking for a design pattern, so here is one:

  • store the category as custom data-attribute inside the header buttons:

    //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>'
    

    For the showAll button just set an empty attribute:

    <button type="button" class="btn btn-default btn-xs" data-category>All</button>

  • inside the _renderItem function store the category for each list item:

    return $('<li data-category="' + item.objectType + '">')

After that, You can use event delegation to filter the list items:

  1. set display: block for all list items
  2. set display: none for the non-matched list items

If You set the styles in a strict loop, there will be just only one redraw* in the browser:

// 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"));
})

Feel free to ask for more details, but I believe, You will get the idea.

(*) Is DOM rendering GUARANTEED to block during a single (synchronous) function's execution?

like image 197
deblocker Avatar answered Nov 14 '22 22:11

deblocker


Call a backend api, get autocomplete results and put them in an array. I assume, that functionality doesn't need to be written out here (if you need help with that, please let me know). Once you have an array with your results you can use https://www.w3schools.com/jsref/jsref_filter.asp

Basically you can filter your array with a logic you can implement and make custom to your needs. As a simple example:

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 uses the function checkAdult to only return elements in the array that are bigger than the ageToCheck. Here you can easily implement logic to filter your results locally, to prevent repetitive calls to the backend.

Hope this helps! If you need further details, let me know. :)

Considering your comment:

Kind of, yes. I have the autocomplete functionality working, and I also have the dynamic creation of the filter buttons within the autocomplete working. What I need to figure out now is how to hide/remove the autocomplete results that do not match the ui.item.objectType associated with the corresponding filter button. by the way, this question has nothing to do with java. the script you see above is javascript/jquery/ajax

this is exactly that.

like image 44
lehm.ro Avatar answered Nov 14 '22 23:11

lehm.ro