CRUD usando ExtJs e ASP NET MVC 3

Depois de falar sobre ExtJs Grid Panel e ExtJs FormPanel, hoje vou descrever como integrar os dois componentes, fazendo um CRUD usando Entity Framework para salvar as informações no banco de dados SqlServer

A idéia é criar um cadastro simples de uma entidade chamada Carro onde vamos listar os dados em uma grid e com as opções de Adicionar, Editar e Deletar, então mãos a obra

OBS: Não vou demostrar como criar o EDMX do Entity Framework, para isso veja esse artigo CRUD com Entity Framework e ASP NET MVC

Model

Primeiro de tudo vamos criar nosso model CarroModel, veja abaixo o código

public class CarroModel
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public string Cor { get; set; }
    public string Modelo { get; set; }
    public string IdModelo { get; set; }
    public decimal Valor { get; set; }
    public string TipoCombustivel { get; set; }
    public string IdTipoCombustivel { get; set; }
    public int QtdePortas { get; set; }
}

Controller

Agora vamos criar controller CarroController

public class CarroController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

Vamos criar um novo método chamado GetAll, que será responsável por recuperar os carros gravados no banco

public JsonResult GetAll(int start, int limit)
{
    TesteExtJsEntities entities = new TesteExtJsEntities();
    List lstCarros = entities.CreateObjectSet().ToList();

    // transforma no Model
    List retorno = lstCarros.Select(
                                obj => new CarroModel
                                {
                                    Id = obj.id,
                                    Nome = obj.nome,
                                    Cor = obj.cor,
                                    Valor = obj.valor,
                                    QtdePortas = obj.qtde_portas,
                                    Modelo = obj.modelo.nome,
                                    IdModelo = obj.modelo.id_modelo.ToString(),
                                    TipoCombustivel = obj.tipo_combustivel.nome,
                                    IdTipoCombustivel = obj.tipo_combustivel.id_tipo_combustivel.ToString()
                                }).ToList();

    int total = lstCarros.Count;
    lstCarros = lstCarros.Skip(start).Take(limit).ToList();
    return Json(new { carros = lstCarros, total = total }, JsonRequestBehavior.AllowGet);
}

Agora vamos criar o método para recuperar todos os Modelos e Combustiveis, será usado para carregar as combos na tela de cadastro

public JsonResult GetAllModelo()
{
    TesteExtJsEntities entities = new TesteExtJsEntities();
    List lstModelos = entities.CreateObjectSet().ToList();

    // transforma no Model
    List retorno = lstModelos.Select(
                            obj => new ModeloModel
                            {
                                Id = obj.id_modelo,
                                Nome = obj.nome
                            }).ToList();

    return Json(new { modelos = retorno, total = retorno.Count }, JsonRequestBehavior.AllowGet);
}

public JsonResult GetAllCombustivel()
{
    TesteExtJsEntities entities = new TesteExtJsEntities();
    List lstCombustivel = entities.CreateObjectSet().ToList();

    // transforma no Model
    List retorno = lstCombustivel.Select(
                            obj => new ModeloModel
                            {
                                Id = obj.id_tipo_combustivel,
                                Nome = obj.nome
                            }).ToList();

    return Json(new { combustiveis = retorno, total = retorno.Count }, JsonRequestBehavior.AllowGet);
}

O método Save, receberá os parâmetros e vai criar ou atualizar o objeto do tipo carro e persistir no banco usando o entity framework

