Utilizando JQGrid com WebForms    

Olá pessoal!

Muitas pessoas têm dificuldades de utilizar plugins JQuery com WebForms. Principalmente porque desenvolvedor WebForm muitas vezes sabe usar apenas ServerControls, desconhecendo muitos conceitos básicos de Web em sí. Não é a toa que fiz uma série de post nesse blog mostrando conceitos de Web com WebForms.

Geralmente, quando uma pessoa vê exemplos de JQuery, eles estão em PHP, e a pessoa acredita que não funcionaria com WebForms, ou mesmo que saiba que seja possível, não sabe como adaptar para WebForm. Obs.: Geralmente esse problema não acontece com quem desenvolve com Asp.Net MVC.

Nos últimos post, mostrei alguns conceitos que podem ajudar, todos relacionados a PageMethods, como esse e esse. Se você não leu nenhum deles, ou está caindo diretamente nesse post, aconselho fortemente a ler pelo menos esse: Invocando PageMethods diretamente com JQuery.

Se você entender bem esse post que citei, nem precisará desse para utilizar o JQGrid ou qualquer outro plugin JQuery.

Então vamos lá!

Nesse post, vou utilizar as funcionalidades básicas do JQGrid, como Listar, Pesquisar e Editar/Adicionar.

O mais básico, e que todo mundo utiliza, é o Listar. Vou fazer o seguinte no meu HTML/JavaScript:

   1: <script type="text/javascript">
   2:     $().ready(function () {
   3:          $.ajaxSetup({
   4:                dataFilter: function (data, type) {
   5:                    if (type == "json" || type == undefined) {
   6:                        var msg = eval('(' + data + ')');
   7:                        if (msg.hasOwnProperty('d'))
   8:                            return JSON.stringify(msg.d);
   9:                        else
  10:                           return data;
  11:                   }
  12:                   else return data;
  13:               }
  14:            });
  15:  
  16:         $("#tbGrid").jqGrid({
  17:             mtype: "post",
  18:             url: '/Default.aspx/Listar',
  19:             datatype: "json",
  20:             ajaxGridOptions: { contentType: 'application/json; charset=utf-8' },
  21:             colNames: ['ID', 'Nome', 'Idade', 'E-mail'],
  22:             colModel: [
  23:                        { name: 'PessoaID', index: 'PessoaID', width: 55 },
  24:                        { name: 'Nome', index: 'Nome', width: 150 },
  25:                        { name: 'Idade', index: 'Idade', width: 100 },
  26:                        { name: 'Email', index: 'Email', width: 150 }
  27:                    ],
  28:             rowNum: 10,
  29:             rowList: [10, 20, 30],
  30:             pager: '#pager',
  31:             sortname: 'PessoaID',
  32:             viewrecords: true,
  33:             sortorder: "desc",
  34:             caption: "JQGrid com WebForms",
  35:             serializeGridData: function (dados) {
  36:                 return JSON.stringify(dados)
  37:             },
  38:             jsonReader: { repeatitems: false, id: "PessoaID" }
  39:  
  40:         });
  41:         $("#tbGrid").jqGrid('navGrid', '#pager', { edit: false, add: false, del: false });
  42:     });
  43: </script>
  44: <table id="tbGrid">
  45: </table>
  46: <div id="pager">
  47: </div>

Até ai não fiz nada de mais, adicionei configurações padrões na Grid. Adicionei quatro colunas que virão do meu PageMethod, que está na página Default.aspx com o nome Listar.

Como será que deve ficar meu PageMethod para aceitar a requisição dessa Grid? Se não sabe, ai vai uma dica: Utilizando o Fiddler ou o DevelopToolBar, você pode ver qual HTTP Request o Grid está fazendo. Verificando quais campos estão lá você saberá que são os mesmos parâmetros que deve ter no seu PageMethod.

No meu caso, o JQGrid mandou os seguintes campos:

{"_search":false,"nd":1314831637255,"rows":10,"page":1,"sidx":"PessoaID","sord":"desc"}

No meu caso, o que importa é :

  • rows – Representa a quantidade de linha por página.
  • page – Indica a página atual.
  • sidx – Indica o campo pelo qual a Grid está ordenada. Sidx significa Search Index
  • sord – Indica a ordem que o campo definido em sidx deve ser ordenado.

Nesse caso, se meu PageMethod tiver esses campos já será o suficiente. Abaixo segue a implementação dele:

   1: [WebMethod]
   2: public static Reader<Pessoa> Listar( string sidx, string sord, int page, int rows)
   3: {
   4:     var modelo = new Modelo();
   5:     var lista = modelo.Pessoas.ToList();
   6:     var reader = new Reader<Pessoa>(lista,page,rows);
   7:     return reader;
   8: }

Como ele faz a busca, muda de solução para solução. No meu caso eu estou realizando a pesquisa em um SQLServer CE com EntityFramework. Minha classe Reader é responsável por realizar uma “paginação” nos registros retornados e retornar os campos de acordo com o JSONReader da JQGrid. Todo o código fonte dele está abaixo, apenas por curiosidade, visto que você não precisa dele para fazer seu JQGrid funcionar, ele apenas ajuda:

   1: public class Reader<T>
   2: {
   3:    /// <summary>
   4:    /// Número de páginas retornadas no registro
   5:    /// </summary>
   6:    public int total { get; set; }
   7:    /// <summary>
   8:    /// Página atual que a Grid exibirá
   9:    /// </summary>
  10:    public int page { get; set; }
  11:    /// <summary>
  12:    /// Número total de registros na base.
  13:    /// </summary>
  14:    public int records { get; set; }
  15:    /// <summary>
  16:    /// Lista com cada registro.
  17:    /// </summary>
  18:    public List<T> rows { get; set; }
  19:  
  20:    /// <summary>
  21:    /// Construtor recebe a lista e faz a paginação virtual.
  22:    /// </summary>
  23:    /// <param name="lista">Lista com os registros</param>
  24:    /// <param name="paginaAtual">Página atual.</param>
  25:    /// <param name="totalPorPagina">Total de registros por páginas</param>
  26:    public Reader(List<T> lista,int paginaAtual, int totalPorPagina)
  27:    {
  28:  
  29:        var totalRegistros = lista.Count();
  30:        page = paginaAtual;
  31:        var ini = (page - 1) * totalPorPagina;
  32:        var fim = totalRegistros > totalPorPagina ? totalPorPagina : totalRegistros;
  33:        
  34:        if (ini > 0)
  35:        {
  36:            fim = totalPorPagina;
  37:            fim = fim - 10 >= 0 ? totalRegistros % totalPorPagina : fim;
  38:        }
  39:        var totalPags = totalRegistros / totalPorPagina;
  40:        if (totalRegistros % totalPorPagina > 0)
  41:        {
  42:            totalPags++;
  43:        }
  44:        total = totalPags;
  45:        page = page;
  46:        records = totalRegistros;
  47:        rows = lista.ToList().GetRange(ini, fim);
  48:    }
  49: }

Com os códigos acima minha Grid já está funcionando:

image

Se, por exemplo, eu mudo a quantidade de registros por página, a Grid vai passar os novos valores nos parâmetros do PageMethod, e minha pesquisa retornará a nova quantidade de registros por página:

{"_search":false,"nd":1314832264231,"rows":"20","page":1,"sidx":"PessoaID","sord":"desc"}

E se eu quiser utilizar os campos de pesquisa do JQGrid? Será que o PageMethod já está preparado? Vamos ver. Vou buscar pelo usuário de nome “Frederico” na lista da Grid, vamos ver o que a JQGrid envia para o servidor utilizar o Fiddler ou o IEDeveloperToolbar, ou qualquer ferramenta de sua vontade. Utilizando a “Lupa” da Grid e selecionando o campo na popup que aparece:

image

O que ele envia é o seguinte:

{"_search":true,"nd":1314832456208,"rows":"20","page":1,"sidx":"PessoaID","sord":"desc", "searchField":"Nome","searchString":"Frederico","searchOper":"eq","filters":""}

Veja que alguns campos foram adicionados e outros estão com novos valores, os campos que teremos que adicionar em nosso PageMethod são:

  • _seach – Indica se está sendo realizando uma pesquisa ou se é apenas a listagem do dados
  • searchField – Indica por qual campo está sendo pesquisado.
  • searchString – Indica qual é o valor pesquisado.
  • searchOper – Indica qual é o operado utilizado para a pesquisa.

Vou alterar meu PageMethod  para ficar da seguinte forma:

   1: [WebMethod]
   2: public static Reader<Pessoa> Listar(GridParams param)
   3: {
   4:     var modelo = new Modelo();
   5:     List<Pessoa> lista;
   6:  
   7:     if (param._search)
   8:     {
   9:         var operador = new Func<string,string,string>((op,valor) =>
  10:         {
  11:              int saida;
  12:              bool isNum = Int32.TryParse(valor, out saida);
  13:             switch(op){
  14:                 case "ne":
  15:                     return isNum? string.Format(" <>{0}", valor): string.Format(" <>\"{0}\"", valor);
  16:                 default:
  17:                     return isNum ? string.Format(" = {0}", valor) : string.Format(" = \"{0}\"", valor);
  18:                 }
  19:         });
  20:  
  21:         var sb = new StringBuilder();
  22:         sb.Append(param.searchField);
  23:         sb.Append(operador(param.searchOper,param.searchString));
  24:         lista = modelo.Pessoas.Where(sb.ToString()).ToList();
  25:     }
  26:     else{
  27:         lista = modelo.Pessoas.ToList();
  28:     }
  29:     
  30:     var reader = new Reader<Pessoa>(lista, param.page, param.rows);
  31:     return reader;
  32: }

Primeira mudança importante é que troquei todos os parâmetros por uma classe. A classe encapsula todos aqueles campos que a JQGrid envia para o servidor, independente do Request. Apesar de ser bem simples, segue o código:

   1: public class GridParams
   2: {
   3:     public string sidx;
   4:     public string sord;
   5:     public int page;
   6:     public int rows;
   7:     public bool _search;
   8:     public string searchField;
   9:     public string searchString;
  10:     public string searchOper;
  11: }

