Quan instal·lem un programa, fem una còpia d'arxius o carreguem una pàgina des d'Internet, una barra de progrés ens ajuda a saber com va la feina.
Barra ajustable
A les pàgines web, aquesta pot ser una manera d'indicar que la pàgina no està inactiva, que s'ha engegat un procés i encara no s'ha acabat. També podríem usar un missatge del tipus "Espereu un moment ...", però una barra informa més.

Hi ha moltes maneres de fer barres de progrés, però aquí només en veurem una. Amb tres exemples, ens aproximarem a un estil de programació que genera codi curt i net. La barra és l'excusa. El que compta és l'algoritme i el llenguatge n'és una eina.

  Conceptes JavaScript en aquest capítol
Mètodes setTimeout() i clearTimeout() : creen i eliminen un temporitzador.
Mètodes setIntervalt() i clearInterval() : creen i eliminen un interval de temps.
Funció eval() : avalua una expressió o bloc de codi.
Operador -= : resta el valor de la dreta a la variable de l'esquerra i li assigna el resultat.

  1.- Barra gràfica
Per començar, farem una barra gràfica amb els elements imprescindibles i poc codi. Donem per suposat que les imatges que la formen són les úniques o, si més no, les primeres que hi ha a la pàgina. No és la millor manera, perquè poques vegades ho tindrem tan fàcil, però ens servirà per entendre'n el funcionament i poder ampliar-la més endavant.

Es tracta de dibuixar una tira d'imatges iguals i, després, substituir-les progressivament per unes altres. Al cos (<body>) de la pàgina farem un bucle que dibuixi les imatges:

  for( i=0; i<40; i++ ) {
    document.write( '<img src="images/punt.gif" width="5" height="20">' )
  }


Utilitzem com a base un arxiu .gif de 1x1 píxels, "estirat" fins a 5x20. Posem 40 imatges iguals d'aquesta mida amb el mètode write, perquè no quedi separació entre elles. Tanquem javascript i fem un parell de vincles, l'un per engegar la barra i l'altre per reiniciar.

A la capçalera, declarem una variable (tm) pel temporitzador. També declarem el número d'imatge (nImg), que ens farà d'índex, i el posem a zero. A continuació fem la funció pinta(), que anirem activant mentre quedi barra per pintar.

Temporitzadors i recursivitat. Dins de la funció, primer "netegem" el temporitzador amb el mètode clearTimeout() i després comparem el número d'imatge amb la longitud de la barra. Si encara és menor, canviem la imatge actual per una de nova, incrementem l'índex i fem un temporitzador que torni a engegar la funció al cap d'un temps: tm = setTimeout( "pinta()", 50 ).

El mètode setTimeout() avalua una expressió o crida una funció al cap del temps especificat al segon paràmetre, en mil·lisegons. La funció ha d'anar entre cometes, si no volem que s'executi immediatament. Només ho fa una vegada.

<script language="javascript">
var tm
var nImg = 0
function pinta() {
  clearTimeout( tm )
  if ( nImg<40 ){
    document.images[id].src = "images/barra.gif"
    nImg++
    tm = setTimeout( "pinta()", 50 )
  }
}
</script>
</head>

<body ...>
<h2>Barra de progrés</h2>
<script language="javascript">

for( i=0; i<40; i++ ) {
  document.write( '<img src="images/punt.gif" width="5" height="20"> ' )
}
</script>

<h3><a href="javascript:
pinta()">Començar</a> &nbsp; &nbsp;
<a href="javascript:
location.reload()">Reiniciar</a></h3>
...
 
2.- Millorar la funcionalitat
L'exemple anterior, tot i que aquí funciona, està confeccionat, perquè es basa en l'Array images. Si la pàgina conté imatges abans de la barra, no funcionaria.

Tenim l'excusa per provar noves maneres. Farem la mateixa barra amb lleugeres variacions: 25 quadres en lloc de 40 i els colors invertits. També canviarem el temporitzador: en lloc de setTimeout() utilitzarem setInterval().

L'objectiu és fer una barra de progrés que puguem posar en qualsevol lloc. Si baixem les imatges des d'una funció situada a la capçalera: Per cridar-la des d'un lloc del cos (<body>) cal escriure <script language="javascript">base()</script>. També farem diferent el vincle per activar la barra, amb un enllaç fals i l'event onClick="return inicia()".

