Hem vist en la pràctica anterior com podem determinar dades referents a la data i hora del sistema i com les visualitzem en el document. Ara es tracta de veure una aplicació una mica més complexa: La creació d'un calendari.

Gairebé tota la pràctica girarà a l'entorn d'una funció encarregada de crear el calendari. Aquesta serà la funció més complexa i a la que dedicarem més temps a explicar.

Un cop tindrem feta la funció de creació del calendari, la complementarem amb exemples d'utilització i altres funcionalitats addicionals per fer-la més atractiva i interactiva.

  Conceptes JavaScript en aquest capítol.
Objecte Date : Proporciona mètodes per treballar amb dates.
Mètode getFullYear() : Retorna el número de l'any que conté l'objecte Date.
Mètode getMonth() : Retorna el número del mes que conté l'objecte Date.
Mètode getDay() : Retorna el número del dia de la setmana que conté l'objecte Date.
Mètode getDate() : Retorna el dia que conté l'objecte Date.
Mètode setDate() : Estableix el número del dia que conté l'objecte Date.
Propietat opener : Propietat de l'objecte Window que conté el nom de la finestra mare.
 
1.-
Descripció del codi. Inicialitzacions.
Començarem dissenyant el codi per crear un calendari mitjançant una funció. S'ha elegit fer-ho en forma de funció ja que així serà més fàcilment transportable.

La funció de nom calendari haurà de rebre dos paràmetres:, l'any i el número de mes (0 .. 12) que desitgem visualitzar. La seva sintaxi serà la següent:

                calendari(pAny, pMes);

Si els paràmetres pAny i/o pMes valen 0, la funció prendrà per defecte l'any i el més del sistema. Un cop invocada la funció, aquesta retorna una cadena que conté tot el codi HTML de la taula que formarà el calendari.

De forma genèrica, la funció està dividida en dues parts principals: la inicialització i la creació de la taula dins una variable de cadena de nom cal. Cada part es pot subdividir en:

Inicialitzacions prèvies:
  Inicialitzar Arrays i variables.
  Obtenir la data actual del sistema.
  Si els paràmetres que se li han passat són diferents de zero, substituirà la data d'avui per la dels paràmetres.
  Determina si es tracta d'un any de traspàs.
  Determinar el dia de la setmana del primer dia del mes.

Creació de la taula calendari:
  Representar la primera fila de la capçalera on hi ha el nom del mes i l'any.
  Representar la fila on hi ha els noms dels dies de la setmana.
  Representar les primeres cel·les en blanc, si n'hi ha.
  Representar les cel·les de les files on hi ha els números dels dies.
  Representar les últimes cel·les en blanc, si n'hi ha.


El codi i la descripció de la funció és extensa i per tant es dividirà en blocs funcionals, al final de la pràctica, s'ha d'introduir tot el codi en conjunt, per treballar dins una pàgina HTML d'aplicació. Per facilitar la lectura del codi, les línies de la funció calendari() estan numerades. En l'aplicació pràctica, els números de línia no s'han de posar.

La funció és la següent:

