Internacionalização de ficheiros de JavaScript

August 29th, 2010

Os últimos posts no meu blog têm sido acerca do tema da internacionalização de aplicações, nomeadamente:

O post de hoje contínua o sobre o método alternativo com ASP.NET, recorrendo a ao GetText do GNU. Como vimos, a aplicação do método _() para substituir texto localizável tornava-se um recurso simples e eficiente na internacionalização de ASP.NET. Em especial, é um método adoptado pela restantes linguagens e evita o esforço extra necessário para introduzir o texto em ficheiros .resx.

Procurei uma solução semelhante para Javascript, uma vez que a aplicação que estou a internacionalizar recorre muito a funcionalidade em javascript. Em especial, há mensagens de erro específicos e mensagens de confirmação contextualizadas. Especifico a ASP.Net, não encontrei nada de útil. Até achei muito estranho não detectar nada de muito útil nesse campo.

De qualquer forma, há alguns métodos genéricos a aplicar, incluíndo plugins em jQuery (http://plugins.jquery.com/project/gettext), mas gostei da descrição dada em 24ways.org. Simplesmente introduzi a função para _() no meu ficheiro core de javascript (é uma pequena biblioteca e funções e abstracções comuns a todas as páginas das minhas aplicações, e incluído em todas as páginas):

function _(s) {
	if (typeof(i18n)!='undefined' && i18n[s]) {
		return i18n[s];
	}
	return s;
}

Com isto é esperado existir uma variável i18n definido com a lista de traduções em pares chave-valor em JSON, do tipo:

var i18n = {
"" : "",
"Hello" : "Olá",
"Goodbye" : "Adeus",
//(...)
}

O método em 24ways.oprg refere ainda funções para formatar strings que podem ser uteis. Eu no entanto já tinha uma implementação de string.format para javascript (http://www.geekdaily.net/2007/06/21/cs-stringformat-for-javascript/).

Portanto, com a função no core, e a variável i18n adicionada, nos diversos ficheiros de javascript, onde fosse usado uma string, este deveria ser englobado por _():

if (confirm(_('Confirma a alteração de estado nos processos seleccionados?') )) {
//...
}

Assim, quando executado, e porque o método _() está sempre presente, o texto será substituído pelo existente no dicionário ou se não encontrado, o próprio texto usado como argumento da função.

Resta apenas dois passos – gerar os ficheiros .po, que deve seguir um esquema semelhante ao mencionado no artigo de ASP.Net, usando instruções póst-build e o gettext, e depois transformar os registos dos ficheiros .po em json. O primeiro dos passos é relativamente directa, apenas necessitando de construir uma nova lista de ficheiros (apenas de .js) e adicionar as entradas envolvidas em _() no .pot.

O segundo passo é mais “dificil”. Não há nada para o fazer directamentem senão um script em perl (http://jsgettext.berlios.de/doc/html/po2json.html), e como não me servia, decidi construir um script T4 simples, para usar no processo.

O script assume alguns pressupostos, pelo que, se utilizar, deve de os ter em conta e alterar consoante o necessário. O primeiro é que os ficheiros resultantes, quer os .po, quer os .js vão estar cada um numa pasta dedicada à língua específica como por exemplo:

/Scripts/locale/xx/messages.po
/Scripts/locale/xx/i18n.js

Também, considero que o ficheiro .po será gerado e unido com recurso a msgmerge, e portanto apenas devo transformar os ficheiros .po em ficheiros .js

Assim, o po2json.tt, colocado na pasta (/Scripts/locale/) pode ser executado após o build. Ele procurará o ficheiro .po de cada língua no array, e transformarará em um ficheiro i18n.js. Eu decidi definir as linguas manualmente, mas com pequenas alterações, podem ser detectadas automáticamente.

O T4 é o seguinte:

<#@ Template Language="C#v3.5" Hostspecific="True" #>
<#@ Output Extension=".js" #>
<#@ Import Namespace="System.IO" #>
<#@ Import Namespace="System.Collections.Generic" #>
<#@ Import Namespace="System.Text.RegularExpressions" #>
<#@ Include File="MultiOutput.t4" #>
var i18n = {
<#
    string localeFolderPath = @"D:\Codigo\ProjectoWeb\Scripts\locale\";
    string[] suportedLang = {"pt"};
    string pofileName = "messages.po";
    string msgid = "msgid";
    string msgstr = "msgstr";
    string txtREGEX = "\"(.*)\"";
 
    foreach (string lang in suportedLang)
    {
        string[] lines = File.ReadAllLines(localeFolderPath + "\\" + lang + "\\"  + pofileName);
        List<KeyValuePair<string, string>> entries = new List<KeyValuePair<string, string>>();
        int currentLine = 0;
 
        while (currentLine < lines.Length)
        {
            string line = lines[currentLine];
            if (line.StartsWith(msgid))
            {
                string keyString = "";
                keyString += Regex.Match(line, txtREGEX).Groups[1].Value;
 
                //iterate while still a msgid
                line = lines[++currentLine];
                while(line.StartsWith("\""))
                {
                    keyString += Regex.Match(line, txtREGEX).Groups[1].Value;
                    line = lines[++currentLine];
                }
 
                if (line.StartsWith(msgstr))
                {
                    string valueString = "";
                    valueString += Regex.Match(line, txtREGEX).Groups[1].Value;
 
                    if (++currentLine < lines.Length)
                    {
                        line = lines[currentLine];
                        while (line.StartsWith("\""))
                        {
                            valueString += Regex.Match(line, txtREGEX).Groups[1].Value;
                            line = lines[++currentLine];
                        }
                    }
                    //add pair to entries list
                    entries.Add(new KeyValuePair<string, string>(keyString, valueString));
                }     
            }
 
            currentLine++;
        }
 
        //build outoput
        for( int i = 0; i< entries.Count; i++)
        {
            KeyValuePair<string, string> entry = entries[i];
 
#>
"<#=  entry.Key #>" : "<#= entry.Value #>"<#=    i<entries.Count-1 ? "," : "" #>
<#
        }  
    SaveOutput(localeFolderPath + "\\" + lang + "\\"  + "i18n.js"); 
 
    }
#>
};

Procura cada “msgid” e processa as línguas seguintes convenientemente. Se houver erros, o processo será abandonado. O tt recorre ainda a um MultiOutput.t4, para gerar mais que um ficheiro a partir do .tt, convenientemente localizado. Não necessita de estar incluído no projecto (para não obrigar a incluir as dependências de TextTempleting).

Para incluir o ficheiro na lista de scripts a puxar, basta regista-lo no ScriptManager, no Page_Load da MasterPage :

ScriptManager.RegisterClientScriptInclude(this, typeof(string), "i18n", Page.ResolveUrl("~/Scripts/locale/" + lang + "/i18n.js"));

onde a variável lang é uma string com as letras da língua, para identificar a pasta onde será armazenado. Eu obtenho a a língua para a aplicação (transversal), mas outra possibilidade seria definir a língua pelas preferências do utilizador. Também, o caminho poderia ser proveniente de uma chave de configuração no web.config, se necessário.

Creio que está é uma solução simples, que pode, se necessário ser melhorado com mais funções (especialmente de formatações e plural / singular) como descrito no artigo do 24ways.org. Para já, esta é me suficiente.

Download po2json t4

ASP.Net – Método alternativo de internacionalização (i18n)

August 25th, 2010

A internacionalização de aplicações (internationalization – i18n) para posterior localização (localization – l10n) é um processo multi-passo, que envolve a detecção de texto que deve ser apresentado em múltiplas línguas, bem como a preparação da base de código para que o texto possa ser correctamente traduzido.

Cada tecnologia tem a sua própria forma de implementar mecanismos que auxiliam a tradução. .Net recorre a recursos compilados na forma de ficheiros .resx . Os ficheiros .resx são de uma forma geral pares chave-valor, estruturados em XML, que são compilados para dlls carregados no inicio da aplicação.

Apesar de uma solução interessante, é extremamente tediante usa-los em ASP.Net. Com as bibliotecas de uma API, ok. O suporte a nível de código C# (ou VB ) torna o uso relativamente simples. Já com ASP.Net, não é possível dizer o mesmo. As razões estão bem descritas em “Why Internationalization is hopelessly Broken in ASP.Net“, mas resumindo, tudo tem de ser controlos ASP.Net com o runat=”server”, não há suporte simplificado a texto estático (escrito directamente na página), a referência de chaves das strings obriga a inserir meta:resoursekey=”" em todas os controlos, e os ficheiros .resx não são facilmente legíveis por pessoal não técnico.

A alternativa? Seguir o método do “resto do mundo” e usar ficheiros de strings em formatos simplificados, nomeadamente os ficheiros .po. Quem já explorou um projecto open-source ou projectos aplicacionais com add-ins linguísticos, muito provavelmente já se cruzaram com este formato de ficheiros. São ficheiros de texto simples, com texto assoiado a uma chave (msgid) e texto associado à tradução (msgstr). Por exemplo:

msgid "Pesquisar Candidatos"
msgstr "Search Candidates"

De notar neste exemplo que a chave está em PT e a tradução em EN. Não é recomendado esta abordagem, uma vez que as línguas latinas tem acentos que não são óptimos para chaves.

Continuando, o uso de ficheiros .PO, noutras frameworks, é suportado por por uma função gettext(), onde é passo a chave (geralmente o próprio texto, na língua base) e retorna a versão traduzida, conforme as preferências de utilizador. Melhor, há geralmente uma forma simplificada da função escrito _(). Assim, cada string a analisar chama a função gettext(), obtendo a versão traduzida do texto. Bem mais simples que os .resx e meta:resourcekey do .NET.

Usando Fairlylocal

O FailyLocal é um projecto opensource iniciado por Jason Kester, e que implementa a funcionalidade do gettext e ficheiros PO para ASP.Net. Como descrito no link do projecto, para pedir a tradução, na página basta utilizar:

<%= _("texto a traduzir") %>

ou no codebehind:

controlo.InnerHtml = _("texto a traduzir");

Tão simples e eficaz. Em termos de biblioteca, basta adicionar os ficheiros ao projecto da aplicação (têm de ser um projecto de aplicação web, em vez de um projecto de website, uma vez que o segundo não permite evento post-build, que o primeiro permite, e que constrói dinamicamente os ficheiros PO).

Em termos de execução, os ficheiros PO são carregados no arranque da aplicação, para um dicionário estático em memória, onde depois são realizadas as consultas do gettext(). Neste caso, todas as paginas devem herdar de InternationalPage ou InternationalMaster (ou a página base da aplicação derivar destas) em vez de Page directamente, para que _() fique disponível.

Para as minhas necessidades, efectuei pequenas alterações. Primeiro, para facilitar o uso em mutlipos projectos, adicionai os ficheiros ao projecto da minha framework, para que tenha sempre disponível. Segundo, porque necessito de aceder aos métodos, quer para a página instanciada, quer quando estou a usar WebMethods, tornei a propriedade LanguageCode e o método _() static. Finalmente, porque necessito de definir a língua ao nível da aplicação e não para cada utilizador, modifiquei o getter de LanguageCode para obter o código de lingua directamente da thread, em vez da sessão do utilizador:

public static string LanguageCode
{
   get
   {
       if (_languageCode == null)
       {
           _languageCode = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName.ToLower();
	}
	return _languageCode;
    }
    set
    {
	_languageCode = value;
    }
}

Incluíndo os scripts post-build, os ficheiros .PO são actualizados após cada build, podendo ser traduzidos de seguida, ou directamente ou recorrendo a programas como o Poedit. Ferramentas como o Pepipopum permitem traduzir automaticamente (via Google translate API) o ficheiro PO. As chaves devem estar em inglês para funcionar correctamente.

Finalmente, convém referir que o sistema tem fallback automático para o texto da própria chave – se o testo não estiver traduzido no ficheiro .po, o texto a usar é o próprio argumento do _(), o que ajuda muito se tiver a internacionalizar uma aplicação já existente.

Uma macro util

Ainda assim, com esta simplificação, internacionalizar uma aplicação existente não deixa de ser um trabalho tediante. Localizar o texto a traduzir e envolver no método _() é “chato”.

Para ajudar, criei uma macro:

Public Module GetTextSurrond
 
    '' surround text with <%= _(" and ") $>
    ''
    Sub ASPXSurround()
        Dim textSelection As EnvDTE.TextSelection
 
        textSelection = DTE.ActiveDocument.Selection()
        textSelection.Text = "<%= _(""" + textSelection.Text + """) " ''%>"
    End Sub
 
End Module

Associando esta Macro a um atalho de teclas, por exemplo Ctrl+G, Ctrl+T, ajudará a usar a macro, bastando seleccionar o texto e executar o comando (nos ficheiros .aspx).

Conclusão

Pela curta experiência que tenho com esta alternativa de tradução, não deixa de me parecer N vezes mais vantajoso que o uso de ficheiros .resx no ASP.Net. Os ficheiros .PO são apenas strings, portanto para recursos binários outra estratégia deve ser adoptada.

Localizar Enumerações

July 5th, 2010

Continuando no desafio de localizar a minha aplicação, estou agora a atacar as enumerações. Esta também é uma óptima oportunidade de efectuar algum refactoring às classes e estruturas existentes.

Parte das enumerações usadas são automaticamente escritas com uma estrutura que era óptima.. até agora. A aplicação é N-layer, dividido em camadas lógicas. Por enquanto, e porque ainda não foi necessário mais, mantém-se monotier. As camadas lógicas neste caso são UI, BLL (Business Lógic Layer) e DAL (Data Access Layer), mais os repositórios de dados. Comum a todas as camadas há duas bibliotecas – MAAPPFramework que é um conjuntos de classes base, interfaces base, e muitos helpers comuns a várias aplicações (O MAAPP significa Miguel Alho Application : D ) e a camada de BO (Business Objects) que inclui as classes conceptuais e DTOs (Data Transfer Objects) do domínio. Os objectos nesta biblioteca são “dummy objects”, no sentido que não tem funcionalidade (senão algumas ordenações e extensões). Apenas suportam dados e estão devidamente decoradas para serem serializadas. Esta biblioteca é transformada em dll e utilizada por todas as camadas. Esta arquitectura está muito bem exemplificado pelo Imar Spaanjars nos seus artigos Building Layered Web Applications with Microsoft ASP.NET 2.0.

As enumerações que eu tinha incluíam sempre um valor numérico definido explicitamente (evitando súbitas alterações de referências para com a base de dados numa nova geração de código) e uma descrição com texto legível, em vez de um texto colado em CamelCase. um exemplo:

namespace Namespace.BO
{
    [Serializable]  
    public enum EnumTipoContacto
    {	
        [Description("Telefone")]
	Telefone = 1,
        [Description("Telemóvel")]
        Telemovel = 2,
        [Description("Email")]
        Email = 3,
        [Description("Fax")]
        Fax = 4,
        [Description("Outro Tipo de Contacto")]
        Outro = 5
    }
}

A descrição escrita como atributo, recorrendo ao System.ComponentModel.DescriptionAttribute permite em runtime ter uma versão legível do nome da enumeração, óptima para apresentação na UI, em vez do tipo valor devolvido pelo método ToString(). No exemplo em cima, O caso do “Outro” demonstra a possível diferença. Para obter os valores, na minha framework tenho um método numa helper class que permite obter o valor da descrição, bem como uma extension method para ajudar:

public class EnumDescription
{
        public static string GetDescription(Enum value)
        {
            FieldInfo fieldInfo = value.GetType().GetField(value.ToString());
            DescriptionAttribute[] attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
            return (attributes.Length > 0) ? attributes[0].Description : value.ToString();
        }
 
        //(...)
}
 
public static class EnumExtension
{
    public static string GetDescription(this Enum e)
    {
         return EnumDescription.GetDescription(e);
    }
}

Considerando que o código é gerado a partir de um modelo que controlo, é uma óptima solução, desde que localizado a uma definição linguística. Seria possível criar múltiplos atributos (costumizados) para suportar descrições localizadas, por exemplo, mas a manutenção e separação não me parece optimizado. Procurei outra solução que me permitiria utilizar ficheiros de recursos (.resx).

Uma das técnicas que encontrei parece simples e óptima. A descrição passa a ser definida no ficheiro de recursos, e a chave é o nome qualificado da enumeração. Optei pelo nome completo porque é me fácil de gerar com T4. Assim, a minha enumeração fica simplificada:

[Serializable]
public enum EnumTipoContacto
{	
	Telefone = 1,
        Telemovel = 2,
        Email = 3,
        Fax = 4,
        Outro = 5,
}

e o meu ficheiro .resx terá um conjunto de chaves valores qualificados, que pode ser localizado na versão do ficheiro para outra língua:

(...)
<data name="Namespace.BO.EnumTipoContacto.Telefone" xml:space="preserve">
    <value>Telefone</value>
</data>  
 <data name="Namespace.BO.EnumTipoContacto.Telemovel" xml:space="preserve">
    <value>Telemóvel</value>
</data>  
 <data name="Namespace.BO.EnumTipoContacto.Email" xml:space="preserve">
    <value>Email</value>
</data>  
 <data name="Namespace.BO.EnumTipoContacto.Fax" xml:space="preserve">
    <value>Fax</value>
</data>  
 <data name="Namespace.BO.EnumTipoContacto.Outro" xml:space="preserve">
    <value>Outro Tipo / Desconhecido</value>
</data> 
(...)

Para obter o valor da chave, a operação é ligeiramente diferente:

public static string GetLocalizedDescription(EnumTipoContacto tipo)
{
      ResourceManager resources = Namespace.BO.Properties.Enum.ResourceManager;
 
      string resourceKey = String.Format("{0}.{1}", tipo.GetType(), tipo);
      string localizedDescription = resources.GetString(resourceKey);
 
      if (localizedDescription.IsNullOrEmpty())
           return tipo.ToString();
      else
           return localizedDescription;
}

Neste exemplo, tipo.GetType() devolve Namespace.BO.EnumTipoContacto, permitindo construir a chave. é necessário ter em atenção que linha

ResourceManager resources = Namespace.BO.Properties.Enum.ResourceManager;

obriga-me a que a assemblagem gerada seja o Namespace.BO.dll e o ficheiro de recursos é o Enum.resx ou Enum.xx.resx onde xx representa o código cultural (“es”, por exemplo). Novamente, porque o meu código é em boa parte autogerado, tenho controlo sobre esse formato.

Outro método muito útil, para usar como DataSource para preencher DropDownLists e controlos do género é a seguinte:

public static List<KeyValuePair<string, string>> GetDDLList()
{
            List<KeyValuePair<string, string>> kvPairList = new List<KeyValuePair<string, string>>();
            ResourceManager resources = Namespace.BO.Properties.Enum.ResourceManager;
 
            foreach (Enum enumValue in Enum.GetValues(typeof(EnumTipoContacto)))
            {
                string resourceKey = String.Format("{0}.{1}", enumValue.GetType(), enumValue);
                string localizedDescription = resources.GetString(resourceKey);
 
                if(localizedDescription.IsNullOrEmpty())
                    kvPairList.Add(new KeyValuePair<string, string>(enumValue.ToString(), enumValue.ToString()));
                else
                    kvPairList.Add(new KeyValuePair<string, string>(enumValue.ToString(), localizedDescription));
            }
 
            return kvPairList;
}

Semelhante à anterior, permite obter uma lista de KeyValuePairs aceites como DataSource dos controlos.

Este foi, numa fase inicial, o tipo de estrutura que mais me preocupava, no sentido que os atributos poderiam complicar a globalização, e obrigar a um refactoring interno profundo. Felizmente, um simples workaround e refactoring evitou grandes mexidas. Também, porque nem os nomes dos métodos (nomeadamente o GetDDLList() ) nem os tipos de dados foram alterados, a camada superior continua a funcionar sem ter sido afectado.

.Less (Dot Less) -> “CSS on Steroids”

June 3rd, 2010

Ainda no outro dia, falava com os meus colegas de como seria muito bom que o CSS tivesse estruturas de herança incorporados e variáveis.. especialmente variáveis, para não ter de definir constantemente as mesmas coisas.

E hoje enquanto lia o blog do Spaanjaars, descubir o .Less, uma biblioteca dedicada a extender o CSS, introduzindo variáveis e esquemas de herança. Soa bem? Se soa!! :D

Com o .Less, podemos escrever codigo com variáveis:

@brand_color: #4D926F;
 
#header {
  color: @brand_color;
}
 
h2 {
  color: @brand_color;
}

Ou nestings de selectores:

#header {
    color: red;
    a {
       font-weight: bold;
       text-decoration: none;
    }
}

Ou misturas de elementos:

.rounded_corners(@radius: 5px) {
  -moz-border-radius: @radius;
  -webkit-border-radius: @radius;
  border-radius: @radius;
}
 
#header {
  .rounded_corners;
}
 
#footer {
  .rounded_corners(10px);
}

Lindo, né? Originalmente, o Less foi construído para o Ruby (é um gem para o Ruby), mas um grupo converteu-o para .Net, criando o .Less. E ainda bem! No .Net, funciona como um HTTPHandler. Os ficheiros de CSS são ficheiros .less que são transformados aquando do pedido da página. É também possível usar templates T4 para criar versões estáticas do CSS.

A experimentar!

jQuery, Ajax, e Rails

May 14th, 2010

Para além das lides habituais com o desenvolvimento em ASP.Net com C# e  muito Javascript, tenho tido nas últimas semanas oportunidades para trabalhar com Ruby on Rails. É uma linguagem e framework bastante interessante, sem dúvida, e uma abordagem diferente à construção de páginas, visto que aplica o padrão MVC.

Ainda estou a aprender muito do muito básico, mas tenho conseguido com alguma comunicação com os colegas, aplicar conceitos que já aplicava no .Net, especialmente no que diz respeito ao uso de javascript no browser e a troca de dados por Ajax.

Em termos de Ajax, tenho-me concentrado essencialmente em usar o jQuery para efectuar os pedidos e manipular os dados. Muito raramente (mais para o nunca), uso os controlos da biblioteca de Ajax da MS. não é que sejam menos bons, simplesmente gosto de usar o jQuery e construir de baixo para cima os componentes. Por vezes os truques escondidos dos controlos estragam a experiência de desenvolvimento. Aprendi isso com os controlos da DevExpress…

Com jQuery, o pedido por Ajax é realizado simplesmente usando o $.ajax() , com as opções devidamente preenchidas. Até já tinah construido uma função numa biblioteca “core” pessoal para abstrair alguns detalhes como a definição de JSON como formato de pedido e resposta:

function GetAjaxData(DTO, serviceAddress, webMethodName, successFuntion, errorFunction) {
    $.ajax({
        type: "POST",
        url: serviceAddress + "/" + webMethodName,
        contentType: "application/json; charset=utf-8",
        data: JSON.stringify(DTO),
        dataType: "json",
        success: successFuntion,
        error: errorFunction
    });
}

Asp.Net Way

O GetAjaxData é apenas um género de wrapper em torno do $.ajax(), fixando alguns parâmetros e aplicado os argumentos como os dados a transmitir, o caminho e nome do método a executar, e os callbacks. Por exemplo, se tiver um método de página (gosto de usar assim) que deva chamar, teria algo do género:

public class QQPagina: Page
{
    //...
 
    [WebMethod]
    public static int MetodoAChamar(int id)
    {
        return id*1000;
     }

O método apresentado é algo ridículo, mas serve para explicar. Temos um método da classe da página (supomos “QQPagina.aspx”), que se chama “MetodoAChamar”, recebe um parâmetro inteiro “id” e devolve um inteiro. É importante notar que o método é static (portanto ao nível da classe e não instanciado – não que acesso às propriedades instanciadas da classe). Também, apesar de usar aqui um método numa página Aspx, podia ter colocado o mesmo numa classe de WebService. Ambos os métodos teriam o atributo “[WebMethod]“, mas o método no WebService não é static.

Para executar o método, podia fazer algo do género:

function ChamarMetodo(id){
     GetAjaxData(
             {"id":5},
             "QQPagina.aspx", 
             "MetodoAChamar", 
             function(msg){ alert(msg.d); },
             function(msg){ alert(msg.responseText) }
     );
}

A função no JavaScript ChamarMétodo efectua o pedido Ajax, e apresenta o resultado num alerta.reparem que os dados são preparados no argumento DTO e devidamente formatado em JSON. O endereço da página é relativa à actual.

Se tudo estiver OK, o resultado seria uma caixa de mensagem a aparecer com 5000 escrito e um botão de OK.na verdade, o retorno pode ser objectos complexo (devolvidos estruturados em JSON) para depois construir parte da interface, o HTML directo para reconstruir uma parte da página. É de lembrar que os dados retornados são encapsulados numa propriedade base do resultado que é o “d”.

O que gosto deste método é o facto de poder usar um código mais limpo no cliente, e mais controlo sobre o momento e operações de pedidos.

Ruby on Rails Way

Para Ruby On Rails (RoR), há uma serie de formas de conseguir, e a framework tem uma integração interessante com o Prototype. Pessoalmente estou treinado no jQuery, e portanto procurei formas de conseguir usar este.

A maior dificuldade que encontrei foi perceber o mecanismo da chamada, já que o uso do padrão MVC supõe algumas considerações. O endereço à qual é efectuado o pedido deve ter em conta o esquema de routing, por exemplo, e é necessário ter atenção à forma como é renderizado o layout – não deve ser renderizado normalmente, visto que só queremos transmitir uma pequena porção de HTML ou os dados para o construir.

Começando pelo função de JavaScript que chama o método do controlador, mantém-se praticamente idêntico, excepto que, devido à aplicação do routing, é conveniente escrever o endereço e o método de uma só vez. O rails até ajuda nesse aspecto:

function CallAjaxMethod(DTO, serviceAddress, successFuntion, errorFunction) {
    $.ajax({
        type: "POST",
        url: serviceAddress,
        contentType: "application/json; charset=utf-8",
        data: JSON.stringify(DTO),
        dataType: "json",
        success: successFuntion,
        error: errorFunction
    });
}

Tem outro nome neste momento, e menos um parâmetro, e pode ser introduzido numa biblioteca de funções base. O Rails permite injectar código para chamar código remoto, usando blocos como o “link_to_remote” ou o “forms_remote_tag”. Neste caso, na View, vou introduzir o código entre tags de script explicitamente, em vez de depender do Rails para o gerar:

function FuncaoQueUtilizaAjax(id){
     //processa uma serie de coisas...
     CallAjaxMethod(
          {"id": id, "nome": "Miguel Alho"},
          "&lt;%= url_for(:action => 'doAjaxOperation') %&gt;",
          function(msg){ alert(msg) },
          function(msg){ alert(msg.responseText); }
     );
}

É em muito semelhante ao ChamarMetodo() que usamos no exemplo para o .Net, mas neste caso é o rails a dar-nos o endereço do URL a que faremos o pedido usando o “<%= url_for(:action => ‘doAjaxOperation’) %>”, onde doAjaxOperation é o nome do método a executar no controlador. Assim, no Controlador, devemos ter:

class ConceitoActualController < ApplicationController
     #...
 
     def doAjaxOperation
          # criar um novo objecto com os argumentos enviados em POST
          postData = ActiveSupport::JSON.decode(request.raw_post())
 
          # processamento ....
          output =  "Olá " + postData["nome"] + ", " + postData["id"]
          render (:json => output)
          # alternativamente podia ser 
          # render :text => output.to_json
     end

Assim ‘doAjaxOperation’ é um método da classe do controlador actual (deve ser publico!) e porque tem o “render(:json …) ou o render :text, o método irá ignorar o layout, evitando reconstruir toda a página, e apenas lançar aquilo que necessitamos (que neste caso seria “Olá Miguel Alho, 1″). outra opção para não renderizar o layout seria utilizar “render :layout => false” dentro do método.

Os argumentos que enviamos, vão no POST da mensagem (é interessante ver isto no FireBug!), e para os decifrar, usamos ActiveSupport::JSON.decode(request.raw_post()). postData passa a ter o objecto nele e podemos ter acesso as propriedades do POST enviadas no pedido.

No fim, deve ser tido em conta que, ao contrario do .Net, os dados da resposta são entregues directamente no argumento da função do sucesso, e não encapsulados numa propriedade “d”. Portanto, em vez de termos um “msg.d” com a string “Olá Miguel Alho, 1″, o próprio msg teria essa informação.

Para mim, a transição não foi de todo fácil ,mas confesso que não tenho ainda boas bases nem do Ruby, nem do Rails, para facilitar. Felizmente, com o conhecimento adquirido no .Net com esta matéria, foi pelo menos ter uma boa base de arranque e encontrar o caminho.Espero que esta informação possa ajudar alguém!

FileUpload, iframes, e actualização de listas.

August 21st, 2009

Andei à procura de uma solução para um problema de envio de ficheiros, e não estava a encontrar nas pesquisas uma solução válida, dentro do contexto que estava a desenvolver. Felizmente lembrei-me duma que resultou e que vou descrever. E até é bastante simples! lol…

Então o contexto. Tenho uma página complexa (tem imensos blocos de funcionalidade ortogonais). Um das secções / blocos representa um conjunto de documentos associados a uma entidade. A funcionalidade da página está bastante dependente de AJAX para evitar carregamentos de página e ter uma interface mais fluída. Porque o bloco de adição dos documentos requer um upload, queria que o upload também fosse efectuado sem refresh da página.

Aparentemente, não é possível fazer o envio directamente com uma chamada a um método por AJAX. A solução então passa por usar um iframe na página, e o conteúdo desse iframe ser um novo formulário de envio. Segui esse caminho – implementei o iframe e criei a página de upload (o formulário tem um control de Fileupload, uma DropDownList, e um botão de envio). Neste caso, a funcionalidade de upload está contido neste formulário e este é o único responsável pelo upload. e funciona correctamente como esperado. O postback é contido no formulário do iframe e apenas nesse ponto é que há refresh. E é aceitável.

O problema que surge agora é que na página original, tenho a lista de documentos associados à entidade, e necessito de actualizar essa lista com o novo documento associado. A minha lista de documentos é carregada por chamadas AJAX, usando os métodos de templates javascript que mencionei no penúltimo post e portanto bastaria chamar novamente a função que preenche a lista para conseguir actualiza-la. Procurei então um método de comunicação entre frames, para que o a acção de click do botão ou do refresh da página actuasse sobre a página pai. Má ou impossível solução. No entanto, no meio disto, lembrei-me – “bem, se cada vez q é feito um upload, há um postback e a página do iframe é recarregada, será que dá para simplesmente apanhar o evento de ‘load’ do iframe?”. E a verdade é que sim, sempre que a página do iframe é carregado, o load do iframe dispara. Assim, fiquei com :

$(document).ready(function() {
      $('#docuploadframe').bind('load', function() { LoadDocList(); });
 });

Conclusão: em cada upload de documento, o load do iframe é disparado (após upload, ou mesmo erro), e então recarrego a lista de documentos após esse evento, via AJAX. Simples!

O tab do ASP.NET desapareceu no IIS!!??

August 21st, 2009

Não aconteceu comigo, mas ontem foi, talvez, a segunda vez que ouvi falar desta situação. um colega ontem teve o mesmo problema e ajudei a encontrar uma solução para o problema. Aparentemente, até é um problema comum e parece estar relacionado com um parâmetro de configuração de módulos 32bits em 64bits, e especialmente de Windows Server 2003 instalado num VMWare Server. A solução está descrita em vários locais da web – vou apenas transcrever/traduzir a solução para que possa ser útil a mais pessoal.

  1. Pare o serviço do IIS
  2. Localize o ficheiro “metabase.xml” em c:\windows\system32\inetsrv e abra-o no notepad ou outro editor
  3. Procure a linha que tenha a chave “Enable32BitAppOnWin64″ e elimine-o
  4. Guarde o ficheiro
  5. Reinicie o IIS (e restantes serviços que possam ter parado)
  6. Verifique que a tab existe

Esta solução está em http://www.bobnedved.com/post/2007/12/My-ASPNET-Tab-is-missing-in-IIS!!.aspx

display:inline-block .. tão útil!

August 17th, 2009

Pois é, esta semana e a passada é a do frenesim das interfaces. Mas ainda bem. Resumindo, tive uma certa “infelicidade” em comprar os controlos da DevExpress. Ainda por cima comprei uma licença Enterprise. Não são de todo maus, mas na realidade conseguem ser bastante. Tipo, têm funcionalidade boa compactada no controlo, e o aspecto visual e funcionamento acaba por ser relativamente intuitivo para o utilizador final. O problema é a API (quer server quer cliente) que é demasiado complexo e muitas vezes nada intuitivo (o que me tem causado muiiiitoo tempo perdido e muitas dores de cabeça). A coisa que mais me chateia naquilo é o HTML que é renderizado… meu Deus.. TABELAS POR TODO O LADO!!! . Género, para desenhar um select box são necessárias mil tabelas encaixadas umas nas outras. É código feio, muito feio, e difícil depois de fazer o que quer que seja com Javascript e jQuery, porque simplesmente não sabes onde actuar (o menu da aplicação, renderizado no cliente, ocupava umas 1500 linhas de código – agora são 160, incluindo javascript, e consigo facilmente encontrar as coisas que preciso!). Os controlos tem todos uma API do lado cliente, mas até hoje ainda não percebi nada daquilo, e a documentação daquilo é fraquinho.

O bom é que o serviço de apoio é bom, e o CodeRush é um excelente produto, e esse recomendo sem dúvida. É um auxilio fantástico. Mas os controlos podem esquecer. A menos que o projecto seja muito simples, sem camadas e objectos e camadas e afins, (tipo o ui conectar-se à BD), aquilo acaba por dar muitas dores de cabeça. Mais fácil gerar código e fazer as interfaces à mão com HTML e controlos ASP.Net, e ainda combinar CSS com jQuery. é o que tenho feito ultimamente com o meu colaborador, e acredita, estou bem mais satisfeito, e acredito que tenho uma interface bem mais agradável e funcional. Caí um bocado no “hype” daquilo, e infelizmente, hoje ando a refazer interfaces e designs…

Portanto, agora, tenho criado umas interfaces bem mais limpas e bem mais funcionais. Ainda mantenho alguns dos controlos em locais em que não “estragam” nada e mantém funcionalidade válida. Mas onde possível e onde se justifica, estou a limpar. E como é claro, há uma série de coisas que estou a aprender às custas disso (e uma biblioteca de javascript com funcionalidade universal às interfaces, que tem sido muito útil).

Então uma revisão rápida às “coisas” aprendidas:

  • É possível criar templates do lado cliente para renderizar dados vindos em chamadas AJAX. Tenho testado duas formas, com bons resultados em ambos, sendo mais ou menos útil numa ou noutra situação. Um á muito boa quando a resposta AJAX é uma lista. Neste caso é a solução do script do tipo “text/html” com o template de renderização em html, e os campos a preencher em entre tags <#= #> (semelhante ao T4). É depois usado uma função javascript para correr o template e substituir o os campos com o resultado do pedido AJAX. O método está descrito no blog do Dan Whalin e ainda no do Rick Strahl. Recomendo uns testes. É muito rápido na execução (a latência é reduzida por haver poucos dados na transmissão) e é óptimo para soluções de paginação.O segundo método é o de ter uma div escondida, com a estrutura de apresentação dos dados e os campos bem identificados, e usar o clone() do jquery, para criar uma cópia do bloco, preenche-lo, e apresentar no local onde necessitar. O clone é opcional e depende do que é pretendido, mas é uma solução muito útil para apresentar detalhe e blocos de inserção / edição. E é facil de implementar e perceber. Arisco dizer mais fácil que soluções compradas…Sério.
  • JSON é fantástico. Dados bem estruturados e que ocupam muito pouco espaço. Muito menos que XML. Diria mais limpo, até. Mas não é um mar de rosas total. Por exemplo, a serialização de Datas é muito estranho, porque a data é em si uma estrutura de dados complicado de representar. Alías, no javascript, tens uma forma única que é através do construtor “new Date()”. Problema é que JSON é transmitida como texto e a deserialização dá barraca.Solução? O serviço .NET envia uma string do tipo “/Date(1241796300465)/” O numero no interior com 13 digitos, representa o numero de milisegundos desde 1 de Janeiro de 1970, até à data ali representada. para transformar isso, nada como um pouco de código Javascript:
    function GetJSDateFromNET(netDateString) {
       var myregexp = /\d{13}/;
       var match = myregexp.exec(netDateString);
       if (match != null) {
          result = match[0];
       } else {
          result = "";
       }
     
       var foo = new Date(parseInt(result));
       return foo;
    }

    Assim, procuro os 13 digitos via regex, e crio a data a partir do número no encontrado, que é efectivamente suportado pelo construtor de Datas do javascript. Também evito um eval que poderia ser de alguma forma perigosa, imagino.

  • Prototypes. Muito bom! Melhora a escrita no javascript permitindo encadear funções de instâncias (métodos), e extender as funcionalidades de tipos basicos como strings, inteiros e datas. Já tinha visto, mas nunca tinha percebido ou notado o potencial. Agora, sim. Por exemplo, para a função anterior, já posso aplicar uma função directamente ao tipo string para criar a data, e se quiser, encadiar uma transformação para string formatada:
    msg.d.CampoDeDataDoJSON.toDateJsonNet().toYMDString();

    que resultaria numa string com o formato “yyyy-MM-dd”. Para isso tenho:

    String.prototype.toDateJsonNet = function() { return GetJSDateFromNET(this); }
     
    function DateToYMDString(date) {
        return date.getFullYear() + "-" + (date.getMonth() + 1).toString().lpad('0', 2) + "-" + date.getDate().toString().lpad('0', 2);
    }
     
    Date.prototype.toYMDString = function() { return DateToYMDString(this);}

    De uma forma simples, o prototype acrescenta funcionalidade ao tipo (aqui é com um tipo de dados base, mas podia ser um tipo que eu criei). Também te deixa o código mais limpo e legível, o que é sempre importante!

  • display:inline-block. Esta é fantástica. E resolve um problema “clássico” de colunas, sem recorrer a tabelas nem floats. Tem algumas manhas, e dependências de browsers que aplicam incorrectamente (mais IE hacks, mas simples), e bem explicadas aqui, aqui, e aqui. Para o meu caso quero duas colunas, lado a lado, baseados em divs.
    <div class="col1">
    	...
    </div>
    <div class="col2">
    	...
    </div>
    .col1{width:70%; display:inline-block; vertical-align:top;*display:inline;*zoom:1}
    .col2{width:29%; display:inline-block; vertical-align:top;*display:inline;*zoom:1 }

    O segredo está na combinação “display:inline-block” com “vertical-align:top;” o primeiro permite colocar divs lado a lado, mas verticalmente ficam alinhados pela base do texto contido nas divs. para isso, serve o vertical-align:top. Isto funciona bem no FF3.5. Para resolver para IE8, basta o “hack” do asterisco como prefixo no display:inline e zoom:1, que são interpretados apenas pelo IE. Apenas testei no IE8 (que é o q necessitava) e resulta bem. O que mais gosto disto é que a div que suporta as duas colunas 8um género de contentor clássico para centrar tudo) cresce quando qualquer das duas colunas cresce, o que não acontece com as soluções de float, como tinha anteriormente. Esta fui uma bela revelação para mim, que já tive este problema diversas vezes!

E por hoje chega. Ficam assim aqui algumas das fantásticas revelações destas semanas!!! :D

Ler um DateTime, passo por JSON, com Javascript

August 7th, 2009

O título é algo confuso, mas o problema é pertinente. Quando se utiliza JSON para transmitir dados entre servidor e browser-cliente, enquanto a generalidade de objectos serializáveis são convertidos correctamente (um inteiro é um inteiro, uma string é uma string…), os valores de datas não são convertidos como um tipo Date do javascript, nem DateTime do .NET. A generalidade dos valores passos no JSON são convertidos para uma forma literal correspondente – os números numa sequência de caracteres numéricos, as strings como sequência de caracteres, etc. Mas para o tipo que representa uma data, simplesmente não existe um literal único – basta pensar que há dezenas de formas de representar uma data – só a parte do calendário, ou com hora, ou com variações de barras e hifens, ou com a troca de texto e ordem com base em culturas variadas.

Na serialização JSON do .NET, a Microsoft decidiu adoptar uma convenção para enviar os dados num formato literal – a data é representada pelo número de milissegundos desde a data de referência de 1 de Janeiro de 1970. O formato em que é enviado é:

/Date(xxxxxxxxxxxxx)/

Evidentemente, esta string não é uma data por si só, apenas uma representação literal (e independente de constrangimentos culturais). Necessitamos de converter os ticks numa data. No javascript o único método de conversão que temos é a função Date(), que funciona como construtor do objecto da Data. Dos vários overloads que existem, há um que recebe o número de milisegundos desde 1 de Janeiro de 1970, tal como o número que recebemos da resposta JSON!

Sendo assim, podemos usar a seguinte função para obter a estrutura da Data, a partir do literal devolvido pelo servidor:

function GetJSDateFromNET(netDateString) {
    var myregexp = /\d{13}/;
    var match = myregexp.exec(netDateString);
    if (match != null) {
        return new Date(parseInt(match[0]));
    } else {
        return null;
    }
}

Assim, com o valor devolvido pode-se fazer algo do género:

...
var data = GetJSDateFromNET(valorDataJSON);
alert(data.getYear());
...

A função, reutilizável, permite ler correctamente o literal de data enviado pelo servido, e processar a data do lado do cliente.

referencias:
An Introduction to JSON @ MSDN
Encosia

Um livro aberto de C#

June 15th, 2009

Eu sei.. tanto tempo sem um posto. Mas as razões são muitas. Ora vejamos – terminar um projecto, coordenar um segundo, dar aulas, gerir estágios, preparar e assistir num vídeoclip, ler, ler, ler, apoiar entidades locais.. e tentar descansar e exercitar um pouco (que bem preciso!).

Mas, felizmente, o projecto principal está numa fase final, o segundo é curto e está delegado, os projectos actuais de estágio são interessantes (e relativamente curtos), o vídeo só necessita de mais um dia de filmagem com um apoio simples da minha parte, as aulas estão a terminar, e os dias estão porreiros para jogar um ténis (nos recentes campos locais). Mesmo assim o tempo é curto, e apesar de encontrar tanta coisa interessante, parar para escrever o post é quase “mais um” obstáculo produtivo. É difícil.. é difícil…

Este post, não tem, no entanto, o objectivo de me desculpar (se bem que também serve tal propósito), mas é com a intenção de lançar um desafio. Tenho vontade de escrever um livro de programação. Preferencialmente aberto. De C#, que é a minha linguagem eleita. Ou com ASP.NET à mistura (ou separado). E que seja dinâmico – wiki style, talvez, mas que permita uma exportação para um formato “bonito” e até possível de imprimir e encadernar. Que seja aberto a comentários e discussão (global e localizado) e que os comentários estejam incluídos. Que seja uma colaboração. E em português – não um português tecnicamente correcto e isento, mas um que seja em jeito de conversa… que se compreenda. Que aborde, todos os níveis de conhecimento no seu decorrer. Que seja um bom guia estruturado. Não um tutorial, mas um guia e fonte de conhecimento suficientemente profundo. Que seja não só instrutório, mas também pragmático e apresente várias abordagens ao mesmo objectivo. Que demonstre padrões e arquitecturas variadas usando a linguagem. Que seja do básico, mas também do técnico, e de engenharia (talvez seja possível criar filtros para o efeito). Que seja suficientemente bom para alguém aprender a língua, ao ponto de poder ser usado em aulas (secundário ou superior).

Alguém com vontade??? :D