29 de abril de 2011

Design Patterns com Delphi: Observer - Parte II

No post anterior, apresentei o tipo de problema que o Design Pattern comportamental Observer se propõe a resolver, introduzindo os conceitos envolvidos na implementação de uma solução. Aqui, mostro uma sugestão de como efetivamente implementá-lo em projetos Delphi.

Dependendo do contexto onde o padrão Observer será empregado, a implementação dele poderá sofrer ligeiras variações. Uma das diferença mais comuns está relacionada a quantas instâncias de classes poderão ser notificadas sobre um mesmo evento. Eventos da VCL, por exemplo, podem ser respondidos por no máximo uma única função, vinculada a uma classe. Por isso, uma variável simples é o bastante para conter a referência da função que será chamada. Já no exemplo que aparece no post anterior, diversos usuários podem requerer notificação para a mesma atualização num objeto de negócio. Por isso, nossa implementação precisará armazenar os observers em algum tipo de lista.

Para facilitar, reproduzo abaixo o diagrama UML representando a proposta de implementação do exemplo do post anterior:
Diagrama UML para o padrão Observer

Pelo diagrama, a classe que define o comportamento base de todo Subject é a TWBusinessObj. É ela, portanto, quem tem que ser capaz de armazenar as instâncias dos Observer que receberão avisos de alteração. Veja no código abaixo que o controle de quais instâncias devem receber os avisos é feito com uma lista de objetos, ou seja, tantas instâncias quantas forem necessárias podem "assinar" o serviço e serem avisadas :
TWBOState=class;
TWBOObserver=class;

TWBusinessObj=class
protected
_ListaObsv : TObjectList;
{ ... }
public
procedure AttachObsv (AObsv: TWBOObserver);
procedure DetachObsv (AObsv: TWBOObserver);
procedure Notify;

function getState: TWBOState;virtual;abstract;

{ ... }

Constructor Create;
Destructor Destroy;override;
end;

implementation

Constructor TWBusinessObj.Create;
begin
_ListaObsv := TObjectList.Create (false);
end;

Destructor TWBusinessObj.Destroy;
begin
FreeAndNil (_ListaObsv);
inherited;
end;

procedure TWBusinessObj.AttachObsv (AObsv: TWBOObserver);
begin
_ListaObsv.Add(AObsv);
end;

procedure TWBusinessObj.DetachObsv (AObsv: TWBOObserver);
begin
_ListaObsv.Remove(AObsv);
end;

procedure TWBusinessObj.Notify;
var i : integer;
lObsv : TWBOObserver;
begin
for i := 0 to _ListaObsv.Count - 1 do
try
lObsv := _ListaObsv.Items [i] As TWBOObserver;
lObsv.Update(Self);
except
end;
end;

Há outros detalhes para prestar atenção. O construtor da lista recebe um parâmetro com valor false para indicar que ela não é responsável por devolver a memória utilizada pelos objetos que ela contém. Isso não pode acontecer pois ainda podemos precisar dos objetos inseridos na lista mesmo que o TWBusinessObj seja deletado. Lembre-se de que, neste exemplo, os objetos que receberão as notificações são instâncias que representam usuários no sistema e que eles podem requerer notificações de vários objetos de negócio diferentes ao mesmo tempo.

Um outro ponto é que a classe TWBusinessObj é abstrata e, portanto, não poderá ser diretamente instanciada. Cada herança dela precisará remover a abstração, implementando a função getState para fornecer informações específicas sobre o estado interno de suas propriedades. Veja um exemplo:
TWPedidoVenda=class(TWBusinessObj)
protected
{ ... }
_State : TWBOState;
public
{ ... }
function getState : TWBOState;override;
procedure setDataEntrega (ADt: TDateTime);
end;

{ ... }
function TWPedidoVenda.getState : TWBOState;
begin
Result := _State;
end;

procedure TWPedidoVenda.setDataEntrega (ADt: TDateTime);
begin
_State.ModifyProperty ('DT_ENTREGA', ADt);
Notify;
end;

