Combobox com Ajax


O problema

Você precisa exibir/associar um e apenas um usuário que está responsável por uma estação de tratamento. Além disso, a tela deverá oferecer um módulo busca, para filtrar os dados para a escolha do usuário.

Nosso Entity seria definido assim:

public class EstacaoTratamento : Entidade
{
    #region Constantes
        public const int NomeMaxLength = 150;
    #endregion
    #region Atributos do Banco
        [Required, StringLength(NomeMaxLength)]
        public String Nome { get; set; }
        public float? Latitude { get; set; }
        public float? Longitude { get; set; }
        public long? RespTecnicoId { get; set; }
    #endregion

    #region Chaves Estrangeiras
        [ForeignKey("RespTecnicoId")]
        public User RespTecnico { get; set; }
    #endregion
}

Resolução para telas de Create/edit

  1. Criar um método no service para retornar uma lista de DTO.

    Este método será utilizado pelo controller quando o usuário quiser filtrar os dados da tela.

     public async Task<List<UserDto>> GetUsersBy(string nameOrEmail)
     {
         var usersquery = Repository.GetAll()
             .Where(u =>
                 u.Name.Contains(nameOrEmail) || u.EmailAddress.Contains(nameOrEmail)
         );
         var users = await usersquery.ToListAsync();
    
         return ObjectMapper.Map<List<UserDto>>(users);
     }
    
  2. Fazer com que o controller utilize o método definido no service

    No nosso caso o método responsável por montar a tela de edição (view _createOrEdit.cshtml) é o public async Task<ViewResult> CreateOrEdit(int? id).

    Este método, quando invocado, retorna um elemento do tipo CreateOrEditEstacaoTratamentoModalViewModel.

    Assim, nosso método é definido como:

     public async Task<ViewResult> CreateOrEdit(int? id)
     {
         GetEstacaoTratamentoForEditOutput getEstacaoTratamentoForEditOutput = new GetEstacaoTratamentoForEditOutput
             {
                 EstacaoTratamento = new EstacaoTratamentoDto()
             };
         //para editar
         if (id.HasValue)
         {
             getEstacaoTratamentoForEditOutput.EstacaoTratamento = await _estacaoTratamentoAppService.Get(new EntityDto<int> { Id = id.Value });
             if (getEstacaoTratamentoForEditOutput.EstacaoTratamento == null)
             {
                 throw new UserFriendlyException(404, L("ItemNotFound"));
             }
         }       
    
         var viewModel = new CreateOrEditEstacaoTratamentoModalViewModel()
         {
             EstacaoTratamento = getEstacaoTratamentoForEditOutput.EstacaoTratamento
         };
         //Como o responsável técnico não é obrigatório, então precisamos verificar se tem algum valor
         if (getEstacaoTratamentoForEditOutput.EstacaoTratamento.RespTecnicoId.HasValue)
         {
             var responsavelTecnico = _userAppService.Get(new EntityDto<long>(getEstacaoTratamentoForEditOutput.EstacaoTratamento.RespTecnicoId.Value)).Result;
             if (responsavelTecnico != null)
             {
                 viewModel.Responsaveis = new List<SelectListItem>();
                 viewModel.Responsaveis.Add(
                     new SelectListItem(responsavelTecnico.Name, responsavelTecnico.Id.ToString(),true)
                 );
             }
             else
             {
                 throw new UserFriendlyException(404, L("EstacaoTratamento.RespTecnico.NotFound"));
             }
         }
         return View("_CreateOrEdit", viewModel);
     }
    
  3. Alterar os DTO incluindo as propriedades necessárias

    • CreateOrEditEstacaoTratamentoInput
    [AutoMapTo(typeof(EstacaoTratamento))]
     public partial class CreateOrEditEstacaoTratamentoInput : IEntityDto
     {
         public int Id { get; set; }
         [Required]
         [StringLength(EstacaoTratamento.NomeMaxLength)]
         public string Nome { get; set; }
         public Single? Latitude { get; set; }
         public Single? Longitude { get; set; }
         public long? RespTecnicoId { get; set; }
     }
    
    • CreateOrEditEstacaoTratamentoModalViewModel
     public class CreateOrEditEstacaoTratamentoModalViewModel
     {
         public CreateOrEditEstacaoTratamentoInput EstacaoTratamento { get; set; }
         public bool IsEditMode => EstacaoTratamento != null && EstacaoTratamento.Id != 0;
         public List<SelectListItem> Responsaveis { get; set; }
     }
    
  4. Adicionar o campo na view do tipo select

     <div class="col-sm-12">
         <div class="form-group">
             <div class="form-line">
                 <label for="RespTecnico" class="form-label">@L("EstacaoTratamento.RespTecnico")</label>
             </div>
             <br />
             <br />
             <div class="form-line">
                 <select id="RespTecnico"
                         name="RespTecnicoId"
                         asp-for="EstacaoTratamento.RespTecnicoId"
                         asp-items="Model.Responsaveis"
                         class="form-control"
                         title="Escolha um @L("EstacaoTratamento.RespTecnico")"
                         data-live-search="true"></select>
             </div>
         </div>
     </div>
    

    Note que:

    • id="RespTecnico" será utilizado pelo Javascript para realizra as consultas ajax;
    • name="RespTecnicoId" valor que será enviado no JSON na requisição de salvar/editar;
    • asp-for="EstacaoTratamento.RespTecnicoId" é o campo que contém o valor inicial a ser selecionado;
    • asp-items="Model.Responsaveis" é a coleção com os valores a serem exibidos inicialmente (no caso de alteração será adicionado o responsável que foi coletado previamente);
    • data-live-search="true" informa que o campo é “pesquisável”;
  5. Adicionar a chamada AJAX no JS

    No arquivo _CreateOrEdit.js adicione a chamada JS para configurar o componente com AJAX.

     (function ($) {
         $(function () {
             /** Omitido */
             $('#RespTecnico').ajaxSelectPicker({
                 preserveSelected: false, 
                 minLength: 2,
                 ajax: {
                     url: abp.appPath + 'ETESystem/EstacaoTratamento/GetUsersBy',
                     type: 'POST',
                     dataType: 'json',
                     data: {
                         userNameOrEmail: '{ { { q } } }' //Remova os espaços entre as chaves
                     }
                 },
                 preprocessData: function (data) {
                     var i, l = data.items.length, array = [];
                     if (l) {
                         for (i = 0; i < l; i++) {
                             array.push($.extend(true, data.items[i], {
                                 text: data.items[i].name,
                                 value: data.items[i].id,
                                 data: {
                                     subtext: data.items[i].emailAddress
                                 }
                             }));
                         }
                     }
                     return array;
                 }
    
             });
             /** Omitido */
         });
     })(jQuery);
    

    Observe que o parâmetro preserveSelected: false é obrigatório para remover o erro que existe ao selecionar um novo item da lista. Para maiores informações consulte aqui e aqui. —

