Desenvolvimento

17 mai, 2019

Criando um CRUD com THF – Parte 03: pesquisando pelos nossos clientes

Publicidade

Olá, mundo!

O objetivo deste artigo é implementar o mecanismo de pesquisa em nossa tela de listagem de clientes, deixando-a mais funcional.

Pré-requisitos

Ter lido os artigos anteriores seria ótimo para um melhor entendimento. Acesse-os através dos links a seguir:

Para continuar do ponto anterior, basta clonar o projeto com o que já foi implementado.

Passo 1  – Montando nossa pesquisa

Vamos abrir o arquivo customer-list.component.ts e criar a propriedade searchTerm, que será responsável por armazenar o termo que o usuário quer usar como pesquisa em nossa página.

Além disso, vamos criar uma propriedade chamada filter, do tipo ThfPageFilter, que é responsável por exibir o campo de pesquisa no cabeçalho de nossa página e disparar as funções que precisamos.

Por enquanto vamos chamar diretamente a função que já criamos para buscar os dados do nosso back-end (loadData). Dessa forma, veremos as diferenças visuais em nossa página.

Por último, vamos alterar a linha que monta a url de carga na função loadData, ou seja, vamos alterar como urlWithPagination é inicializada.

A partir de agora, além da página, também estamos enviando o que o usuário digitar no campo de pesquisa.

Inclusão das propriedade searchTerm e filter

...

// NOVO IMPORT
import { ThfPageFilter } from '@totvs/thf-ui/components/thf-page';

...
export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  // NOVA PROPRIEDADE PARA ARMAZENAR O QUE O USUÁRIO ESTÁ DIGITANDO DURANTE A PESQUISA
  private searchTerm: string = '';

  ...

  // NOVO OBJETO PARA CONTROLAR O MECANISMO DE PESQUISA
  public readonly filter: ThfPageFilter = {
    action: this.loadData.bind(this),
    ngModel: 'searchTerm',
    placeholder: 'Pesquisar por ...'
  };

  constructor(private httpClient: HttpClient) { }

  ...
        
  public loadData() {
    // INCLUSÃO DO TERMO DE PESQUISA NA URL DA API
    const urlWithPagination = `${this.url}?page=${this.page}&search=${this.searchTerm}`;

    ...
  }

}

Não podemos nos esquecer de atualizar o componente thf-page-list no arquivo customer-list.component.html com a propriedade t-filter.

Adição da propriedade t-filter

<thf-page-list
  t-title="Listagem de clientes"
  [t-filter]="filter">

  ...

</thf-page-list>

Com isso, o visual da nossa página ficará da seguinte forma:

Listagem de clientes com pesquisa

Notem que nossa página continua trazendo os resultados, mas a busca em si ainda não está funcional, pois a nossa paginação não está sendo tratada da forma correta e não estamos limpando os dados apresentados anteriormente.

Requisição com o termo de pesquisa, mas sem a paginação correta.

Temos dois problemas para resolver:

  • corrigir a paginação sempre que efetuarmos uma nova pesquisa precisamos buscar a primeira página dos resultados retornados, e quando carregar uma nova página, precisamos re-enviar o termo pesquisado novamente para não buscarmos dados diferente do que queremos;
  • precisamos limpar a lista de clientes quando uma nova pesquisa for feita. Ou seja, quando for a primeira página, nós devemos zerar a nossa lista de cliente – caso contrário, precisamos concatenar o resultado.

Antes de tudo, criaremos uma nova função que será chamada quando o usuário fizer uma pesquisa.

Função onActionSearch

...
export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  public readonly filter: ThfPageFilter = {
    // PASSA A CHAMAR A NOVA FUNÇÃO AO INVÉS DO LOADDATA
    action: this.onActionSearch.bind(this),
    ngModel: 'searchTerm',
    placeholder: 'Pesquisar por ...'
  };

  constructor(private httpClient: HttpClient) { }

  ...
  
  // FUNÇÃO QUE SERÁ DISPARADA QUANDO FOR EFETUADA UMA NOVA PESQUISA
  private onActionSearch() {
    // ZERA A PAGINAÇÃO
    this.page = 1;
    // CHAMA A FUNÇÃO DE CARGA DE DADOS PASSANDO O VALOR DE PESQUISA
    this.loadData({ search: this.searchTerm });
  }

  ...

}