public JsonResult Save(string Id, string Nome, string Cor, string Valor, string QtdePortas, string Modelo, string TipoCombustivel, string action)
{
    try
    {
        TesteExtJsEntities entities = new TesteExtJsEntities();

        if(action.Equals("insert"))
        {
            carro objCarro = new carro();
            objCarro.nome = Nome;
            objCarro.cor = Cor;
            objCarro.valor = Convert.ToDecimal(Valor);
            objCarro.qtde_portas = Convert.ToInt32(QtdePortas);
            objCarro.id_tipo_combustivel = Convert.ToInt32(TipoCombustivel);
            objCarro.id_modelo = Convert.ToInt32(Modelo);

            entities.AddTocarro(objCarro);
            entities.SaveChanges();
        }
        else if (action.Equals("update"))
        {
            int cod = int.Parse(Id);
            carro objCarro = entities.CreateObjectSet().Where(a => a.id == cod).ToList().First();

            // Altera os dados
            objCarro.nome = Nome;
            objCarro.cor = Cor;
            objCarro.valor = Convert.ToDecimal(Valor);
            objCarro.qtde_portas = Convert.ToInt32(QtdePortas);
            objCarro.id_tipo_combustivel = Convert.ToInt32(TipoCombustivel);
            objCarro.id_modelo = Convert.ToInt32(Modelo);

            entities.ApplyCurrentValues(objCarro.GetType().Name, objCarro);
            entities.SaveChanges();
        }

        return Json(new { success = true }, JsonRequestBehavior.AllowGet);
    }
    catch (Exception ex)
    {
        return Json(new { success = false, message = ex.Message }, JsonRequestBehavior.AllowGet);
    }
}

E por último o método para deletar um carro

public JsonResult Delete(int id)
{
    try
    {
        TesteExtJsEntities entities = new TesteExtJsEntities();
        carro entity = entities.carro.Single(a => a.id == id);
        entities.DeleteObject(entity);
        entities.SaveChanges();
        return Json(new { success = true }, JsonRequestBehavior.AllowGet);
    }
    catch (Exception ex)
    {
        return Json(new { success = false, message = ex.Message }, JsonRequestBehavior.AllowGet);
    }
}

View

Nesse exemplo vamos usar apenas um arquivo cshtml, para isso clique com o botão direito sobre a action Index do controller e selecione a opção Add View, conforme abaixo:

Primeiro de tudo temos que adicionar as referências das bibliotecas do ExtJs, veja abaixo

<link rel="stylesheet" type="text/css" href="../../ext-3.2.1/resources/css/ext-all.css"/>
<script type="text/javascript" src="../../ext-3.2.1/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../../ext-3.2.1/ext-all.js"></script>
Criando a Grid

Vamos criar uma grid onde será listado os registros com as opções de Adicionar / Excluir / Editar

Ext.onReady(function () {
    //titulo do formulário
    var formTitle = 'Cars ';

    // criamos um JsonStore que vai armazenar as informações da entidade carro
    var dsCarros = new Ext.data.JsonStore({
        url: '@Url.Content("~/Carro/GetAll")',
        root: 'carros',
        totalProperty: 'total',
        idProperty: 'id',
        fields: ['Id', 'Nome', 'Cor', 'Valor', 'QtdePortas', 'Modelo', 'IdModelo', 'TipoCombustivel', 'IdTipoCombustivel']
    });
    dsCarros.load({ params: { start: 0, limit: 15} });

    // criando a grid que vai listar os registros da entidade carro
    var grid = new Ext.grid.GridPanel({
        width: 760,
        height: 395,
        title: 'List Cars',
        store: dsCarros,
        stripeRows: true,
        // grid columns
        columns: [{
            id: 'id',
            header: "Id",
            dataIndex: 'Id',
            width: 50,
            sortable: true
        }, {
            id: 'nome',
            header: "Nome",
            dataIndex: 'Nome',
            width: 200,
            sortable: true
        }, {
            id: 'cor',
            header: "Cor",
            dataIndex: 'Cor',
            width: 100,
            sortable: true
        }, {
            id: 'valor',
            header: "Valor",
            dataIndex: 'Valor',
            width: 100,
            sortable: true
        }, {
            id: 'qtde_portas',
            header: "Qtde Portas",
            dataIndex: 'QtdePortas',
            width: 100,
            sortable: true
        }, {
            id: 'modelo',
            header: "Modelo",
            dataIndex: 'Modelo',
            width: 100,
            sortable: true
        }, {
            id: 'tipo_combustivel',
            header: "Tipo Combustivel",
            dataIndex: 'TipoCombustivel',
            width: 100,
            sortable: true
        }],
        // paging bar on the bottom
        bbar: new Ext.PagingToolbar({
            pageSize: 15,
            store: dsCarros,
            displayInfo: true,
            displayMsg: 'Displaying topics {0} - {1} of {2}',
            emptyMsg: "No topics to display"
        }),
        //Aqui apenas adicionamos uma barra no topo com com os botões de adicionar, editar e deletar
        tbar: [{
            text: 'Adicionar',
            iconCls: 'btn-add',
            scope: this
        }, {
            text: 'Editar',
            iconCls: 'btn-edit',
            scope: this
        }, {
            text: 'Deletar',
            iconCls: 'btn-delete',
            scope: this
        }]
    });

    // mostramos a grid em uma div chamada div_grid
    grid.render('div_grid');
});

