Desenvolvimento - ASP. NET
ASP.NET MVC: Exibindo dados no formato mestre-detalhe
Veja neste artigo como exibir dados no formato mestre-detalhe em uma aplicação ASP.NET MVC.
por Joel RodriguesÉ comum encontrarmos, nos mais variados tipos de sistemas, relacionamentos entre objetos do tipo mestre-detalhe, que basicamente é um relacionamento 1..n, ou um-para-muitos onde o registro do lado 1 é chamado mestre, e os n registros do outro lado são chamados de detalhes.
Um exemplo muito comum desse tipo de relacionamento, e que utilizaremos neste artigo, é o que ocorre entre uma venda e seus itens, onde uma venda (mestre) possui vários itens (detalhes).
Neste artigo veremos uma forma bastante simples e prática para listar em uma mesma tela os dados de todos os registros, tanto mestre quanto seus detalhes, utilizando um recurso muito conhecido em que permitimos ao usuário expandir ou ocultar os dados dos detalhes a partir de um botão contido no registro mestre.
Para que possamos trabalhar com um cenário ligeiramente próximo do real, vamos antes de tudo montar nossa camada modelo, na qual teremos as classes Venda, ItemVenda e Produto, que se relacionam entre si e representam nossos objetos de negócio, e a classe Context, que aqui basicamente simulará nossa classe de acesso a dados e gerará para nós alguns dados para teste. Essas classes podem ser vistas nas Listagens 1 a 4.
Listagem 1. Classe Produto (model)
public class Produto { private string _referencia; public string Referencia { get { return _referencia; } set { _referencia = value; } } private string _descricao; public string Descricao { get { return _descricao; } set { _descricao = value; } } private decimal _preco; public decimal Preco { get { return _preco; } set { _preco = value; } } }
Note que a classe Produto se relaciona com a classe ItemVenda no formato 1..n (um Produto pode estar em vários itens, mas um item só pode conter um produto).
Listagem 2. Classe ItemVenda (model)
public class ItemVenda { private Produto _produto; public Produto Produto { get { return _produto; } set { _produto = value; } } private int _quantidade; public int Quantdade { get { return _quantidade; } set { _quantidade = value; } } }
Entre a classe Venda e a ItemVenda temos um relacionamento também do tipo 1..n, em que uma venda pode conter vários itens, mas um item só pode estar contido em uma venda (no banco de dados esse relacionamento é concretizado através de uma chave estrangeira na classe de itens apontando para a chave primária da venda).
Listagem 3. Classe Venda (model)
public class Venda { private int _codigo; public int Codigo { get { return _codigo; } set { _codigo = value; } } private DateTime _dataHora; public DateTime DataHora { get { return _dataHora; } set { _dataHora = value; } } private List<ItemVenda> _itens; public List<ItemVenda> Itens { get { if (_itens == null) _itens = new List<ItemVenda>(); return _itens; } set { _itens = value; } } }
A classe Context basicamente contém uma coleção de vendas, representando a tabela do banco de dados que possivelmente seria mapeada através de uma ferramenta de ORM, e gera alguns dados para teste.
Listagem 4. Classe Context (model)
public class Context { public Context() { _vendas = new List<Venda>(); GerarDadosParaTeste(); } private List<Venda> _vendas; public List<Venda> Vendas { get { return _vendas; } set { _vendas = value; } } private void GerarDadosParaTeste() { Produto p1 = new Produto() { Referencia = "001", Descricao = "Produto 1", Preco = 10 }; Produto p2 = new Produto() { Referencia = "002", Descricao = "Produto 2", Preco = 20 }; Produto p3 = new Produto() { Referencia = "003", Descricao = "Produto 3", Preco = 30 }; Venda v1 = new Venda() { Codigo = 110, DataHora = DateTime.Now }; Venda v2 = new Venda() { Codigo = 220, DataHora = DateTime.Now.AddDays(-1) }; Venda v3 = new Venda() { Codigo = 330, DataHora = DateTime.Now.AddDays(-2).AddHours(1) }; v1.Itens.Add(new ItemVenda() { Produto = p1, Quantdade = 1 }); v1.Itens.Add(new ItemVenda() { Produto = p2, Quantdade = 1 }); v2.Itens.Add(new ItemVenda() { Produto = p1, Quantdade = 5 }); v2.Itens.Add(new ItemVenda() { Produto = p2, Quantdade = 2 }); v3.Itens.Add(new ItemVenda() { Produto = p1, Quantdade = 2 }); v3.Itens.Add(new ItemVenda() { Produto = p2, Quantdade = 4 }); v3.Itens.Add(new ItemVenda() { Produto = p3, Quantdade = 7 }); _vendas.Add(v1); _vendas.Add(v2); _vendas.Add(v3); } }
Nosso controller é bastante simples e por isso não será mostrado aqui, ele basicamente contém uma action em que instanciamos um objeto Context e passamos a lista de vendas para uma view, cujo código pode ser visto na Listagem 5.
Listagem 5. View para exibição dos dados
@model IEnumerable<MvcApplication1.Models.Venda> <table> <tr> <th></th> <th> @Html.DisplayNameFor(model => model.Codigo) </th> <th> @Html.DisplayNameFor(model => model.DataHora) </th> <th></th> </tr> @foreach (var venda in Model) { <tr> <td> <button class="btnDetails">+</button> </td> <td> @Html.DisplayFor(modelItem => venda.Codigo) </td> <td> @Html.DisplayFor(modelItem => venda.DataHora) </td> </tr> <tr class="collapse"> <td colspan="3"> <table> <thead> <tr> <th>Referência</th> <th>Produto</th> <th>Preço</th> <th>Quantidade</th> <th>Subtotal</th> </tr> </thead> <tbody> @foreach (var item in venda.Itens) { <tr> <td>@item.Produto.Referencia</td> <td>@item.Produto.Descricao</td> <td>@item.Produto.Preco</td> <td>@item.Quantdade</td> <td>@(item.Produto.Preco * item.Quantdade)</td> </tr> } </tbody> </table> </td> </tr> } </table>
Note na listagem acima que para cada venda na coleção nós teremos duas linhas (tr) na tabela. Uma para exibir o cabeçalho da venda e outra onde ficará uma tabela interna com os dados dos itens. Nesta segunda linha definimos uma classe CSS chamada colapse, que simplesmente deixa essa linha invisível.
Adicionamos também uma coluna a mais nos dados da venda e nela colocamos um botão, que será usado para expandir os dados dos detalhes.
Na Listagem 6 vemos o código CSS utilizado neste exemplo.
Listagem 6. CSS para ocultar a linha dos detalhes e destacar os dados
.collapse { display:none } td table { background-color:beige }
Se executarmos a aplicação neste ponto, teremos um resultado como o que é visto na Figura 1.
Figura 1. Dados da tabela master sem detalhes
Agora, basta que tratemos o evento click daquele botão que adicionamos em cada linha da tabela para que ele exiba (ou oculte) os itens da venda. Na Listagem 7 podemos ver o código jQuery utilizado para isso, que foi colocado dentro da section scripts que deve ser adicionada no final da view.
Listagem 7. Script para expandir/ocultar os detalhes
@section scripts{ <script> $(function () { $(".btnDetails").click(function () { $(this).parent().parent().next().toggleClass("collapse"); $(this).text($(this).text() == "+"?"-":"+"); }); }); </script> }
Na primeira linha do corpo do evento apresentado no código acima temos a seguinte sequência de comandos:
- $(this): referencia o botão que foi clicado.
- parent(): referencia o elemento pai do botão, no caso a coluna (td).
- parent(): referencia o elemento pai do pai do botão, no caso a linha (tr).
- next(): seleciona o próximo elemento após a linha referenciada, que nesse caso é a linha onde estão os itens da venda.
- toggleClass(“collapse”): adiciona ou remove a classe collapse da linha dos itens selecionada, invertendo seu estado.
Já na segunda linha nós basicamente alteramos o texto do botão para + (exibir detalhes) ou – (ocultar detalhes), invertendo seu estado atual.
Agora, se executarmos a aplicação podemos clicar nos botões e teremos um resultado como o que pode ser visto na Figura 2.
Figura 2. Dados dos detalhes exibidos
Repare que é bastante simples exibir dados no formato mestre-detalhe em uma aplicação ASP.NET MVC, basta definir corretamente os relacionamentos entre as classes e usar um pouco de JavaScript para dar funcionalidade ao front-end.