Agora vamos criar a função showMore e fazer com que a propriedade t-show-more do thf-table passe a chamar essa função.

Assim, quem passa a ser responsável por chamar nossa função que faz a requisição para o servidor (loadData) é a nova função, e não o thf-table diretamente. Dessa forma, ela que controla qual a próxima página que deve ser chamada.

Função showMore

...

export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  constructor(private httpClient: HttpClient) { }

  ...
        
  // FUNÇÃO RESPONSÁVEL POR SOLICITAR UMA NOVA PÁGINA E MANTER A PESQUISA ATIVA
  showMore() {
    this.loadData({ page: ++this.page, search: this.searchTerm });
  }

}

Não esqueça de atualizar o thf-table.

Evento t-show-more atualizado

<thf-page-list
  t-title="Listagem de clientes"
  [t-filter]="filter">

  <!-- PASSA A USAR A FUNÇÃO SHOWMORE AO INVÉS DE LOADDATA -->
  <thf-table
    t-sort="true"
    [t-columns]="columns"
    [t-items]="customers"
    [t-loading]="loading"
    [t-show-more-disabled]="!hasNext"
    (t-show-more)="showMore()">
  </thf-table>

</thf-page-list>

Se você percebeu a função loadData, vai precisar de um refactory, já que ela deixa de ser responsável só pelo controle de paginação e passa a lidar com mais parâmetros.

Refactory da função loadData

...

export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  constructor(private httpClient: HttpClient) { }

  ...

  // PASSA A RECEBER A PÁGINA E O VALOR DE PESQUISA
  private loadData(params: { page?: number, search?: string } = { }) {
    this.loading = true;

    // PASSAMOS O QUERYPARAMETERS COMO PARAMETRO DA FUNÇÃO GET, ASSIM NÃO PRECISAMOS MAIS CONCATENAR A URL
    this.customersSub = this.httpClient.get(this.url, { params: <any>params })
      .subscribe((response: { hasNext: boolean, items: Array<any>}) => {
        // QUANDO FOR A PRIMEIRA PÁGINA APENAS REATRIBUIMOS OS DADOS RETORNADOS
        this.customers = !params.page || params.page === 1 
          ? response.items 
          // A PARTIR DA SEGUNDA PÁGINA NÓS CONCATENAMOS OS DADOS RETORNADOS
          : [...this.customers, ...response.items];
        this.hasNext = response.hasNext;
        this.loading = false;
      });
  }

}

Perceba que agora loadData não controla mais as páginas que devem ser exibidas. Também deixamos de controlar os parâmetros diretamente na url e passamos a mandar os query parameters (ou query string) como parâmetro para a função get do HttpClient fazer a concatenação necessária.

Com isso, nossa função de pesquisa rápida passa a funcionar 100%.

Função de pesquisa rápida 100% funcional.

Passo 2 – Criando a pesquisa avançada

Agora que já temos nossa pesquisa rápida funcionando 100%, vamos criar nossa pesquisa avançada, onde o usuário poderia ter mais opções de pesquisa.

Importante: o uso de uma modal nem sempre é ou será a sua melhor opção. Para fins de estudo e, para a maioria dos casos, isso já é suficiente, mas sinta-se à vontade para implementar a sua busca avançada da maneira que achar melhor.

Vamos iniciar criando um modal em nossa aplicação. Abra o arquivo customer-list.component.html e inclua o thf-modal. Crie uma propriedade chamada #advancedFilter de referência para ele e configure o seu título.

Inclusão do thf-modal em nossa páginda

<thf-page-list ...>
  
  ...

</thf-page-list>

<thf-modal
  #advancedFilter
  t-title="Busca avançada">

</thf-modal>

Criaremos uma função que irá abrir o nosso modal e atualizar a propriedade filter para que chame essa nova função quando o usuário quiser fazer uma pesquisa avançada.

Não se esqueça da propriedade que fará referência ao modal que criamos no nosso template.

Inclusão da função openAdvancedFilter em nosso componente

import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';

...

// NOVO IMPORT PARA O MODAL
import { ThfModalComponent } from '@totvs/thf-ui/components/thf-modal';

...

