Quando desenvolvemos uma aplicação ASP.NET MVC, em geral, utilizamos o modelo padrão de funcionamento, em que uma chamada a um método de ação de um controller (action) resulta no direcionamento do usuário para uma nova página. Existem ainda casos em que podemos retornar algum resultado em formato não visual, como JSON ou XML.

Neste formato, quando desejamos realizar algum procedimento em que haja entrada de dados, o mais comum é criarmos links que direcionam o usuário para uma nova página contendo um formulário, que por sua vez, quando submetido, faz com que uma action seja executada e em seguida redirecione o usuário para a página anterior, ou para outra específica.

Porém, haverá casos em que, dependendo do modelo de interface visual utilizado em nossa aplicação, bem como o objetivo desta, podemos desejar que as ações referentes a uma certa página fiquem mais centralizadas, reduzindo o número de redirecionamentos e carregamentos de página. Nestes casos, uma das formas mais comumente utilizadas consiste na utilização de janelas modais, que se abrem na forma de popup sobre a interface principal, sem que seja necessário redirecionar o usuário para uma página totalmente nova.

Para implementar este tipo de funcionalidade utilizamos principalmente JavaScript (além de HTML e CSS, claro), uma vez que se trata de uma funcionalidade do front-end. Mas não é necessário que tenhamos todo o trabalho de desenvolver essas funções, pois existem diversos frameworks que realizam boa parte do processo e oferecem uma interface simples para utilização.

Neste artigo utilizaremos a jQuery UI, uma biblioteca de componentes visuais desenvolvida com base na jQuery, a principal biblioteca JavaScript da atualidade. Para implementar a janela modal, esta biblioteca nos oferece o componente Dialog, cuja documentação completa pode ser encontrada no link apresentado no final deste artigo.

Na aplicação que desenvolveremos aqui simularemos um pequeno cadastro de clientes, com as operações de CRUD (Create, Read, Update e Delete ou Criação, Leitura, Atualização e Exclusão) sendo realizadas em janelas modais. Não é nosso objetivo aqui tratar questões como design e validações, por isso criaremos uma aplicação ASP.NET MVC vazia.

A classe modelo que utilizaremos pode ser vista na Listagem 1. Questões relacionadas à persistência dos dados não serão abordadas aqui, uma vez que não influenciarão diretamente na parte do front-end onde faremos uso das janelas modais. Então criaremos apenas uma classe contendo uma lista estática para armazenar temporariamente alguns itens (Listagem 2).


public class Cliente
{
    private int codigo;
 
    public int Codigo
    {
        get { return codigo; }
        set { codigo = value; }
    }
        
    private string nome;
 
    public string Nome
    {
        get { return nome; }
        set { nome = value; }
    }
 
    private string telefone;
 
    public string Telefone
    {
        get { return telefone; }
        set { telefone = value; }
    }
}
Listagem 1. Classe cliente (model)

public class Context
{
    private static List<Cliente> clientes;
 
    public static List<Cliente> Clientes
    {
        get {
            if (clientes == null)
                IniciarClientes();
            return Context.clientes;
        }
        set { Context.clientes = value; }
    }
 
    private static void IniciarClientes()
    {
        clientes = new List<Cliente>();
        clientes.Add( new Cliente() { Codigo = 1, Nome = "Cliente 001", Telefone = "3322-1144" });
        clientes.Add( new Cliente() { Codigo = 2, Nome = "Cliente 002", Telefone = "1234-5678" });
        clientes.Add( new Cliente() { Codigo = 3, Nome = "Cliente 003", Telefone = "9988-7766" });
    }
}
Listagem 2. Classe Context (responsável por armazenar os dados de teste)

Com essas classes criadas podemos passar à criação do controller. Como aqui não temos nenhum mecanismo real de persistência, criaremos um controller a partir da opção “MVC Controller with empty read/write actions”, que gera para nós um controlador com as ações padrão (Index, Details, Create, Edit e Delete).

Em nossa aplicação, a interface será organizada da seguinte forma: teremos uma view principal (Index) na qual serão inseridas as views secundárias quando for necessário. Como já temos as ações Create, Details, Edit e Delete, precisamos criar uma nova action chamada List, responsável por renderizar a lista de clientes em uma tabela. O código dessa action pode ser visto na Listagem 3. As demais actions serão omitidas deste artigo, pois não há nelas nenhum detalhe relevante a ser abordado aqui, porém, o código fonte encontra-se disponível para download no topo da página e pode ser estudado posteriormente.


public ActionResult List()
{
    return PartialView(Context.Clientes);
}
Listagem 3. Action responsável por listar os registros

Seguindo a ordem, agora que já temos o model e o controller, o próximo passo é criar as views. Mas antes disso é necessário importarmos os arquivos da jQuery UI. Para isso, é necessário que façamos o download do pacote no site da biblioteca e copiemos os arquivos para dentro das pastas Content e Scripts de nosso projeto. A estrutura do projeto deve ficar como mostra a Figura 1.

