quarta-feira, 30 de Setembro de 2009

Tabela com múltiplos THEAD versus TBODY (IE Quirk)

Caso se crie uma table com mais do que um thead, algo deste género:


<table id="myTable" cellspacing="0px">
<thead>
<tr>
<th colspan="7">Outubro</th>
</tr>
<tr>
<th>Domingo</th>
<th>2ª-Feira</th>
<th>3ª-Feira</th>
<th>4ª-Feira</th>
<th>5ª-Feira</th>
<th>6ª-Feira</th>
<th>Sábado</th>
</tr>
</thead>
<thead class="days">
<tr>
<th>4</th>
<th>5</th>
<th>6</th>
<th>7</th>
<th>8</th>
<th>9</th>
<th>10</th>
</tr>
</thead>
<tbody>
<tr>
<td>TESTE</td>
...
</tr>
...
</tbody>
<tfoot>
...
</tfoot>
</table>

Tudo funciona perfeitamente (em FF3.0.14 ou no IE6.02900), inclusive aplicar CSS específico ao thead cuja class="days".


Mas se tentarem aceder por javascript ao array the objectos tBodies da tabela, as coisas ai mudam de figura.


var myTable = document.getElementById( 'myTable' );

var rows = myTable.tBodies[0].rows;

alert( 'Dados da primeira coluna do body = ' + rows[0].cells[0].innerHTML );

No caso do FF, retorna exactamente o esperado "TESTE", no entanto, no caso do IE vai retornar "4" ou seja, o conteúdo da primeira coluna do segundo thead.


Isto acontece porque o IE, erradamente associa o segundo thead (days), como se fosse o primeiro tbody, o que claramente não é, inclusivamente é incoerente com a aplicação de estilos, que essa sim funciona correctamente.


A solução para o problema, passa por fugir dele :) isto é, evitar o uso do thead adicional, e aplicar o estilo (class) directamente no tr. Não esquecer de actualizar o CSS de forma a corresponder com a alteração efectuada.


<table id="myTable" cellspacing="0px">
<thead>
<tr>
<th colspan="7">Outubro</th>
</tr>
<tr>
<th>Domingo</th>
<th>2ª-Feira</th>
<th>3ª-Feira</th>
<th>4ª-Feira</th>
<th>5ª-Feira</th>
<th>6ª-Feira</th>
<th>Sábado</th>
</tr>
<tr class="days">
<th>4</th>
<th>5</th>
<th>6</th>
<th>7</th>
<th>8</th>
<th>9</th>
<th>10</th>
</tr>
</thead>
...
</table>

quarta-feira, 19 de Dezembro de 2007

JSPs e comentários

Comentários em JSPs pode não ser tão linear como deveria ser. Vejam o código seguinte:
<%
...
SimpleDateFormat javascriptDateFormat = new SimpleDateFormat( "yyyy, (MM-1), dd" );
// usage: var mydate = new Date( <%= javascriptDateFormat.format( myJavaDate ) %> )
...
%>