@Component({
  selector: 'app-customer-list',
  templateUrl: './customer-list.component.html',
  styleUrls: ['./customer-list.component.css']
})
export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  public readonly filter: ThfPageFilter = {
    action: this.onActionSearch.bind(this),
    // FILTER ATUALIZADO PARA HABILITAR A BUSCA AVANÇADA
    advancedAction: this.openAdvancedFilter.bind(this),
    ngModel: 'searchTerm',
    placeholder: 'Pesquisar por ...'
  };

  ...

  // PROPRIEDADE QUE ARMAZENA A REFERÊNCIA DO MODAL
  @ViewChild('advancedFilter') advancedFilter: ThfModalComponent;

  constructor(private httpClient: HttpClient) { }

  ...

  // FUNÇÃO QUE IRÁ ABRIR NOSSA BUSCA AVANÇADA
  openAdvancedFilter() {
    this.advancedFilter.open();
  }

  ...

}

Com isso, já conseguimos habilitar a busca avançada e abrir um simples modal para preenchimento do usuário.

Página com abertura do modal funcionando.

Legal, mas agora vamos trabalhar em cima do nosso formulário de pesquisa.

Como vamos trabalhar com formulário e inputs, precisamos importar o FormsModule do Angular em nosso SharedModule.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
// IMPORT DO FORMSMODULE
import { FormsModule } from '@angular/forms';

import { ThfModule } from '@totvs/thf-ui';

@NgModule({
  imports: [
    CommonModule,
    // NOVO MÓDULO
    FormsModule,

    ThfModule
  ],
  exports: [
    CommonModule,
    // NOVO MÓDULO
    FormsModule,

    ThfModule
  ]
})
export class SharedModule { }

Isso não vai alterar em nada o visual do nosso projeto, então vamos ao nosso formulário.

No arquivo customer-list.component.html vamos incluir alguns campos para pequisa dentro no nosso modal:

  • nome: vamos utilizar um thf-input. Pode-se dizer que é o tipo de entrada de dado mais básico que existe e vai servir para fazer pesquisas por nome.

Inclusão do thf-input dentro do thf-modal

<thf-page-list ...>
  
  ...
  
</thf-page-list>

<thf-modal
  #advancedFilter
  t-title="Busca avançada">

  <!-- INCLUIR FORM -->
  <form #f="ngForm">
    <!-- INCLUIR UM INPUT PARA PESQUISA -->
    <thf-input
      class="thf-sm-6"
      name="name"
      [(ngModel)]="name"
      t-label="Nome">
    </thf-input>
  </form>

</thf-modal>

Com isso, já teremos um resultado diferente em nosso modal.

Modal com campo para pesquisa por nome.

Se você preencher o campo e apertar o botão “OK”, não vai ter nenhum retorno, mas não se preocupe. Nós vamos, primeiro, criar nosso formulário e depois vamos tratar de pegar esses dados e enviar para o nosso back-end.

  • cidade: para esse campo vamos usa o thf-combo, que é um campo que trás uma lista de valores pré-definidos que podem ser filtrados, bem próximo a um campo com autocomplete.

Modal com thf-combo

<thf-page-list ...>
  
  ...
  
</thf-page-list>

<thf-modal
  #advancedFilter
  t-title="Busca avançada">

  <form #f="ngForm">
    ...
    
    <!-- INCLUIR UM THF-COMBO PARA PESQUISA -->
    <thf-combo
      class="thf-sm-6"
      name="city"
      [(ngModel)]="city"
      t-label="Cidade"
      [t-options]="cityOptions">
    </thf-combo>
  </form>

</thf-modal>

Como o thf-combo precisa de uma lista pré-definida para o usuário poder escolher o valor desejado, você precisa definir essa lista no customer-list.component.ts em uma propriedade ou passar um serviço que tenha essa lista de dados. Para o nosso exemplo, vamos criar nossa lista dentro do nosso código mesmo.

Lista de cidades que serão exibidas em nosso combo de pesquisa

...

// NOVO IMPORT PARA A INTERFACE DO THFCOMBOOPTION
import { ThfComboOption } from '@totvs/thf-ui/components/thf-field';

...

export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  // NOSSA LISTA DE OPÇÕES PARA O NOSSO COMBO
  public readonly cityOptions: Array<ThfComboOption> = [
    { label: 'Araquari', value: 'Araquari' },
    { label: 'Belém', value: 'Belém' },
    { label: 'Campinas', value: 'Campinas' },
    { label: 'Curitiba', value: 'Curitiba' },
    { label: 'Joinville', value: 'Joinville' },
    { label: 'Osasco', value: 'Osasco' },
    { label: 'Rio de Janeiro', value: 'Rio de Janeiro' },
    { label: 'São Bento', value: 'São Bento' },
    { label: 'São Francisco', value: 'São Francisco' },
    { label: 'São Paulo', value: 'São Paulo' }
  ];

  ...

  constructor(private httpClient: HttpClient) { }

  ...

}