Estrutura do projeto com arquivos da jQuery UI
Figura 1. Estrutura do projeto com arquivos da jQuery UI

Agora sim podemos começar a criar nossas views. A primeira delas será a view Index, que irá conter as referências aos arquivos da jQuery UI, como vemos na Listagem 4. Note que há uma div chamada divClientes onde inicialmente renderizamos o resultado da action List, que terá como resultado uma Partial View contendo os registros listados e mais alguns detalhes que serão vistos a seguir.


@{
    Layout = null;
}
 
<!DOCTYPE html>
 
<html>
  <head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
    <meta charset="utf-8" />
    <link href="~/Content/jquery-ui.css" rel="stylesheet" />
    <link href="~/Content/jquery-ui.theme.css" rel="stylesheet" />
    <script src="~/Scripts/jquery.js"></script>
    <script src="~/Scripts/jquery-ui.js"></script>
</head>
<body>
    <div id="divClientes">
        @Html.Action("List")
    </div>
  </body>
</html>
Listagem 4. View Index

Esta div será usada posteriormente via JavaScript para recarregar a lista de clientes atualizada, após cada ação de adição, edição ou exclusão.

A próxima view a ser criada é a List, presente na Listagem 5, que contém uma tabela para listar os itens da lista.


@model IEnumerable<ASPNETMVC_jQueryUI_Dialog.Models.Cliente>
 
@{
    Layout = null;
}
 
<table class="ui-widget ui-state-default ui-corner-all">
    <thead class="ui-widget-header">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Codigo)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Nome)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Telefone)
        </th>
        <th>
            <button class="ui-state-default ui-corner-all btn-create">
                <span class="ui-icon ui-icon-plus"/>
            </button>
        </th>
    </tr>
    </thead>
@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Codigo)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Nome)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Telefone)
        </td>
        <td>
            <button class="btn-delete ui-state-default ui-corner-all" 
            data-codigo="@item.Codigo">
                <span class="ui-icon ui-icon-trash"/>
            </button>
            <button class="btn-edit ui-state-default ui-corner-all" 
            data-codigo="@item.Codigo">
                <span class="ui-icon ui-icon-pencil"/>
            </button>
            <button class="btn-details ui-state-default ui-corner-all" 
            data-codigo="@item.Codigo">
                <span class="ui-icon ui-icon-document"/>
            </button>
        </td>
    </tr>
}    
</table>
 
<div id="modal">
 
</div>
Listagem 5. View List

Nesta view existem alguns detalhes que merecem atenção, além da utilização das classes básicas da jQuery UI apenas para formatação visual:

  • No header da tabela criamos um botão que servirá para adicionar novos registros. Definimos para ele a classe btn-create, que será usada posteriormente no código JavaScript;
  • Em cada linha da tabela criamos três botões com as seguintes classes: btn-delete, btn-edit e btn-details, que serão usados para excluir, editar e exibir os detalhes de um registro, respectivamente. Note que cada um dos botões possui um atributo data-codigo, que em tempo de execução irá conter o valor da propriedade Codigo do cliente. Este código, por ser chave primária, será usado para fazer uma chamada assíncrona às demais actions.
  • Criamos uma div vazia com o id “modal”. Essa div será usada para carregar as views secundárias e será exibida na forma de janela modal usando o método dialog da biblioteca.

Executando a aplicação teremos um resultado igual ao que vemos na Figura 2.

View List carregada dentro da view Index
Figura 2. View List carregada dentro da view Index

Ainda dentro da view List, vamos adicionar agora o código JavaScript (usando jQuery) responsável por abrir cada uma das janelas modais. O código apresentado na Listagem 6 deve ser adicionado no final da view List.


<script>
  $(function () {
      $(".btn-create").click(function () {
          $("#modal").load("/clientes/create").attr("title", 
          "Adicionar cliente").dialog();                
      });

      $(".btn-details").click(function () {
          var codigo = $(this).attr("data-codigo");
          $("#modal").load("/clientes/details/" + codigo).attr("title", "Dados do cliente").dialog();
      });

      $(".btn-edit").click(function () {
          var codigo = $(this).attr("data-codigo");
          $("#modal").load("/clientes/edit/" + codigo).attr("title", "Editar cliente").dialog();
      });

      $(".btn-delete").click(function () {
          var codigo = $(this).attr("data-codigo");
          $("#modal").load("/clientes/delete/" + codigo).attr("title", "Excluir cliente").dialog();
      });
  });
</script>
Listagem 6. Código jQuery responsável por abrir os dialogs