1 function calendari(pAny, pMes)
2 {
3   var nomDiaSem = new Array("Dl", "Dm", "Dc", "Dj", "Dv", "Ds", "Dg");
4   var nomMes = new Array("Gener", "Febrer", "Març", "Abril", "Maig", "Juny", "Juliol",
                                  "Agost", "Setembre", "Octubre", "Novembre", "Desembre");
5   var diesMes = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
6   var ampleCol = 30;

 
En aquestes primeres línies tenim la capçalera de la funció calendari. Se li passen dos paràmetres, pAny i pMes. Les línies 3, 4 i 6 són variables amb continguts que es poden modificar per canviar les abreviacions, l'idioma, l'ample de columna, etc.

La línia 5 conté els dies de cada mes començant pel Gener, cal observar que en la dada que correspon al més de Febrer s'hi ha posat 28 dies. Si l'any és de traspàs, aquest valor s'haurà de canviar a 29. Més endavant es fa aquesta comprovació i es modifica si s'escau.

La variable ampleCol conte l'amplada de cada cel·la de la taula del calendari en píxels, si es desitja fer el calendari més ample es pot fer canviant aquest valor.

7     var avui = new Date();
8     if (pAny!=0) var any=pAny;
9     else var any=avui.getFullYear();
10   if (pMes!=0) var mes=pMes-1;
11   else var mes=avui.getMonth();
12   var dia=avui.getDate();
13   var dataCal = new Date(any, mes, dia);
14   if ( ((any%4==0) && (any%100 != 0)) || (any%400==0) ) diesMes[1] = 29;
15   var numDies = diesMes[mes];

 
Cada cop que s'utilitza una variable per primera vegada ha d'anar precedida per la corresponent declaració de variable var.

Mitjançant Date(), a la línia 7, s'obté la data del sistema i s'assigna a la variable tipus Date de nom avui. Aquesta data s'utilitzarà per assignar-la a les variables any, mes i dia, a no ser que s'hagi posat en els paràmetres pAny i/o pMes un valor diferent de 0.

Les línies 8 i 9 s'encarreguen d'assignar a la variable any, l'any que indica el sistema, en cas que pAny valgui 0 o en cas contrari, l'any indicat per pAny. Les línies 10 i 11 fan el mateix per la variable mes. La variable dia sempre contindrà el valor de la data actual (línia 12).

Cal notar que per determinar el valor del dia, mes i any actuals, s'utilitzen els mètodes getDate(), getMonth() i getFullYear() de Date. A la línia 10, es resta una unitat de pMes per que el paràmetre pMes enumera els mesos entre 1 i 12, en canvi la variable mes els enumera entre 0 i 11.

A la línia 13 es crea un nou objecte tipus Date, però en aquest cas, ja no és amb la data per defecte del sistema, sinó amb la data que nosaltres li passem com a paràmetres. Així doncs, dataCal contindrà la data que es mostrarà en el calendari (ens interessa per tenir el mes i any que s'ha de mostrar). Cal recordar que avui sempre conté la data actual del sistema i no té per que ser la mateixa que dataCal. Si totes dues són diferents serà perquè algun dels paràmetres passats a la funció és diferent de 0.

La línia 14 verifica si l'any és de traspàs o no, per això verifiquem si any és un múltiple de 4 i no és múltiple de 100, o bé, any és múltiple de 400. En cas que la condició sigui certa, voldrà dir que és un any de traspàs i per tant, el nombre de dies del mes de Febrer haurà de valer 29. Amb diesMes[1], fem referència al segon element (Febrer) de l'array diesMes i el modifiquem, si s'escau.

Finalment, assignem a la variable numDies el nombre de dies que tindrà el mes a representar, obtenint el valor de l'Array diesMes.

16   var primerDia = dataCal; //Determinem el dia de la setmana del primer dia del mes
17   primerDia.setDate(1);
18   var diaSem1 = (primerDia.getDay()==0)?7:primerDia.getDay(); //1->dilluns,
                                                                                              7->diumenge

 
Una altra dada que és necessari conèixer és el dia de la setmana en què comença el mes, és a dir, quin dia de la setmana és el dia 1 del més a representar.

Per això ens pot ser molt útil el mètode getDay() de Date. Aquest mètode retorna un valor enter entre 0 i 6, on 0 correspon al Diumenge i 6 al Dissabte. Noteu que l'ordenació dels dies és la pròpia dels països anglosaxons.

Per determinar aquest dia, assignem, en la línia 16, a primerDia el valor de dataCal, es a dir, ara primerDia també serà un objecte tipus Date i conté la mateixa data que dataCal. A la línia 17, utilitzem el mètode setDate(), aquest mètode assigna a un objecte tipus Date el valor numèric corresponent al dia passat com a paràmetre. Per tant ara primerDia conté la data del primer dia del mes sense haver modificat el valor del mes i de l'any.

Finalment, a la línia 18, utilitzant el mètode getDay(), assignem el número del dia de la setmana a diaSem1. Com que nosaltres volem tenir una referència dels dies de la setmana segons els nostres usos i costums i no, tal com ho fan en els països anglosaxons, hem d'efectuar una conversió amb l'ajut de l'operador ternari condicional ? : .

Aquest operador ternari ? : ja s'ha estudiat al mòdul 2, pràctica 4 i en el mòdul 3 pràctica 3. En aquest cas, té la funció de seleccionar entre dos valors en funció de la condició. Si el valor retornat per getDay() és 0, s'assignarà un 7 a diaSem1. En canvi, per a qualsevol altre valor retornat per getDay(), serà assignat directament a diaSem1. Amb això tindrem que diaSem1, valdrà un valor entre 1 i 7 on 1 correspondrà a Dilluns i 7 a Diumenge.

Fins aquí ja s'han inicialitzat tots els valors previs a la creació del calendari. I en resum, sabem que les variables més importants que necessitarem a partir d'ara són:

avui Objecte Date amb la data actual del sistema (pot coincidir, o no amb dataCal).
dataCal Objecte Date amb la data que es vol representar al calendari.
dia Valor numèric corresponent al dia d'avui.
mes Valor numèric corresponent al mes a representar. (0 - Gener, 11 - Desembre)
any Valor numèric corresponent a l'any a representar.
diaSem1 Valor numèric del dia de la setmana del dia 1 (1 - Dilluns, 7 - Diumenge).
ampleCol Amplada en píxels de cada columna del calendari.
nomMes Array amb els noms dels mesos.
nomDiaSem Array amb els noms dels dies de la setmana.

  2.- Descripció del codi. Creació de la taula - calendari.
Les línies de codi que venen a partir d'ara s'encarreguen de crear la taula - calendari. Únicament s'indica el codi imprescindible per a la correcta visualització, més endavant ja s'afegiran altres elements estètics i de funcionalitats més específiques.

Gairebé tota la representació es basa en l'escriptura de codi HTML, formant l'estructura de la taula. Tota aquesta estructura de la taula s'introduirà seqüencialment en una variable de tipus cadena de nom cal. Al final, tot el contingut d'aquesta variable serà retornat per la funció.

Les cel·les que hagin d'estar en blanc, s'omplen amb espais  . Totes les línies de codi són per crear una estructura de taula similar a la següent, on, evidentment, els valors canviaran segons quin sigui el mes a representar.

                                                          

19   var cal="<table border='1'>\n";
20   cal += "<tr><th colspan='7'>" + nomMes[mes] + " " + any
                                                                        + "</th></tr>\n";
 
La línia 19 inicialitza la taula. Declarem inicialment la variable cal i li assignem la directiva <table...>.
La línia 20 s'encarrega d'escriure la capçalera de la taula, resultant un format similar al següent:

                                        

Noteu com en la línia 20 s'utilitza l'operador +=, en aquesta aplicació, aquest operador assigna a la variable de l'esquerra la concatenació dels valors que hi ha a la dreta i a l'esquerra. Per exemple: Si 'vble' és una variable que val "cadena1" i fem vble += " cadena2", la variable 'vble' valdrà "cadena1 cadena2", això és equivalent a fer: vble = vble + "cadena2".

21   cal += "<tr>";
22   for(var i=0; i<7; i++) cal += "<th width=' " + ampleCol + " '>" +
                                                                      nomDiaSem[i] + "</th>";
23   cal += "</tr>\n";

 
Aquest bloc de codi recorre l'array nomDiaSem i crea una fila de la taula amb 7 cel·les, una per a cada dia. En cada cel·la s'introdueix el nom del dia de la setmana llegit de l'array nomDiaSem. En aquesta secció també s'especifica l'amplada de la cel·la segons el valor d' ampleCol, per tant, s'està introduint l'ample de les columnes de tota la taula. El tros de taula creat serà el següent:

                                              
24   var columna = 1;
25   cal += "<tr>";
26   for(i=1; i<diaSem1; i++) {
27      cal += "<td>&nbsp;</td>";
28      ++ columna;
29   }
 
Segons quin sigui el mes a representar, pot passar que les primeres cel·les estiguin en blanc. El blucle for, entre les línies 26 i 29, s'encarrega d'escriure cel·les en blanc fins a arribar al valor de diaSem1, recordem que aquesta variable té el valor numèric del nom del dia corresponent al dia 1r del mes.

                                                                    

A la línia 24 s'inicialitza una variable de nom columna, aquesta variable s'utilitzarà en aquest bloc i els següents per saber en quina columna s'està escrivint de les 7 que té el calendari. Això servirà per saber quan s'ha de fer un canvi de fila.

30   for(i=1; i<=numDies; i++) {
31      cal += "<td><center>" + i + "</center></td>";
32      if(++columna > 7) {
33          cal += "</tr>\n";
34          if (i < numDies) cal += "<tr>";
35          columna = 1;
36      }
37   }
 
Aquestes línies són les responsables d'escriure el cos principal del calendari. Per això es crea un bucle for on la variable i canviarà des de 1 fins al màxim nombre de dies del calendari (numDies).

A la línia 31, per cada valor d'i, s'afegeix a cal una cel·la que contindrà el numero de dia corresponent que s'obté de la variable i.

A més, a cada cel·la, també s'incrementa el valor de la variable columna, si aquesta valgués 7, voldria dir que ja s'ha completat una fila i s'ha de començar la següent, per tant, si columna val 7, s'afegeix a cal un </tr> per tancar la fila. En el cas que no s'hagi acabat d'escriure el calendari també s'afegeix un <tr> per obrir la nova fila i es torna a posar el valor de columna a 1, tal com es pot veure a les línies 33, 34 i 35.

Al sortir del bucle for, ja s'hauran afegit a cal totes les cel·les que tenen un contingut numèric corresponent a un dia del mes, de forma similar a la següent imatge:

                              
38   if (columna!=1) {
39      for(i=0; i <= 7-columna; ++i) cal += "<td>&nbsp;</td>";
40      cal += "</tr>\n";
41   }
42   cal += "</table>";
43
44   return cal;
45 }
 
Finalment, en el cas que el valor de columna sigui diferent de 1, voldrà dir que encara no s'ha acabat d'omplir amb cel·les l'última fila. Per això, s'inicia un altre bucle for que introdueix les cel·les en blanc necessàries per completar la fila. Si columna valgués 1, (línia 38) no faria falta acabar de completar la fila, ja que aquesta hauria acabat amb un valor numèric corresponent a un número de dia.

                                                                          

La línia 42 tancarà la taula. Amb tot això ja estarà creat el calendari i també haurà acabat la funció. Ara solament queda retornar el valor de la variable cal, que conté tota la informació HTML de la taula creada representant el calendari desitjat, tal com es fa a la línia 44.

Per provar el seu funcionament s'ha de crear una pàgina web que incorpori aquesta funció i l'executi. El següent exemple ens mostra el seu funcionament. En aquest cas s'invoca a la funció amb els paràmetres 0 per l'any i 0 pel mes, per tant, ens mostrarà el calendari del mes i any que indiqui el rellotge del sistema. Si volem un altre mes i/o any s'haurà de canviar el valor dels paràmetres de la invocació de la funció dins el bloc <body>.

<html>
<head>
<title>Calendari</title>
<script language="JavaScript">
    ...codi corresponent a la funció calendari()... descrit des de la línia 1 fins la 45
</script>
</head>
<body>
<script language="JavaScript">
    document.writeln(calendari(0,0));
</script>
</body>
</html>

 

3.-
Afegim colors i altres funcionalitats.
El que ve a continuació són variacions sobre el codi anterior de la funció calendari(). No es rescriurà tot el codi de nou, solament s'indicaran les línies que es modifiquen, s'esborren o s'afegeixen per introduir les noves funcionalitats.

Si s'afegeix alguna línia al codi de la funció original, aquesta línia portarà com a número el mateix de l'anterior seguit d'una lletra ( p. e. 32, 32a, 32b, ... ).

Es tracta d'afegir colors a les diferents cel·les del calendari, a més es desitja que la cel·la que mostra el dia, mes i any actuals quedi marcada amb un altre color. Per fer-ho s'afegeix la directiva d'HTML bgcolor als <th> i <td> corresponents per indicar el color de fons desitjat. Així es fa a les línies 20, 22, 27, 31 i 39. Per fer que la cel·la que indica el dia actual quedi d'un altre color es fa mitjançant un condicional if. Aquest condicional queda indicat en les noves línies 31a i 31b. Si el dia, any i hora coincideix amb el dia, any i hora actuals, la cel·la quedarà marcada en vermell, en cas contrari en groc.

A continuació podem veure les modificacions i afegits a fer (per provar-ho es pot fer amb la mateixa plana HTML definida al final de l'apartat anterior):

Modificar:

20 cal += "<tr><th colspan='7' bgcolor='#ffff00'>" + nomMes[mes]
                                                              + " " + any + "</th></tr>";

22 for(var i=0; i<7; i++) cal += "<th width=' " + ampleCol +
                                         " '
bgcolor='#ffff00'>" + nomDiaSem[i] + "</th>";
27 cal += "<td bgcolor='#ffffc0'>&nbsp;</td>";
39 for(i=0; i <= 7-columna; ++i) cal += "<td
                                                                 
bgcolor='#ffffc0'>&nbsp;</td>";


Modificar i afegir:

31 cal += "<td bgcolor=";
31a if (i==dia && avui.getMonth()==dataCal.getMonth() &&
          avui.getFullYear()==dataCal.getFullYear()) cal += " '#ff8080' ";
31b else cal += " '#ffffc0' ";
31c cal += "><center>" + i + "</center></td>";

 

4.-
El calendari interactiu.
Des del principi, ja s'ha dit que la funció calendari() que hem creat admet dos paràmetres, l'any i el mes. Fins ara no els hem fet anar i ens hem limitat a posar-hi 0 per l'any i 0 pel dia. Per tant, sempre ens apareix el calendari del mes actual.

En aquest apartat, es tracta de fer que el nostre calendari sigui interactiu, és a dir, des d'una finestra mare, hem de poder elegir el mes i/o l'any desitjat i fer que es mostri el corresponent calendari en una finestra filla.

Encara més, també farem que cada dia del calendari sigui un link que al fer-li clic a sobre ens indiqui, en un quadre de text de la finestra mare, la data seleccionada.

Com en l'apartat anterior, tampoc rescriurem el codi de la funció calendari(), simplement afegirem i/o modificarem aquelles línies que facin falta.

La funció esta preparada per rebre paràmetres, però, no n'esta per fer que cada dia sigui un 'link'. Per facilitar la tasca de posteriors modificacions, crearem una nova funció que s'encarregarà d'escriure el link en el lloc on vagi el dia.

Aquesta nova funció es diu escriuDia(pAny, pMes, pDia) i se li han de passar els paràmetres corresponents al dia, mes i any que s'està escrivint a sobre el calendari. Aquesta funció s'encarregarà d'escriure el dia i a més deixarà preparat el 'link' per poder retornar la data elegida a la finestra mare.

Per referenciar la finestra mare des d'una finestra filla s'utilitza la propietat opener de l'objecte Window. Aquesta propietat conté el nom de la finestra mare.

La nova funció serà la següent:

function escriuDia(dia,mes,any)
{
    ++mes;
    return "<a href=' ' onClick='opener.document.form1.data.value=\"" +
                           dia +"-"+ mes +"-"+ any +"\"; return false;'>" + dia + "</a>";
}

 
En aquesta nova funció, s'ha d'incrementar el valor del mes, ja que internament a la funció calendari(), la variable mes es troba dins el rang entre 0 i 11. La funció retorna una cadena amb el codi HTML necessari per fer el 'link' desitjat, conjuntament amb el número de dia.

Si es desitgés donar una nova funcionalitat als links dels dies que formen el calendari, solament caldria modificar el contingut de la funció escriuDia().

Per incorporar la nova funció escriuDia() dins la funció calendari(), modificarem i/o afegirem les línies següents a la funció calendari() :

Modificar:

31c cal += "><center>";


Afegir:

31d cal += escriuDia(i,mes,any);
31e cal += "</center></td>";

 
Per provar el funcionament s'ha de crear una pàgina HTML que permeti enviar i rebre paràmetres com la que es veu a continuació. Dins el bloc <head>, a part de les funcions calendari() i escriuDia(), també hi ha la funció popup().

La funció popup() s'encarrega de crear la finestra que contindrà el calendari, a la pràctica 4 del mòdul 3 s'explica com obrir finestres filles des d'una finestra mare.

Dins el bloc <body> es crea un formulari, de nom form1, amb tres camps de text i dos botons. En els dos primers camps de text s'introdueix el mes i any a enviar a la funció. El tercer camp de text rebrà el valor al que hem fet clic en el 'link' del calendari.

Quan fem clic sobre el botó "Crear cal.", s'invoca la funció popup() mitjançant el seu controlador d'event onClick, passant-li a la vegada el valor del mes i any. La funció popup() és la que crearà la finestra filla i a la vegada invocarà la funció calendari() passant-li els mateixos paràmetres.

El codi HTML és el següent:

<html>
<head>
<title>Calendari</title>
<script language="JavaScript">

  var finestra;

    .... afegir la funció calendari() .....

    .... afegir la funció escriuDia() .....

function popup(pAny, pMes)
{
  window.focus();
  opt = 'resizable=1,scrollbars=1,width=300,height=225,left=5,top=60';
  finestra=window.open('', 'popup', opt);
  with (finestra.document) {
    writeln('<html>\n<head>\n<title>Calendari</title>\n</head>');
    writeln('<body bgcolor="#c0c0c0">');
    writeln(calendari(pAny, pMes));
    writeln('</body>\n</html>');
    close();
  }
  finestra.focus();
}
</script>
</head>

<body bgcolor="#99ffcc">
<form name="form1">
  <table border="0" width="250" align="center">
  <tr>
    <td>Mes (1..12, 0 actual):</td>
    <td align="center"><input type="text" name="mes" size="5" value="0"></td>
  </tr>
  <tr>
    <td>Any (0 actual):</td>
    <td align="center"><input type="text" name="any" size="5" value="0"></td>
  </tr>
  <tr>
    <td colspan="2" align="center">
      <input type="button" value="Crear cal." onClick="popup(any.value,mes.value);">
    </td>
  </tr>
  <tr>
    <td>Data elegida:</td>
    <td><input type="text" name="data" size="11" value=""></td>
  </tr>
  </table>
</form>
</body>
</html>