A classe TWBOState que aparece nesse código é uma base que eu montei para conter as informações de estado específicas de cada objeto de negócio, permitindo recuperar o valor atual de cada propriedade e aquele que havia antes, caso ela tenha sofrido uma alteração. No trecho mostrado acima, uma instância dessa classe registra a alteração na data de entrega do pedido e, em seguida, o próprio pedido dispara notificações dessa alteração usando a função Notify mostrada no primeiro quadro desse post.

Fica faltando, então, a segunda parte da operação: fazer com que cada Observer interprete como lhe for conveniente as modificações notificadas, implementando a função Update:
TWBOObserver=class
public
procedure Update (AObj: TWBusinessObj);virtual;abstract;
end;

TWAuditoria=class(TWBOObserver)
public
procedure Update (AObj: TWBusinessObj);override;
end;

TWUsuario=class(TWBOObserver)
public
procedure Update (AObj: TWBusinessObj);override;
end;

{ ... }

procedure TWAuditoria.Update (AObj: TWBusinessObj);
var lState : TWBOState;
begin
lState := AObj.getState();
GravaAuditoria (lState);
end;

procedure TWUsuario.Update (AObj: TWBusinessObj);
var lState : TWBOState;
begin
lState := AObj.getState();
EnviaEMail (lState);
end;

Em algum ponto do sistema teremos que usar a função AttachObsv para vincular a classe de auditoria e os usuários apropriados, isto é, fazer com que essas classes "assinem" o serviço de notificação de um objeto de negócio:
var _Auditoria : TWAuditoria;
_Gerente : TWUsuario;
{ ... }
Constructor TWFormVenda.Create (AOwner: TComponent);
begin
inherited;
_Pedido := TWPedidoVenda.Create;
_Pedido.AttachObsv (_Auditoria);
_Pedido.AttachObsv (_Gerente);
{ ... }
end;

O planejamento do exemplo incluiu ainda a possibilidade de uma classe cancelar a assinatura, suspendendo as notificações para si. Para isso, basta chamar a função DetachObsv quando (e se) for necessário.

25 de abril de 2011

Design Patterns com Delphi: Observer - Parte I

Quando projetamos um sistema computacional baseado em classes, temos a tendência de focar aspectos isolados, tratando grupos de classes associadas de forma que resolvem um problema particular no domínio da aplicação. Na verdade, isso é uma característica intrínseca de projetos orientados a objetos, pois a responsabilidade de cada classe deve estar bem definida para que se consiga extrair o melhor desse tipo de abordagem: induzir o reaproveitamento de código e facilitar a manutenção do sistema.

Mesmo assim, muitas classes que aparentemente não guardam relação entre si podem precisar conhecer mudanças de estado umas das outras. Tal situação é alvo do Design Pattern comportamental Observer. Nele, uma classe expõe uma espécie de serviço por assinatura relativo a uma determinada característica sua. Outros objetos, então, podem assinar o serviço para serem notificados sempre que essa característica sofrer uma manutenção relevante – quando ela for alterada ou acessada, por exemplo.

Um cenário onde esse padrão pode ser aplicado ocorre quando há uma classe representando uma massa de dados que é acessível por diversas outras classes. É o caso das planilhas de cálculo, onde os mesmos dados podem ser exibidos numa tabela ou num gráfico ou ainda servirem de base para cálculos apresentados em outras tabelas. É interessante, portanto, que essas classes sejam notificadas toda vez que houver uma alteração na massa de dados, pois assim elas poderão refletir imediatamente quaisquer alterações e sempre apresentar informações atualizadas para o usuário. Não é à toa que a descrição acima remeta tão fortemente ao conceito de MVC (Model-View-Controller) : a parte View é tradicionalmente implementada como um Observer.

Outro exemplo de aplicação desse padrão é em sistemas com auditoria. Aqui, classes que permitam manutenção em dados podem ser preparadas para prover um serviço que reporte certas alterações. Então, essas alterações podem ser registradas no banco de dados para uma eventual auditoria ou disparar a emissão de alertas a usuários com credenciais especiais que tenham solicitado receber notificações sobre as alterações. O diagrama UML abaixo mostra uma solução para esse cenário usando o Observer.
Diagrama UML para o padrão Observer