Deixem el cos i anem a les funcions. La primera, base(), pinta l'espai de la barra. Abans era al <body> (sense "function") i ara és al <head>. La novetat més important és que, ara, les imatges tenen un nom, format per la paraula "quadre" i el número d'índex del bucle. Un cop hem passat per aquí, les imatges de la barra s'anomenen "quadre0", "quadre1", ... Amb això, ja ens és igual si la pàgina conté més imatges abans o després.

La funció inicia() que hem associat a l'event onClick, ha de retornar fals, això ja ho sabem. Abans, però, crea un temporitzador: tm = setInterval( 'pinta()', 50 ), que crida la funció "pinta()" cada 50 mil·lisegons (i continuaria indefinidament si no la parem).

La funció pinta() també està canviada. Primer crea el nom de les imatges amb la paraula "quadre" i l'índex (nImg), de forma que ens hi podem referir amb "document." més el nom:

  eval( 'document.' + nom + '.src = "images/punt.gif"' )

La funció interna eval(), la "perla" de JavaScript, permet avaluar expressions formades per constants, variables, literals o tots junts, i les executa. En aquest cas, cada vegada el nom de la imatge és diferent (quadre0, quadre1, ...) i per això no el podem posar com a part d'una instrucció. La funció eval() ho soluciona. A cada pas donarà com a resultat:

  document.quadre0.src = "images/punt.gif",
  document.quadre1.src = "images/punt.gif", ...


Finalment, haurem d'incrementar el comptador i, si ha arribat a 25, "netejar" el temporitzador perquè no continuï actuant: if ( nImg==25 ) clearInterval( tm ). Com passa sovint en JavaScript, el codi és més fàcil d'entendre que l'explicació:

<script language="javascript">
var tm
var nImg = 0
function base() {
  for( i=0; i<25; i++ ) {
    document.writeln( '<img src="images/barra.gif" width="8"' )
    document.write( 'height="16" name="quadre' + i + '">' )
  }
}
function inicia() {
  tm = setInterval( 'pinta()', 50 )
  return false
}
function pinta() {
  var nom = "quadre" + nImg
  eval( 'document.' + nom + '.src = "images/punt.gif"' )
  nImg++
  if ( nImg==25 ) clearInterval( tm )
}
</script>
</head>

<body ...>
<h2>Barra de progrés</h2>
<script language="javascript">base()</script>
<br><br><a href="" onClick="
return inicia()">Començar</a> &nbsp; &nbsp;
<a href="javascript:
location.reload()">Reiniciar</a></h3>
...
 
3.- Vertical i a la carta
L'estructura del segon exemple queda més clara, però el temporitzador del primer és millor. Al següent exemple veurem una barra vertical, que s'omple de baix a dalt, i que funciona sense la limitació del número d'imatges, és a dir, s'adapta als passos que hagi de fer. Per comprovar-ho, establirem els passos i el temps d'espera amb un formulari.

Amb el Dreamweaver, fem una taula de dues cel·les. En una hi posarem la barra. L'altra contindrà un formulari amb dos camps de text, passos i retard, i dos botons, l'un per engegar la barra i l'altre per reiniciar la pàgina. Utilitzem l'event onClick = "return inicia( form1 )".

Un cop dissenyat el cos, passarem a les funcions JavaScript de la capçalera. En aquest cas, comencem declarant quatre variables globals:

  - factor : un número que indica com ha de créixer la barra.
  - temps : quants mil·lisegons ha d'esperar fins al proper intent.
  - tm : "temps mort", variable per referenciar el temporitzador.
  - nImg : número d'imatge que es substitueix a cada pas. S'inicialitza al 21.

Ja coneixem la funció base(), que dibuixa la barra. Ara anirà dins d'una cel·la. Cada imatge serà de 40x8 amb un píxel de separació per sobre i per sota (vspace). Com abans, el nom de les imatges es forma amb "quadre" i l'índex del bucle.

Deixem, de moment, la funció de pintar, i fem la funció inicia( f1 ), que processa el formulari. L'hem passat com a paràmetre i, a dins de la funció s'assigna a una variable, f1. També assignem a la variable "temps" el valor del camp "retard", convertit a enter amb parseInt().