Criei essa classe para receber todos os parâmetros porque o Asp.Net não aceita WebServices/PageMethods com parâmetros opcionais ou com Overloads, então criei um método com apenas um parâmetro, que pode ter todos os campos definidos ou não. Visto que a JQGrid utiliza o mesmo método para listar e pesquisar, essa é a única solução.

O resto é apenas a lógica do método, que você pode mudar com a sua no lugar, a minha é só para exemplo. O segredo está em validar o parâmetro _search, se ele for true quer dizer que o usuário está realizando uma pesquisa, e não apenas carregando a lista. Para simular, criei apenas lógica necessária para o usuário usar um Campo como critério igual ou diferente do valor que for fornecido.

Com isso minha lógica já estará funcionando.

Como mudei o parâmetro do PageMethod, preciso apenas alterar um evento na configuração da Grid, para encapsular o nome do parâmetro:

   1: serializeGridData: function (dados) {
   2:    return JSON.stringify({param: dados})
   3: },

Esse evento é invocado pela Grid logo antes da requisição ser enviada para o servidor, então eu coloco os parâmetros dentro do campo param. E pronto! sua Grid está funcionando perfeitamente com Asp.Net WebForm.

Para finalizar, vamos fazer a Grid conseguir criar e alterar registros

Adicionando  e Alterando registro com JQGrid

Antes de mais nada, devo habilitar algumas configurações no Pager da grid, fazendo que o botão de adicionar e editar apareça:

   1: $("#tbGrid").jqGrid('navGrid', '#pager', { edit: true, add: true, del: false });

E alterar o ColModel para dizer como será a edição de cada campo:

   1: colModel: [
   2:    { name: 'PessoaID', index: 'PessoaID', width: 55, editable: false, editoptions: { readonly: true, size: 10} },
   3:    { name: 'Nome', index: 'Nome', width: 150, editable: true, editoptions: { size: 50} },
   4:    { name: 'Idade', index: 'Idade', width: 100, editable: true, editoptions: { size: 2 }, editrules: {integer:true} },
   5:    { name: 'Email', index: 'Email', width: 150, editable: true, editoptions: { size: 50 }, editrules: { email: true} }
   6: ],

Depois adicionar a opção para definir o caminho do PageMethod responsável por salvar o registro:

   1: editurl: "/Default.aspx/Salvar"

Também devemos alterar algumas configurações da Grid para conseguir tratar o JSON, porque infelizmente o JQuery tem um problema ao converter os dados para JSON automaticamente, então nós mesmos temos que fazer isso. Para isso, vamos alterar algumas informações padrões da Grid:

   1: $.extend($.jgrid.edit,
   2: {
   3:     ajaxEditOptions: { contentType: "application/json;charset=utf-8" },
   4:     serializeEditData: function (data) {
   5:         return JSON.stringify(data);
   6:     }
   7: });

Feito isso, não precisamos mais fazer nada no JavaScript, apenas preparar o PageMethod. que você verá que é autoexplicativo:

   1: [WebMethod]
   2: public static void Salvar(string Nome, int Idade, string Email, string oper, string id)
   3: {
   4:     var modelo = new Modelo();
   5:     if (oper == "add")
   6:     {
   7:         var pessoa = new Pessoa
   8:         {
   9:             Nome = Nome,
  10:             Idade = Idade,
  11:             Email = Email
  12:         };
  13:         modelo.Pessoas.Add(pessoa);
  14:     }
  15:     else
  16:     {
  17:         var pessoa = modelo.Pessoas.Where("PessoaID = " + id).ToList()[0];
  18:         pessoa.Nome = Nome;
  19:         pessoa.Idade = Idade;
  20:         pessoa.Email = Email;
  21:     }
  22:     modelo.SaveChanges();
  23: }

E pronto!

Com isso você já pode testar a alteração e inclusão clicando nos botões que aparecem na barra da Grid:

Alterando registro com JQGrid X WebForms

Criando registro com JQGrid X WebForms

É isso pessoal, com o que foi passado nesse post você já consegue ir muito além. Você viu que tudo é possível com a estrutura que o Asp.Net fornece para o WebForm.

Espero que isso ajude. Muitas pessoas já me perguntaram sobre JQGrid com WebForm, isso deve ajudar muita gente.

Abraços e até o próximo!

 

::Atualização

Estou publicando o exemplo do código para esclarecer as dúvidas de alguns. Qualquer problema, comente ai embaixo.

Abs.

JQGridWebForm.zip (3,04 mb)

2. setembro 2011 00:39 by Frederico B. Emídio | Comments (15) | Permalink

Resolvendo o problema do “d” na serialização JavaScript (JSON) do Asp.Net    

Olá pessoal!

Hoje vou falar de um problema que muitos desenvolvedores devem encarar quando tentam utilizar a Serialização para JavaScript a partir da versão 3.5 do framework.

Muitas vezes você deve ter migrado uma aplicação sua da versão 2.0 para a versão 3.5 ou superior, e suas requisições Ajax, ou plugins JQuery, pararam de funcionar e você perdeu horas tentando resolver. Ou quando descobriu o motivo, entrou em pânico porque viu que deveria alterar o retorno de todas as suas chamadas para começar a tratar uma propriedade “d” nos seus retornos JSON.

Se vicê já se deparou com isso, vamos aos fatos!

Exemplificando

Vou fazer um código simples, um PageMethod que retorno o nome e a idade de uma pessoa, vamos ver o retorno na versão 2.0 do .Net e na versão 3.5 ou superior, e ver o que pode impactar nossa aplicação. Esse exemplo é igual ao do post anterior:

Meu código C# (PageMethod)

   1: [WebMethod]
   2: public static object ObterPessoa()
   3: {
   4:    return new { Nome = "Frederico", Idade = 25 };
   5: }

Meu código JavaScript:

   1: $().ready(function () {
   2:        $.ajax({
   3:            type: "post",
   4:            contentType: "application/json",
   5:            url: "Default.aspx/ObterPessoa",
   6:            success: retorno
   7:        });
   8:    });
   9:  
  10:    function retorno(data) {
  11:        alert(data.Nome + " - " + data.Idade);
  12: }

O resultado como esperado:

image

A comunicação no Fiddler, conforme o esperado (Clique para ampliar):

image

O que quero mostrar é que o resultado do JSON foi exatamente como o esperado:

{"Nome":"Frederico","Idade":25}

E se eu mudar para o site na versão 3.5? Sem alteração nenhuma de código, meu alerta ficará assim:

image

E o Fiddler mostraria o problema (clique para ampliar):

image

Novamente, repare no JSON retornado:

{"d":{"Nome":"Frederico","Idade":25}}

Repare que agora, o .Net adicionou uma propriedade “d” no objeto, e todo o meu retorno está dentro dessa propriedade “d”.

Isso pode gerar muitos problemas. Imagine que você utilize um plugin Autocomplete, que espera uma lista de itens, e ao invés disso ele recebe um objeto apenas com uma propriedade “d” que por sua vez tem uma lista de itens.

Isso pode gerar grandes impactos em códigos. Veja, para meu simples código funcionar eu já teria que mudar o meu método que trata o retorno, para ficar da seguinte forma:

   1: function retorno(data) {
   2:    alert(data.d.Nome + " - " + data.d.Idade);
   3: }

E finalmente o alerta paracer como o esperado:

image

Veja que tive que adicionar o “d”. Imagine agora no impacto se você estiver utilizando JQGrid, tendo que adicionar o mapa para o D em todos os seus JSONReaders.

Agora que entendemos o problemas, vamos aos motivos

A Microsoft resolveu implementar essa alteração na forma de serialização para resolver um problema de segurança, conhecido como JSON Hijacking, que consiste em um hacker interceptar uma requisição JSON e alterar o comportamento de sites mediante requisições desse tipo, lendo e escrevendo informações de forma anônima, como se fosse outra pessoa. Como o objetivo do post não é falar dos motivos, você pode ver mais informações sobre isso nos links abaixo (Uma rápida busca no Google por JSON Hijacking pode te dar outros resultados).

http://haacked.com/archive/2009/06/25/json-hijacking.aspx

http://www.thespanner.co.uk/2011/05/30/json-hijacking/

http://www.openajax.org/whitepapers/Ajax%20and%20Mashup%20Security.php

Resolvendo o problema com JQuery

Como é muito provavel que qualquer controle Ajax da própria Microsoft ou do AjaxToolkit já esteja preparado para essa alteração do “d”. O foco da solução é para o mundo JQuery. A maior parte dos problemas ocorre com plugins, então como podemos solucionar esse problema de forma geral, uma vez por todas, para todos os plugins?

Simples, usando uma técnica que já vimos no último post: Definindo informações padrões do método ajax do JQuery.

Utilizaremos o método ajaxSetup para definir um filtro a todo retorno que recebermos de requisições assíncronas iniciadas pelo JQuery ou qualquer pluigin baseado nele, como o JQGrid e Autocomplete, citados nesse post.

O método Ajax do JQuery faz uso de um evento chamado dataFilter, responsável por fazer qualquer tratamento nos dados retornados do servidor no momento em que a resposta chega, antes mesmo de ser repassado para qualquer plugin que esteja invocando o método. Nesse evento nós podemos fazer o tratamento necessário e retirar a propriedade “d”. Vejamos um código

   1: $.ajaxSetup({
   2:    dataFilter: function (data, type) {
   3:    
   4:        if (type == "json" || type == undefined) {
   5:            var msg = eval('(' + data + ')');
   6:            if (msg.hasOwnProperty('d'))
   7:                return JSON.stringify(msg.d);
   8:            else
   9:                return data;
  10:        }
  11:        else return data;
  12:    }
  13: });

