sexta-feira, 26 de outubro de 2012

MVC Helpers vs ExtJS 3.4 Sencha

Eai Caros "Encodes" (...)
Sexta-feira de muita chuva em Londrina é totalmente inspirador para um Post novo hehe, hoje vo tenta mostra a vocês a ideia de utilização de helpers no MVC 3.
Iniciando podemos começar falando um pouco do "RAZOR". Esse é o novo motor de visões de paginas adotado pela microsoft, esse novo "cara" segue o mesmo modelo de arquivos (.ASPX/.ASCX/.MASTER) que já estamos acostumados no WEB FORM.
Para uma mais refinada explicação vou utilizar uma definição feita em um ótimo blog que costumo visitar. (ScottGu's Blog
Compacto, Expressivo, e Fluente: o Razor minimiza o número de caracteres e teclas digitadas necessárias em um arquivo, e permite um fluxo de trabalho de codificação rápido e fluente. Ao contrário das sintaxes de outros modelos, você não precisa interromper sua codificação para explicitamente indicar blocos do servidor dentro do código HTML. O analisador é inteligente o suficiente para inferir isso a partir do seu código. Isto permite uma sintaxe muito compacta e expressiva, que é limpa, rápida e divertida para digitar.
Fácil para Aprender: o Razor é fácil de aprender e te permite ser produtivo rapidamente com um mínimo de conceitos. Você usa todas as suas habilidades existentes de linguagens de programação e HTML.
Não é uma nova linguagem: Nós conscientemente optamos por não criar uma nova linguagem imperativa com o Razor. Em vez disso, queríamos permitir que os desenvolvedores utilizassem seus atuais conhecimentos de linguagens de programação C#/VB (ou outras) com o Razor, entregando uma sintaxe de marcação para modelagem que permite um fluxo de trabalho de construção de código HTML bem legal com a sua linguagem de preferência.
Funciona com qualquer Editor de Texto: o Razor não necessita de uma ferramenta específica e te permite ser produtivo em qualquer editor de texto velho e simples (notepad funciona muito bem).
Tem ótimo suporte à Intellisense: o Razor foi projetado para não necessitar de uma ferramenta específica ou editor de código, mas ainda assim ele terá um excelente suporte para intellisense dentro do Visual Studio.
Unidade Testável: A nova implementação deste motor de visões suportará a capacidade de testar visões de maneira unitária (sem a necessidade de um controlador ou de um servidor web, e pode ser hospedado em qualquer projeto de teste unitário - não há necessidade de um app-domain [domínio de aplicação] especial).
Após entendermos algumas definições do Razor podemos partir para uma outra ideia que seria de criar o nosso próprio ajudante para criação de layouts.
O que vou mostrar a seguir seria basicamente utilizar o recurso das chamadas Razor em uma pagina HTML seguido de alguns parâmetros dinâmicos e gerando através do ExtJs (O que é ExtJs?) É um framework JavaScript de ricos recursos para desenvolvimento de layouts, ficando extremamente conhecido no Brasil, sendo agora de propriedade da SENCHA (http://www.sencha.com/) ele vem sendo atualizado constantemente a comunidade é ativa e nem preciso elogiar também a documentação !
Se você não conhece e tiver algum interesse em estudar mais ...

Docs - Versão 3.4 utilizada neste Post

Docs - Versão 4.1 (ATUAL)

Continuando (...) o que o ExtJs tem haver com o Razor? hehe na verdade como disse no começo vou agregar esse dois recursos para um ganho significativo na produtividade para a criação de uma Grid. Se você tem uma noção mais voltada para arquitetura pode estar pensando "Abstrair o que já está abstraído?" realmente um termo que me perguntei varias vezes até começar a trabalhar em algo do tipo, porém a PRODUTIVIDADE vs PADRONIZAÇÃO falou mais alto. Na verdade isso seria papo para uma longa discução mas ao final do POST seja você capaz de tirar suas próprias conclusões! Chega de "BLA BLA" e para manter o costume sirva-se de um café e vamos codar  ...

Primeiramente, criei um projeto PADRÃO utilizando um template já pronto do VS2010 e também uma pasta "Helpers".


Na pasta Helpers adiciona um classe chamada "HelperGrid.cs" essa classe irá receber e processar as chamadas vinda da pagina feita pelo Razor.
Vamos implementar a classe HelperGrid.cs, resumidamente nela eu implementei uma logica simples, aonde coloquei toda a ideia de parâmetros que uma Grid poderia receber dinamicamente sendo mais simplificado ainda, utilizei da minha imaginação e adicionei alguns métodos aonde enxerguei que a minha Grid poderia ser dinâmica. Segue o código:
 namespace MvcApplication1.Helpers  
 {  
   public static class HelperGrid  
   {  
     public static ExtGrid ExtGrid(this HtmlHelper helper, string id)  
     {  
       return new ExtGrid(helper, id);  
     }  
   }  
   public class ExtGrid  
   {  
     private HtmlHelper helper;  
     private string id;  
     private string url;  
     private string pkTabela;  
     private string tituloGrid;  
     private List<ColunasGrid> listaColunasGrid = new List<ColunasGrid>();  
     private string elementoRoot;  
     private int pageSizeGrid;  
     public ExtGrid(HtmlHelper helper, string id)  
     {  
       this.helper = helper;  
       this.id = id;  
     }  
     /// <summary>  
     ///   
     /// </summary>  
     /// <param name="url"></param>  
     /// <param name="pkTabela"></param>  
     /// <returns></returns>  
     public ExtGrid SetDataStore(string url, string pkTabela)  
     {  
       this.url = url;  
       this.pkTabela = pkTabela;  
       return this;  
     }  
     /// <summary>  
     ///   
     /// </summary>  
     /// <param name="root"></param>  
     /// <returns></returns>  
     public ExtGrid SetElementRoot(string root)  
     {  
       this.elementoRoot = root;  
       return this;  
     }  
     /// <summary>  
     ///   
     /// </summary>  
     /// <param name="pageSize"></param>  
     /// <returns></returns>  
     public ExtGrid SetPageSize(int pageSize = 20)  
     {  
       this.pageSizeGrid = pageSize;  
       return this;  
     }  
     /// <summary>  
     ///   
     /// </summary>  
     /// <param name="Name"></param>  
     /// <param name="Header"></param>  
     /// <param name="Tamanho"></param>  
     /// <param name="Visivel"></param>  
     /// <param name="Xtype"></param>  
     /// <returns></returns>  
     public ExtGrid Columns(string Name, string Header, int Tamanho, bool Visivel = true, string Xtype = null)  
     {  
       ColunasGrid colunasGrid = new ColunasGrid(Name, Header, Tamanho, Visivel, Xtype);  
       this.listaColunasGrid.Add(colunasGrid);  
       return this;  
     }  
     /// <summary>  
     ///   
     /// </summary>  
     /// <param name="titulo"></param>  
     /// <returns></returns>  
     public ExtGrid SetTitulo(string titulo = null)  
     {  
       this.tituloGrid = titulo;  
       return this;  
     }  
     /// <summary>  
     ///   
     /// </summary>  
     public void Render()  
     {  
       GridXml gridXmlAtributos = new GridXml()  
       {  
         url = this.url,  
         pkTabela = this.pkTabela,  
         tituloGrid = this.tituloGrid,  
         elementoRoot = this.elementoRoot,  
         pageSize = this.pageSizeGrid  
       };  
       foreach (ColunasGrid column in this.listaColunasGrid)  
       {  
         GridColumnXml layoutColumnGrid = new GridColumnXml()  
         {  
           name = column.name,  
           header = column.header,  
           tamanho = column.tamanho,  
           visible = column.visivel,  
           xtype = column.xtype  
         };  
         gridXmlAtributos.columns.Add(layoutColumnGrid);  
       }  
       string xml;  
       try  
       {  
         xml = HelperUtil.Serialize<GridXml>(gridXmlAtributos);  
       }  
       catch (Exception ex)  
       {  
         throw new Exception("Erro ao serializar o XML" + ex.Message);  
       }  
       helper.ViewContext.Writer.Write("<div id=\"" + this.id + "\"");  
       helper.ViewContext.Writer.Write(" data-isGrid=\"true\"");  
       helper.ViewContext.Writer.WriteLine(">");  
       helper.ViewContext.Writer.WriteLine("<script type=\"text/xml\" id=\"" + this.id + "grid_xml\">");  
       helper.ViewContext.Writer.WriteLine(xml);  
       helper.ViewContext.Writer.WriteLine("</script>");  
       helper.ViewContext.Writer.WriteLine("</div>");  
     }  
   }  
   public class ColunasGrid  
   {  
     public string name { get; set; }  
     public string header { get; set; }  
     public int tamanho { get; set; }  
     public bool visivel { get; set; }  
     public string xtype { get; set; }  
     public ColunasGrid(string name, string header, int tamanho, bool visivel, string xtype)  
     {  
       this.name = name;  
       this.header = header;  
       this.tamanho = tamanho;  
       this.visivel = visivel;  
       this.xtype = xtype;  
     }  
   }  
   /// <summary>  
   ///   
   /// </summary>  
   [XmlType(TypeName = "gridLayout")]  
   public class GridXml  
   {  
     public string url;  
     public string pkTabela;  
     public string tituloGrid;  
     public string elementoRoot;  
     public int pageSize;  
     public string modeFilter;  
     public int charFiltro;  
     public List<GridColumnXml> columns;  
     public GridXml()  
     {  
       this.columns = new List<GridColumnXml>();  
     }  
     public readonly static XmlSerializer Serializer = new XmlSerializer(typeof(GridColumnXml));  
   }  
   /// <summary>  
   ///   
   /// </summary>  
   [XmlType(TypeName = "column")]  
   public class GridColumnXml  
   {  
     [XmlAttribute]  
     public string name;  
     [XmlAttribute]  
     public string header;  
     [XmlAttribute]  
     public int tamanho;  
     [XmlAttribute]  
     public bool visible;  
     [XmlAttribute]  
     public string xtype;  
   }  
 }  


Vale colocar algumas observações na parte do código:

       helper.ViewContext.Writer.Write("<div id=\"" + this.id + "\"");  
       helper.ViewContext.Writer.Write(" data-isGrid=\"true\"");  
       helper.ViewContext.Writer.WriteLine(">");  
       helper.ViewContext.Writer.WriteLine("<script type=\"text/xml\" id=\"" + this.id + "grid_xml\">");  
       helper.ViewContext.Writer.WriteLine(xml);  
       helper.ViewContext.Writer.WriteLine("</script>");  
       helper.ViewContext.Writer.WriteLine("</div>");  

Esse é o trecho que podemos dizer que é mais importante nessa logica, pois através do ViewContext que é disponibilizado pelo "helper" que seria basicamente todo conteúdo da pagina colocamos um pacote XML serializado que como você pode perceber no restante do código anterior é a nossa hierarquia de classes. Porque um pacote XML contido dentro de um <script> </script> ?  Isso é devido a renderização da pagina pois se escrevermos simplesmente o texto do XML serializado o browser irá tentar "fazer alguma coisa" com a string repassada prejudicando o nosso objetivo que como vamos ver adiante é capturar esse pacote via JavaScript   (...)

AHH, já ia esquecendo implemente uma outra classe chamada "HelperUtil.cs" nela coloquei o codigo responsável por serializar qualquer objeto repassado <T>, segue o codigo:
   public static class HelperUtil  
   {  
     public static string Serialize<T>(T value)  
     {  
       if (value == null)  
       {  
         return null;  
       }  
       XmlSerializer serializer = new XmlSerializer(typeof(T));  
       System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings();  
       settings.Encoding = new System.Text.UnicodeEncoding(false, false);  
       settings.Indent = false;  
       settings.OmitXmlDeclaration = false;  
       using (StringWriter textWriter = new StringWriter())  
       {  
         using (System.Xml.XmlWriter xmlWriter = System.Xml.XmlWriter.Create(textWriter, settings))  
         {  
           serializer.Serialize(xmlWriter, value);  
         }  
         return textWriter.ToString();  
       }  
     }  
   }  

Ao chamar o metodo Render(); do nosso helper o pacote XML será escrito na pagina que foi feita a chamada, nesta VIEW o código é bastante reduzido pois ai que está a "graça" de tudo hehe nela será contida apenas os parâmetros requisitados para compor a nossa Grid e como estamos utilizando MVC a Url.Content do nosso controlador aonde a busca dos dados será feita.


Index.cshtml

 @using MvcApplication1.Helpers  
 @{   
   Html.ExtGrid("DIV_GRID_CONSULTA_PESSOA").SetDataStore(@Url.Content("~/Teste/GetAll"), "idPessoa").  
      SetTitulo("Consultar Pessoas").  
      SetElementRoot("Pessoa").  
      SetPageSize(20).  
      Columns("idPessoa", "Pessoa", 50, true, "Int").  
      Columns("nmPessoa", "Nome", 820, true, "String").  
      Render();  
 }  

ATENÇÃO: não se esqueça de adicionar o @using do seu helper pois é extremamente valido, sem ele você não terá acesso aos métodos disponibilizados no helper.

Teria uma outra maneira de implementar essa referencia diretamente no Web.config mas optei por fazer na VIEW para uma melhor visualização conceitual e também expressar a importância pois sem a referencia você não terá acesso a sua classe ajudante.

Seguindo a nossa chamada que foi feita a partir da nossa VIEW o pacote XML foi gerado como retorno na pagina, agora iremos montar no JavaScript como iria ficar a nossa Grid. Antes de "codarmos" o JS teremos que adicionar em nosso projeto a pasta contendo o Source da estrutura do ExtJs, enfatizando que disse no começo utilizei a versão 3.4, aproveitando também criamos uma outra pasta chamada 
"ext-js-helpers" para colocarmos o nosso JS que será responsável pela criação da Grid.
Agora o escopo do nosso projeto ficaria mais ou menos assim (...)

Vamos implementar o nosso JS ...
 jQuery(document).ready(function () {  
   var grids = jQuery("div[data-isGrid=\'true']");  
   grids.each(function (i, obj) {  
     var grid = new Grid(i, obj);  
   });  
 });  
 function Grid(index, rootDiv) {  
   
   var $this = this;  
   var id = $(rootDiv).attr('id');  
   var id_grid_panel = 'GRID_'+id;  
   // Vamos pegar o nosso XML...  
   var atributosGrid = $($("#" + id + "grid_xml").html());  
   // aqui vamos pegar os atributos do nosso xml  
   var url        = $(atributosGrid).find('url').text();  
   var pkTabela   = $(atributosGrid).find('pkTabela').text();  
   var tituloGrid = $(atributosGrid).find('tituloGrid').text();  
   var root       = $(atributosGrid).find('elementoRoot').text();  
   var pageSize   = parseInt($(atributosGrid).find('pageSize').text());  
   
   var columnsModel = [];  
   var columnsField = [];  
   
   $(atributosGrid).find('columns column').each(function (i, obj) {  
     columnsModel.push({  
       id: $(obj).attr("name"),  
       header: $(obj).attr("header"),  
       dataIndex: $(obj).attr("name"),  
       width: parseInt($(obj).attr("tamanho")),  
       sortable: true  
     });  
     columnsField.push($(obj).attr("name"));  
   });  

   // criamos um JsonStore...  
   var StoreGrid = new Ext.data.JsonStore({  
     url: url,  
     root: root,  
     totalProperty: 'total',  
     idProperty: pkTabela,  
     fields: columnsField  
   });  

   StoreGrid.load({ params: { start: 0, limit: pageSize } }); 
 
   var grid = new Ext.grid.GridPanel({  
     id: id_grid_panel,  
     width: 900,  
     height: 395,  
     title: tituloGrid,  
     store: StoreGrid,  
     viewConfig: { emptyText: 'Nenhum registro encontrado', deferEmptyText: false },  
     stripeRows: true,  
     // grid columns  
     columns: columnsModel,  
     bbar: new Ext.PagingToolbar({  
       pageSize: pageSize,  
       store: StoreGrid,  
       displayInfo: true,  
       displayMsg: 'Exibindo Tópicos {0} - {1} of {2}',  
       emptyMsg: "Nenhum tópico para mostrar"  
     })  
   });  
   grid.render(id);  
 }  

Veja que não tem segredo !
O código é basicamente implementado a partir do ExtJs para gerar a instancia do objeto Ext.grid.GridPanel e as propriedades vindas do pacote XML sendo capturado utilizando a bilbioteca do JQuery e abusando do comando Push() para montar as propriedades de Object esperado pelo ExtJs dinamicamente.
Após a correta implmentação não vamos esquecer de adicionar os scripts a nossa pagina ...



Com isso feito o resultado da nossa Grid seria mais ou menos isso !

Os dados gerados não são dados vindo de uma base "quente" e sim dados aleatórios (de um Model) serializados no método que informamos no  @Url.Content.
Perceba a requisição feita através do firebug no Firefox

Algo bastante importante para chamarmos atenção seria os metodos SetElementRoot() passado na VIEW que corresponde ao elemento PAI do JSON de retorno e também é claro os campos que como pode perceber o objeto que utilizei tinha muito mais propriedades do que mostrei na Grid porém exibi apenas o campo idPessoa e nmPessoa do mesmo, a ideia é exatamente essa deixar intuitivo para o desenvolvedor que string NAME repassada ao Helper corresponde ao nome da propriedade do objeto serializado.

É isso galera, espero que isso tenha sido útil e aqueles que lerem absorvam a ideia que quis repassar dessa junção de Helper e ExtJs. 
OBS: vale ressaltar novamente a quantidade de código na VIEW para a criação do mesmo, deixando seu projeto mais limpo e padronizado.

Abraço.  =))
Assim que tenho visto que não há coisa melhor do que alegrar-se o homem nas suas obras, porque essa é a sua porção; pois quem o fará voltar para ver o que será depois dele?
Eclesiastes 3:22 

Nenhum comentário:

Postar um comentário