ext js logo

Introdução ao padrão MVC no Ext JS 4

Ext JS 4 trouxe uma nova maneira de construir interfaces ricas com a introdução do padrão MVC. Ao permtir a separação das regras de negócios e componentes visuais é obtido um código mais limpo e fácil de dar manuntenção.

A arquitetura do padrão MVC usado pelo extjs4 é definido da seguinte maneira:

  • Model: é uma classe que representa uma tabela do banco de dados.
  • View: é um componente visual como por exemplo datagrid, combobox, panel…
  • Controller: é onde fica o código que faz a aplicação funcionar. Serve como uma ponte entre o Model e a View, possui regras de negócio e renderiza componentes.

ExtJS utiliza convenções para facilitar a vida do desenvolvedor.

Estrutura de arquivos:
Um diretório app para cada projeto e dentro dele os diretórios model, controller, view e store. O arquivo app.js no mesmo nível do diretório app.
Dentro do diretório view deve ser criado um subdiretório com o nome de cada controller que renderiza alguma view.

Nomes de arquivos:

  • Models: CamelCase no singular.
  • View: um subdiretório com o nome do controller em minúsculo e dentro CamelCase para o nome do componente.
  • Controllers: CamelCase no plural.
  • Stores: CamelCase no plural.

Como exemplo, vou mostrar como criar um projeto usando a estrutura abaixo:
extjs4-mvc-tutorial tree

Primeiramente instale um servidor web tal como Apache ou Nginx. A configuração padrão já é suficiente para o propósito desse artigo. Faça o download da versão gratuita do ExtJS 4 em http://www.sencha.com/products/extjs/download?page=a.

$ mkdir -p /var/www/html/extjs4-mvc-tutorial/js
$ cd /var/www/html/extjs4-mvc-tutorial/js
$ wget http://cdn.sencha.io/ext-4.0.7-gpl.zip
$ unzip ext-4.0.7-gpl.zip
$ mkdir -p app/model
$ mkdir -p app/controller
$ mkdir -p app/view/contatos
$ mkdir -p app/store

Crie o arquivo /var/www/html/extjs4-mvc-tutorial/index.html:

<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <title>Ext JS 4 MVC Tutorial</title>
  <link rel="stylesheet" type="text/css" href="js/ext-4.0.7-gpl/resources/css/ext-all-gray.css" />
  <script type="text/javascript" src="js/ext-4.0.7-gpl/ext-all-debug.js"></script>
  <script type="text/javascript" src="js/app.js"></script>
</head>
<body></body>
</html>

Agora o arquivo /var/www/html/extjs4-mvc-tutorial/js/app.js:

Ext.Loader.setConfig({enabled:true});
Ext.application({
    name: 'extjs4-mvc-tutorial',
    appFolder: '/extjs4-mvc-tutorial/js/app',
    autoCreateViewport: false,
 
    controllers: ['Contatos',],
 
    launch: function() {
        viewport = Ext.create('Ext.container.Viewport', {
            id: 'viewport',
            layout: 'border',
            items: [{
                region: 'center',
                border: false,
                autoScroll: true,
                items: [{
                    xtype: 'contatosGrid',
                    store: 'Contatos',
                }]
            }]
        });
    }
});

app.js cria a aplicação extjs4-mvc-tutorial e define o caminho da appFolder. autoCreateViewport: false permite que eu crie uma viewport assim que a aplicação for criada.
A linha Ext.Loader.setConfig({enabled:true}) é necessária para o carregamento dinâmico das dependências do projeto.
Adicionei um controller na aplicação. Devido a convenção utilizada, extjs sabe exatamente onde onde procurar essa classe controller dentro do appFolder. Ao carregar o controller dinamicante, suas dependências também são carregadas, como por exemplo o store Contatos e a view contatosGrid.

Crie o model /var/www/html/extjs4-mvc-tutorial/js/app/model/Pessoa.js

Ext.define('extjs4-mvc-tutorial.model.Pessoa', {
    extend: 'Ext.data.Model',
    fields: ['id', 'email'],
});

Um model extende de Ext.data.Model e automaticamente ganha os métodos set() e get(). Na versão 4 é possível inserir um Proxy dentro do Model e com isso obtém um funcionamento semelhante ao padrão Active Record.

Crie também a store /var/www/html/extjs4-mvc-tutorial/js/app/store/Contatos.js