À partida parece estar tudo bem, mas não está, porque o pre-processador das JSPs não gosta de tags embebidas (<% ... %>). Como executa/processa antes do compilador Java, e não considera os comentários java (// ou /* ... */) caput!
A solução passa então por comentar usando comentários JSP.
<%
...
SimpleDateFormat javascriptDateFormat = new SimpleDateFormat( "yyyy, (MM-1), dd" );
<%-- // usage: var mydate = new Date( <%= javascriptDateFormat.format( myJavaDate ) %> ) --%>
...
%>

Mas novamente, esbarramos com a limitação das tags embebidas não serem suportadas pelo Pre-processador JSP. Vamos resolver o problema fechando e abrindo o bloco envolvente de forma a evitar que um bloco fique contido no outro:
<%
...
SimpleDateFormat javascriptDateFormat = new SimpleDateFormat( "yyyy, (MM-1), dd" );
%> <%-- // usage: var mydate = new Date( <%= javascriptDateFormat.format( myJavaDate ) %> ) --%> <%
...
%>

E agora já funciona e até seria um resultado expectável, não fosse haver aqui algo estranho!
A formatação da data, que está em código JSP, está dentro de um comentário JSP, ora reparem bem:
<%-- // usage: var mydate = new Date( <%= javascriptDateFormat.format( myJavaDate ) %> ) --%>

Ou seja, tags para código JSP (<% ... %>) podem estar contidas em comentários JSP (<%-- ... --%>), mas o inverso não é verdade !
Confuso no mínimo.

segunda-feira, 17 de Dezembro de 2007

CSS - IE6 versus Selector de Atributos

O IE6 não suporta CSS "attribute Selectors", como no exemplo seguinte:
div.Field input[type="text"]
{
color: blue;
}

Assim, temos que circundar o problema, e a solução mais comum, é adicionar uma class específica para o caso que queremos resolver, e atribuir a class ao elemento respectivo, por exemplo:
div.Field input.Text /* IE6 Does NOT support attribute selector */
{
color: blue;
}

Passamos então a representar o conteudo da seguinte forma:
<div class="Field">
<label for="Name">Nome</label>:<input class="Text" type="text" id="Name" value="">
</div>

Mas numa perspectiva futura, podemos querer suportar ambas as regras, com algo como o seguinte:
div.Field input[type="text"],
div.Field input.Text /* IE6 Does NOT support attribute selector */
{
color: blue;
}

Perfeitamente válido ! Certo ?
Sim, é perfeitamente válido segundo o standard; Mas não, não funciona no IE6!
E de nada vale trocar a ordem das regras, dado que o comportamento é exactamente o mesmo.

O problema é devido ao IE6 rejeitar as regras que não suporta, conjuntamente com qualquer outra que lhe esteja associada.

Para resolver o problema temos que duplicar as formatações, de forma a que a rejeição da regra não suportada, não afecte as restantes, ou seja:
div.Field input[type="text"]
{
color: blue;
}

div.Field input.Text /* IE6 Does NOT support attribute selector */
{
color: blue;
}

Saber isto pode poupar-vos muito tempo :)

terça-feira, 13 de Novembro de 2007

Validação de Parametros

Hoje deparei-me com o seguinte Código Java numa página JSP:
boolean bDisabled = (new Boolean ((String) (request.getParameter("bDisabled")))).booleanValue();
É mais um caso de conversões implícitas, usadas abusivamente. Mas desta vez o programador, gostou de complicar a coisa, para além do razoável.
Nomeadamente, é preciso que quem chame a página, saiba exactamente, qual é a conversão implícita esperada, que é "true" para true e "false" para false, e tem que respeitar o case.
Assumindo eu, que é válida a seguinte afirmação:
Se não é "true" é implicitamente "false".
Então podemos optar por algo bem mais simples.
boolean bDisabled = "true".equals( request.getParameter( "bDisabled" ) );
A vantagem é óbvia em termos de legibilidade e eficiência.
Nota: No caso em que o parâmetro não é enviado, assumimos que seja equivalente a false, o que é uma assumpção razoável, mas que validei no código existente como perfeitamente válida.
Caso não fosse, bastaria testar esse caso também, e gerar uma excepção para qualquer outro caso, por exemplo.

sexta-feira, 2 de Novembro de 2007

Estruturas de dados em Javascript

Aqui temos mais um caso típico de código Javascript:
for (i=0;i < arrParameterRange.length ; i++)
{
// set the temporary variables, to improve the code readability
sParamFormat = arrParameterRange[i][0];
sParamRangeFrom = arrParameterRange[i][1];
sParamRangeTo = arrParameterRange[i][2];
...
alert( sParamFormat + " for range " + sParamRangeFrom + " : " + sParamRangeTo );
...
}
O que me levou a escrever sobre este código foi o comentário que lhe está associado.
De facto a intenção é boa, mas estão a tentar resolver o problema errado.

O problema deste código reside na forma como os dados estão a ser guardados, nomeadamente estão a usar Arrays de Arrays.
A inicialização típica destes arrays é algo como o código seguinte:
var arrParameterRange = new Array();
arrParameterRange[0] = new Array( 'DumbFormat', 'DumbFrom_0', 'DumbTo_0' );
arrParameterRange[1] = new Array( 'DumbFormat', 'DumbFrom_1', 'DumbTo_1' );
...
E isto depois implica código semelhante ao que vimos no início, com indices HardCoded por todo o lado, que não é nada fácil de ler, expecialmente quando temos sub Arrays com dimensão 10 ou mais, pois nós humanos perdemos a capacidade de associar eficazmente os indices com os dados.