No click do botão create basicamente utilizamos o método load da jQuery para fazer uma requisição assíncrona e carregar o conteúdo da view Create (que será criada a seguir) dentro da div “modal”, que até então encontra-se vazia. Em seguida, definirmos o atributo title dessa div que será usado como cabeçalho da janela modal. Por fim, utilizamos o método dialog, que é o responsável por exibir a janela.

Nos demais botões, o procedimento é bastante semelhante, porém neles é necessário que passemos o código do cliente selecionado para as actions, pois lá será filtrado somente aquele cliente e esse objeto será repassado à view correspondente. Uma vez concluída a chamada à action, essa nos entregará a view já preenchida, que será carregada dentro da div modal e aberta na forma de popup posteriormente.

Como ainda não criamos as demais views, por isso esse código ainda não funciona. Então passemos à criação das views secundárias, começando pela view Details, cujo código é exibido na Listagem 7.


@model ASPNETMVC_jQueryUI_Dialog.Models.Cliente
 
<p>@Html.DisplayNameFor(model => model.Codigo): 
@Html.DisplayFor(model => model.Codigo) </p>
<p>@Html.DisplayNameFor(model => model.Nome): 
@Html.DisplayFor(model => model.Nome) </p>
<p>@Html.DisplayNameFor(model => model.Telefone): 
@Html.DisplayFor(model => model.Telefone) </p>
Listagem 7. View Details

Como é comum para esta view, ela é bastante simples, pois serve apenas para exibição de dados estáticos referentes ao registro, sem controles de entrada de dados. Observe que é este conteúdo que será inserido dentro da div modal lá na view List quando utilizarmos o método load da jQuery.

Agora, se executarmos a aplicação já podemos clicar no terceiro botão de cada linha para ver os detalhes do registro, como mostra a Figura 3.

Janela modal com detalhes do cliente
Figura 3. Janela modal com detalhes do cliente

Passemos agora à criação da View Delete (Listagem 8), cujo código também é bastante simples, mas já possui um trecho de JavaScript que merece atenção.


@model ASPNETMVC_jQueryUI_Dialog.Models.Cliente
 
<form id="formDelete">
    <p> Deseja realmente excluir o cliente <strong>
    @Html.DisplayFor(model => model.Nome)</strong>?</p>
 
    @Html.HiddenFor(model => model.Codigo)
</form>
 
<hr />
 
<div>
    <button class="ui-state-default ui-corner-all" id="btnDelete"
    >Excluir</button>
    <button class="ui-state-default ui-corner-all" id="btnClose"
    >Cancelar</button>
</div>
 
<script>
    $("#btnDelete").click(function () {
        $.post("/clientes/delete", $("#formDelete").serialize()
          ).done(function () {
              $("#divClientes").load("/clientes/list");
          });
        $("#modal").dialog("close");
    });
    $("#btnClose").click(function () {
        $("#modal").dialog("close");
    });
</script>
Listagem 8. View Delete

Nesta view temos um form contendo apenas uma mensagem e um campo oculto (hidden) com o código do cliente. Este campo é necessário para que repassemos à action Delete, via método POST, qual é o cliente que será excluído. Neste form poderíamos adicionar outras informações, como os demais campos do registro, mas para este exemplo apenas o código é necessário.

No código JavaScript tratamos o evento click dos dois botões adicionados logo abaixo do form. Ao clicarmos no botão btnDelete, utilizaremos o método post da jQuery para postar as informações contidas no form para a action Delete. Como esta função faz a requisição utilizando o método POST, sabemos que a action invocada será aquela marcada com o atributo [HttpPost]. A função serialize pega os dados do formulário selecionado e os retorna no formato padrão utilizado na passagem de parâmetros pela URLs (URL-encoded). Esses dados serão recebidos pela nossa action para efetuar a ação de exclusão em nossa base de dados. O método done é executado quando o post é finalizado, e neste caso fazemos com que a div divClientes, contida na view Index, recarregue o conteúdo da view List, trazendo os dados já atualizados. Em seguida, executamos o método dialog passando como parâmetro o valor “close”, que indica que o modal deve ser fechado. O mesmo é feito no click do botão btnClose, que simplesmente fecha o modal.

Executando novamente nossa aplicação, temos o resultado mostrado na Figura 4 e se clicarmos em Excluir, a lista será atualizada sem que haja novo carregamento da página, pois tudo agora é feito de forma assíncrona via Ajax (Figura 5).

Janela modal para exclusão de registro
Figura 4. Janela modal para exclusão de registro
Lista atualizada após exclusão
Figura 5. Lista atualizada após exclusão

Vamos agora criar a view Edit, cujo código pode ser visto na Listagem 9 e difere da view Delete apenas pelo fato de conter campos para entrada de dados pelo usuário. Aqui utilizamos os HTML Helpers padrão do framework para gerar os inputs, que como se trata da view Edit, já virão preenchidos com os valores das propriedades do objeto selecionado. Lembre-se que na view List, quando fazemos a requisição à action Edit usando o método load da jQuery, passamos para ela o código do cliente que estamos editando. Essa action resultará em uma view fortemente tipada com um formulário para edição dos dados. Essa view, por sua vez, será carregada dentro da div modal e exibida na forma de popup (Figura 6).


