|
 |
Como fazer um Calendário de Advento em JavaScript |
- Introdução
- Arquitectura da aplicação
- Preparação
- Criação da página HTML
- Cookies
- Inicialização
- Abrir e fechar janelas
- Calendário completo ?
|
1. Introdução
Nestas páginas mostra-se, passo a passo, como se pode fazer um Calendário de Advento em JavaScript. O objectivo principal é ilustrar a utilização de programação e web design para criar uma aplicação funcional; por isso, o Calendário pode parecer esteticamente rudimentar aos olhos dos leitores.
|
O que é o Calendário de Advento ? |
Um Calendário de Advento é um objecto associado ao Natal, típico dos países do Centro e do Norte da Europa.
Para os cristãos, o Advento é o período de quatro domingos que antecede o Natal no dia 25 de Dezembro.
Com um Calendário de Advento prepara-se a chegada do Natal, representando cada um dos dias de 1 a 24 de Dezembro por uma porta ou janela numerada. A cada dia que passa, abre-se a porta com o número correspondente, revelando uma surpresa. Consoante a cultura e religião das famílias e a idade dos destinatários - na maioria dos casos são crianças - esta pode ser um doce, uma pequena prenda, um desenho, uma história alusiva ao Advento,
etc.. |
|
Se num calendário material as surpresas são materiais, num calendário na Web as surpresas deverão ser imagens, imagens animadas, sons, textos, filmes ou aplicações. Neste caso, usou-se uma solução muito simples: uma única imagem dividida num mosaico de 24 partes, uma por cada dia. À medida que os dias passam, o mosaico completa-se:
 fig.1 - O mosaico com os dias 1 a 12 "abertos"
Se se procurar abrir uma "janela" de uma data posterior ao dia actual, aparece por instantes um boneco de neve, sugerindo paciência:
fig.2 - No dia 12, faltam dois dias para se poder abrir o dia 14
Quando se chega ao dia 24 e todas as "janelas" estão abertas a imagem completa-se, desaparecendo o reticulado do fundo. |
|
2. Arquitectura da aplicação
Conhecido o objectivo e a operação da aplicação, o primeiro passo é fazer a sua especificação funcional, definindo uma arquitectura que contemple os módulos internos, os interfaces entre módulos e o utilizador, etc. de modo a atingir os objectivos. Como esta a aplicação é elementar, bastou definir quatro regras:
- Uma página para o calendário e uma página para o resultado final.
- Dada a simplicidade do calendário, esta solução pareceu adequada; além disso, a página final é quase idêntica à página do calendário.
O calendário ocupa o centro da página, restando um rodapé para as ligações às páginas de apoio.
- Todos os links têm sempre efeito
- Esta é uma regra fundamental da web - por vezes descurada - que moldou o interface da aplicação: o utilizador deve receber sempre um eco dos links "clicados", qualquer que seja o estado da aplicação.
Se é óbvio que a porta se deve abrir ao clicar, revelando o elemento do mosaico, já não é óbvio o que se deve fazer quando se clica de novo a imagem com o link. Para evitar esta
ambiguidade, ao clicar numa porta aberta, "fecha-se" a porta. Além disso, quando se clica numa janela que não pode ainda ser aberta porque o dia correspondente ainda não chegou, surge o boneco de neve, permitindo deste modo dar uma resposta clara à acção do utilizador.
- O estado anterior do calendário é guardado
- O objectivo do Calendário de Advento é abrir uma janela por dia. Daí que seja importante armazenar o estado do calendário para que não seja necessário reiniciar o processo a cada novo dia.
Para este efeito, usaram-se cookies.
- As imagens serão reutilizadas
- Para acelerar o tempo de download e melhorar a transição entre as duas páginas da aplicação, as imagens da primeira página serão reutilizadas na segunda página.
Assim, quando o calendário está completo, passa-se para a segunda página suavemente pois a maioria dos conteúdos já está em cache.
|
Para ver o calendário completo, abra-o numa nova janela. |
3. Preparação
Antes de criar as páginas HTML, é necessário um trabalho de preparação prévia dos elementos visuais:
- Escolher uma fotografia.
A dimensão da fotografia - ou qualquer imagem - é definida de acordo com o tamanho do monitor. Optou-se por uma fotografia ligeiramente inferior a 800x600 pixels, pois esta é a resolução mínima mais comum na maioria dos monitores.
"Cortar" 24 janelas na fotografia. Optou-se por definir uma sequência aleatória para manter o efeito de mosaico. As imagens da janelas podem ter tamanhos diferentes, mas neste caso optou-se por uma solução mais simples com 24 quadrados iguais. Optou-se ainda por manter um espaçamento entre as "janelas" para manter um modelo reticulado, até que a fotografia se complete. Nessa altura o reticulado desaparece.
 fig.3 - Modelo da fotografia com as 24 janelas cortadas