Fem el mateix amb els "passos" i els assignem a una variable local, nPas. Si és menor que 1, li posem 1, i guardem a la variable "factor" el quocient de dividir 20 entre el número de passos. Com que el divisor ha estat triat per l'usuari, el resultat pot ser decimal. Només ens queda cridar la funció de pintar i retornar "false" al botó del formulari que l'havia activat.

La funció pinta() és la peça clau. Tot i que no és complicada, cal analitzar-la detingudament. La farem de manera que pinti la barra independentment del número de passos. Recordem que tenim 20 imatges. Haurem de preveure que els passos siguin menors o majors que aquest número. Com? Mirem i provem un moment el codi, i ho aclarim després.

<script language="javascript">
var factor, temps, tm
var nImg = 21
function base() {
  for( i=1; i<21; i++ ) {
    document.writeln( '<img src="images/punt.gif" width="40"' )
    document.writeln( 'height="6" vspace="1" name="quadre' + i + '"><br>' )
  }
}
function pinta() {
  clearTimeout(tm)
  nImg -= factor
  if( nImg>=1 ) {
    if( factor>1 ) {
      for( i=20; i>nImg-1; i-- ) {
        eval( 'document.quadre' + i + '.src="images/barra.gif"' )
      }
    }
    eval( 'document.quadre' + Math.floor(nImg) + '.src="images/barra.gif"' )
    tm = setTimeout( 'pinta()', temps )
  }
  else document.img1.src="images/barra.gif"
}
function inicia( f1 ) {
  temps = parseInt( f1.retard.value )
  var nPas = parseInt( f1.passos.value )
  if( nPas<1 ) nPas = 1
  factor = 20/nPas
  pinta()
  return false
}
</script>
</head>

<body ...>
<table width="350" cellpadding="3" align="center">
<form name="form1">
  <tr>
    <td align="center" width="42" nowrap bgcolor="#99AA99">
      <script language="javascript">
base()</script>
    </td>
    <td align="center" bgcolor="#BBCCBB"><h3>Barra de progrés</h3>
      Passos: <input type="text" name="passos" size="3" value="20"
        maxlength="3"> &nbsp;
      Retard: <input type="text" name="retard" size="3" value="50"
        maxlength="3"> milisegons<br><br><br>
      <input type="button" name="ok" value="Començar"
        onClick="
return inicia(form1)"> &nbsp; &nbsp;
      <input type="button" name="reset" value="Reiniciar"
        onClick="
location.reload()"><br>
    </td>
  </tr>
</form>
</table>
...
 
La funció pinta() comença amb la neteja del temporitzador, clearTimeout( tm ), i, a continuació assigna al número d'imatge el valor que tenia menys el del factor: nImg -= factor. És a dir, hem començat per la imatge de sota i anirem restant fins arribar a la primera. Com que "factor" pot ser un decimal, "nImg" també ho pot ser. Aquest detall és important.

Després, comprovem que el número d'imatges és encara major o igual que 1: if( nImg >=1 ). Si és així, hem de considerar que, potser, haurem de representar amb 20 imatges un número menor que 20, o, dit d'una altra manera, el "factor" pot ser major que 1 if( factor>1), i s'haurà de pintar més d'una imatge cada vegada. Per això, fem un bucle que les pinti des del peu de la barra fins a l'anterior de la que toca:

  eval( 'document.quadre' + i + '.src="images/barra.gif"' )

Sigui quin sigui el factor, ara hem de pintar la imatge de la barra que correspon al valor actual. Com que "nImg" pot ser decimal, utilitzem el mètode Math.floor(), que, com hem vist en altres pràctiques, elimina els decimals i es queda amb l'enter:

  eval( 'document.quadre' + Math.floor(nImg) + '.src="images/barra.gif"' )

Finalment, només ens queda establir el temporitzador que tornarà a executar la mateixa funció al cap del temps que hagi establert l'usuari: tm = setTimeout( 'pinta()', temps ). Si ja s'ha arribat a la primera imatge, pot ser que el valor de "nImg" sigui menor que 1 i encara no l'hagi pintada. Per això, la pintem directament, sense "eval()", perquè sabem el seu nom:

  else document.img1.src="images/barra.gif"

Tenim una funció universal, que s'adaptarà a qualsevol necessitat. Hem utilitzat dues tècniques avançades, la recursivitat i l'avaluació de codi. I ens ha quedat curteta. Ho hem trobat difícil? En programació, el que compta és l'algoritme.