Note que o padrão é estruturado de forma que a classe com regras de negócio expõe o serviço de notificação mas somente quando surge necessidade (ou por opção do usuário) é que as demais classes fazem a assinatura para receber tais notificações. Isto é, a notificação não é compulsória; ela é feita somente sob demanda da própria classe interessada.

Os participantes de uma solução com esse design pattern recebem os nomes explicados no quadro a seguir:
O Subject é uma classe que introduz os meios pelos quais o serviço por assinatura é publicado. É ela quem dita como outras classes podem se cadastrar para receber as notificações e, se for possível suspender o cadastro, como isso poderá ser feito. Por isso, ela deve ser capaz de manter referências às instâncias das classes que solicitam notificações, sendo efetivamente a provedora do serviço de assinatura. No diagrama anterior, esse papel cabe à classe TWBusinessObj. Obviamente, nada impede que essa classe execute outras tarefas, como se deduz do diagrama.

O Concrete Subject é a classe que possui alguma característica de interesse que poderá ser monitorada. Ela é, portanto, a origem real das modificações que devem ser notificadas. Ela deve ainda fornecer mecanismos para que as classes que solicitarem notificações possam ter acesso ao seu estado interno, recuperando as informações alteradas. Além disso, deve estar apta a disparar as notificações sempre que isso for necessário. No exemplo, este é o papel das classes TWPedidoVenda e TWTituloAPagar. Por ser uma herança do Subject, este tipo de participante da solução compartilha o mecanismo de gerenciamento de assinaturas.

Observer é o nome dado à classe que estipula a forma como um assinante do serviço do Subject receberá as notificações. É comum que esse papel seja representado por uma classe abstrata que apenas introduza a função destinada a utilizar os novos dados atualizados. É o caso de TWBOObserver no diagrama anterior. Veja que a classe do tipo Subject (no exemplo, TWBusinessObj) permite anexar e remover instâncias de TWBOObserver (o Observer). Por isso, qualquer herança desse Observer pode se cadastrar para receber as notificações.

Um Concrete Observer implementa a função especificada pelo Observer, usando de fato o estado interno do Subject que originou a notificação para realizar quaiquer operações que ele julgue necessárias. Este é o papel das classes TWAuditoria e TWUsuario no exemplo.

O padrão Observer é dos mais utilizados, tanto que alguns frameworks, como Java e .Net, já trazem embutidos mecanismos para agilizar sua implementação. Isso se dá através da introdução de interfaces (com nomes como IObserver e IObservable) ou de palavras chaves da própria linguagem de programação (delegate e event). A VCL também usa o Observer em vários pontos, principalmente no tratamento de eventos de seus componentes.

No próximo post, apresento uma implementação desse exemplo usando Delphi.

18 de abril de 2011

Criando um editor de HTML com o TWebBrowser - parte III

Um problema clássico enfrentado por quem cria um editor HTML com o TWebBrowser está em como relatar ao usuário informações a respeito das formatações existentes no ponto do editor onde o cursor está atualmente ou sobre alterações introduzidas no documento editado. Isto porque o componente TWebBrowser não disponibiliza um evento apropriado que possa ser interceptado para exibir tais informações. Não há, por exemplo, eventos específicos para mudanças do documento ou que reflita mudanças na posição atual do cursor.

O problema, portanto, pode ser desmembrado em dois: como descobrir que houve uma mudança no texto ou que houve uma alteração na posição do cursor e como recuperar a formatação existente na posição atual do cursor.

Para a primeira questão, podemos usar um evento definido na interface IHtmlDocument2. Apenas para relembrar: a propriedade Document do TWebBrowser implementa essa interface, bastando fazer um cast para ter acesso às propriedades, métodos e eventos dela:
function TForm1.DOMInterface: IHtmlDocument2;
begin
Result := WebBrowser1.Document As IHtmlDocument2;
end;

Um bom evento do IHtmlDocument2 para obtermos notificações das manutenções que precisamos é o onkeydown. Ele é disparado sempre que uma tecla é pressionada quando o componente está com o foco, não importa se é uma letra, número ou tecla de controle. Portanto, isso inclui as setas de navegação, page-up/page-down, as combinações de tecla, etc.