Execute o projeto e veja o resultado!

Criando o formulário Adicionar

Agora temos que criar o formulário que será responsável por Adicionar e Alterar os registros, primeiramente criamos um objeto do tipo Form e depois um objeto do tipo Window que conterá o formulário, veja abaixo

//Criamos o formulário
var formulario = new Ext.form.FormPanel({
    //Tiramos a borda azul
    border: false,
    // deixa o form azul
    frame: true,
    //Definimos a largura dos labels
    labelWidth: 70,
    items: [{
        //Tipo do campo
        xtype: 'textfield',
        //Nome a ser enviado pro server e para ser carregado do store
        name: 'Id',
        //Id para recuperar o campo
        id: 'txtId',
        //Largura do campo
        width: 30,
        // campo invisivel
        hidden: true
    }, {
        xtype: 'textfield',
        name: 'Nome',
        id: 'txtNome',
        //Nome visível ao usuário
        fieldLabel: 'Nome',
        width: 300,
        //Não permite campo em branco
        allowBlank: false
    }, {
        xtype: 'textfield',
        name: 'Cor',
        id: 'txtCor',
        fieldLabel: 'Cor',
        width: 100,
        allowBlank: false
    }, {
        xtype: 'textfield',
        name: 'Valor',
        id: 'txtValor',
        fieldLabel: 'Valor',
        width: 50,
        allowBlank: false
    }, {
        xtype: 'textfield',
        name: 'QtdePortas',
        id: 'txtQtdePortas',
        fieldLabel: 'Qtde Portas',
        width: 50,
        allowBlank: false
    }, {
        xtype: 'combo',
        hiddenName: 'Modelo',
        name: 'cmbModelo',
        id: 'cmbModelo',
        fieldLabel: 'Modelo',
        width: 150,
        allowBlank: false,
        col: true,
        emptyText: 'Selecione...'
    }, {
        xtype: 'combo',
        hiddenName: 'TipoCombustivel',
        id: 'cmbCombustivel',
        name: 'cmbCombustivel',
        fieldLabel: 'Combustivel',
        width: 150,
        allowBlank: false,
        col: true,
        emptyText: 'Selecione...'
    }]
});

//Aqui criamos a janela que conterá nosso form
var winForm = new Ext.Window({
    title: formTitle,
    height: 250,
    width: 430,
    //Bloqueia o resto da aplicação
    modal: true,
    //Quando fechada a janela é apenas escondida para não precisar ser criada novamente
    closeAction: 'hide',
    //Aqui definimos que o filho desta tela irá ocupar toda a área disponivel na janela
    layout: 'fit',
    // adicionamos o formulário na janela
    items: formulario,
    //Alinhamos os botões no meio
    buttonAlign: 'center',
    buttons: [{
        text: 'Salvar',
        iconCls: 'btn-save',
        scope: this
    }, {
        text: 'Cancelar',
        iconCls: 'btn-cancel',
        scope: this
    }]
});