Com isso, nós teremos o seguinte resultado:

Modal com combo de cidade.

Gênero: como o gênero tem poucas opções de escolha, em nosso exemplo vamos usar o thf-radio-group.

Adicionando o thf-radio-group no thf-modal

<thf-page-list ...>
  
  ...
  
</thf-page-list>

<thf-modal
  #advancedFilter
  t-title="Busca avançada">

  <form #f="ngForm">
    ...
    
    <!-- INCLUIR UM THF-RADIO-GROUP PARA PESQUISA -->
    <thf-radio-group
      class="thf-sm-7"
      name="genre"
      [(ngModel)]="genre"
      t-columns="3"
      t-label="Gênero"
      [t-options]="genreOptions">
    </thf-radio-group>
  </form>

</thf-modal>

Da mesma forma que o thf-combo, nós precisamos configurar a lista de opções que serão exibidas pelo thf-radio-group.

Configuração dos valores exibidos pelo thf-radio-group

...

// NOVO IMPORT PARA A INTERFACE DO THFRADIOGROUPOPTION
import { ThfComboOption, ThfRadioGroupOption } from '@totvs/thf-ui/components/thf-field';

...

export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  // NOSSA LISTA DE OPÇÕES PARA O NOSSO RADIO-GROUP
  public readonly genreOptions: Array<ThfRadioGroupOption> = [
    { label: 'Feminino', value: 'Female' },
    { label: 'Masculino', value: 'Male' },
    { label: 'Outros', value: 'Other' }
  ];

  ...

  constructor(private httpClient: HttpClient) { }

  ...

}

Confira o resultado:

Modal com o campo para seleção de gênero.
  • status: para o campo de status vamos usar o thf-checkbox-group, pois o usuário vai poder escolher mais de uma opção.

Inclusão do thf-checkbox-group no thf-modal

<thf-page-list ...>
  
  ...
  
</thf-page-list>

<thf-modal
  #advancedFilter
  t-title="Busca avançada">

  <form #f="ngForm">
    ...
    
    <!-- INCLUIR UM THF-CHECKBOX-GROUP PARA PESQUISA -->
    <thf-checkbox-group
      class="thf-sm-5"
      name="status"
      [(ngModel)]="status"
      t-label="Status"
      [t-options]="statusOptions">
    </thf-checkbox-group>
  </form>

</thf-modal>

O thf-checkbox-group segue o mesmo conceito dos componentes anteriores. Vamos ter que definir uma lista de valores válidos.

Lista de opções para o thf-checkbox-group

...

// NOVO IMPORT PARA A INTERFACE DO THFCHECKBOXGROUPOPTION
import { ThfCheckboxGroupOption, ThfComboOption, ThfRadioGroupOption } from '@totvs/thf-ui/components/thf-field';

...

export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  // NOSSA LISTA DE OPÇÕES PARA O NOSSO THF-CHECKBOX-GROUP
  public readonly statusOptions: Array<ThfCheckboxGroupOption> = [
    { label: 'Ativo', value: 'Active' },
    { label: 'Inativo', value: 'Inactive' }
  ];

  ...

  constructor(private httpClient: HttpClient) { }

  ...

}

Com isso, nosso formulário de pesquisa avançada está finalizado:

Modal com todos os campos de pesquisa configurados.

Agora precisamos pegar os valores digitados pelo usuário e enviar para o nosso back-end. Primeiro, vamos criar uma função que vai pegar os valores digitados pelo usuário e chamar nossa função loadData passando um objeto com esses valores.

Não podemos nos esquecer de criar no nosso componente as propriedades que estão ligadas ao ngModel dos nossos inputs.

Nova função onConfirmAdvancedFilter e novas propriedades

...