- Guardar as margens da fotografia em imagens individuais para completar na versão completa, no caso de se pretender o efeito reticulado. Neste exemplo, há cinco imagens com a largura da fotografia e 28 imagens com a altura das "janelas".
- Desenhar 24 portas numeradas para ocultar os elementos do mosaico, usando um programa de edição de imagem.
As portas devem ter as mesmas dimensões das imagens que ocultam.
- Desenhar uma porta para os dias que não podem ainda ser abertos. Para este caso, usou-se o boneco de neve (ver fig. 2).
O código da página do calendário será explicado nas próximas secções, indicando-se à esquerda as amostras de código relevantes. |
|
4. Criação da página HTML
Após a preparação das imagens para o calendário, é necessário codificar a página HTML com o calendário. |
<tr> <td colspan="13"><img src="pix2.gif" width="750" height="12"></td> </tr> <tr> <td><img src="pix2.gif" width="15" height="110"></td> <td><A HREF="javascript:dia(12);"><img NAME="i12" src="12.gif" width="110" height="110" border="0"
alt="12"></A></td> <td><img src="pix2.gif" width="12" height="110"></td> <td><A HREF="javascript:dia(19);"><img NAME="i19" src="19.gif" width="110" height="110" border="0" alt="19"></A></td> <td><img src="pix2.gif" width="12"
height="110"></td> <td><A HREF="javascript:dia(3);"><img NAME="i3" src="3.gif" width="110" height="110" border="0" alt="3"></A></td> <td><img src="pix2.gif" width="12" height="110"></td> <td><A HREF="javascript:dia(8);"><img NAME="i8" src="8.gif"
width="110" height="110" border="0" alt="8"></A></td> <td><img src="pix2.gif" width="12" height="110"></td> <td><A HREF="javascript:dia(21);"><img NAME="i21" src="21.gif" width="110" height="110" border="0" alt="21"></A></td> <td><img
src="pix2.gif" width="12" height="110"></td> <td><A HREF="javascript:dia(2);"><img NAME="i2" src="2.gif" width="110" height="110" border="0" alt="2"></A></td> <td><img src="pix2.gif" width="15" height="110"></td> </tr>
|
A grelha inclui as imagens visíveis com as janelas e a imagem transparente <img src="pix2.gif"> que é usada como espaçador, variando a dimensão de acordo com as margens da fotografia.A apresentação da grelha do calendário baseia-se numa tabela de quatro linhas com seis janelas em cada linha.
O código à esquerda corresponde à primeira linha do calendário, com os dias 12-19- 3- 8-21- 2.
Cada célula <TD>...</TD> contém uma imagem, e as imagens da grelha têm uma designação "NAME=i..." associada, para que as funções JavaScript possam manipulá-las. A dimensão das imagens é igual para todas as janelas: quadrados de 110 pixels de lado. A ajuda "alt=.." serve para guiar os utilizadores depois da janela estar aberta.
As ligações (links) das janelas que se abrem estão associadas ao comando JavaScript HREF="javascript:dia(...);" que permitirá alterar as imagens quando o utilizador "clica" nas imagens.
Há um espaçador vertical de 12 pixels de largura entre cada duas janelas vizinhas na mesma linha. No início e no fim da linha, os espaçadores têm 15 pixels de largura. As linhas estão separadas por um espaçador de 750 pixels de largura e 12 pixels de altura (cf. a primeira linha do código).
O calendário é rematado com links para
- esta página
- a explicação em inglês e
- uma comando para inicalizar o calendário, fechando todas as janelas.
|
|
5. Cookies
Todo o calendário se apoia no conceito dos cookies. Um cookie serve para armazenar ao estado do calendário no computador do utilizador, de modo a que a cada nova visita este encontre o calendário tal como o deixou. Se mudar de computador ou de browser, então o calendário surgirá no estado inicial com todas as janelas fechadas.
Embora seja possível usar o calendário num browser que não aceite cookies, a experiência é menos agradável. |
document.cookie= "advent=000000000000000000000000;";
|
Um cookie tem duas partes: o nome e uma string de caracteres. É ainda recomendada uma terceira parte que contém o prazo de validade do cookie. Neste caso, o nome é advent= e a string é um conjunto de 24 algarismos, um por cada janela:
- "0" para uma janela fechada e
- "1" para uma janela aberta
Se o utilizador não aceitar cookies, o estado das janelas não é armazenado e é necessário recomeçar o processo a cada novo dia. |
hoje = new Date(), janelas = new Array(25), isCookie;
function init() { var i,c=0;
|
6. Inicialização
O programa usa três variáveis globais:
- a data
hoje ,
- um array com o estado de cada uma das janelas e
- uma flag que assinala se os cookies são aceites pelo utilizador.
|
if(document.cookie.length>0) isCookie=true; else { document.cookie="advent=000000000000000000000000;"; if(document.cookie.length>0) isCookie=true; else isCookie=false; }
|
O primeiro passo é verificar se o cookie para estão activos. Se estiverem, isCookie=true . Caso contrário, inicializa-se o cookie. Se ainda assim, os cookies não ficarem activos, significa que o browser está não aceita cookies. |
ckOffset=document.cookie.indexOf("advent=")+6;
|
O passo seguinte é calcular o índice da string onde começa a sequência de 24 flags. Este valor varia entre browsers. |
if(isCookie) { for(i=1;i<25;i++) { janelas[i]=document.cookie.charAt(ckOffset+i); if(janelas[i]=='1') { len=document.images['i'+i].src.length; document.images['i'+i].src = document.images['i'+i].src.substr(0,len-3)+"jpg"; } } teste(); } return; }
|
O remanescente da inicialização só se aplica no caso dos cookies estarem activos.
Nesse caso, a string dos cookies é percorrida e copiada, posição a posição, para o array janelas[] . Note-se que o array começa na posição 0 (onde se guarda o sinal '=' ), enquanto as janelas do calendário começam na posição 1.
Se uma das posições da janela ficar a '1', a imagem correspondente com o número do dia é trocada pela fotografia.
Nota importante: nesta passagem, é necessário que as imagens das janelas fechadas e as das janelas abertas tenham exactamente o mesmo nome, com excepção da extensão. Para este exemplo, usou-se JPG para as fotografias e GIF para as janelas fechadas.
 |
 |