@model ASPNETMVC_jQueryUI_Dialog.Models.Cliente
 
<form id="formEdit">
        <div class="editor-label">
            @Html.LabelFor(model => model.Codigo)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Codigo)
            @Html.ValidationMessageFor(model => model.Codigo)
        </div>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.Nome)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Nome)
            @Html.ValidationMessageFor(model => model.Nome)
        </div>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.Telefone)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Telefone)
            @Html.ValidationMessageFor(model => model.Telefone)
        </div>
</form>
<hr />
<div>
    <button class="ui-state-default ui-corner-all" id="btnSave">Salvar</button>
    <button class="ui-state-default ui-corner-all" id="btnClose">Cancelar</button>
</div>
 
<script>
    $("#btnSave").click(function () {
        $.post("/clientes/edit", $("#formEdit").serialize()
          ).done(function () {
            $("#divClientes").load("/clientes/list");
         });
        $("#modal").dialog("close");
    });
    $("#btnClose").click(function () {
        $("#modal").dialog("close");
    });
</script>
Listagem 9. View Edit

Repare novamente na presença do método serialize que irá enviar o conteúdo do formulário para a action Edit marcada com [HttpPost]. Após a chamada assíncrona, a lista será mais uma vez atualizada (Figura 7) e o dialog será fechado.

Janela modal para edição de registro
Figura 6. Janela modal para edição de registro
Lista atualizada após edição
Figura 7. Lista atualizada após edição

Por fim, vamos agora criar a view Create, que será exibida como resultado da action Create invocada a partir do botão que inserimos no header da tabela. Esta view é extremamente semelhante à view Edit, diferindo apenas na action que é utilizada para postar os dados (Create ao invés de Edit). O código dessa view pode ser visto na Listagem 10.


@model ASPNETMVC_jQueryUI_Dialog.Models.Cliente
 
<form id="formCreate">
    @Html.ValidationSummary(true)
 
    <div class="editor-label">
        @Html.LabelFor(model => model.Codigo)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Codigo)
        @Html.ValidationMessageFor(model => model.Codigo)
    </div>
 
    <div class="editor-label">
        @Html.LabelFor(model => model.Nome)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Nome)
        @Html.ValidationMessageFor(model => model.Nome)
    </div>
 
    <div class="editor-label">
        @Html.LabelFor(model => model.Telefone)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Telefone)
        @Html.ValidationMessageFor(model => model.Telefone)
    </div>
 
</form>
    <hr />
    <div>
        <button class="ui-state-default ui-corner-all" id="btnSave">Salvar</button>
        <button class="ui-state-default ui-corner-all" id="btnClose">Cancelar</button>
    </div>
 
<script>
    $("#btnSave").click(function () {
        $.post("/clientes/create", $("#formCreate").serialize()
          ).done(function () {
            $("#divClientes").load("/clientes/list");
         });
        $("#modal").dialog("close");
    });
    $("#btnClose").click(function () {
        $("#modal").dialog("close");
    });
</script>
Listagem 10. View Create

A action Create que é chamada na view List apenas nos retorna essa view com um form vazio para inserirmos os dados do novo registro. Já action Create marcada com [HttpPost] irá receber os dados desse formulário que são postados aqui de forma assíncrona, sem que haja reload da página.

Na Figura 8 temos a janela modal aberta para inserção dos dados e em seguida, na Figura 9, temos nossa lista atualizada com o novo registro inserido.

Janela modal para inserção de registro
Figura 8. Janela modal para inserção de registro
Lista atualizada após inserção
Figura 9. Lista atualizada após inserção

Trabalhar com métodos assíncronos nos permite oferecer uma interface mais fluida durante ações que necessitem de requisições adicionais ao servidor, como é o caso do CRUD aqui demonstrado. Desenvolvemos todas as nossas funcionalidades de cadastro em uma única tela, sem que fosse necessário redirecionar o usuário a outras páginas e depois retornar à listagem dos dados.

Este modelo, porém, deve ser utilizado com cautela, pois como utilizamos JavaScript, o simples fato de o usuário bloquear a execução de scripts em seu browser faria com que nossa aplicação não funcionasse. Existem também outras funções da jQuery que podem ser usadas a fim de melhorar a execução desses procedimentos, permitindo por exemplo o tratamento de erros no caso em que as requisições falhassem. Na documentação oficial, cujo link pode ser visto logo abaixo, é possível encontrar vários exemplos de como utilizar os métodos load, post, serialize, e outros.

Links:
  • http://jqueryui.com/
  • http://jquery.com/