export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  // NOVAS PROPRIEDADES QUE ARMAZERAM O QUE FOR DIGITADO NA PESQUISA AVANÇADA
  public city: string;
  public genre: string;
  public name: string;
  public status: Array<string> = [];
    
  ...

  constructor(private httpClient: HttpClient) { }

  ...
    
  // NOVA FUNÇÃO QUE SERÁ DISPARADA ASSIM QUE A PESQUISA AVANÇADA FOR CONFIRMADA
  private onConfirmAdvancedFilter() {
    const filters: any = {
      name: this.name || '',
      city: this.city || '',
      genre: this.genre || '',
      // COMO O THF-CHECKBOX-GROUP DEVOLVE UM ARRAY COM OS VALORES SELECIONADOS, 
      // VAMOS TRANSFORMAR O ARRAY EM UMA STRING COM OS VALORES SEPARADOS POR ','
      status: this.status ? this.status.join() : ''
    }

    // CASO A PESQUISA RÁPIDA TENHA SIDO USADA ANTES, LIMPAMOS O CAMPO
    this.searchTerm = undefined;
    // COMO SERÁ UMA PESQUISA NOVA, NÓS INICIALIZAMOS A PÁGINAÇÃO
    this.page = 1;

    // CHAMAMOS NOSSA FUNÇÃO DE CARGA DE DADOS
    this.loadData(filters);

    // FECHAMOS A PESQUISA AVANÇADA
    this.advancedFilter.close();
  }
    
  ...

}

Nossa pesquisa ainda não vai funcionar, pois faltou informar para o thf-modal que essa é a função que deve ser chamada ao confirmar a pesquisa avançada.

Para isso, vamos criar duas propriedades, uma para ação de confirmação e outra para a ação de cancelamento, caso o usuário desista de fazer a pesquisa avançada.

Definição das ações de confirmação e cancelamento da pesquisa avançada

...

// NOVO IMPORT
import { ThfModalComponent, ThfModalAction } from '@totvs/thf-ui/components/thf-modal';

...

export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  // OBJETO QUE DEFINE A AÇÃO DE CONFIRMAÇÃO 
  public readonly advancedFilterPrimaryAction: ThfModalAction = {
    // VAI CHAMAR A NOSSA FUNÇÃO QUE PASSA OS PARAMETROS QUE DEVEM SER FILTRADOS
    action: this.onConfirmAdvancedFilter.bind(this),
    label: 'Pesquisar'
  };

  // OBJETO QUE DEFINE A AÇÃO DE CANCELAMENTO
  public readonly advancedFilterSecondaryAction: ThfModalAction = {
    // SIMPLESMENTE FECHA O MODAL SE HOUVER CANCELAMENTO
    action: () => this.advancedFilter.close(),
    label: 'Cancelar'
  };

  ...

  constructor(private httpClient: HttpClient) { }

  ...

}

Agora vamos configurar o thf-modal com essas novas propriedades.

Modal com as ações configuradas

<thf-page-list ...>
  
  ...
  
</thf-page-list>

<!-- AÇÕES CONFIGURADAS -->
<thf-modal
  #advancedFilter
  t-title="Busca avançada"
  [t-primary-action]="advancedFilterPrimaryAction"
  [t-secondary-action]="advancedFilterSecondaryAction">

  ...
  
</thf-modal>

Com isso, nossa função de pesquisa avançada já está funcionando!

Pesquisa avançada funcionando.

Mas (sempre tem o mas), a paginação está com problemas. Se clicarmos em “Carregar mais”, os valores de pesquisa serão ignorados, então precisamos salvar o que foi digitado ou o valor da última pesquisa avançada e enviar quando a função showMore for disparada pelo thf-table.

Para isso, vamos atualizar a função onConfirmAdvancedFilter para salvar os parâmetros e vamos atualizar a função showMore para enviar esses parâmetros quando o mesmo estiver preenchido.

Refactory nas funções que disparam nossas pesquisas

...