Veja que nesse código, eu verifico o tipo do retorno, e se ele for JSON (ou se não foi especificado eu considero que é JSON) eu transformo em um objeto JavaScript normal (eval), verifico se tem o “d” e se tiver, utilizo o valor do “d” com retorno, se não tem o “d” ou não é JSON eu retorno o próprio valor retornado do servidor.

Para fazer funcionar, adicionei um referência ao arquivo “json2.js”, que pode ser encontrado no Google, e desta forma essa biblioteca fica responsável por transformar meu objeto sem o “d” em JSON novamente. Obs.: Essa biblioteca só é necessária em navegadores mais antigos.

Eu só preciso configurar esse método uma vez, no arquivo MasterPage ou Layout (para MVC), por exemplo, e todas as requisições já estarão configuradas para funcionar utilizando esse método.

Caso você queira apenas utilizar esse método em uma única requisição ajax, pode passá-lo como parâmetro do próprio método $.ajax().

E pronto! Seus plugins estarão funcionando no Asp.Net 3.5 ou superior, sem que você tenha que mudar qualquer código de plugin, ou seu próprio código “legado”.

Espero que possa ajudar como ajudou a mim. O código foi baseado em um código desse post do Encosia.

Abraços e até o próximo!

23. agosto 2011 20:45 by Frederico B. Emídio | Comments (1) | Permalink

Invocando PageMethods diretamente com JQuery    

Olá pessoal!

Apesar de já ter tratado de PageMethods em um dos meus primeiros posts, vou falar um pouco mais dele novamente, porque vejo que muita gente ainda não entendeu com funciona, o que torna difícil a utilização do mesmo de outra forma que não seja através do proxy que o próprio Asp.Net cria no JavaScript.

Para entender o básico, veja o primeiro post sobre o assunto, para eu não precisar repetí-lo aqui.

Resumidamente, adicionamos um PageMethod criando um método estático em uma página, e decorando esse método com o atributo [WebMethod]. E no JavaScript acessamos esse método através do proxy no JavaScript da seguinte forma: PageMethods.[NomeDoMetodo].

Basicamente o Asp.Net disponibiliza o método como um WebService, e o JavaScript acessa o mesmo através da classe XmlHttpRequest.

Mas por que muitas vezes não conseguimos acessar esses métodos através do JQuery? Alguns Plugins como JQGrid e Auto-Complete as vezes têm certa dificuldade de conseguir enviar o chamado ou processar a resposta. Vamos a alguns exemplos.

Vou fazer um código bem simples, meu PageMethod vai ser assim:

   1: [WebMethod]
   2: public static string ObterPessoa()
   3: {
   4:     return "Retorno do Server";
   5: }

E meu JavaScript:

   1: $().ready(function () {
   2:     $.ajax({
   3:         url: "Default.aspx/ObterPessoa",
   4:         success:retorno
   5:     });
   6: });
   7:  
   8: function retorno(data) {
   9:     alert(data.d);
  10: }

Veja que o código é bem simples. Estou utilizando o mínimo do método ajax do JQuery, ou seja, os demais valores estão sendo utilizados os valores Default. Vendo o código, o retorno deveria uma alerta do JavaScript com o valor “Retorno do Server”. Mas a resposta é: Nada! Exatamente, nada acontece, inclusive não retorna erro algum.

Nesse momento é que o desenvolvedor começa a passar as mãos na testa e falar: “Maldito Murphy!”.

Se olharmos as chamadas o Fiddler ou outra ferramenta que monitora os Requests, teremos algumas informações interessantes. Na minha IE Developer Toolbar tenho as seguintes informações (clique para ampliar):

image

Perceba que na última linha tem a minha chamada para o PageMethod, sem erro, mas com um código diferente (304). Lembre que códigos de erros são apenas os 4xx e 5xx, ou seja, de fato não houve erro.

O HTTP Return Code 304 indica que o arquivo (ou resource) solicitado não teve alteração, ou seja, o Browser pode obter o valor do cache se quiser, ou solicitar a página mesmo assim. Então pense, se aparentemente é a primeira vez que estamos chamando o método, por que ele está falando que já está em cache? Qual será o retorno? Vamos dar uma olhada no retorno (clique para ampliar):

image

Veja que é o próprio código da página, e não do PageMethod, ou seja, o Asp.Net, ao receber o Request, está entendendo que o que está sendo solicitado é a página e não o método.

Isso acontece porque estamos invocando o método com o verbo HTTP GET, e a primeira coisa que devemos saber é que PageMethods só funciona com HTTP POST!

Outra coisa que podemos ver na primeira imagem, é que o tipo da solicitação é “text/html” e o retorno do Asp.Net está de acordo, ou seja, o texto HTML da página.

Portanto, a segunda coisa que devemos saber é que para o PageMethod funcionar é que o tipo da requisição deve ser json ou xml.

Vamos mudar apenas essas duas coisa e ver o que acontece:

   1: $().ready(function () {
   2:     $.ajax({
   3:         type: "post",
   4:         contentType: "application/json", 
   5:         url: "Default.aspx/ObterPessoa",
   6:         success:retorno
   7:     });
   8: });
   9:  
  10: function retorno(data) {
  11:     alert(data.d);
  12: }

E os prints da Developer Toolbar:

image

Veja que algumas coisas mudaram:

Primeiro que o método agora é POST e o código do retorno é 200, ou seja, sucesso (OK). Além disso, o tipo da requisição também mudou, sendo agora application/json. Só o fato dessas informações terem mudado já é alguma coisa. Mas vamos ver o retorno:

image

E o alerta, como o esperado.

image

Assim já fazemos nosso PageMethod funcionar. Poderíamos terminar o post por aqui, mas tem mais algumas coisas que precisamos saber, por exemplo, como eu passo parâmetros?

Passando parâmetros para PageMethods com JQuery

Vou mudar meu PageMethod e meu JavaScript para passar parâmetros. A alteração é muito simples, preciso apenas passar meu dados no formato JSON, assim o Asp.Net vai saber converter os dados do requests para os parâmetros do método. Vamos lá:

   1: [WebMethod]
   2: public static string ObterPessoa(string nome, string idade)
   3: {
   4:    return string.Format("Nome {0}. Idade {1}",nome,idade);
   5: }

E o JavaScript:

   1: $().ready(function () {
   2:    $.ajax({
   3:        type: "post",
   4:        contentType: "application/json",
   5:        url: "Default.aspx/ObterPessoa",
   6:        success: retorno,
   7:        data: '{nome:"Frederico",idade:"25"}'
   8:    });
   9: });
  10:  
  11: function retorno(data) {
  12:    alert(data.d);
  13: }

Dessa forma conseguimos passar os dados, e podemos ver com o alerta que o retorno foi como o esperado:

image

Conclusão

Vimos que é possível invocar PageMethods do JQuery sem muito esforço. Precisamos apenas saber quais parâmetros passar na chamada. Muitas vezes nossos plugins não funcionam com PageMethods porque não sabemos quais parâmetros utilizar. Uma vez que já sabemos, fica fácil.

Tenha em mente que para um PageMethod funcionar, você deve invocá-lo:

  • Utilizando o verbo Post
  • Utilizando o Content-Type como application/json.

Isso já é suficiente para funcionar.

Se você vai invocar muitas vezes métodos através do Ajax do JQuery, você pode alterar os valores Defaults e invocar de forma mais sucinta, da seguinte forma:

   1: $().ready(function () {
   2:     $.ajaxSetup({
   3:         type: "post",
   4:         contentType: "application/json"
   5:     });
   6:     
   7:     //E chamar sem precisar configurar novamente.
   8:     $.ajax({
   9:         url: "Default.aspx/ObterPessoa",
  10:         success: retorno,
  11:         data: '{nome:"Frederico",idade:"25"}'
  12:     });
  13: });

Essa tática é muito boa para utilizar com plugins, afinal, você não consegue (ou não deve) alterar a chamada internas dos plugins, como o JQGrid, então assim você alterar o padrão, para que o plugin utilize o método $.ajax da forma que você deseja.

O interessante de utilizar JQuery, é que você não precisa utilizar o ScriptManager com EnablePageMethods igual a true, porque você não precisará do Proxy do JavaScript. Sua página ficará consideravelmente mais leve se você tiver vários PageMethods em uma página só.

Em breve publicarei um breve post para explicar o porquê do “.d” no retorno do PageMethods, o que também muitas vezes dificulta a utilização de PageMethods com plugins JQuery.

Bom pessoal, espero que ajude. Até o próximo.

16. agosto 2011 09:32 by Frederico B. Emídio | Comments (1) | Permalink

Como funciona o UpdatePanel?    

Olá pessoal!

Hoje vou falar de um tema bem simples do WebForm, mas que muitas pessoas utilizam errado, acreditando que estão fazendo a coisa certa. Vou falar do UpdatePanel.

O UpdatePanel é um controle do Asp.Net Ajax, utilizado para renderizar partes parciais de uma tela sem a necessidade de fazer um completo PostBack. Isso todos já sabem.

Para saber como utilizá-lo, é necessário conhecer algumas propriedades básicas:

Triggers – É uma coleção de controles que têm a capacidade de fazer o conteúdo do UpdatePanel atualizar. Existem dois tipos: Assíncronos e Síncronos. Em geral, quando utilizamos UpdatePanel utilizamos apenas os tipos Assincronos, ou seja, que atualizam sem realizar o PostBack. o Triggers do tipo Síncrono tem o comportamento normal, realizando o PostBack da página inteira.
ChildrenAsTriggers – Indica se os controles internos de um UpdatePanel automaticamente serão tratados como Triggers. Caso essa propriedade esteja como false, para que um botão interno do UpdatePanel dispare uma atualização parcial este deve ser colocado na lista de Triggers.
UpdateMode – Indica se a atualização do UpdatePanel vai sempre acontecer quando um evento assíncrono ocorrer ou apenas quando for invocado o método Update do UpdatePanel a partir do Code-behind.
RenderMode – Não é uma propriedade tão importante, mas não custa nada conhecer. Essa propriedade indica como será renderizado o controle referente ao UpdatePanel no HTML.  Os possíveis valores são: Block, que é o padrão, e renderizará o controle como DIV; e a outra opção é Inline, que renderizará um SPAN.