Para recebermos as notificações, temos que alimentar a propriedade onkeydown com a instância de uma classe que implemente a interface básica IDispatch. Quando o evento ocorre, a função Invoke definida por esta interface é executada, permitindo-nos particularizar uma resposta ao evento. Conforme eu disse no post sobre uso de interfaces em Delphi, podemos criar uma herança de TInterfacedObject e implementar nela os métodos introduzidos pela IDispatch:
TWMSHTMLEvent = procedure(Sender: TObject; Event: IHTMLEventObj) of object;
TWMSHTMLEventConnector = class(TInterfacedObject, IDispatch)
private
FDoc: IHtmlDocument2;
FOnEvent: TMSHTMLEvent;

{ Métodos do IDispatch }
function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;

public
constructor Create(ADoc: IHtmlDocument2; Handler: TWMSHTMLEvent);
end;

{ ... }

function TWMSHTMLEventConnector.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult;
begin
Result := S_OK;

if Assigned(FOnEvent) then
FOnEvent(Self, FDoc.parentWindow.event);
end;

No trecho de código acima, criei também um tipo chamado TWMSHTMLEvent para representar um evento externo. Este tipo enviará como parâmetros o Sender (componente que gerou o evento) e uma instância de IHTMLEventObj, interface que traz as informações de um evento HTML, tais como o código da tecla pressionada e o elemento HTML existente no ponto onde o evento ocorreu.

Embora sejam obrigatórias para o IDispatch, as demais funções não são relevantes no contexto de um evento HTML, podendo ser implementadas apenas ajustando o valor de retorno como E_NOTIMPL.

Com a classe implementadora do IDispatch definida como mostrado acima, podemos manter a resposta ao evento HTML dentro do próprio Form onde o TWebBrowser está inserido, e, portanto, mantendo separadas as responsabilidades de cada classe. Note também que o Document é passado no construtor. Precisamos dele para obter as informações do último evento ocorrido, dado que é repassado para o Form através do parâmetro que criamos no nosso próprio evento.

Na verdade, nossa classe pode ser utilizada para responder a qualquer um dos eventos definidos em IHtmlDocument2, incluindo aqueles associados aos movimentos do mouse e a cliques em qualquer ponto da página HTML.

E qual é o melhor lugar para fazer a associação entre a instância de nossa classe e o evento onkeydown ? Nós só teremos um Document montado depois que ao menos uma página HTML tenha sido completamente carregada. Portanto, o evento OnNavigateComplete2 do componente TWebBrowser é o local mais apropriado:
procedure TForm1.FormCreate(Sender: TObject);
begin
_KeyDownEvt := Nil;
WebBrowser1.Navigate('http://balaiotecnologico.blogspot.com/');
end;

procedure TForm1.WebBrowser1NavigateComplete2(ASender: TObject; const pDisp: IDispatch; var URL: OleVariant);
begin
if (_KeyDownEvt = Nil) then
_KeyDownEvt :=
TWMSHTMLEventConnector.Create (DomInterface, DoOnWebKeyDown);
DomInterface.onkeydown := _KeyDownEvt As IDispatch;
end;

Veja que a criação da instância da classe TWMSHTMLEventConnector pra conectar os eventos ocorre um única vez. Já a atribuição ao onkeydown acontece toda vez que uma nova página for carregada. Isso se dá por que o documento é criado uma única vez mas suas propriedades são sempre reajustadas para refletir a configuração da nova página. A função DoOnWebKeyDown passada ao construtor é implementada no próprio Form e é ela a responsável por responder ao evento em questão.

Agora, vamos à segunda parte do problema: obter informações sobre a tag HTML existente na posição atual do cursor no editor. Para isso, vamos usar a propriedade selection do IHtmlDocument2, que pode conter tanto o atual ponto de inserção do editor quanto todo o texto selecionado, se houver uma seleção.
function TForm1.GetHtmlAtCurPos: string;
var Element, Rng: OleVariant;
begin
Result := '';
try
Rng := DOMInterface.selection.createRange;