Curiosidade: Está provado experimentalmente que o limite do humano médio é de 7 itens. Isto é relevante em questões relacionadas com usabilidade (número máximo de opções de um menu, por exemplo), assim como em outras aplicações.
alert( arrParameterRange[i][2] + " : " + arrParameterRange[i][3] );
Existem alguns programadores mais iluminados que tentam dar a volta ao problema, definindo "constantes" para usar para os indíces, mas novamente estamos a resolver o problema errado.
var RANGE_IDX_FORMAT = 0;
var RANGE_IDX_FROM = 1;
var RANGE_IDX_TO = 2;
...

alert( arrParameterRange[i][RANGE_IDX_FROM] + " : " + arrParameterRange[i][RANGE_IDX_TO] );
O problema que deviamos estar a resolver, era como estruturar correctamente os dados. E para isso temos objectos em Javascript, que servem a sua função, mas que também servem para dar nomes aos dados, nomeadamente às propriedades do objecto. Isto porque Objectos sem métodos são equivalentes a estruturas (dado que é tudo público, em Javascript).

Então vamos definir um objecto em Javascript para guardar os nossos dados:
var ParamRange = function( format, from, to )
{
this.format = format;
this.from = from;
this.to = to;
}
E com esta simples alteração, já conseguimos código legível e fácil de manter:
var arrParameterRange = new Array();
arrParameterRange[0] = new ParamRange( 'DumbFormat', 'DumbFrom_0', 'DumbTo_0' );
arrParameterRange[1] = new ParamRange( 'DumbFormat', 'DumbFrom_1', 'DumbTo_1' );
...

for( i=0; i < arrParameterRange.length ; i++ )
{
var paramRange = arrParameterRange[ i ];
...
alert( paramRange.format + " for range " + paramRange.from + " : " + paramRange.to );
...
}
Claro está que o melhor seria mesmo definir métodos para isolar as propriedades do objecto, e providenciar as funcionalidades necessárias, mas este tema já vai longo e ainda tinha muito que escrever se fosse por ai.

Conversões implícitas

Recentemente deparei-me com o seguinte código numa JSP (Java+Javascript):
if( <%= sTestParam.equals("true") %> )
{
...
}
À primeira vista não parece haver problema nenhum com este código, mas este código é tudo menos intuitivo por usar uma conversão implícita abusivamente e por assumir um pressuposto que não é garantido na especificação do Java.

Se olharmos com cuidado, temos um metodo que nos devolve um booleano (Java), que por sua vez devido ao método de embeber resultados numa JSP ( <%= ... %> ) é implicitamente convertido para String (Boolean.toString), que retorna "true" ou "false", e que finalmente é usado como código Javascript, que mais tarde o browser interpretará como um booleano novamente.

O problema principal é isto tudo ser muito obscuro, mas existe também um problema subjacente potencial, é que não existe garantia nenhuma que as futuras versões do Java continuem a converter um booleano implicitamente para "true" ou "false", isto pode mudar e depois este código deixa de funcionar, é pouco provável mas possível.
Como exemplo puramente ilustrativo, poderia passar a retornar "True", "_true_" ou "'True'" ou "Vero" par uma implementação italiana.
javascript:void(0)
A solução seguinte é bem mais intuitiva e explícita, e sem usar qualquer conversão implícita:
<% if( sTestParam.equals("true") ) { %>
...
<% } %>
Ter em atenção que conversões implícitas são úteis e até facilitam o trabalho, quando bem aplicadas. Mas como em tudo, quando se abusa das ferramentas e da sua função, acaba por se perder os benefícios e eventualmente criar problemas.

segunda-feira, 22 de Outubro de 2007

Sem comentários

Hoje deparei-me com o código Javascript seguinte:
function returnFalse()
{
return false;
}