Além de conhecer essas propriedades, também temos que saber que para utilizar um UpdatePanel, e qualquer outro controle do Asp.Net Ajax, temos que utilizar o controle ScriptManager. Esse controle é responsável por adicionar à página todos os JavaScripts necessários para fazer funcionar as capacidades Ajax do site. Ele deve ser adicionado antes de qualquer controle Ajax.

Com isso, já poderíamos criar uma página totalmente Ajax, apenas utilizando um ScriptManager, mas será que isso está certo? O intuito desse post é mais responder essa questão do que qualquer outra coisa.

Como funciona UpdatePanel

Antes de vermos aos boas práticas de utilização do UpdatePanel, precisamos saber como ele funciona. Lembre-se, não existe mágica, e saber como funciona pode ajudar muito a resolver problemas que parecem coisa de Murphy.

Muitas pessoas acham que apenas o conteúdo interno do UpdatePanel é enviado ao Server, e isso é um grande erro. A primeira coisa que devemos saber é que ao atualizarmos uma região de uma página, todos os campos Forms de da página são enviado para o servidor. Isso inclui controles como Input, Select, etc; que por sua vez inclui todo o ViewState da página.

Acredite, sem enviar todos os campos para o servidor, incluindo o ViewState, não seria possível você ler os valores dos campos no CodeBehind. Isso deveria ser óbvio, mas já vi muitas pessoas descordarem nesse ponto.

O que muda é o retorno, que virá apenas o que deve ser atualizado. No retorno, o que altera naturalmente não é a página inteira, mas apenas o conteúdo do UpdatePanel.

Com isso em mente, ao utilizar UpdatePanel, você deve saber:

Não importa quão pequeno seja seu UpdatePanel, o envio para o Server vai ser sempre o mesmo.

Além disso, o ciclo de vida da página permanece inalterado, ou seja, todos os eventos serão invocado da mesma forma como seriam em um PostBack padrão. Portanto, se você clicar no botão btnSalvar, não será apenas o método btnSalvar_Click que será invocado, mas também o PageLoad e outros métodos necessários para renderização, como PreReder e Render dos controles envolvidos.

Para isso, o Asp.Net também enviará informações extras que controlam essas informações, como o controle clicado.

Vamos a um exemplo.

Vou utilizar o código do post sobre exportar para Excel, adicionando funcionalidades Ajax. Meu HTML ficaria da seguinte forma:

   1: <asp:ScriptManager ID="mng" runat="server">
   2: </asp:ScriptManager>
   3: <h2>Exportando para Excel</h2>
   4: <p>Nome:<asp:TextBox ID="txtNome" runat="server"></asp:TextBox></p>
   5: <p>Idade:<asp:TextBox ID="txtIdade" runat="server"></asp:TextBox></p>
   6: <p>Email:<asp:TextBox ID="txtEmail" runat="server"></asp:TextBox></p>
   7: <p>
   8: <asp:Button Text="Salvar" runat="server" ID="btnSalvar" OnClick="btnSalvar_Click" />
   9: </p>
  10: <asp:UpdatePanel runat="server" >
  11:     <ContentTemplate>
  12:         <asp:GridView ID="grid" runat="server" AllowPaging="true" PageSize="5" AutoGenerateColumns="true"
  13:             OnPageIndexChanging="grid_PageIndexChanging">
  14:         </asp:GridView>
  15:     </ContentTemplate>
  16:     <Triggers>
  17:         <asp:AsyncPostBackTrigger ControlID="btnSalvar"  EventName="Click"/>
  18:     </Triggers>
  19: </asp:UpdatePanel>
  20: <asp:Button ID="btnExportar" Text="Exportar" runat="server" OnClick="btnExportar_Click" />

Veja que apenas adicionei o UpdatePanel e o ScriptManager. Não mudei nada no CodeBehind. Com isso minha página já está funcionando com Ajax.

Apesar do meu UpdatePanel estar apenas na Grid, veja que o envio para o server, quando eu clicar no botão Salvar, enviará o conteúdo dos campos de cadastro e o ViewState. Veja a imagem do Fiddler (Clique para ampliar).

Fiddler do UpdatePanel

Na Imagem acima estou mostrando que o envio inclui todo o conteúdo da página, que será o mesmo se não tiver UpdatePanel, mas o retorno retorna só o que deve ser atualizado, que deve ser a Grid, além da atualização do ViewState.

A única diferença é que se não tiver UpdatePanel, a página seria enviada inteira, e isso incluiria todas as tags HTML (head, body, etc), que não são enviadas no update assíncrono.

Como utilizar corretamente um UpdatePanel

Agora que entendemos como funciona, devemos saber algumas boas práticas, e responder àquela pergunta: “Será que estou fazendo certo?”. Por exemplo, muitas pessoas têm uma tela enorme de cadastro, e acredita que apenas colocando um único UpdatePanel na página toda o problema está resolvido. Realmente resolve, mas não da melhor forma.

Nessas situações, as vezes para atualizar um simples Combo, todo o conteúdo do formulário é atualizado assincronamente. Isso deixa a página extremamente lenta, pois é enviado um enorme conteúdo para o serve, e o retorno, que poderia ser apenas uma linha de HTML, acaba sendo a página inteira.

Para isso, segue algumas dicas para fazer suas páginas rápidas e bem feitas com utilização UpdatePanel, e evitar diversos erros inexplicáveis.

  1. Não utilize UpdatePanels que englobem a página toda, a não ser que sua página realmente seja pequena e tenha realmente que atualizar toda de uma vez.
  2. Utilize em geral a propriedade ChildrenAsTrigger como false a maior parte do tempo, ao menos que saiba realmente que é necessário que todos os filhos sejam Triggers.
  3. Utilize mais de um UpdatePanel na sua página. Um para cada pequeno contexto que deve ser atualizado de acordo com a navegação do usuário.
  4. Controle quais controles devem atualizar cada UpdatePanel através da coleção de Triggers de cada UpdatePanel.
  5. Saiba que um controle pode ser Trigger de mais de um UpdatePanel.
  6. Saiba que um controle dentro de um UpdatePanel pode ser Trigger de outro UpdatePanel.
  7. Utilize com cuidado as regras acima, uma quantidade exagerada de UpdatePanels e Triggers em uma página pode deixá-la pesada, devido a quantidade de Scripts. Além disso, alguns poucos Browsers tem dificuldade de limpar memória, e a atualização de HTML pode causar um estouro de memória (acredito, isso é possível).

Bom, com essas dicas acredito que seu uso de UpdatePanel vai ficar mais tranquilo, e terá menos erros desconhecidos.

Espero que possa ajudar.

Até o próximo.

21. julho 2011 05:36 by Frederico B. Emídio | Comments (6) | Permalink

Download de arquivo com UpdatePanel    

Olá pessoal!

Agora, vou fazer um post bem rápido sobre um erro que ocorre quando é tentado realizar download em uma página com UpdatePanel.

Não é um post para detalhar o uso de UpdatePanel, isso você pode ver aqui, mas apenas um de seus comportamentos.

É um post simples, apenas para responder um pergunta: Não é possível fazer download com UpdatePanel? A resposta é: Sim, é possível. Veremos como logo abaixo.

Tomando como base o post sobre exportar para Excel, vou acrescentar a funcionalidade Ajax a ele um pouco diferente do que fiz no post de UpdatePanel.

Como explicado no post anterior, o UpdatePanel só deve estar na região que deve ser atualizada, e com certeza um arquivo para ser baixado não deve estar na região de atualização.

Vamos ao código!

Veja o código abaixo, extraído do post de UpdatePanel:

   1: <asp:ScriptManager ID="mng" runat="server"></asp:ScriptManager>
   2: <h2>Exportando para Excel</h2>
   3: <p>Nome:<asp:TextBox ID="txtNome" runat="server"></asp:TextBox></p>
   4: <p>Idade:<asp:TextBox ID="txtIdade" runat="server"></asp:TextBox></p>
   5: <p>Email:<asp:TextBox ID="txtEmail" runat="server"></asp:TextBox></p>
   6: <p><asp:Button Text="Salvar" runat="server" ID="btnSalvar" OnClick="btnSalvar_Click" /></p>
   7: <asp:UpdatePanel runat="server" >
   8:     <ContentTemplate>
   9:         <asp:GridView ID="grid" runat="server" AllowPaging="true" PageSize="5" AutoGenerateColumns="true"
  10:             OnPageIndexChanging="grid_PageIndexChanging">
  11:         </asp:GridView>
  12:     </ContentTemplate>
  13:     <Triggers>
  14:         <asp:AsyncPostBackTrigger ControlID="btnSalvar"  EventName="Click"/>
  15:     </Triggers>
  16: </asp:UpdatePanel>
  17: <asp:Button ID="btnExportar" Text="Exportar" runat="server" OnClick="btnExportar_Click" />

Apesar dessa página estar utilizando UpdatePanel, o Download do botão btnExportar vai funcionar. Por que? Por que a ação dele não é assíncrona. Ele não é Trigger de nenhum UpdatePanel.

Veja bem, quando tentamos baixar um arquivo dentro de um UpdatePanel, o conteúdo retornado para o Browser não é um HTML válido, logo, não será possível adicioná-lo dentro do UpdatePanel e o Asp.Net/ JavaScript reclama.

Se utilizarmos o botão Exportar dentro do UpdatePanel, teremos um erro, veja a seguir:

   1: <asp:UpdatePanel runat="server" >
   2:     <ContentTemplate>
   3:         <asp:GridView ID="grid" runat="server" AllowPaging="true" PageSize="5" AutoGenerateColumns="true"
   4:             OnPageIndexChanging="grid_PageIndexChanging">
   5:         </asp:GridView>
   6:         <asp:Button ID="btnExportar" Text="Exportar" runat="server" OnClick="btnExportar_Click" />
   7:     </ContentTemplate>
   8:     <Triggers>
   9:         <asp:AsyncPostBackTrigger ControlID="btnSalvar"  EventName="Click"/>
  10:     </Triggers>
  11: </asp:UpdatePanel>