if (DOMInterface.selection.Type_ = 'None')
or (DOMInterface.selection.Type_ = 'Text') then
Element := Rng.parentElement
else if (DOMInterface.selection.Type_ = 'Control') then
Element := Rng.commonParentElement;
except
end;

if (VarType(Element) <> varEmpty) then
begin
Result := Element.innerHtml;
if Length(Result) = 0 then
Result := Element.outerHtml;
end else
Result := '';
end; end;

Basicamente, a função acima recupera a seleção atual (ou o ponto de inserção), descobre quem é o elemento HTML onde ele está inserido e retorna o texto HTML correspondente a este elemento. De posse do elemento HTML, é possível criar rotinas mais sofisticadas para capturar detalhes da formatação, como uso de negrito ou cores.

Embora seja estranho usar um OleVariant para ter acesso a propriedades do elemento HTML, este é um recurso bastante usado quando se trabalha com COM; ainda mais quando o tipo exato retornado depende do contexto em que uma função é chamada. Isso não dá erro de compilação no Delphi porque a ligação com a função real é feita somente em tempo de execução. Neste caso, se tentar usar uma função que não existe, um erro será reportado na execução do programa.

Agora, podemos implementar no Form uma resposta ao evento de tecla pressionada:
procedure TForm1.DoOnWebKeyDown(Sender: TObject; EventObj: IHTMLEventObj);
var code : integer;
begin
EventObj.cancelBubble := True;

{ Recupera a tecla que foi pressionada }
code := EventObj.keyCode;

if not (code in [VK_PRIOR, VK_NEXT, VK_END, VK_HOME, VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN])
then _Modified := True;

_Painel.Text := GetHtmlAtCurPos;
end;

A assinatura da função retratada no quadro acima tem que ser exatamente igual à do evento externo criado por nós pois ela é passada para a classe que implementa o IDispatch, através de seu construtor .

Mais Informações
Criando um editor de HTML com o TWebBrowser: conceitos básicos, disponibilizando interações para o usuário, Interface IHtmlDocument2.

14 de abril de 2011

Enviando emails com Delphi - Parte I

O email é há bastante tempo uma ferramenta corriqueira, das mais usadas nesses tempos de internet. Mas, a utilidade dele não está restrita à troca de informações entre pessoas - seja por razões pessoais ou profissionais. Um sistema computacional também pode tirar proveito dessa ferramenta ao permitir, por exemplo, notificar usuários a respeito de alguma ocorrência no escopo do sistema.

Num sistema, são diversas as situações onde o envio de um email é aplicável: uma situação de erro encontrada, algum problema potencial que exija intervenção do usuário, alguma ação dentro de um workflow (por exemplo, uma aprovação que necessite do aval do usuário), troca de correspondência automática com Clientes e Fornecedores (envio de um pedido ou de uma cotação, por exemplo), remeter informações em forma de relatórios para lastrear tomadas de decisões, etc.

A forma mais utilizada para enviar de emails é através do protocolo SMTP - Simple Mail Transfer Protocol. Este é um protocolo simples, constituído pela troca de mensagens em formato texto enviadas numa ordem precisa através de TCP/IP. Mesmo sendo um protocolo relativamente simples, é mais fácil usar classes prontas que o implementem, como as publicadas pelo Projeto Indy e que são distribuídas junto com o IDE do Delphi e do C++ Builder.

Um exemplo básico usando as classes do Indy pode ser visto no quadro abaixo. Ele mostra o uso de algumas das principais propriedades necessárias para concretizar um envio:
var IdSMTP : TIdSMTP;
IdMessage : TIdMessage;
begin
IdSMTP := TIdSMTP.Create (Nil);
IdMessage := TIdMessage.Create (Nil);

{ Informações básicas para uma conexão }
IdSMTP.Host := edServidor.Text;
IdSMTP.Port := edPorta.Text;

{ Tenta conectar no servidor, aguardando no máximo um minuto }
IdSMTP.Connect (60000);

{ Nome e endereço de quem está enviando }
IdMessage.From.Address := edEnderecoDe.Text;
IdMessage.From.Name := edNomeDe.Text;