Depois de criado o formulário, temos que adicionar o evento no botão Adicionar para abrir a nossa janela, para isso vamos na tool bar da grid no botão Adcionar e vamos adicionar um Handler, veja abaixo:

...
tbar: [{
    text: 'Adicionar',
    iconCls: 'btn-add',
    scope: this,
    handler: adicionar
}, {
    text: 'Editar',
    iconCls: 'btn-edit',
    scope: this
}, {
    text: 'Deletar',
    iconCls: 'btn-delete',
    scope: this
}]
...

//Função que será chamada ao clicar no botão de adicionar da janela principal
var adicionar = function () {
    //Defino uma propriedade na janela do form pra indicar que estamos abrindo-a
    //para editar um registro e assim poder definir o que fazer na hora de salvar
    winForm.update = false;

    //Aqui altero o titulo pegando aquele titulo salvo acima e adicionando um texto
    //indicando que ação estamos executando
    winForm.setTitle(formTitle + '[Inserindo]');

    //Mostramos a janela
    winForm.show();

    //Limpo o formulário para iniciar a inserção de um novo registro
    formulario.getForm().reset();
};

Execute o projeto e clique no botão Adicionar, a tela abaixo deverá aparecer

Veja que as combos ainda estão vazias, precisamos criar um novo JsonStore para cada combo, veja abaixo:

// criamos um JsonStore que vai armazenar as informações da entidade modelo
var dsModelo = new Ext.data.JsonStore({
    url: '@Url.Content("~/Carro/GetAllModelo")',
    root: 'modelos',
    totalProperty: 'total',
    idProperty: 'id',
    fields: ['Id', 'Nome']
});

// criamos um JsonStore que vai armazenar as informações da entidade combustivel
var dsCombustivel = new Ext.data.JsonStore({
    url: '@Url.Content("~/Carro/GetAllCombustivel")',
    root: 'combustiveis',
    totalProperty: 'total',
    idProperty: 'id',
    fields: ['Id', 'Nome']
});

Agora no formulário temos que setar o store com as informações

...
{
	xtype: 'combo',
	hiddenName: 'Modelo',
	name: 'cmbModelo',
	id: 'cmbModelo',
	fieldLabel: 'Modelo',
	width: 150,
	allowBlank: false,
	col: true,
	//Store de onde o combo pegara sua lista de dados
	store: dsModelo,
	//Campo que será usado como valor
	valueField: 'Id',
	//Campo que será mostrado na lista
	displayField: 'Nome',
	//necessário para o combo buscar os dados do store
	triggerAction: 'all',
	emptyText: 'Selecione...'
}, {
	xtype: 'combo',
	hiddenName: 'TipoCombustivel',
	id: 'cmbCombustivel',
	name: 'cmbCombustivel',
	fieldLabel: 'Combustivel',
	width: 150,
	allowBlank: false,
	col: true,
	//Store de onde o combo pegara sua lista de dados
	store: dsCombustivel,
	//Campo que será usado como valor
	valueField: 'Id',
	//Campo que será mostrado na lista
	displayField: 'Nome',
	//necessário para o combo buscar os dados do store
	triggerAction: 'all',
	emptyText: 'Selecione...'
}
...

Veja que a nossa tela de Adicionar ja preenche as combos

Pronto! nosso formulário está pronto, agora vamos implementear as ações dos botões Salvar e Cancelar, novamente temos que adicionar um handler para cada botão conforme abaixo:

...
buttons: [{
    text: 'Salvar',
    iconCls: 'btn-save',
    scope: this,
    handler: salvar
}, {
    text: 'Cancelar',
    iconCls: 'btn-cancel',
    scope: this,
    handler: cancelar
}]
...

Agora vamos implementar as funções salvar e cancelar conforme abaixo

//Função chamada ao clicar no botão de cancelar do formulário
var cancelar = function () {
    formulario.getForm().reset();
    //Apenas fechamos a janela
    winForm.hide();
};