Agora, na linha 6, o meu botão está dentro do UpdatePanel, e como ao clicar nele o Asp.Net Ajax vai preparar tudo para fazer Assíncrono, ao ter o retorno inválido (um Excel, por exemplo, não é um retorno válido) vai gerar o erro, que pode variar de acordo com a versão do Framework utilizado.

Mas então só botões que estão fora de um UpdatePanel podem fazer download? Lógico que não! Se você tiver o botão em uma GridView, que deve estar dentro de um UpdatePanel, e ele deve realizar um download, como fazer? Ou por qualquer motivo o seu botão tem que estar dentro do UpdatePanel? Simples, coloque o evento do botão, ou da Grid, como um Trigger Síncrono. Veja:

   1: <asp:UpdatePanel runat="server" >
   2:     <ContentTemplate>
   3:         <asp:GridView ID="grid" runat="server" AllowPaging="true" PageSize="5" AutoGenerateColumns="true"
   4:             OnPageIndexChanging="grid_PageIndexChanging">
   5:         </asp:GridView>
   6:         <asp:Button ID="btnExportar" Text="Exportar" runat="server" OnClick="btnExportar_Click" />
   7:     </ContentTemplate>
   8:     <Triggers>
   9:         <asp:AsyncPostBackTrigger ControlID="btnSalvar"  EventName="Click"/>
  10:         <asp:PostBackTrigger ControlID="btnExportar" />
  11:     </Triggers>
  12: </asp:UpdatePanel>

Agora meu botão, que está dentro do UpdatePanel, vai consegui realizar o Download, porque na linha 10 eu o adicionei como um Trigger de PostBack normal (Síncrono).

Caso eu não faça isso, terei o seguinte erro (Asp.Net 4):

Erro em tempo de execução do Microsoft JScript: Sys.WebForms.PageRequestManagerParserErrorException: The message received from the server could not be parsed.

A mensagem pode mudar, como disse, de acordo com o FrameWork utilizado. Poderia ser:

Microsoft JScript runtime error: Sys.WebForms.PageRequestManagerParserErrorException: The message received from the server could not be parsed. Common causes for this error are when the response is modified by calls to Response.Write(), response filters, HttpModules, or server trace is enabled.

Como disse no post sobre UpdatePanel, não existe mágica. Internamente o Framework Ajax da Microsoft utilizar os objetos Ajax do Browser (XMLHttpRequest), assim como o Jquery faz, e através desses objetos não é possível realizar download. Você poderia transferir os bytes de um arquivo de forma assíncrona, mas não seria possível convertê-lo em arquivo, porquê o JavaScript, em geral, não tem permissão no Browser para realizar esse tipo de função (acessar IO, memória, etc). caso contrário seria muito fácil fazer vírus.

Bom, espero que ajude quem precise dessa ajuda.

Até o próximo.

20. julho 2011 21:09 by Frederico B. Emídio | Comments (10) | Permalink

Asp.Net MVC 4 – Novidades para CSS e JavaScript    

Olá pessoal!

Hoje vou fazer um post bem rápido, apenas para comentar sobre a nova versão do Asp.Net MVC.

Para quem não sabe, a equipe do Asp.Net já está planejando a nova versão do Asp.Net MVC, e maiores detalhes podem ser encontrados aqui.

Para planejar as melhorias, eles definiram cinco guidelines, e no momento oportuno falarei de cada uma delas. Essas Guidelines vão de publicação e temas para Mobile à publicação na de sites na Nuvem.

O que achei interessante, e quis escrever esse post para comentar, é um item de publicação de site extremamente útil. Apesar de eles falarem que são apenas planos, e que qualquer coisa pode mudar, acho interessante comentar.

Eles estão planejando realizar um Build Integrado para CSS e JavaScript. Uma coisa que sempre senti falta no momento de compilar Web Applicattion.

A idéia seria, no momento da compilação do seu site, realizar a combinação de todos os seus arquivos CSS e JavaScript em apenas um (Um de cada tipo, naturalmente), e após realizar a combinação, fazer a minificação, ou seja, tirar todos os espaços desnecessários e comentários, além de alterações de nomes de algumas variáveis e funções, para diminuir consideravelmente o tamanho do arquivo.

Isso faria seu site carregar bem mais rápido, pois iria diminuir o número de requests por recursos do site (seria apenas um para cada tipo de recurso), além de diminuir o tempo de downloads, graças ao arquivo minificado (minified). Se você utilizar o Fiddler, por exemplo, para ver como é gasto o tempo de download de suas páginas, verá que esses arquivos realmente fazem diferença no load da página.

Como todos sabem, já existem ferramentas de minified disponíveis na internet, mas fazer todo esse trabalho manualmente para cada arquivo, e eventualmente combinar esses arquivos é uma tarefa bem enfadonha, e essa nova feature do Asp.Net MVC seria de muita ajuda.

Vamos torcer para que o time realmente implemente essa funcionalidade, porque acredito que é tipo de anuncio que dá brilhos nos olhos, mas é esquecido durante o processo de desenvolvimento. Afinal, as implicações de alterar e combinar arquivos automaticamente são inúmeras, e não são apenas boas implicações, afinal, alteraria nome de arquivo, o que teria que validar todas as referências em arquivos aspx ou cshtml, etc.

Vamos aguarda para ver o que o time do Asp.Net mostrará nos próximos meses. Todas as novidades serão mostradas nesse blog.

Abraços e até o próximo.

19. julho 2011 04:54 by Frederico B. Emídio | Comments (0) | Permalink

Exportando para Excel com Asp.Net WebForms    

Boa noite Pessoal!

Depois de um longo tempo sem escrever nada devido à alguns projetos que estão me tomando todo o tempo, resolvi retornar os post respondendo algumas dúvidas que têm chegado até mim de amigos e leitores do Blog.

A maioria deles são relacionados a WebForms, então vou deixar um pouco Asp.Net MVC e JavaScript de lado, e voltar para o mundo WebForm, que ainda é o universo mais utilizado do Asp.Net e tem muita coisa interessante para mostrar.

A dúvida hoje é bem rápida e simples, vou mostra uma forma muito rápida de exporta dados para Excel.

É comum em qualquer projeto ter a necessidade de exportar alguma coisa para Excel, então é bom sempre ter alguma prática na manga, e essa dica pode ajudar muito.

Naturalmente, essa prática não vai resolver todas as situações. Para algumas necessidades mais complexas talvez seja necessário utilizar alguma ferramenta de terceiro, ou o OWC da Microsoft. Quando você quiser fazer alguma mais simples, como apenas exportar uma tabela, essa técnica vai te ajudar muito.

Excel e HTML

Um conhecimento que temos que ter quando trabalhos com Excel é saber que ele interpreta HTML, ou seja, se você fizer uma página simples, com um HTML puro, e colocar a extensão xls, sua página já abrirá no Excel sem problemas.

Se você estruturar uma tabela (table) bem direitinho, seu Excel vai ficar muito bom. Você poderá utilizar inclusive CSS para aplicar cores, cellpadding para mesclar, etc.

Solução

O objetivo deste post não é fazer um cadastro, e sim mostrar como exportar dados para um Excel, mas de qualquer forma, vou explicar como é minha aplicação.

É uma aplicação bem simples. Fiz um cadastro de pessoas com três campos, que não faz a menor validação de dados, porque não é esse o objetivo, e fiz a persistência com EntityFramework Code First, salvando os dados em um SQL Server CE.

Além dos campos para cadastro, também coloquei um GridView para mostrar meus dados. Minha tela ficou como o print abaixo:

Tela de cadastro WebForm

Para implementar o Modelo fiz as seguintes classes:

   1: public class Pessoa
   2: {
   3:     public int PessoaID { get; set; }
   4:     public string Nome { get; set; }
   5:     public int Idade{ get; set; }
   6:     public string Email{ get; set; }
   7: }
   8:  
   9: public class Modelo: DbContext
  10: {
  11:     public DbSet<Pessoa> Pessoas { get; set; }
  12: }

E meu HTML também é bem simples:

   1: <h2>
   2:    Exportando para Excel
   3: </h2>
   4: <p>Nome: <asp:TextBox ID="txtNome" runat="server"></asp:TextBox></p>
   5: <p>Idade: <asp:TextBox ID="txtIdade" runat="server"></asp:TextBox></p>
   6: <p>Email: <asp:TextBox ID="txtEmail" runat="server"></asp:TextBox></p>
   7: <p><asp:Button Text="Salvar" runat="server" id="btnSalvar" 
   8:        onclick="btnSalvar_Click" /></p>
   9:  
  10: <asp:GridView ID="grid" runat="server" AllowPaging="true" PageSize="5" 
  11:    AutoGenerateColumns="true" onpageindexchanging="grid_PageIndexChanging"></asp:GridView>
  12:  
  13: <asp:Button ID="btnExportar" Text="Exportar" runat="server" 
  14:    onclick="btnExportar_Click"/>