{ Destinatários }
IdMessage.Recipients.EMailAddresses := edEnderecoPara.Text;

{ Destinatários em cópia }
IdMessage.CCList.EMailAddresses := edCcPara.Text;

{ Destinatários em cópia oculta }
IdMessage.BccList.EMailAddresses := edBCcPara.Text;

{ Endereço para resposta, isto é, se um destinatário der um "reply", a resposta será enviada para este endereço }
IdMessage.ReplyTo.EMailAddresses := edResponderPara.Text;

{ Assunto da mensagem }
IdMessage.Subject := edAssunto.Text;

{ Corpo da mensagem, isto é, o texto dela }
IdMessage.Body.Text = edMensagem.Text;

{ Envia o e-mail }
IdSMTP.Send (IdMessage);

{ Desconecta }
IdSMTP.Disconnect ();

FreeAndNil (IdMessage);
FreeAndNil (IdSMTP);
end;

Esse é o exemplo mais básico possível, acessando um servidor de email que não exige autenticação nem conexão segura. A mensagem enviada também é simples - apenas texto comum, sem anexos. Ele demonstra a interação entre as classes TIdSMTP (que implementa o protocolo de comunicação em si) e TIdMessage (que contem a mensagem que será enviada). Primeiro, uso o TIdSMTP para estabelecer uma conexão com o servidor; uma vez conectado, ele pode enviar a mensagem previamente preparada; finalmente, depois que o email foi enviado, pode-se solicitar a desconexão ao servidor.

Dificilmente um servidor de email hoje não exigirá autenticação do usuário. Mas, essa configuração é trivial, bastando passar o nome do usuário e sua senha antes de tentar se conectar :
if (UsaAutenticacao) then
begin
IdSMTP.AuthenticationType := atLogin;
IdSMTP.Username := edUsuario.Text;
IdSMTP.Password := edSenha.Text;
end;

Ajustar o valor da propriedade AuthenticationType também é necessário pois é ela que instrui o componente a submeter a autenticação.

Já adicionar segurança na comunicação através de SSL ou TLS demandará um pouco mais de trabalho. Isso envolve a criação de instância de uma nova classe - TIdSSLIOHandlerSocket (na versão 9 do Indy) ou TIdSSLIOHandlerSocketOpenSSL (na versão 10). O código abaixo mostra um exemplo:
_SslHandler := TIdSSLIOHandlerSocketOpenSSL.Create(Nil);
_SslHandler.SSLOptions.Method := sslvSSLv23;
_SslHandler.SSLOptions.Mode := sslmClient;

{ Associa ao SMTP a configuração para segurança }
IdSMTP.IOHandler := _SslHandler;

O código até que é relativamente simples. A instância da classe é criada e são ajustadas algumas propriedades, como a versão do protocolo (método de autenticação) que será usada. Caso seja necessário associar um certificado digital ao envio, as opções do SSL permitem fazer as configurações pertinentes. Antes do SMTP se conectar, o novo manipulador para comunicação segura é atribuído à instância da classe de envio.

O problema é que o Indy não implementa SSL nem TLS. As classes disponibilizadas apenas mapeiam o funcionamento existente nas bibliotecas mantidas pelo Projeto OpenSSL, um projeto open source dedicado exclusivamente à segurança de comunicação baseada em criptografia e nos protocolos SSL/TLS. Há diversas versões das bibliotecas do OpenSSL espalhadas pela internet, compatíveis com inúmeras versões de Delphi/C++ Builder e diferentes sistemas operacionais. Em último caso, o site do projeto disponibiliza os fontes para quem preferir gerar sua própria versão.

Anexar arquivos à mensagem também requer uma nova classe mas o uso dela é mais simples. Para cada arquivo que desejar anexar, instancie um TIdAttachment vinculado à mensagem antes de enviá-la:
TIdAttachment.Create (
IdMessage.MessageParts,
'c:\temp\arq.doc');

O primeiro parâmetro passado ao construtor é uma estrutura da mensagem onde são armazenadas partes externas que a constituem (como os anexos). O outro parâmetro é o nome de um arquivo mas, na versão 10 do Indy, há mecanismos para você anexar diretamente um stream qualquer, isto é, não é preciso que seja um arquivo.