//Função a ser chamada quando clicar no botão salvar do formulário
var salvar = function () {
    //Verificamos se o formulário está valido de acordo com cada campo
    if (formulario.getForm().isValid()) {
        //Se sim colocamos uma mascara de "Salvando" na janela do form
        //Impedindo que o usuário fique funçando na tela
        winForm.el.mask('Salvando', 'x-mask-loading');

        //Usamos a função do form para salvar os dados, submit
        //os dados vão por AJAX em POST
        formulario.getForm().submit({
            //Arquivo que faz a interação com o banco
            url: '@Url.Content("~/Carro/Save")',
            params: {
                //Aqui temos if compacto para verificar qual a ação que estava
                //sendo executada no form
                action: winForm.update ? 'update' : 'insert'
            },
            //Função chamada se retornado success:true
            success: function (form, action) {
                if (action.result.success) {
                    //Então se tudo ok retiramos a mascara de 'Salvando'
                    winForm.el.unmask();
                    //E fechamos a janela
                    winForm.hide();
                    //Recarregamos o grid para visualizarmos as mudanças
                    dsCarros.reload();
                }
            },
            //Função chamada se retornado success:false
            failure: function (form, action) {
                //Se tivemos problemas tiramos a mascara
                winForm.el.unmask();
                //E mostramos uma mensagem ao usuário informando o erro vindo do servidor
                Ext.Msg.alert('Erro', action.result.message);
            },
            scope: this
        })
    } else {
        //Se temos algum campo inválido avisamos ao usuário
        Ext.Msg.alert('Atenção', 'Exixtem campos inválidos');
    }
};

Veja o formulário preenchido abaixo

Criando o formulário Editar

Para criar a opção de Editar vamos usar o mesmo formulário, apenas recuperando os valores do banco e preenchendo os campos, primeiro temos que criar um handler para o Editar, veja abaixo

...
tbar: [{
    text: 'Adicionar',
    iconCls: 'btn-add',
    handler: adicionar
}, {
    text: 'Editar',
    iconCls: 'btn-edit',
    scope: this,
    handler: editar
}, {
    text: 'Deletar',
    iconCls: 'btn-delete',
    scope: this
}]
...

Agora a função editar que vai preencher o formulário

//Função a ser chamada quando clicar no botão de editar da janela principal
var editar = function () {
    //Para editar precisamos que o usuário tenha selecionado um registro então
    //verificamos se existe alguama seleção no nosso grid.
    if (grid.getSelectionModel().hasSelection()) {
        //Mudamos nossa propriedade para indicar que a janela está em modo de atualização
        winForm.update = true;
        //Mudamos o titulo para indicar ao usuário a ação que está sendo executada
        winForm.setTitle(formTitle + '[Alterando]');
        //mostramos a janela
        winForm.show();

        // recupero o registro selecionado na grid e preencho os valores
        // dessa forma não preciso ir até o banco novamente
        var record = grid.getSelectionModel().getSelected();
        Ext.getCmp("txtId").setValue(record.data.Id);
        Ext.getCmp("txtNome").setValue(record.data.Nome);
        Ext.getCmp("txtCor").setValue(record.data.Cor);
        Ext.getCmp("txtValor").setValue(record.data.Valor);
        Ext.getCmp("txtQtdePortas").setValue(record.data.QtdePortas);
        Ext.getCmp("cmbModelo").setValue(record.data.IdModelo);
        Ext.getCmp("cmbCombustivel").setValue(record.data.IdTipoCombustivel);
        Ext.getCmp("cmbModelo").setRawValue(record.data.Modelo);
        Ext.getCmp("cmbCombustivel").setRawValue(record.data.TipoCombustivel);
    } else {
        //Caso não tenhamos nenhuma linha selecionada avisamos ao usuário
        Ext.Msg.alert('Atenção', 'Selecione um registro');
    }
};

Veja na imagem abaixo o resultado, veja q estamos editando o registro selecionado na grid