export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  // NOVA PROPRIEDADE PARA ARMAZENAR OS FILTROS DA PESQUISA AVANÇADA
  private searchFilters: any;

  ...

  constructor(private httpClient: HttpClient) { }

  ...

  // AGORA A FUNÇÃO SHOWMORE ANALISA O QUE PRECISA SER ENVIADO, 
  // O TERMO DA PESQUISA RÁPIDA OU OS FILTROS AVANÇADOS
  showMore() {
    let params: any = {
      page: ++this.page
    };

    if (this.searchTerm) {
      params.search = this.searchTerm
    } else {
      params = { ...params, ...this.searchFilters }
    }

    this.loadData(params);
  }
  
  ...

  private onActionSearch() {
    // QUANDO FOR UMA PESQUISA RÁPIDA LIMPA OS PARAMETROS DA BUSCA AVANÇADA
    this.searchFilters = {};
    this.page = 1;

    this.loadData({ search: this.searchTerm });
  }

  private onConfirmAdvancedFilter() {
    // PASSAMOS A ARMAZENAR OS FILTROS DA BUSCA AVANÇADA
    this.searchFilters = {
      name: this.name || '',
      city: this.city || '',
      genre: this.genre || '',
      status: this.status ? this.status.join() : ''
    }

    this.searchTerm = undefined;
    this.page = 1;

    this.loadData(this.searchFilters);

    this.advancedFilter.close();
  }

}

Pronto (e sem nenhum mas), nossa pesquisa avançada está funcionando perfeitamente.

Passo 3 – Exibindo os filtros das buscas

Para deixar nossa busca ainda melhor, vamos incluir disclaimers em nossa página para o usuário saber o que está sendo filtrado. Para isso, vamos usar a propriedade t-disclaimer-group do thf-page-list.

Vamos criar uma propriedade em nosso componente e associar a mesma ao nosso thf-page-list.

Nova propriedade para controlar nossos disclaimers

...

// NOVO IMPORT
import { ThfDisclaimerGroup } from '@totvs/thf-ui/components/thf-disclaimer-group';

...

export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  // NOVA PROPRIEDADE PARA EXIBIÇÃO DOS NOSSOS FILTROS
  public readonly disclaimerGroup: ThfDisclaimerGroup = {
    title: 'Filtros aplicados em nossa pesquisa',
    disclaimers: [ ]
  };

  ...

  constructor(private httpClient: HttpClient) { }

  ...

}

Disclaimer group adicionado ao page list

<!-- DISCLAIMER GROUP ADICIONADO -->
<thf-page-list
  t-title="Listagem de clientes"
  [t-disclaimer-group]="disclaimerGroup"
  [t-filter]="filter">

  ...
  
</thf-page-list>

...

Novamente, isso não fez nenhum efeito prático em nossa página, mas acalme-se – queremos mostrar os disclaimers apenas quando existir alguma pesquisa ativa, seja ela rápida ou avançada.

Primeiro vamos atualizar os disclaimers quando houver uma pesquisa rápida.

Disclaimer atualizado com o termo pesquisado

...

export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  constructor(private httpClient: HttpClient) { }

  ...

  private onActionSearch() {
    this.searchFilters = {};
    this.page = 1;

    this.loadData({ search: this.searchTerm });

    // DEPOIS DE DISPARAR A PESQUISA A LISTA DE DISCLAIMER É ATUALIZADA
    this.disclaimerGroup.disclaimers = [{
      label: `Pesquisa rápida: ${this.searchTerm}`,
      property: 'search',
      value: this.searchTerm
    }];
  }

  ...

}

Com as alterações, temos o seguinte resultado:

Notem que temos um pequeno problema. Ao excluir o disclaimer, nós não atualizamos nossa tabela. Para isso, precisamos refazer a pesquisa sem o filtro.

Para resolver esse problema precisamos criar uma função que faça isso e vincular essa nova função ao nosso objeto que controla o disclaimer.

...

export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  public readonly disclaimerGroup: ThfDisclaimerGroup = {
    // FUNÇÃO QUE SERÁ DISPARADA QUANDO O THFDISCLAIMERGROUP SOFRER ALGUMA ALTERAÇÃO
    change: this.onChangeDisclaimerGroup.bind(this),
    disclaimers: [ ],
    title: 'Filtros aplicados em nossa pesquisa'
  };

  ...

  constructor(private httpClient: HttpClient) { }

  ...

  // AO REMOVER O DISCLAIMER VAMOS REINICIAR OS FILTROS DA PESQUISA 
  // E VAMOS CHAMAR A FUNÇÃO LOADDATA NOVAMENTE SEM FILTROS
  private onChangeDisclaimerGroup(disclaimers) {
    this.searchTerm = undefined;
    this.page = 1;

    this.loadData();
  }

  ...

}

Legal, mas temos um problema aqui e vale a pena parar para entender algumas coisas. Se você colocar um debugger dentro da função loadData, você vai perceber que ela está sendo disparada mais de uma vez.