Pode parecer estranho chamar o construtor e não atribuir o resultado a variável alguma. Acontece que, ao vincular o anexo ao MessageParts, este passa a ser responsável por devolver os recursos usados (memória). Então, não é preciso manter uma variável com a instância nem aplicar-lhe o destrutor quando não for mais usada já que isso será feito automaticamente.

Em outros posts, mostro como enviar email no formato HTML, inclusive usando o componente PageProducer para gerar conteúdo. Também mostrarei como incluir imagens embutidas no HTML a ser enviado.

8 de abril de 2011

Erros a evitar na interface com o usuário de aplicações web para smartphones e tablets

Está cada vez mais complicado projetar e implementar sistemas para a Web. Já era problemático por causa da infinidade de versões de navegadores, cada um com sua visão particular de como representar os padrões definidos por HTML e CSS. Agora, a popularização de smartphones e tablets vem acrescentar ainda mais variáveis à equação pois esses aparelhos estão chegando ao mercado com uma imensa variedade de resoluções de tela - além, claro, de muitos fabricantes terem suas próprias versões de navegadores exclusívas para dispositivos móveis.

Outro fator introduzido por esses dispositivos é a forma de interagir com o usuário. Eles normalmente são touch screen, o que significa que não há um ponteiro de mouse para nortear a navegação - ela é feita através de toques com os dedos e o usuário é livre pra tocar onde bem entender. Isso certamente traz novas preocupações aos desenvolvedores.

Mesmo assim, desenvolver com HTML e CSS ainda é a melhor opção se você quer que seu sistema possa ser utilizado em uma gama mais ampla de dispositivos. A alternativa seria criar aplicações exclusivas para um determinado aparelho mas aí seu sistema ficará restrito a ele já que cada fabricante tem um SDK próprio.

Segue abaixo a tradução de parte de uma matéria publicada no site Infoworld.com sobre os erros mais comuns cometidos por quem desenvolve para Web focando esse cenário caótico de dispositivos. A matéria original completa em inglês pode ser acessada neste link.

Erro de interface Web 1: Uso de efeitos em elementos sob o cursor do mouse
Em algum momento, desenvolvedores web se apaixonaram pela ideia de destacar áreas da página ou só exibir uma conteúdo quando o usuário passar com o cursor do mouse sobre um componente específico. O problema para smartphones e tablets deveria ser óbvio: quando não há um cursor de mouse, não há forma de se passá-lo sobre as tais áreas.

Isso não significa que você deva evitar o uso desses efeitos. Mas, para cada efeito de destaque ou de exibição de conteúdo associado à posição do cursor do mouse, deveria haver um outro evento acionado pelo toque e que fizesse essencialmente a mesma coisa para aqueles usuários que dispõe de telas sensíveis ao toque. Usuários de smartphones não deveriam ser penalizados com uma nova carga da página para cada nível de navegação só por que você projetou seus menus para funcionarem exclusivamente com um mouse.

Erro de interface Web 2: Usar widgets e controles customizados
Projetistas adoram dar uma aparência exclusiva aos seus botões e outros widgets. Mas, padrões de interface gráfica diferem de uma plataforma para outra, e, quando controles não são prontamente identificáveis e acessíveis em cada dispositivo, a usabilidade fica sofrível.

Barras de rolagem customizadas são um exemplo particularmente notável. Ocasionalmente, os projetistas sobrepõem os controles padrões usando JavaScript, substituindo-os por outros widgets mais elegantes, leves e visualmente mais atraentes. O problema para usuários de tablets é duplo: não só widgets minúsculos são mais difíceis de se alcançar com o dedo como também usuários de tablets não usam as barras para produzir uma rolagem; eles simplesmente arrastam o dedo pela tela. Forçá-los a usar seu controle customizado só deixa sua interface capenga.

De modo similar, não dê como certa a presença de qualquer dispositivo de entrada. Caixas de diálogo, por exemplo, deveriam ter sempre algum controle visualmente identificável para fechá-las. Smartphones e tablets até podem tecnicamente possuir um teclado mas raramente vêm com uma tecla Escape.