Criando o formulário Deletar

E finalmente vamos criar a opção para deletar um registro, novamente adicione um handler para o botão cancelar conforme abaixo

...
tbar: [{
    text: 'Adicionar',
    iconCls: 'btn-add',
    scope: this,
    handler: adicionar
}, {
    text: 'Editar',
    iconCls: 'btn-edit',
    scope: this,
    handler: editar
}, {
    text: 'Deletar',
    iconCls: 'btn-delete',
    scope: this,
    handler: deletar
}]
...

E agora a função deletar

//Função a ser chamada quando clicar no botão de deletar da tela principal
var deletar = function () {
    //Novamente verificamos se o usuário selecionou alguma linha
    if (grid.getSelectionModel().hasSelection()) {
        //Separamos o registro selecionado para uma variavel para evitar de
        //chamar estas funções com frequencia ja que usarei este registro mais de uma vez abaixo
        var record = grid.getSelectionModel().getSelected();

        //Perguntamos ao usuário se ele realmente deseja excluir o registro
        Ext.Msg.confirm('Atenção', 'Você está prestes a excluir o carro <strong>' + record.data.Nome + '</strong>. Deseja continuar?', function (btn) {
            //Testamos qual botão ele clicou
            if (btn == 'yes') {
                //Se ele aceitou blz, criamos um AJAX passando o registro que queremos deletar
                Ext.Ajax.request({
                    //Aqui o arquivo php que interage com nosso banco
                    url: '@Url.Content("~/Carro/Delete")',
                    //Paramentros que passaremos por POST
                    params: {
                        //E passamos o login do cara que queremos deletar, pq só o login?
                        //Pq o login é nossa chave primária, só preciamos dela pra fazer um delete
                        id: record.data.Id
                    },
                    //Função chamada quando não houver nenhum erro de pagina como 404, 500
                    success: function (r) {
                        //Se tudo OK, pegamos a resposta que é um JSON e decodificamos para um objeto
                        var obj = Ext.decode(r.responseText);
                        //Verificamos se obtivemos sucesso na ação
                        if (obj.success) {
                            //Se sim efetuar um reload
                            dsCarros.reload();
                        } else {
                            //Caso tenha acontecido um erro mostra uma mensagem ao usuário com um texto vindo do servidor
                            Ext.Msg.alert('Erro', obj.msg);
                        }
                    },
                    //Função executada se tivermos um erro de arquivo n encontrado ou coisa do tipo, 404, 500, etc
                    failure: function () {
                        //Mostramos uma mensagem ao usuário pedindo para contatar o administrador
                        Ext.Msg.alert('Erro', 'Ocorreu um erro ao se comunicar com o servidor, tente novamente. Se o erro persistir entre em contato com o adiministrador do sistema')
                    },
                    scope: this
                })
            }
        }, this)
    } else {
        //Se não tivermos uma linha selecionada no grid avisa ao usuário
        Ext.Msg.alert('Atenção', 'Selecione um registro');
    }
};

Veja o resultado

Conclusão

Mais ua vez podemos ver que o ExtJs realmente tem um resultado super bom, esse exemplo pode ainde sofre melhorias mas ja da para ter uma idéia de como podemos criar um CRUD usando ExtJs

Esse exemplo foi baseado em outro exemplo em PHP que pode ser visto aqui

Download do projeto

Qualquer dúvida, opinião, reclamação mande seu comentário!

Aquele Abraço!

Leia mais

Sobre Leandro Prado

Leandro Silveira Prado é graduado em Sistemas de Informação pela PUC-PR, trabalho com desenvolvimento WEB desde 2003. Possui uma vasta experiência em integração de sistemas ja prestou serviços a grandes empresas como FBits Fábrica de Software, Instituto Curitiba de Informática, América Latina Logística e atualmente trabalha como Consultor ALM na especificações.com. Fanático por futebol e torcedor do melhor time do paraná - COXA