Sem segredos, o CodeBehind dessa página não podia ser mais objetivo:

   1: protected void Page_Load(object sender, EventArgs e)
   2: {
   3:    if (!IsPostBack)
   4:    {
   5:        var modelo = new Modelo();
   6:        grid.DataSource = modelo.Pessoas.ToList();
   7:        grid.DataBind();
   8:    }
   9: }
  10: protected void grid_PageIndexChanging(object sender, GridViewPageEventArgs e)
  11: {
  12:    var modelo = new Modelo();
  13:    grid.PageIndex = e.NewPageIndex;
  14:    grid.DataSource = modelo.Pessoas.ToList();
  15:    grid.DataBind();
  16: }
  17: protected void btnSalvar_Click(object sender, EventArgs e)
  18: {
  19:    var modelo = new Modelo();
  20:    var pessoa = new Pessoa{Nome= txtNome.Text, Email=txtEmail.Text , Idade=Int32.Parse(txtIdade.Text)};
  21:    modelo.Pessoas.Add(pessoa);
  22:    modelo.SaveChanges();
  23:    grid.DataSource = modelo.Pessoas.ToList();
  24:    grid.DataBind();
  25:    txtEmail.Text = txtNome.Text = txtIdade.Text = "";
  26: }
  27: protected void btnExportar_Click(object sender, EventArgs e)
  28: {
  29:    var modelo = new Modelo();
  30:    var tw = new StringWriter();
  31:    var hw = new HtmlTextWriter(tw);
  32:  
  33:    hw.WriteLine("<h1>Dados Exportados</h1><br/>");
  34:  
  35:    DataGrid grid = new DataGrid();
  36:    grid.DataSource = modelo.Pessoas.ToList();
  37:    grid.HeaderStyle.Font.Bold = true;
  38:    grid.DataBind();
  39:    grid.RenderControl(hw);
  40:  
  41:    Response.Clear();
  42:    Response.AppendHeader("content-disposition", "attachment; filename=Arquivo.xls");
  43:    Response.ContentType = "application/vnd.ms-excel";
  44:    Response.Charset = "utf-8";
  45:    Response.ContentEncoding = System.Text.Encoding.GetEncoding("ISO-8859-1");
  46:    this.EnableViewState = false;
  47:    Response.Write(tw.ToString());
  48:    Response.End();
  49: }

O PageLoad e o evento PageIndexChanging não requer explicação, é bem simples e é comumente utilizado em todas as aplicações WebForms. O evento do botão Salvar também é tranquilo, e está utilizando EF para persistir o cadastro (sem nenhuma validação) e logo em seguida ele realiza o bind ao GridView.

Tudo que importa mesmo para esse post é o método bntExportar_Click.

Como falei anteriormente, o Excel interpreta HTML, então o que esse método faz é gerar um HTML para nós. Vamos explica-lo linha a linha:

Da linha 29 e 31 não tem segredo. Estou criando as varáveis necessárias e as mais importante são o StringWriter e  HtmlTextWriter, essas variáveis são responsáveis para renderizar o HTML no Response da página.

Na linha 33 e crio um título para minha planilha, adicionando uma tag h1.

O segredo está entre as linhas 35 e 39. Nessas linhas eu estou criando uma tabela (tag <TABLE>) para ser uma tabela no Excel. Eu poderia criar tag por tag dentro de alguns FORs por exemplo, mas preferi utilizar um controle do WebForm para renderizar isso para mim.  No caso, estou utilizando o DataGrid para fazer toda a lógica de criar a tabela. Eu poderia utilizar um GridView, mas o DataGrid é mais leve para nosso objetivo.

Na linha 36 eu adiciono os dados ao meu DataGrid,

Na linha 37 eu defino que o cabeçalho da tabela deve ter as letras em negrito, para mostrar que podemos utilizar as opções do DataGrid para gerar o Excel. Essas opções incluem qualquer uma do DataGrid, tais como: eventos, estilos, etc.

Na linha 38 eu realizo o DataBind.

E na linha 39 eu rederizo, ou seja, gero o HTML na variável do tipo HtmlTextWriter (hw).

Entre as linhas 41 e 45 eu faço o tratamento geral da página, limpando o conteúdo total da página (41), colocando as diretivas para forçar o download da página (42), já que não queremos que ela seja renderizada no Browser, dizemos que o tipo da página é Excel (43) e definimos informações de encoding do arquivo (44 e 45).

Na linha 46 eu desabilito o ViewState, para que não tenha conteúdo desnecessário no meu Arquivo.

Na linha 47 eu escreve o meu HTML armazenado na variável tw ao conteúdo do meu Response. E finalizo a criação do meu Response na linha 48.

Fazendo isso, ao clicar no meu botão de exportar da página, minha página gerará um Excel como o da imagem abaixo:

Imagem de Excel gerado pelo Asp.Net

Novamente, muito simples!

O que deve ficar claro nesse post, é que podemos utilizar as classes/controles do Asp.Net para auxiliar-nos na geração do HTML, e que qualquer HTML pode ser utilizado para geração de Excel, então podemos combinar nossos conhecimentos e facilitar nossa vida em situações que aparentemente poderiam ser mais complicadas.

Essa solução funciona em todas as versões do WebForm, e também é possível utilizar em Asp.Net MVC, com algumas pequenas adaptações.

Espero que ajude e até o próximo post!

10. julho 2011 00:06 by Frederico B. Emídio | Comments (4) | Permalink

Trabalhando com Classes no JavaScript - Parte II (Herança)    

Olá pessoal!

Falando um pouco mais do mesmo assunto abordado no post anterior, vou tratar de mais um tema que aproxima o JavaScript de Orientação a Objetos. Veja bem, aproxima, mas ainda não o torna em uma linguagem Orientada a Objetos.

Hoje vou mostrar como podemos implementar Herança ou extensão de objeto no JavaScript. Vamos lá!

Tipo de Herança em JavaScript

No JavaScript padrão conseguimos estender classes, ou seja, herdar características de uma classe em uma outra classe de três formas:

  • Através do método apply
  • Através do método call
  • Através da propriedade prototype

Muitas bibliotecas JavaScript espalhadas pela internet criam novas formas de estender objetos, como no Jquery o método $.extend ou a própria forma de criar plugins. Como cada uma implementa de uma forma diferente, não irei mostrar esses tipos, apenas o nativos do JavaScript, listados acima, sendo estes compatíveis com qualquer browser com suporte a JavaScript.

Os métodos Apply e Call no JavaScript

Os métodos Apply e Call eu expliquei no meu último post. Eles são métodos internos do JavaScript, disponíveis em classes e métodos, Visite o post para entender melhor.

Como dito no último post, a função dele é alterar o contexto do this no JavaScript, mas dependendo da forma que você utilizá-lo, você poderá “ampliar” o contexto do this para o contexto da classe sobre a qual está se executando o apply ou o call, ou seja, realizar a herança. Veja o código abaixo:

   1: var w = function (s) { document.write(s); }
   2: var br = function () { w("<br/>") };
   3: var Aninal = function (nome) {
   4:     //Atributo
   5:     this.nome = null;
   6:     //Métodos
   7:     this.comer = function (comida) {
   8:         w(this.nome + " come " + comida);
   9:     }
  10:     //Código do meu construtor
  11:     this.nome = nome;
  12: }

Animal é minha classe base, e nela tento falar que qualquer animal (especificado pelo parâmetro nome) pode comer a comida passada por parâmetro do método comer(). Como eu faria para especializar essa classe, implementando a mesma função para homem? Vou mostrar utilizando o método apply como fazer isso:

   1: var Homem = function () {
   2:     //Herdo da classe Animal passando
   3:     //o nome para o construtor da classe base
   4:     Aninal.apply(this, ["Homem"]);
   5:     //Método exclusivo da classe Homem
   6:     this.falar = function () {
   7:         w("Olá");
   8:     }
   9: }
  10: var homem = new Homem();
  11: //O Resultado vai ser:
  12: //Homem come Arroz e Feijão
  13: homem.comer("Arroz e Feijão");

Veja que desta forma a herança já foi implementada. A classe Homem herda de Animal e utiliza o método comer sem alteração.

Eu poderia fazer as duas classes mais inteligentes. Digamos eu queira fazer o método Comer um pouco mais esperto, fazendo ele validar se o tipo de comido pode ser comido. Digamos que para Homem possa comer “Arroz e Feijão” e para Cachorro possa comer apenas “Ração”. Como você implementaria isso? Um switch poderia ser utilizado, mas teria que ter um case para cada tipo de animal, e se fosse descoberto um novo tipo de animal, você teria que alterar o switch. Isso é bem ruim! É para esse tipo de problema que utilizamos classes, abstrações e espacializações. Vamos implementar o problema acima e ver como fica com herança.

   1: var Aninal = function (nome) {
   2:     //Atributo
   3:     this.nome = null;
   4:     //Métodos
   5:     this.comer = function (comida) {
   6:         //O método podeComer será implementado apenas nas classes filhas
   7:         if (this.podeComer(comida)) {
   8:             w(this.nome + " come " + comida);
   9:         }
  10:         else {
  11:             w(this.nome + " NÃO come " + comida);
  12:         }
  13:     }
  14:     //Código do meu construtor
  15:     this.nome = nome;
  16: }
  17:  
  18:  
  19: var Homem = function () {
  20:     Aninal.apply(this, ["Homem"]);
  21:  
  22:     this.podeComer = function (comida) {
  23:         return comida.toLowerCase()=="arroz e feijão";
  24:     }
  25: }
  26:  
  27: var Cachorro= function () {
  28:     Aninal.apply(this, ["Cachorro"]);
  29:     
  30:     this.podeComer = function(comida){
  31:         return comida.toLowerCase()=="ração";
  32:     }
  33: }
  34:  
  35: var homem = new Homem();
  36: var cachorro= new Cachorro();
  37:  
  38: homem.comer("Arroz e Feijão");br();//pode
  39: homem.comer("pedra");br();//não pode
  40: cachorro.comer("Ração");br();//pode
  41: cachorro.comer("Arroz");br();//não pode

Execute o código e veja que a validação está funcionando, e está sendo delegada para a classe filha. A classe animal está como uma classe abstrata no C# e o método podeComer como um método abstrato.

Você pode perguntar: “Mas e se a classe filha não implementar esse método? Vai dar pau em tempo de execução. Sim! O que nós podemos fazer é uma validação para ver se o método existe, e se não existir, não executar a validação, ou mesmo jogar um exceção mais legivel. Veja:

   1: this.comer = function (comida) {
   2:     //O método podeComer será implementado apenas nas classes filhas
   3:     if (!this.podeComer || this.podeComer(comida)) {
   4:         w(this.nome + " come " + comida);
   5:     }
   6:     else {
   7:         w(this.nome + " NÃO come " + comida);
   8:     }
   9: }