A primeira chamada acontece quando efetuamos nossa pesquisa, e a segunda acontece quando atualizamos o nosso grupo de disclaimers, fazendo com que a nossa pesquisa passe a funcionar de maneira estranha.

O que acontece é que o nosso disclaimer dispara o evento change a cada mudança, ou seja, quando recebe um disclaimer quando efetuamos uma pesquisa e quando o usuário remove um disclaimer.

Bom, isso pode parecer ruim, mas na verdade podemos usar a nosso favor. Mudando um pouco o fluxo de pesquisa nós podemos fazer com que as pesquisas apenas atualizem o nosso grupo de disclaimers e quando o mesmo sofrer qualquer alteração, disparamos a função loadData baseada no que tem no grupo de disclaimers.

Vamos ver o código refatorado.

Função onActionSearch e onChangeDisclaimerGroup refatoradas

...

// NOVO IMPORT
import { ThfDisclaimer } from '@totvs/thf-ui/components/thf-disclaimer';

...

export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  constructor(private httpClient: HttpClient) { }

  ...

  // AO EFETUAR NOSSA PESQUISA RÁPIDA PASSAMOS APENAS A ATUALIZAR OS DISCLAIMERS
  // SEM PRECISAR CHAMAR A NOSSA FUNÇÃO DE CARGA DE DADOS
  private onActionSearch() {
    this.disclaimerGroup.disclaimers = [{
      label: `Pesquisa rápida: ${this.searchTerm}`,
      property: 'search',
      value: this.searchTerm
    }];
  }

  // AGORA CADA VEZ QUE O GRUPO DE DISCLAIMERS RECEBER UMA ATUALIZAÇÃO, O MESMO
  // REINICIA A PESQUISA COM OS FILTROS NECESSÁRIOS
  private onChangeDisclaimerGroup(disclaimers: Array<ThfDisclaimer>) {
    this.searchFilters = {};

    this.page = 1;

    // TRANSFORMAR OS DISCLAIMERS EM PARAMETROS PARA A PESQUISA
    disclaimers.forEach(disclaimer => {
      this.searchFilters[disclaimer.property] = disclaimer.value;
    });

    if (!this.searchFilters.search) {
      this.searchTerm = undefined;
    }

    this.loadData(this.searchFilters);
  }

  ...

}

Agora só falta acertar nossa pesquisa avançada da mesma forma e teremos nossa funcionalidade de pesquisa 100% funcional.

Função onConfirmAdvancedFilter refatorada

...

export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  constructor(private httpClient: HttpClient) { }

  ...

  // FUNÇÃO REFATORADA PARA ADICIONAR OS DISCLAIMERS QUANDO O USUÁRIO CONFIRMAR A PESQUISA AVANÇADA
  private onConfirmAdvancedFilter() {
    const addDisclaimers = (property: string, value: string, label: string) =>
      value && this.disclaimerGroup.disclaimers.push({property, value, label: `${label}: ${value}`});

    this.disclaimerGroup.disclaimers = [];

    addDisclaimers('city', this.city, 'Cidade');
    addDisclaimers('genre', this.genre, 'Gênero');
    addDisclaimers('name', this.name, 'Nome');
    addDisclaimers('status', this.status ? this.status.join(',') : '', 'Status');

    this.advancedFilter.close();
  }

  ...

}

Olha como o resultado final ficou bacana:

Existem outras formas de tratar o comportamento do disclaimer group, mas para o nosso exemplo, essa é a forma mais simples e prática de resolver e ainda tirar vantagem desse comportamento.

E agora?

Agora, você pode brincar com o exemplo e adicionar novas formas de pesquisar criando filtros pré definidos entre outras opções. Uma outra ideia de implementação é limpar a pesquisa avançada quando o usuário faz a pesquisa rápida.

Você pode pegar os fontes atualizados no repositório do GitHub. Fiz o possível para commitar passo a passo para que seja possível acompanhar a evolução do que fizemos até aqui.

No próximo artigo vamos começar a cadastrar nossos clientes. Vejo vocês por lá!

Referências e dicas de leitura

Documentação oficial do THF:

Documentação oficial do Angular:

***

Artigo original publicado no TOTVS Developers e republicado com autorização do autor, Jhosef Marks: https://medium.com/totvsdevelopers/criando-um-crud-com-thf-pesquisando-pelos-nossos-clientes-75d3d2ecbcc