Web Components Grid Search Filter

    The Ignite UI for Web Components Search Filter feature in Web Components Grid enables the process of finding values in the collection of data. We make it easier to set up this functionality and it can be implemented with a search input box, buttons, keyboard navigation and other useful features for an even better user experience. While browsers natively provide content search functionality, most of the time the IgcGridComponent virtualizes its columns and rows that are out of view. In these cases, the native browser search is unable to search data in the virtualized cells, since they are not part of the DOM. We have extended the Web Components Material table-based grid with a search API that allows you to search through the virtualized content of the IgcGridComponent.

    Web Components Search Example

    The following example represents IgcGridComponent with search input box that allows searching in all columns and rows, as well as specific filtering options for each column.

    Web Components Search Usage

    Grid Setup

    Vamos começar criando nossa grade e vinculando-a aos nossos dados. Também adicionaremos alguns estilos personalizados para os componentes que usaremos!

    .gridSize {
        --ig-size: var(--ig-size-small);
    }
    
    <igc-grid id="grid1" class="gridSize" auto-generate="false" allow-filtering="true">
        <igc-column field="IndustrySector" data-type="string" sortable="true"></igc-column>
        <igc-column field="IndustryGroup" data-type="string" sortable="true"></igc-column>
        <igc-column field="SectorType" data-type="string" sortable="true"></igc-column>
        <igc-column field="KRD" data-type="number" sortable="true"></igc-column>
        <igc-column field="MarketNotion" data-type="number" sortable="true"></igc-column>
        <igc-column field="Date" data-type="date" sortable="true"></igc-column>
    </igc-grid>
    

    Great, and now let's prepare for the search API of our IgcGridComponent! We can create a few properties, which can be used for storing the currently searched text and whether the search is case sensitive and/or by an exact match.

        private grid: IgcGridComponent;    
    
        private searchBox: IgcInputComponent;
        
        private searchIcon: IgcIconComponent;
        private clearIcon: IgcIconComponent;
        private nextIconButton: IgcIconButtonComponent;
        private prevIconButton: IgcIconButtonComponent;
    
        private caseSensitiveChip: IgcChipComponent;
        private exactMatchChip: IgcChipComponent;
    

    Web Components Search Box Input

    Now let's create our search input! By getting the input element we can get its current value. This will allow us to use the IgcGridComponent's findNext and findPrev methods to highlight all the occurrences of the SearchText and scroll to the next/previous one (depending on which method we have invoked).

    Ambos os métodos e findNext os findPrev métodos têm três argumentos:

    • Text: string (o texto que estamos procurando)
    • (opcional) CaseSensitive: boolean (a pesquisa deve diferenciar maiúsculas de minúsculas ou não, o valor padrão é false)
    • (opcional) ExactMatch: boolean (a busca deve ser por uma correspondência exata ou não, o valor padrão é false)

    Ao pesquisar por uma correspondência exata, a API de pesquisa destacará como resultados apenas os valores de células que correspondem inteiramente ao SearchText, levando em conta também a diferenciação entre maiúsculas e minúsculas. Por exemplo, as strings 'software' e 'Software' são uma correspondência exata, desconsiderando a diferenciação entre maiúsculas e minúsculas.

    The methods from above return a number value (the number of times the IgcGridComponent contains the given string).

    <igc-input id="searchBox" name="searchBox">
    </igc-input>
    
    constructor() {
        var grid = this.grid = document.getElementById('grid') as IgcGridComponent;
        this.searchBox = document.getElementById('searchBox') as IgcInputComponent;
        grid.data = new MarketData();
    }
    
    public nextSearch(){
        this.grid.findNext(this.searchBox.value, false, false);
    }
    

    Add Search Buttons

    Para pesquisar e navegar livremente entre nossos resultados de pesquisa, vamos criar alguns botões invocando o findNext e os findPrev métodos dentro dos respectivos manipuladores de eventos de clique dos botões.

    <igc-icon-button id="prevIconBtn" variant="flat" name="prev" collection="material" ></igc-icon-button>
    <igc-icon-button id="nextIconBtn" variant="flat" name="next" collection="material"></igc-icon-button>
    
    constructor() {
        var nextIconButton = document.getElementById('nextIconBtn') as IgcIconButtonComponent;
        var prevIconButton = document.getElementById('prevIconBtn') as IgcIconButtonComponent;
        nextIconButton.addEventListener("click", this.nextSearch);
        prevIconButton.addEventListener("click", this.prevSearch);
    }
    public prevSearch() {
        this.grid.findPrev(this.searchBox.value, this.caseSensitiveChip.selected, this.exactMatchChip.selected);
    }
    
    public nextSearch() {
        this.grid.findNext(this.searchBox.value, this.caseSensitiveChip.selected, this.exactMatchChip.selected);
    }
    

    We can also allow the users to navigate the results by using the keyboard's arrow keys and the Enter key. In order to achieve this, we can handle the keydown event of our search input by preventing the default caret movement of the input with the PreventDefault method and invoke the findNext/findPrev methods depending on which key the user has pressed.

    <input id="searchBox" name="searchBox"/>
    
    constructor() {
         searchBox.addEventListener("keydown", (evt) => { this.onSearchKeydown(evt); });
         this.searchBox.addEventListener("igcInput", (evt) => {
            this.searchIcon.name = evt.detail ? 'clear' : 'search';
            this.grid.findNext(evt.detail, this.caseSensitiveChip.selected, this.exactMatchChip.selected);
         });
    }
    
    public onSearchKeydown(evt: KeyboardEvent) {  
            if (evt.key === 'Enter' || evt.key === 'ArrowDown') {
                evt.preventDefault();
                this.grid.findNext(this.searchBox.value, this.caseSensitiveChip.selected, this.exactMatchChip.selected);
            } else if (evt.key === 'ArrowUp') {
                evt.preventDefault();
                this.grid.findPrev(this.searchBox.value, this.caseSensitiveChip.selected, this.exactMatchChip.selected);
            }
        }
    }
    

    Case Sensitive and Exact Match

    Now let's allow the user to choose whether the search should be case sensitive and/or by an exact match. For this purpose we can use simple checkbox inputs and bind to its change event where we can use the checkbox checked state.

    <span>Case sensitive</span>
    <input id="case" type="checkbox">
    
    <span>Exact match</span>
    <input id="exact" type="checkbox">
    
    constructor() {
        const case = document.getElementById("case") as HTMLInputElement;
        const exact = document.getElementById("exact") as HTMLInputElement;
        case.addEventListener("change", this.updateSearch);
        exact.addEventListener("change", this.updateSearch);
    }
    
    public updateSearch() {
        const search1 = document.getElementById("search1") as HTMLInputElement;
        const case = document.getElementById("case") as HTMLInputElement;
        const exact = document.getElementById("exact") as HTMLInputElement;
        const grid = document.getElementById("grid") as IgcGridComponent;
        grid.findNext(search1.value, case.checked, exact.checked);
    }
    

    Persistence

    What if we would like to filter and sort our IgcGridComponent or even to add and remove records? After such operations, the highlights of our current search automatically update and persist over any text that matches the SearchText! Furthermore, the search will work with paging and will persist the highlights through changes of the IgcGridComponent's PerPage property.

    Adding icons

    Ao usar alguns dos nossos outros componentes, podemos criar uma interface de usuário enriquecida e melhorar o design geral de toda a nossa barra de pesquisa! Podemos ter um belo ícone de pesquisa ou exclusão à esquerda da entrada de pesquisa, alguns chips para nossas opções de pesquisa e alguns ícones de material design combinados com belos botões em estilo ripple para nossa navegação à direita. Podemos envolver esses componentes dentro de um grupo de entrada para um design mais refinado.

    import { defineComponents, IgcInputComponent, IgcChipComponent, IgcIconComponent, IgcIconButtonComponent, registerIconFromText } from "igniteui-webcomponents";
    
    defineComponents(IgcInputComponent, IgcChipComponent, IgcIconComponent, IgcIconButtonComponent);
    

    Por fim, vamos atualizar nosso modelo com os novos componentes!

    <igc-input id="searchBox" name="searchBox">
        <igc-icon id="searchIcon" slot="prefix" name="search" collection="material"></igc-icon>
        <div slot="suffix">
            <igc-chip selectable="true" id="caseSensitiveChip">Case Sensitive</igc-chip>
            <igc-chip selectable="true" id="exactMatchChip">Exact Match</igc-chip>
        </div>
        <div slot="suffix">
            <igc-icon-button id="prevIconBtn" variant="flat" name="prev" collection="material" ></igc-icon-button>
            <igc-icon-button id="nextIconBtn" variant="flat" name="next" collection="material"></igc-icon-button>
        </div>
    </igc-input>
    
    constructor() {
        const prevIconText = "<svg width='24' height='24' viewBox='0 0 24 24'><path d='M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z'></path></svg>";
        const nextIconText = "<svg width='24' height='24' viewBox='0 0 24 24'><path d='M10 6 8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'></path></svg>";
        const clearIconText = "<svg width='24' height='24' viewBox='0 0 24 24' title='Clear'><path d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'></path></svg>";
        const searchIconText = "<svg width='24' height='24' viewBox='0 0 24 24'><path d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z' /></svg>";
    
        registerIconFromText("prev", prevIconText, "material");
        registerIconFromText("next", nextIconText, "material");
        registerIconFromText("clear", clearIconText, "material");
        registerIconFromText("search", searchIconText, "material");
    }
    

    À direita, em nosso grupo de entrada, vamos criar três contêineres separados com as seguintes finalidades:

    • Para exibir alguns chips que alternam as propriedades CaseSensitive e ExactMatch. Substituímos as caixas de seleção por dois chips estilosos que mudam de cor com base nessas propriedades. Sempre que um chip é clicado, invocamos seu respectivo manipulador.
    <div slot="suffix">
        <igc-chip selectable="true" id="caseSensitiveChip">Case Sensitive</igc-chip>
        <igc-chip selectable="true" id="exactMatchChip">Exact Match</igc-chip>
    </div>
    
    constructor() {
        const input = document.getElementById("searchBox") as IgcInputComponent;
        input.addEventListener("change", this.updateSearch);
    }
    public updateSearch() {
        const grid = document.getElementById('grid') as IgcGridComponent;
        const caseSensitiveChip = document.getElementById('caseSensitiveChip') as IgcChipComponent;
        const exactMatchChip = document.getElementById('exactMatchChip') as IgcChipComponent;
        grid.findNext(input.value, caseSensitiveChip.selected, exactMatchChip.selected);
    }
    
    • Para os botões de navegação de pesquisa, transformamos nossas entradas em botões de estilo ondulado com ícones de materiais. Os manipuladores para os eventos de clique permanecem os mesmos - invocando os métodos / findNext​ ​findPrev.
    <div slot="suffix">
        <igc-icon-button id="prevIconBtn" variant="flat" name="prev" collection="material" ></igc-icon-button>
        <igc-icon-button id="nextIconBtn" variant="flat" name="next" collection="material"></igc-icon-button>
    </div>
    
    constructor() {
        const nextIconButton = this.nextIconButton = document.getElementById('nextIconBtn') as IgcIconButtonComponent;
        const prevIconButton = this.prevIconButton = document.getElementById('prevIconBtn') as IgcIconButtonComponent;
        nextIconButton.addEventListener("click", this.nextSearch);
        prevIconButton.addEventListener("click", this.prevSearch);
    }
    
    public nextSearch() {
        this.grid.findNext(this.searchBox.value, this.caseSensitiveChip.selected, this.exactMatchChip.selected);
    }
    
    public prevSearch() {
        this.grid.findPrev(this.searchBox.value, this.caseSensitiveChip.selected, this.exactMatchChip.selected);
    }
    

    Known Limitations

    Limitação Descrição
    Pesquisando em células com um modelo Os destaques da funcionalidade de pesquisa funcionam apenas para os modelos de célula padrão. Se você tiver uma coluna com modelo de célula personalizado, os destaques não funcionarão, então você deve usar abordagens alternativas, como um formatador de coluna, ou definir osearchable propriedade na coluna como falsa.
    Virtualização Remota A pesquisa não funcionará corretamente ao usar virtualização remota
    Células com texto cortado Quando o texto na célula é muito grande para caber e o texto que estamos procurando é cortado pelas reticências, ainda rolaremos até a célula e a incluiremos na contagem de correspondências, mas nada será destacado

    API References

    In this article we implemented our own search bar for the IgcGridComponent with some additional functionality when it comes to navigating between the search results. We also used some additional Ignite UI for Web Components components like icons, chips and inputs. The search API is listed below.

    IgcGridComponent methods:

    IgcColumnComponent properties:

    Componentes adicionais com APIs relativas que foram usadas:

    Additional Resources

    Nossa comunidade é ativa e sempre acolhedora para novas ideias.