Ou:

   1: this.comer = function (comida) {
   2:     //O método podeComer será implementado apenas nas classes filhas
   3:     if(!this.podeComer) throw "Defina o método podeComer.";
   4:     if (this.podeComer(comida)) {
   5:         w(this.nome + " come " + comida);
   6:     }
   7:     else {
   8:         w(this.nome + " NÃO come " + comida);
   9:     }
  10: }

A primeira solução eu acho mais interessante. No JavaScript, você pode verificar se um método existe, caso ele não exista, o retorno (underfined) é tratado como false em um IF. Tudo que eu fiz com o apply no exemplo, poderia ter feito com o call.

Implementando herança com Prototype

A propriedade prototype está presente em todos os objetos no JavaScript. Ela tem um comportamento totalmente diferente de qualquer outra. Com ela, você consegue estender classes e objetos concretos. É interessante que você consegue inclusive estender classes do próprio JavaScript. Para fazer uma analogia, ela funciona como os Extension Methods do C#.

Por exemplo, para definir uma variável do tipo Date, e definir uma data, eu posso fazer assim sempre que necessário:

   1: var data = new Date();
   2:     data.setDate(21);
   3:     data.setMonth(4);
   4:     data.setYear(2011);

Fazer isso todas as vezes não é legal, então eu posso estender a classe Date para ficar mais simples, posso fazer assim:

   1: Date.prototype.definir= function (data) {
   2:     var partes = data.split("/", 3);
   3:     this.setDate(parseFloat(partes[0]));
   4:     this.setMonth(parseFloat(partes[1]) - 1);
   5:     this.setFullYear(parseFloat(partes[2]));
   6: }
   7:  
   8: var data = new Date();
   9: data.definir("21/05/2011");

Veja que eu estou estendendo a classe Date, e toda variável do tipo Date agora terá o método definir. Se eu utilizasse o prototype diretamente em cima da variável, essa extensão teria efeito apenas na variável em questão, e não em todas as variáveis.

Essa é a utilização mais comum do prototype, mas nesse caso estou estendendo uma classe, e não herdando dela, e como eu faço isso? Vamos utilizar o mesmo exemplo dos Animais, e você verá como é feita a herança com prototype. Minha classe Animal é a mesma, vou mudar apenas a Homem e Cachorro, e ficará claro como utilizar o prototype.

   1: var Homem = function () {
   2:     this.podeComer = function (comida) {
   3:         return comida.toLowerCase() == "Arroz e Feijão";
   4:     }
   5: }
   6: //Informo de quem Homem herda
   7: Homem.prototype = new Aninal("Homem");
   8:  
   9: var Cachorro= function () {            
  10:     this.podeComer = function(comida){
  11:         return comida.toLowerCase()=="ração";
  12:     }
  13: }
  14: //Informo de quem Cachorro herda
  15: Cachorro.prototype = new Aninal("Cachorro");
  16:  
  17: //A utilização é a mesma
  18: var homem = new Homem();
  19: var cachorro= new Cachorro();
  20:  
  21: homem.comer("Arroz e Feijão");br();//pode
  22: homem.comer("pedra");br();//não pode
  23: cachorro.comer("Ração");br();//pode
  24: cachorro.comer("Arroz");br();//não pode

Veja que tirei o apply e a herança continuou a funcionar. Com o prototype eu apenas atribuo uma instância da classe que devo herdar e já está tudo funcionado. Simples e rápido.

Bom, por hoje é isso. Acredito que com isso você já seja capaz de entender e implementar herança em JavaScript. Caso tenha dúvidas, é só deixar um comentário.

Abraços e até o próximo.

21. maio 2011 20:45 by Frederico B. Emídio | Comments (0) | Permalink

Entendendo o ‘this’ no JavaScript    

JavaScript é uma linguagem realmente interessante, e todo mundo que queira ser desenvolvedor Web deve ter essa linguagem como pré-requisito. Mas ela tem características um tanto quanto diferentes de uma linguagem como o C#. Não entender essas características faz com que pessoas tenham enorme dificuldade para entender como usar bibliotecas JavaScript, como JQuery ou KnockoutJS. Ou faz as pessoas fazerem milhares de linhas de script para fazer uma página web um pouco mais dinâmica.

Hoje então vou falar de mais uma característica do JavaScript: o operador this.

Como em qualquer linguagem que tenha esse operador, no JavaScript o this faz referência ao contexto atual da execução. Ou seja, caso você esteja desenvolvendo uma classe, o this fará referência à classe, ou ao objeto criado a partir dessa classe.

Mas no JavaScript, o this também tem uma característica totalmente diferente. No JS a gente consegue alterar o contexto do this, ou seja, é possível a gente mudar o que o JavaScript entende como contexto atual.

Por exemplo, se você já utilizou JQuery, já reparou como o this sempre é algo diferente? Veja o exemplo abaixo:

   1: //Vai sair Window
   2: alert(this);
   3:  
   4: var dados = [{ Nome: "Nome 1", Idade: "25" },
   5:             { Nome: "Nome 2", Idade: "23"}];
   6:  
   7:  
   8: $().ready(function() {
   9:     $("#link").click(function() {
  10:         //Vai sair o ID da tag A e não do Window
  11:         alert(this.id);
  12:     });
  13:  
  14:     $(dados).each(function() {
  15:         //Nesse contexto o this vai ser cada item do array.
  16:         alert("Nome: " + this.Nome + " - Idade: " + this.Idade);
  17:  
  18:     });
  19: });

Veja que para cada situação, o Alert vai exibir uma informação, mesmo que eu não esteja trabalhando em contextos diferentes, não estou utilizando classes, por exemplo.

O que está acontecendo no caso acima é que o JQuery está alterando o contexto this, para ficar mais fácil de trabalhar. Você já deve ter percebido que se eu quiser fazer um for manual no meu array, o this não vai ser cada item do array. Ou seja, o código abaixo vai exibir duas vezes o alert Window:

   1: for (var i = 0; i &lt; dados.length; i++) {
   2:    alert(this);
   3: }

Então como eu posso alterar o meu contexto para conseguir simular o que o JQuery faz com o método $.each e demais métodos? Isso é feito através dos métodos apply ou call.

Conhecendo os métodos Apply e Call no JavaScript

