Eu trabalho com Extjs desde que ele era uma extensão para o YUI, ainda hoje há aplicação no ar usando essa antiga tecnologia [por problema causado por algum idiota, você provavelmente será redirecionado para outro site do governo, dá uma olhada no canto esquerdo superior e clique em "IR PARA A SEPLAG"]. Para ver o Extjs no tempo que ele se chamava ext-yui, vá no link de pesquisa avançada, preencha o input descrição em “dados da matéria” com “secretaria de cultura” por exemplo e clique no botão pesquisar.
Esse tutorial tem o objetivo de preparar o conhecimento para outros posts que estou escrevendo e achei necessário uma introdução apenas nos conceitos do Extjs para não confundir com as tecnologias que uso em conjunto como DWR ou no modelo REST com o RubyOnRails.
O Extjs é um framework javascript de propósito geral, ou seja, tem um conjunto de funcionalidades que tratam Ajax, um conjunto de Widgets bem elaborados [componentes visuais como Grid e TabPanel], manipulação de DOM [Document Object Model] e BOM [Browser Object Model], tratamento de eventos, animações como Fade In e Fade Out, parser de JSON, entre outras coisas. Seus componentes são construÃdos com técnicas modernas de orientação a objetos no javascript e manipulação de Scripttag para recursos remotos que não suportam Ajax.
Preparação
Após baixar e descompactar o framework [estou trabalhando na versão 2.x que é estável nessa data], recomendo que deixa a disposição das pastas conforme se encontra e coloque no seu projeto de forma que seja acessÃvel via web, já vem com documentação e exemplos que você deve e vai usar durante o desenvolvimento. Temos a opção de montar o Extjs [marcando a opção "Make build available via CacheFly" ] no CacheFly como um servidor CDN para otimizar o tráfego de sua aplicação principalmente se ela será disponibilizada na internet e não apenas na intranet.
É necessário importar o CSS global, o adapter e o Javascript global conforme mostrado abaixo:
<link rel="stylesheet" type="text/css" href="/javascripts/ext-2.2.1/resources/css/ext-all.css" /> <script src="/javascripts/ext-2.2.1/adapter/ext/ext-base.js" type="text/javascript"></script> <script src="/javascripts/ext-2.2.1/ext-all.js" type="text/javascript"></script>
Pode usar outros temas para o Extjs, estarão na pasta “resources/css“, assim como podemos internacionalizar os componentes com arquivos que se encontram em “build/locale“. Exemplo com o tema “Gray” e i18n em português do Brasil:
<link rel="stylesheet" type="text/css" href="/javascripts/ext-2.2.1/resources/css/ext-all.css" /> <link rel="stylesheet" type="text/css" href="/javascripts/ext-2.2.1/resources/css/xtheme-gray.css"> <script type="text/javascript" src="/javascripts/ext-2.2.1/adapter/ext/ext-base.js"></script> <script type="text/javascript" src="/javascripts/ext-2.2.1/ext-all.js"></script> <script type="text/javascript" src="/javascripts/ext-2.2.1/build/locale/ext-lang-pt_BR-min.js"></script>
Internacionalização é algo pensado no Extjs de forma a facilitar a criação de arquivos de linguagem aproveitando a estrutura da linguagem [dinâmica e fracamente tipada], todas as propriedades de mensagens e textos são públicas para facilitar a reescrita como mostrado abaixo na i18n do componente de DataPicker:
if(Ext.DatePicker){ Ext.apply(Ext.DatePicker.prototype, { todayText : "Hoje", minText : "Esta data é anterior a menor data", maxText : "Esta data é posterior a maior data", disabledDaysText : "", disabledDatesText : "", monthNames : Date.monthNames, dayNames : Date.dayNames, nextText : 'Próximo Mês (Control+Direita)', prevText : 'Mês Anterior (Control+Esquerda)', monthYearText : 'Escolha um Mês (Control+Cima/Baixo para mover entre os anos)', todayTip : "{0} (Espaço)", format : "d/m/Y", okText : " OK ", cancelText : "Cancelar", startDay : 0 }); }
Se você usar o CacheFly gerado pela página do Extjs, ele incluir o adapter e o global em um mesmo arquivo, vai ser algo como:
<link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css"> <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/xtheme-gray.css"> <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
Adapter
Quando o Extjs passou a ser um framework independente do YUI, passou a adotar outros frameworks como base para funções básicas de manipulação de DOM e Ajax, hoje suporta trabalhar em conjunto com YUI, JQuery, Prototype e totalmente independente. Se o projeto já tem Jquery ou outro framework que trabalha com o Extjs, a utilização dos dois é muito fácil e indicada, principalmente para usar os widgets que são provavelmente os mais poderosos hoje em dia em um framework opensource.
//Exemplos de adapters permitidos <script type="text/javascript" src="/javascripts/ext-2.2.1/adapter/ext/ext-base.js"></script> <script type="text/javascript" src="/javascripts/ext-2.2.1/adapter/jquery/ext-jquery-adapter.js"></script>
Widgets
O principal apelo do Extjs que conquista os desenvolvedores é o layout bem trabalhado dos componentes visuais que são de fácil parametrização. Basicamente todos os componentes funcionam da mesma forma, você o instancia passando um objeto literal de configuração com mostrado abaixo:
//Exemplo do grid var grid = new Ext.grid.GridPanel({ autoShow:true, width:750,height:250 //mais parametros }); //Exemplo de uma Window var window = new Ext.Window({ autoShow:true, width:750,height:250 //mais parametros }); //Exemplo de um Painel var panel = new Ext.Panel({ autoShow:true, width:750,height:250 //mais parametros });
Vamos usar o GRID para exemplificar como trabalhamos com o Extjs, o mesmo comportamente se repete em todos os componentes.
A documentação do Extjs é muito bem feita e praticamente vai ser a única coisa que você vai precisar depois de entender como os componentes são formados, afinal não vale a pena decorar todas as propriedades de todos os componentes, concentre-se apenas em entender os conceitos.
A GRID é o Widget mais famoso desse framework e é formado basicamente por um objeto “Ext.data.Store” [que é a fonte de dados da GRID] e um objeto “Ext.grid.ColumnModel” [que é a definição das colunas], como mostrado abaixo:
var grid = new Ext.grid.GridPanel({ autoShow:true, width:750,height:250, //parametros de configuração de layout cm: new Ext.grid.ColumnModel({/*configuração*/}), store: new Ext.data.Store({}), //Ou especialização de um Store sm: new Ext.grid.RowSelectionModel({singleSelect:true}), //ou outra especialização de um AbstractSelectionModel });
O objeto ColumnModel é a definição de colunas do Grid, possui propriedades para definição de layout como largura e altura, tÃtulo da coluna como vai ser exibida e formatação do texto:
var colModel = new Ext.grid.ColumnModel([ { header: "Ticker", width: 60, sortable: true}, { header: "Company Name", width: 150, sortable: true}, { header: "Market Cap.", width: 100, sortable: true}, { header: "$ Sales", width: 100, sortable: true, renderer: money}, { header: "Employees", width: 100, sortable: true, resizable: false} ]);
A propriedade cm [ColumnModel] pode ser também substituÃda pela propriedade “columns” que funciona como um “alias”, dessa forma o Grid cria automaticamente um objeto ColumnModel:
var grid = new Ext.grid.GridPanel({ columns: [ {id:'id', header: "id", width: 200, sortable: true, dataIndex: 'id'}, {header: "Nome", width: 120, sortable: true, dataIndex: 'name'} ] });
Caso haja necessidade de formatar o conteúdo da célula, você pode usar uma função como “renderer” para tratar esse conteúdo:
var grid = new Ext.grid.GridPanel({ columns: [ {id:'id', header: "id", width: 200, sortable: true, dataIndex: 'id'}, {header: "Nome", width: 120, sortable: true, dataIndex: 'name'}, {header: "Criado em", width: 135, sortable: true, renderer: function(value) { return Date.parseDate(value, 'Y-m-d\\TH:i:s\\Z').format('d/m/Y H:i:s'); //2009-06-14T12:51:07Z }, dataIndex: 'created_at'} ] });
A propriedade “store” da GRID é uma especialização do componente “Ext.data.Store” que é formado basicamente por um “proxy” e um “reader“:
var store = new Ext.data.Store({ proxy: new Ext.data.DataProxy(), //ou uma especialização reader: new Ext.data.DataReader() //ou uma especialização });
O “proxy” é o componente que obterá os dados e o “reader” o componente que fará a leitura desses dados para um formato comum a todos os componentes do Extjs na forma de um objeto denominado “Ext.data.Record“. O objeto “Record” representa um registro de dados e é usado seja para GRID, para um Form ou qualquer componente que trabalhe com dados editáveis.
Dessa forma podemos usar uma combinação de Proxy e Reader como HttpProxy e JsonReader:
var store = new Ext.data.Store({ proxy: new Ext.data.HttpProxy({ url: 'projects.json' }), reader: new Ext.data.JsonReader({ totalProperty:'total', root:'results',id:'id' }, Ext.data.Record.create([ {name:'id', mapping:'id'}, {name:'name', mapping:'name'}, {name:'created_at', mapping:'created_at'}, {name:'updated_at', mapping:'updated_at'} ])) });
Devido o costume do uso do HttpProxy, o componente Store possui uma propriedade chamada “url” que estando presente cria um HttpProxy automaticamente como mostrado abaixo:
var store = new Ext.data.Store({ url: 'projects.json', reader: new Ext.data.JsonReader({ totalProperty:'total', root:'results',id:'id' }, Ext.data.Record.create([ {name:'id', mapping:'id'}, {name:'name', mapping:'name'}, {name:'created_at', mapping:'created_at'}, {name:'updated_at', mapping:'updated_at'} ])) });
O objeto “Reader” é o único que foge um pouco a regra de instanciação por receber dois parâmetros, um similar aos outros com um objeto literal de configuração e outro com o mapeamento dos dados. O objeto de configuração tem duas propriedades de que representam o total e a lista de dados. O objeto de mapeamento usa um método “estático” do objeto Record para criar um link entre a propriedade do json [com a propriedade "mapping"] e o Ãndice interno do Record [pela propriedade "name"].
new Ext.data.JsonReader({ totalProperty:'total', root:'results',id:'id' }, Ext.data.Record.create([ {name:'id', mapping:'id'}, {name:'name', mapping:'name'}, {name:'created_at', mapping:'created_at'}, {name:'updated_at', mapping:'updated_at'} ]))
Dessa forma você tem um link entre o ColumnName pela propriedade dataIndex e o Store por meio do Reader, como abaixo:
Ext.data.Record.create([ {name:'nome_linkado', mapping:'name'} ]) // columns: [ {header: "Nome", width: 120, sortable: true, dataIndex: 'nome_linkado'} ]
Para melhorar a navegação da GRID, você pode também acrescentar um componente de Toolbar no header ou no footer:
var grid = new Ext.grid.GridPanel({ autoShow:true, width:750,height:250, //parametros de configuração de layout cm: new Ext.grid.ColumnModel({/*configuração*/}), store: new Ext.data.Store({}), //Ou especialização de um Store sm: new Ext.grid.RowSelectionModel({singleSelect:true}), bbar: new Ext.Toolbar(), //Bottom Toolbar tbar: new Ext.Toolbar() //Top Toolbar });
A Toolbar mais usada é sua especialização com paginação, a Ext.PagingToolbar que necessita ser linkada com o Store:
new Ext.PagingToolbar({ pageSize:10, //propriedade opcional, default é 20 store: store })
A PagingToolbar é I18n, mas se as mensagens não agradarem você pode mudá-las [eu sempre faço]:
new Ext.PagingToolbar({ pageSize:10,store: store, displayInfo: true, displayMsg: 'Exibindo o resultado: {0} a {1} de {2} registros', emptyMsg: "Sem resultados a exibir" })
Uma coisa bacana nesse componente é que você pode agrupar botões [já que é uma Toolbar] e até padronizar o layout:
new Ext.PagingToolbar({ pageSize:10,store: store, displayInfo: true, displayMsg: 'Exibindo o resultado: {0} a {1} de {2} registros', emptyMsg: "Sem resultados a exibir", items: ['-', { pressed: true,enableToggle: true,text: 'Alterar', toggleHandler: function(){} }, { pressed: true,enableToggle: true,text: 'Excluir', toggleHandler: function(){} }] })
Código da GRID inteira:
var store = new Ext.data.Store({ proxy: new Ext.data.HttpProxy({ url: 'projects.json' }), reader: new Ext.data.JsonReader({ totalProperty:'total', root:'results',id:'id' }, Ext.data.Record.create([ {name:'id', mapping:'id'}, {name:'name', mapping:'name'}, {name:'created_at', mapping:'created_at'}, {name:'updated_at', mapping:'updated_at'} ])) }); var colModel = new Ext.grid.ColumnModel([ {id:'id', header: "id", width: 200, sortable: true, dataIndex: 'id'}, {header: "Nome", width: 120, sortable: true, dataIndex: 'name'}, {header: "Criado em", width: 135, sortable: true, renderer: function(value) { return Date.parseDate(value, 'Y-m-d\\TH:i:s\\Z').format('d/m/Y H:i:s'); }, dataIndex: 'created_at'} ]); var pagingToolbar = new Ext.PagingToolbar({ pageSize:10,store: store, displayInfo: true, displayMsg: 'Exibindo o resultado: {0} a {1} de {2} registros', emptyMsg: "Sem resultados a exibir", items: ['-', { pressed: true,enableToggle: true,text: 'Alterar', toggleHandler: function(){} }, { pressed: true,enableToggle: true,text: 'Excluir', toggleHandler: function(){} }] }); var grid = new Ext.grid.GridPanel({ autoShow:true, width:750,height:250, cm: colModel, store: store, sm: new Ext.grid.RowSelectionModel({singleSelect:true}), bbar: pagingToolbar });
Como eu falei, todos os componentes possuem o mesmo comportamento, notaram que dá para aproveitar esse código semelhante e reaproveitar em todos os CRUDs?
Vou falando de um por um de acordo com os posts que forem saindo, aguardem que o próximo sai logo… ou não.
Posted in (X)HTML, Ajax, CSS, Ext, Frameworks, JavaScript, JSON, RIA, Web Development, web2.0, XMLHttpRequest ~ 10 Comments