Ext.define('extjs4-mvc-tutorial.store.Contatos', {
    extend: 'Ext.data.Store',
 
    model: 'extjs4-mvc-tutorial.model.Pessoa',
    autoLoad: true,
    autoSync: true,
    pageSize: 30,
 
    proxy: {
        type: 'rest',
 
        api: {
            read: '/extjs4-mvc-tutorial/contatos/read',
            create: '/extjs4-mvc-tutorial/contatos/create',
            update: '/extjs4-mvc-tutorial/contatos/update',
            destroy: '/extjs4-mvc-tutorial/contatos/delete'
        },
 
        reader: {
            type: 'json',
            root: 'data',
            successProperty: 'success',
            totalProperty: 'total'
        },
 
        writer: {
            type: 'json',
            root: 'data',
        },
 
        listeners: {
            exception: function(proxy, response, operation) {
                var message = Ext.JSON.decode(response.responseText).message;
                Ext.Msg.alert('Ocorreu um erro', message);
            }
        }
    },
});

Crie a view /var/www/html/extjs4-mvc-tutorial/js/app/view/contatos/Grid.js

Ext.define('extjs4-mvc-tutorial.view.contatos.Grid', {
    extend: 'Ext.grid.Panel',
    alias: 'widget.contatosGrid',
 
    plugins: Ext.create('Ext.grid.plugin.CellEditing'),
 
    columns: [{
        xtype: 'rownumberer',
        width: 30,
    }, {
        text: 'Nome',
        sortable: true,
        dataIndex: 'nome',
        field: {
            xtype: 'textfield'
        }
    }, {
        text: 'E-mail',
        flex: 0,
        sortable: false,
        dataIndex: 'email',
        width: 120,
        field: {
            xtype: 'textfield'
        }
    }],
 
    initComponent: function() {
        this.dockedItems = [{
            xtype: 'toolbar',
            items: [{
                text: 'Adicionar',
                action: 'adicionar'
            }, {
                text: 'Deletar',
                action: 'deletar'
            }]
        }, {
            store: this.getStore(),
            displayInfo: true,
            xtype: 'pagingtoolbar',
            dock: 'bottom',
            beforePageText : 'Pagina ',
            displayMsg: '{0} - {1} de {2}',
            emptyMsg: 'Nada para ser exibido',
        }];
        return this.callParent(arguments);
    },
});

A linha plugins: Ext.create('Ext.grid.plugin.CellEditing') adiciona o plugin para editar células no grid.

Por fim, crie o controller /var/www/html/extjs4-mvc-tutorial/js/app/controller/Contatos.js:

Ext.define('extjs4-mvc-tutorial.controller.Contatos', {
    extend: 'Ext.app.Controller',
 
    models: ['Pessoa'],
    stores: ['Contatos'],
    views : ['contatos.Grid',],
 
    init: function() {
        this.control({
            'contatosGrid button[action=adicionar]': {click: this.adicionar},
            'contatosGrid button[action=deletar]': {click: this.deletar},
        });
    },
 
    adicionar: function(button) {
        button.up('grid').getStore().insert(0, this.getModel('Pessoa').create());
    },
 
    deletar: function(button) {
        var grid = button.up('grid'),
            store = grid.getStore(),
            record = grid.getSelectionModel().getSelection()[0];
 
        if (record) {
            if (confirm('Tem certeza?')) {
                store.remove(record);
            }
        }
    }
});

O controller contém a lógica da aplicação, isolando o model e a view.
Para testar esse exemplo, crie o arquivo /var/www/html/extjs4-mvc-tutorial/contatos/read contendo:

$ vim /var/www/html/extjs4-mvc-tutorial/contatos/read
{ success: true, total: 1, data: [{ 'id': 1, 'nome': 'Gustavo', 'email': 'eu@gustavohenrique.net' }] }

Ao carregar a aplicação será exibido no grid o conteúdo do arquivo acima.

Como não foi utilizada nenhuma linguagem de backend, ao tentar cadastrar, deletar ou atualizar um registro no grid ocasionará um erro 404. Mas se testar no firefox ou chrome usando o firebug vai perceber que são enviados requests do tipo POST, DELETE e PUT, afinal o proxy na store foi configurado para trabalhar como REST.

Links

https://github.com/gustavohenrique/extjs4-mvc-tutorial (Código Fonte)
http://docs.sencha.com/ext-js/4-0/
http://www.sencha.com/learn/architecting-your-app-in-ext-js-4-part-1