Apply e Call são métodos internos do JavaScript disponíveis para qualquer método ou classe criada por você. A utilização deles é exatamente igual, mudando apenas a forma que é definido o segundo parâmetro. O apply recebe como segundo parâmetro um array de argumentos, enquanto o call recebe uma lista de parâmetros (como o ParamArray do C#).

Com esses métodos você consegue chamar métodos de uma forma diferentes, como o Invoke na classe MethodInfo do .Net. Esse método tem a capacidade de você definir qual é o contexto do this dentro do método que está sendo invocado.

A utilização de ambos se dá da seguinte forma:

   1: AlgumMetodo.call([contexto do this], param1,param2,param3);
   2: AlgumMetodo.apply([contexto do this], [param1,param2,param3]);

Perceba que a única diferença é o segundo parâmetro, que no call é uma lista de parâmetros e no apply é um único parâmetro do tipo array contendo os parâmetros no array.

Então como eu simulo o método JQuery $.each, por exemplo?

   1: var dados = [{ Nome: "Nome 1", Idade: "25" },
   2:             { Nome: "Nome 2", Idade: "23"}];
   3:  
   4:  
   5:  
   6: function exibirItens() {
   7:     alert("Nome: " + this.Nome + " - Idade: " + this.Idade);
   8: }
   9:  
  10: for (var i = 0; i < dados.length; i++) {
  11:     exibirItens.apply(dados[i]);
  12: }   

Se você testar o código acima, verá que no meu método exibirItens o this passa a ser cada item do meu array dados. Isso porque estou invocando meu método através do método apply, e estou passando no meu primeiro parâmetro o item atual do array. Não estou passando nada no segundo parâmetro porque meu método não espera parâmetros. Se eu fosse utilizar o call nada mudaria (apenas o aply pelo call, claro).

Agora que vimos como é possivel alterar o contexto do this, podemos criar métodos que recebam “delegates” (para falar na língua do .Net) como parâmetros e fazer alguns truques.

Por exemplo, baseado no código acima, vamos criar um método chamado paraCada que vai ter o mesmo comportamento do método $.each do JQuery.

   1: var dados = [{ Nome: "Nome 1", Idade: "25" },
   2:              { Nome: "Nome 2", Idade: "23"}];
   3:  
   4:  
   5: //Coleção é meu array com os dados que quero receber
   6: //Acao é o meu método que quero executar para cada item
   7: function paraCada(colecao,acao) {
   8:    for (var i = 0; i < colecao.length; i++)
   9:        acao.apply(colecao[i]);
  10: }
  11: //Aqui eu faço uso do meu método.
  12: paraCada(dados, function() {
  13:    alert("Nome: " + this.Nome + " - Idade: " + this.Idade);
  14: });

E está pronto! Um método como o $.each, Pense nas possibilidades, são muitas.

Bom, era isso! Acredito que para qualquer pessoa que quer ficar bom em JavaScript, esse conceito é muito importante.

Abraços e até o próximo!

20. maio 2011 23:43 by Frederico B. Emídio | Comments (0) | Permalink

Trabalhando com Classes no JavaScript    

Hoje vou abordar um tema que vejo que não é muito abordado em blogs de linha portuguesa e que vem se tornando cada vez mais necessário  conforme a necessidade de criar ricas interfaces na web se torna mais comum.

Antes de abordar o tema propriamente dito, acho importante dizer o que não será tratado nesse post. Caso você, caro leitor, veja a necessidade de abordar os assuntos que citarei a seguir, é só comentar.

Não vou mostrar nesse post uma introdução básica sobre JavaScript, ou seja, presumo que você já conheça a sintaxe e características básicas da linguagem, como o fato de ser uma linguagem dinâmica; não vou abordar JSON, AJAX, JQuery e outras técnicas comuns do JavaScript; não mostrarei bibliotecas que mudam a forma de criar classes, utilizarei apenas o JavaScript padrão e não vou explicar nomenclatura JavaScript, que é diferente de C#.

Dado o breve aviso, também devo informar que JavaScript não é uma linguagem 100% Orientada à Objetos (OO). Apesar de ser possível criar classes, falta algumas capacidades que tornariam possível um título de uma linguagem 100% Orientada a Objetos.

Então vamos lá!

Minha primeira Classe JavaScript

Classe, na maioria das linguagens de programação, é uma estrutura que descreve estados e comportamentos de um determinado objeto. Cada linguagem tem a sua forma de descrever essa estrutura, em geral com a palavra chave class. No JavaScript essa palavra chave não é utilizada (ainda), e nós utilizamos uma função para criar uma Classe.

Vamos ao primeiro código, a definição básica de uma Classe:

   1: var MinhaClasse= function() { 
   2:     }

Bom, até ai não temos uma classe, pois no JavaScript essa também é uma forma de declarar uma função simples. Para MinhaClasse se tornar uma classe, devemos adicionar atributos (estados) e métodos (comportamento). No JavaScript, assim como no Java, não existe o conceito de Propriedades, como no C# ou VB.Net. O encapsulamento é feito através de métodos Getters e Setters.

A classe MinhaClasse terá um atributo e um método público e um atributo e um método privado, apenas para demonstrar a diferença de visibilidade entre os membros da classe.

   1: var MinhaClasse = function() {
   2:   var propriedadePrivada = "Privado";
   3:   var metodoPrivado = function() {
   4:       w(propriedadePrivada);
   5:   }
   6:  
   7:   this.propriedadePublica = "Público";
   8:   this.metodoPublico = function() {
   9:       w(this.propriedadePublica);
  10:   }
  11: }

Como o código mostra, para fazer uma propriedade ou método privado no JavaScript você declara o método ou o atributo com a palavra chave var. Para tornar um atributo ou método público é utilizado a palavra chave this.

Agora sim MinhaClasse virou uma classe, e utilização é bem simples:

   1: var c = new MinhaClasse();

Se você utilizar o Intelisense do Visual Studio, verá que apenas os membros públicos (marcados com this) aparecerão.

image

É importante notar que o comportamento entre os membros de uma classe no JavaScript é diferente. Os métodos públicos conseguem enxergar métodos e atributos privados, porém, métodos privados não conseguem acessar métodos e atributos públicos. Portanto, o código abaixo NÃO funcionará:

   1: var metodoPrivado = function() {
   2:     w(propriedadePrivada);
   3:     br();
   4:     this.metodoPublico();
   5: }

Por outro lado, o método a seguir funcionará:

   1: this.metodoPublico = function() {
   2:    w(this.propriedadePublica);
   3:    br();
   4:    metodoPrivado();
   5: }

Se por acaso você quisesse criar classes internas, seria o mesmo procedimento de criar uma classe normal, porém seria dentro de uma outra classe já criada. A utilização é a mesma.

Agora que vimos como faz uma classe básica no JavaScript, vamos ao próximo item.

Construtores de Classe em JS

É normal em uma classe você fornecer dados para ela ser iniciada (transformada em objeto) e isso é feito através de um método que chamamos de Construtor. No JavaScript não existe um método especial para fazer isso. Mas perceba que uma classe nada mais é que um função elaborada, e quando chamamos o new de uma classe, automaticamente chamamos essa função. Portanto, para passar parâmetros á uma classe no momento da criação/instanciação, precisamos apenas definir parâmetros à função que define a classe. Veja:

   1: var MinhaClasse = function(nome) {
   2:    var propriedadePrivada = "Privado";
   3:    
   4:    //Outros membros...
   5:  
   6:    //Esse código será executado como Construtor
   7:    if (nome)
   8:        propriedadePrivada = nome;
   9:  
  10: }
  11:  
  12: //Utilizando 
  13: var c = new MinhaClasse("Fred");
  14: c.metodoPublico();

Como automaticamente todo parâmetro é opcional no JavaScript, se você não passar o parâmetro nome, ele ficará como undefined.

É importante que o código do “construtor” esteja sempre no final da Classe. Isso por que o JavaScript é uma linguagem interpretada, seu processamento é feito linha a linha, e se por acaso um código chamar um método ou variável que ainda não foi processado pelo Browser, retornará erro, informando que o método ou atributo não existe. Essa regra se aplica em qualquer realidade do JavaScript, por isso é importante sabermos qual será a ordem do processamento do Browser, para sabermos organizar nosso código.

Métodos Estáticos em JavaScript

É comum precisarmos de métodos estáticos em classes. No JavaScript isso é bem simples:

   1: var MinhaClasse = function() {
   2:   var propriedadePrivada = "Privado";
   3:   var metodoPrivado = function() {
   4:       w(propriedadePrivada);
   5:   }
   6:  
   7:   this.propriedadePublica = "Público";
   8:   this.metodoPublico = function() {
   9:       w(this.propriedadePublica);
  10:       br();
  11:       metodoPrivado();
  12:   }
  13: }
  14: //Definindo método estático
  15: MinhaClasse.metodoEstatico = function() {
  16:   w("Método Estático");
  17: }

Veja que estou apenas adicionando o método fora do “corpo” da classe.Isso torna o método estático. É quase como criar uma classe, com a diferença que você não define membros ao método, caso contrário ele transformaria em uma “sub-classe” (que também pode ser uma opção). A utilização do método estático seria assim:

   1: MinhaClasse.metodoEstatico();

Naturalmente, métodos estáticos só acessam métodos e atributos estáticos, sempre necessitando colocar o nome do classe (como acima). Caso queira acessar um método privado ou público, terá que criar uma instância da Classe.

Namespace

É comum, quando criamos muitas classes, querermos organiza-las em grupos (namespace em .Net e packages em Java). Em JavaScript isso também é possível, e de uma forma bem simples:

   1: //Criação do Namespace
   2: var classes = {}
   3:  
   4: //Criação da classe no Namespace
   5: classes.MinhaClasse = function() {
   6:     //Corpo da classe…

A única diferença para utilizar essa classe agora, é que no momento de criar a instância, devemos informar o Namespace:

   1: var c = new classes.MinhaClasse();
   2:     c.metodoPublico();

Conclusão e Dicas

E é isso! Muito simples criar classes em JavaScript, é muito importante, porque ajuda o seu código a ficar muito mais organizados.

Você pode ter reparado que utilizei dois métodos durante a classe que não são nativos e não mostrei antes, o w() e br(). Isso são apenas atalhos que criei para digitar menos e os códigos não ficarem tão carregados, mas eles são simples linhas como as abaixo:

   1: var w = function (a) { document.write(a) };
   2: var br = function () { w("<br/>") }

Perceba que em JavaScript podemos utilizar variáveis que fazem referências à métodos (como Delegates em .Net). Sabendo disso, ai vai uma dica ao criar classes:

Crie todos os métodos como privado (var), assim você não terá problemas com métodos da sua classe não enxergando métodos da própria classe. Feito isso, deixe público apenas os métodos que interessar, criando uma variável pública (this) com o mesmo nome, referenciando o método privado.

Como exemplo, vou reescrever a classe utilizada acima, reorganizando o código  para mostrar como fica. Abaixo o código completo para os exemplos:

   1: //Atalhos
   2: var w = function (a) { document.write(a) };
   3: var br = function () { w("<br/>") }
   4:  
   5: //Criação do Namespace
   6: var classes = {}
   7:  
   8: //Criação da classe no Namespace. Com construtor recebendo
   9: //parâmetro Nome
  10: classes.MinhaClasse = function(nome) {
  11: //Corpo da classe
  12:    
  13:    //Atributos públicos
  14:    var propriedadePrivada = "Privado";
  15:    var propriedadePublica = "Público";
  16:  
  17:    //Métodos privados
  18:    var metodoPrivado = function() {
  19:        w(propriedadePrivada);
  20:    }
  21:    
  22:    var metodoPublico = function() {
  23:        w(this.propriedadePublica);
  24:        br();
  25:        metodoPrivado();
  26:    }
  27:    
  28:    //Torno public dois membros
  29:    this.propriedadePublica = propriedadePublica;
  30:    this.metodoPublico = metodoPublico;
  31:    
  32:    //Esse código será executado como Construtor
  33:    if (nome)
  34:        propriedadePrivada = nome;
  35: }
  36:  
  37: //Definindo método estático
  38: classes.MinhaClasse.metodoEstatico = function() {
  39:    w("Método Estático");
  40: }
  41:  
  42: //Utilizo minha classe. Alterando o valor da propriedade
  43: //privada pelo construtor.
  44: var c = new classes.MinhaClasse("Fred");
  45: c.metodoPublico();
  46: c.propriedadePublica = "Publico Alterado";
  47: br();
  48: c.metodoPublico();
  49: br();
  50:  
  51: //Utilizo método estático
  52: classes.MinhaClasse.metodoEstatico();

Acredito que com esses exemplos, você conseguirá facilmente trabalhar com classes JavaScript em suas páginas, deixando seu código muito mais limpo e reaproveitável.

Abraços e até o próximo post!

13. maio 2011 00:00 by Frederico B. Emídio | Comments (3) | Permalink

Sobre mim

Minha Imagem

Meu nome é Frederico Batista Emídio, trabalho com desenvolvimento de sistemas profissionalmente a oito anos, porém, já faço sites pessoais a pelo menos dez anos.

Para saber mais clique aqui.

Páginas

Calendário

<<  novembro 2018  >>
seteququsedo
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

Visualizar posts em um Calendário
Sigua @fredemidio

MCP Asp.NET