Erro de interface Web 3: Ter muitas áreas com rolagem
Visualizar websites em telas pequenas frequentemente significa ter que fazer alguma rolagem para vê-la por inteiro. Como mencionado antes, entretanto, lembre-se que usuários de tablets fazem a rolagem arrastando o dedo pela tela. e não através de um componente visual em particular. Quando você divide a página em múltiplos painéis, cada um podendo ser rolado de forma independente, sua interface gráfica pode rapidamente se tornar um campo minado. Uma parte do conteúdo pode rolar num arrastar de dedo e outra parte rolar em sequência, dependendo de onde o dedo do usuário tocou primeiro na tela. Melhor manter o layout o mais simples possível ou, no mínimo, ter certeza de que há margens de um bom tamanho para garantir que o usuário possa escolher com segurança qual painel deve ser rolado ou, ainda, se quer rolar a página toda.

Erro de interface Web 4: Ter um layout de texto inflexível.
Inúmeros projetistas já me explicaram o layout de seus websites em termos precisos das medidas em pixels e regras de tipografia suiças. Se isto nunca foi boa prática para a web - onde usuários podem ajustar a janela do navegador e até mesmo trocar o tamanho das fontes à vontade -, é especialmente uma péssima ideia se você quer que seu site seja visualizado em smartphones.

O navegador Android, por exemplo, tem por padrão um modo em que tentará adequar o comprimento de uma coluna de texto para que caiba no comprimento de tela do aparelho, independente de quaisquer especificações CSS existentes na página web. Se você não levar isso em conta e esperar que todos os elementos visuais se alinhem exatamente do mesmo jeito que num navegador no desktop, você acabará deixando os usuários de smartphones com grandes áreas contendo apenas espaços em branco, o que efetivamente poderá esconder alguns controles e tornar a interface obscura.

Erro de interface Web 5: Fazer assunções sobre o formato da tela.
Um web designer se vangloriou para mim de que ele gosta de estar na vanguarda da tecnologia, sendo esta a razão para que ele projetasse sites que ficam com a melhor aparência nos mais modernos monitores widescreen de LCD. Mas, mesmo que você desconsidere as pessoas que usam monitores mais antigos, você não pode estar na vanguarda da tecnologia se ignorar usuários de dispositivos móveis.

Muitos smartphones podem automaticamente intercambiar entre os modos porta-retrato (vertical) e paisagem (horizontal), dependendo de como o usuário está segurando o aparelho. Além disso, alguns usuários odeiam a função de intercambiar e a desabilitam - caso no qual é melhor você esperar que eles tenham escolhido o mesmo modo para o qual seu site foi desenhado. Fazer assunções sobre o formato de uma página funciona bem no mundo da impressão, mas é uma péssima ideia na web, onde você não sabe o tamanho do papel.

Erro de interface Web 6: Carregar muitas imagens de antemão
Que dó dos pobres usuários de smartphone: não só o acesso deles à internet não é tão rápido quanto a conectividade terrestre como cada vez mais operadoras de celular estão limitando o uso de dados e também impondo taxas extras para o acesso excedente. Smartphones também têm limitação na capacidade de memória. Enquanto faz sentido usar JavaScript para carregar antecipadamente uma sequência de imagens para um slideshow em navegadores no desktop, isso é um tanto quanto grosseiro para com usuários de dispositivos móveis - mais ainda se as imagens são projetadas para aparecer somente quando o usuário passa o ponteiro do mouse sobre um determinado componente - o que, é claro, usuários de tablets não poderão fazer de qualquer maneira.

Erro de interface Web 7: Usar Flash
Odeio dizer isto, mas Flash ainda não tem lugar em dispositivos móveis. Notadamente, o iOS dos aparelhos da Apple não suporta conteúdo em Flash, mas mesmo os aparelhos Android que realmentes suportam Flash oferecem um desempenho apenas medíocre. Pior, aplicações Flash apresentam os problemas de interface expostos anteriormente com mais frequência do que os sites HTML puros. Sinto muito, fãs da Adobe: com o advento do HTML 5, os dias do Flash na web estão contados.