3.gif |
3.jpg |
fig.4 - A janela fechada e a janela aberta correspondente.
Uma vez reposta as janelas no estado em que o utilizador as tinha deixado na última visita, faz-se um teste para determinar se o calendário está completo. |
function reiniciar() { if (document.cookie.length>0) document.cookie = "advent=000000000000000000000000;"; document.location = "1.html"; return; } |
O utilizador pode reinicializar o calendário usando a função reiniciar() . Trata-se, simplesmente, de repor o cookie a 0000 e recarregar a página no browser com o comando document.location="1.html" . |
|
7. Abrir e fechar janelas
A abertura e fecho das janelas são o coração da aplicação e a cada nova acção sobre a janela, o cookie é actualizado.
As operações realizam-se através da função dia(d) , que toma o dia da janela que o utilizador indicou. |
function dia(d) { var str="advent=", len, i, ano, expira; if(d <= hoje.getDate()) { len=document.images['i'+d].src.length; if(janelas[d]=='1') { document.images['i'+d].src = document.images['i'+d].src.substr(0,len-3)+"gif"; janelas[d]='0'; } else { document.images['i'+d].src =
document.images['i'+d].src.substr(0,len-3)+"jpg"; janelas[d]='1'; }
|
A variável str="advent=" guarda a string do cookie e é logo inicializada. A variável expira guardará a data de expiração do cookie.
O primeira teste verifica se o dia da janela seleccionada é anterior ou igual ao dia do mês nesse momento. Em caso afirmativo, troca a imagem da janela, isto é:
- se
janelas[d]=='1' , a fotografia (imagem .JPG) é escondida e "fecha-se" a janela: janelas[d]=='0' , mostrando o número (imagem .GIF).
- se
janelas[d]=='0' , a imagem com o número (.GIF) é escondida e "abre-se" a janela: janelas[d]=='1' , mostrando a fotografia (.JPG).
|
for(i=1;i<25;i++) str += janelas[i];
ano=hoje.getYear(); if(ano<1000) ano+=1900; // patch NN4 expira=new Date(ano+1, hoje.getMonth(), hoje.getDate(), hoje.getHours(), hoje.getMinutes(), hoje.getSeconds()); str += ";expires="+expira.toGMTString(); document.cookie = str;
setTimeout("teste()",5000); }
|
De seguida actualiza-se o cookie, completando a variável str com cada uma das posições do array janelas[] .
Segundo as boas práticas, a cada cookie deve estar associada uma data de expiração. Neste caso, escolheu-se um ano e é necessário calcular essa data.
Algumas versões do Netscape Navigator 4 tinham um bug na função .getYear() e devolviam apenas dois algarismos. Pode ultrapassar-se este problema com o patch indicado, embora este problema seja hoje residual.
De seguida, a string com a data de expiração é acrescentada a str e o cookie é escrito.
Finalmente, após aguardar 5 segundos, é chamado o teste para detectar se o calendário está completo. |
else { document.images['i'+d].src='janela_fechada.gif'; setTimeout("espera("+d+")",1000); } return; }
function espera(d) { document.images['i'+d].src=d+'.gif'; return; }
|
Se o dia escolhido for superior ao dia do mês no momento, a imagem com o número é trocada pela imagem 'janela_fechada.gif' e, após 1 segundo, chama-se a função espera, indicando a janela seleccionada.
A função espera(d) limita-se a repor a imagem com o número do dia na janela seleccionada. |
|
8. Calendário completo ?
Sempre que uma janela muda de estado, verifica-se se as 24 janelas estão abertas e, se tal acontecer, chamar a atenção do utilizador. |
function teste() { // testar as 24 condições var i, completo=true;
if(hoje.getDate()>=24) { for(i=1;i<25;i++) if(janelas[i]!='1') { completo=false; break; } if(completo) document.location="25.html"; } return; }
|
A função teste() verifica se o dia do mês é igual ou superior a 24 e se as 24 janelas estão abertas. Note-se que o encadeamento de condições corresponde à conjunção lógica.
Esta implementação não permite terminar a aplicação depois de 1 de Janeiro, pois a primeira condição falha. Se se pretender que o calendário fique sempre aberto, há que eliminar a primeira condição do teste. Caso contrário, o calendário só fica "completo" a partir do dia 24 de cada mês.
Há múltiplas formas de verificar se as 24 janelas estão abertas. A mais simples é testar as 24 condições em sequência. |
function teste() { // somar o índice das janelas abertas var i,c=0; if(hoje.getDate()>=24) { for(i=1;i<25;i++) if(janelas[i]=='1') c+=i; if((c==300)) document.location="25.html"; } return; } |
Porém, ao escrever esta aplicação, lembrei-me de uma história sobre o matemático Gauss e as progressões aritméticas e escolhi outra variante: somar o índice de todas as janelas abertas e verificar o total. |
Sobre este Presépio |
O Presépio da imagem é feito com figuras de barro populares, compradas em feiras portuguesas.
Este é um dos tipos de Presépio mais comuns nas regiões rurais de Portugal: um tapete de musgo macio que se recolhe nas florestas, sobre o qual se espalham as figuras do Natal: Jesus, Maria, José, o burro e a vaca, os Magos e os pastores. A tradição popular acrescenta muitas figuras do imaginário português: castelos, igrejas, pontes, casas caiadas, etc.. |
|
Nesta aplicação, o "prémio" que o utilizador recebe ao completar o calendário, é vê-lo numa imagem única sem reticulado.
Para tal, criou-se uma segunda página, com o nome 25.html , com um layout rigorosamente igual ao da primeira, onde os espaçadores transparentes são substituídos pelas imagens que completam o mosaico:
 fig.4 - O mosaico completo
|
- Introdução
- Arquitectura da aplicação
- Preparação
- Criação da página HTML
- Cookies
- Inicialização
- Abrir e fechar janelas
- Calendário completo ?
|
Se tiver dúvidas, dificuldades ou comentários a sobre esta aplicação, contacte-me. |
©2004-05 João Gomes Mota |
|