Resolução para telas com grid

Caso necessite exibir algum dado de uma entidade relacionada, como por exemplo os dados do usuário que é responsável pela Estação de Tratamento, adicione o seguinte código no método do service que retorna os dados para o DTO.

public async Task<PagedResultDto<EstacaoTratamentoDto>> GetAll(GetAllEstacaoTratamentoInput input)
{

    var query = CreateEstacaoTratamentoQuery(input);
    query = query.Include(x => x.RespTecnico);
    var resultCount = await query.CountAsync();
    var results = await query
        .OrderBy(input.Sorting)
        .PageBy(input)
        .ToListAsync();

    //Exemplo de como alterar o mapeamento para uma view
    var config = new MapperConfiguration(
        cfg => cfg.CreateMap<EstacaoTratamento,
        EstacaoTratamentoDto>()
        //Ao invés de retornar o Name do usuário, o campo é preenchido como FullName
        .ForMember(d => d.RespTecnicoName, o => o.MapFrom(s => s.RespTecnico.FullName))
    );
    var m = config.CreateMapper();
    var y = m.Map<List<EstacaoTratamento>, List<EstacaoTratamentoDto>>(results);

    return new PagedResultDto<EstacaoTratamentoDto>(resultCount, y);
}

No Javascript do grid use <Entidade>.<Propriedade> em camelCase.


Referências

Para maiores detalhes consulte: