Listas tipo lookup

Escrito el 12 febrero, 2001 – 15:00 | por storm | 2.337 lecturas

Quienes piensan que las interfases en la web estan limitadas a lo que HTML nos tiene acostumbrados estan equivocados, usando JavaScript en este caso y un poco de ingenio podemos construir por ejemplo Listas tipo look-up. En la nota todos los ingredientes, la receta y el plato listo para probarlo.

Listas tipo look-up en JavaScript

Richie Adler

Primera parte

Una lista tipo look-up es aquella en la cual el usuario dispone de un combo de seleccion y ademas de un campo de entrada de texto, a medida que el usuario tipea en el campo de
texto automaticamente el combo se posiciona en el primer elemento de la lista cuyos
primeros ‘n’ caracteres coincidad con los ‘n’ caracteres tipeados por el usuario.

HTML no dispone de un tag INPUT que corresponda a este tipo de interface, por lo cual es necesario construir la interface usando JavaScript. El script debe capturar las teclas que el usuario tipea en el teclado por lo que solo funcionara 100\% en versiones 4.0 o superiores de IE o Netscape.

En primer lugar detectamos el tipo de browser del usuario:

var NS4 = (document.layers) ? true : false;
var IE4 = (document.all) ? true : false;

El browser es netscape si esta definido el objeto document.layers, mientras que el browser solo puede ser explorer si el objeto document.all esta definido.

Luego definimos el tamanio de la lista en otra variable global.


var size = 8;

Esta variable determina la cantidad de opciones que el usuario visualiza sin necesidad de scrollear la lista (no limita la cantidad maxima de opciones de la lista) si la cantidad es menor a la cantidad total de opciones incluimos un scroll bar para scrollear las opciones.

La siguiente variable determina cuando el cursor esta colocado en el campo de texto de la lista.


var active = false;

Inicialmente es falso debido a que el cursor no esta colocado en la lista de texto cuando la pagina se carga.

Modos de operacio

Los dos modos de operacion posible de la lista son:

// select index style:
//   1. Standard
//      Se selecciona una opcion si un substring de la opcion 
//      matchea con lo tipeado. Si no se encuentra un match se 
//      elige el matching mas cercano desde el final,
//      por ejemplo "cz" matchea "cy", "cx" etc....  
//   2. Explorer
//      Simula el index del help del explorer,si el string es 
//      vacio se seleeciona el primer elemento en la lista. 
//      Si ningun substring matchea la seleccion no cambia.

var style = 1;

Cuando el usuario tipea o deletea un caracter del text box el script busca un substring
que matchee en la lista.Recorre las opciones de la lista para ver si alguna de ellas comienza
con el string ingresado. Si mas de un substring matchea entonces se elige el primero.
Si no hay texto en el box de texto se selecciona la primera opcion.

Por ultimo globalmente invocamos a la funcion de inicializacion start()


start();

Inicializacion de la lista

La lista esta creada en JavaScript, si el browser no soporta los elementos creamos una
lista comun (un menu pop-up).

function start() {
  if (!window.Array) return;
  ar = new Array(); // define an array of sites

  // initialize the array
  ar[0] = new site("2ask Best of the Planet Awards",
          "http://208.5.2.150/botp/top/nomination/body.htm");
  ar[1] = new site("Ampersand",
          "http://www.arrowweb.com/amp/submit_body.html");
  ar[2] = new site("ASource",
          "http://www.asource.com/guide.html");
  ar[3] = new site("Benny Blue Web Wow Awards",
          "http://www.hsv.tis.net/~slmartin/WebWow.htm");
  ar[4] = new site("Cool Banana! Site of the Day",
          "http://www.coolbanana.com/Feedback/nominate.html");

  if (ar.sort) ar.sort(byName);
  printList(ar);
  if (NS4) document.captureEvents(Event.KEYUP);
  document.onkeyup = checkKey;
}

Si el browser no soporta arrays en Js la primera linea de la funcion sale de la misma. Luego se define el array que se define como global al no usarse “var” para declararlo,
luego se inicializa el array que es un array de objetos cuyo constructor es “site()”,
el constructor es:

function site(name, url) {
  this.name = name;
  this.url = url;
}

Como podemos ver cada elemento del vector tiene 2 propiedades, un nombre y un url
o valor que es lo que se obtiene al seleccionarlo de la lista.

En:

ar[0] = new site("2ask Best of the Planet Awards",
        "http://208.5.2.150/botp/top/nomination/body.htm");

Estamos seteando ar[0].name=”2ask Best of the Planet Awards” y a
ar[0].url=”http://208.5.2.150/botp/top/nomination/body.htm”

Por ultimo ordenamos el vector usando el metodo sort de un objeto array, este metodo
requiere que le pasemos como parametro una funcion de comparacion de la forma:

arrayVar.sort(compareFunction);

compareFunction es una funcion que establece como se comparan dos elementos del vector,en caso de no pasarse la funcion se comparan los elementos en orden lexicografico.En nuestro caso la funcion es necesaria ya que ordenamos por la propiedad “name” de los objetos del
vector.

La funcion generica es de la forma:

function compareFunction(a, b) {
  if (a is less than b according to the sorting order)
    return -1;
  if (a is greater than b according to the sorting order)
    return 1;
  return 0;
}

Y en nuestro caso la funcion que llamamos byName es:

function byName(a, b) {
  var anew = a.name.toLowerCase();
  var bnew = b.name.toLowerCase();
  if (anew < bnew) return -1;
  if (anew > bnew) return 1;
  return 0;
}

Como podemos ver ordenamos sin tener en cuenta mayusculas y minusculas por la propiedad
“name” de cada uno de los objetos del vector.

Luego de ordenar el vector se llama a la funcio printlist() que es la encargada de
imprimir el codigo HTML correspondiente a la lista.

Por ultimo definimos el handler del evento “keyup” que se dispara cada vez que un usuario toca una tecla en el teclado y la suelta (que es el tipo de evento que nos ineteresa aqui). El evento es capturado a nivel del documento pues el elemento INPUT no soporta el evento
“keyup”.

Creacion de la lista

La funcion printList que dibuja la lista es:

function printList() {
  with (document) {
    write(
      '<FORM NAME="organizer" ',
      'onSubmit="display(); return false;">',
      '<TABLE BGCOLOR="#c0c0c0" CELLSPACING="0" ',
      'CELLPADDING="5" BORDER="2"><TR><TD>',
      '<TABLE BGCOLOR="#c0c0c0" CELLSPACING="0" ',
      'CELLPADDING="5" BORDER="1"><TR><TD>'
    );
    if ((NS4 || IE4))
      write(
        '<INPUT TYPE="text" NAME="prefix" ',
        'SIZE="35" onFocus="active = true" ',
        'onBlur="active = false"><BR>'
      );
    write(
      '<SELECT NAME="list" SIZE="', size,
      '" onChange="update()">'
    );
    for (var i = 0; i < ar.length; ++i) {
      write('<OPTION VALUE="', ar[i].url, '">', ar[i].name);
    }
    write(
      '</SELECT><BR>',
      '<INPUT TYPE="submit" VALUE="Display">',
      '<INPUT TYPE="reset" VALUE="Cancel">',
      '</TD></TR></TABLE></TD></TR><TABLE></FORM>'
    );
}

El nombre del form es “organizer”, el form se compone de los siguientes elementos:

Un campo de texto “prefix”,
Un campo select “list”,
Un boton de submit “Display”,
Un boton de reset “Cancel”

El usuario puede submitear el form clikeando en el boton “Display” o presionando Enter
cuando el cursor esta en el campo de texto.

Cuando el usuario submitea el form el evento onSubmit se dispara e invoca la funcion
display() que es la que devuelve el valor seleccionado por el usuario.

function display() {
  var list = document.organizer.list;
  if (list.selectedIndex > -1) // if an option is selected
    alert(list.options[list.selectedIndex].value);
}

Notemos que la propieadad selectedIndex del elemento select del form es la que determina
cual es la opcion seleccionada del arreglo. Si no hay opcion elegida devuelve -1.

Notemos que el campo de texto solamente se muestra si el browser es 4.0 o superior.
Los browsers mas viejos solamente ven el campo “select” como un select normal por lo
que todo lo demas es irrelevante. Notemos que pese a tener menos funcionalidad
la lista funciona normalmente y permite seleccionar una opcion en cualquier browser.

Como hemos visto el evento “keyup” esta capturado a nivel de documento, para no
sobrecargar al browser si se presiona una tecla se usa la variable global “active”
para determinar si el cursor esta sobre el campo de texto. Los eventos onFocus y onBlur
respectivamente se encargan de setear o des-setear esta variable segun el cursor
este o no este sobre el campo de texto.

Cuando el usuario selecciona una opcion manualmente de la lista el evento onChange
captura el cambio para mostrar el nombre de la opcion elegida en el campo de texto.

Cuando el usuario tipea algunos caracteres en el texto box el script selecciona
la opcion correspondiente. Sin embargo el usuario puede seleccionar una opcion
directamente con el mouse, entonces el evento onChange dispara la funcion update
para mostrar en el campo de texto el nombre de la opcion elegida:

function update() {
  if ((!NS4 && !IE4)) return;
  var form = document.organizer;
  var field = form.prefix;
  var list = form.list;
  field.value = list.options[list.selectedIndex].text;
}

Como podemos ver la opcion es sencilla.

Seleccion de opciones

Para seleccionar la opcion que matchea al presionar teclas usamos en primer
lugar una funcion que permita seleccionar la i-esima entrada de la lista:

function select(list, i) {
  if (list.selectedIndex != i) list.selectedIndex = i;
}

Es importante chequear si la opcion no esta seleccionada, porque el seleccionar
una opcion ya seleccionada hace que los Netscapes 4.0+ flasheen.

La funcion checkKey por ultimo es la disparada por el evento onKeyup cuando el
usuario presiona una tecla.

function checkKey() {
  if (!active) return;
  var form = document.organizer;
  var field = form.prefix;
  var list = form.list;
  var str = field.value.toLowerCase();
  if (str == "") {
    select(list, 0);
    return;
  }
  for (var i = 0; i < list.options.length; ++i) {
    if (list.options[i].text.toLowerCase().indexOf(str) == 0) {
      select(list, i);
      return;
    }
  }
  if (style == 1) {
    for (i = list.options.length - 1; i >= 0; --i) {
      if (str > list.options[i].text.toLowerCase()) {
        select(list, i);
        return;
      }
    }
    select(list, 0);
}

Si active es falso la funcion vuelve.
Luego creamos referencias al form, el campo de texto y la lista select respectivamente.

var form = document.organizer;
var field = form.prefix;
var list = form.list;

El campo de texto es convertido a minusculas para normalizar con el vector de opciones
y luego asignamos el string a str.

Si no hay nada en el box de texto asignamos la primera opcion y la funcion termina.

if (str == "") {
  select(list, 0);
  return;

Si el campo de texto no es vacio entonces buscamos en las opciones por un substring
que matchee o sea recorremos las opciones buscando aquellas cuyo nombre empieza con
el string ingresado:

for (var i = 0; i < list.options.length; ++i) {
  if (list.options[i].text.toLowerCase().indexOf(str) == 0) {
    select(list, i);
    return;
  }

Notar que para normalizar tambien convertimos las opciones a minusculas.

El resto de la funcion se ejecuta si el estilo de matching es "1".

for (i = list.options.length - 1; i >= 0; --i) {
  if (str > list.options[i].text.toLowerCase()) {
    select(list, i);
    return;
  }
}

En este caso buscamos en la lista en orden inverso buscando una opcion que tenga nombre
"menor" (en orden lexicografico) al texto ingresado.

El completo es:

<SCRIPT LANGUAGE="JavaScript">
<!--

var NS4 = (document.layers) ? true : false;
var IE4 = (document.all) ? true : false;
var NS4MAC = NS4 && (navigator.appVersion.indexOf("Macintosh") > -1);

var size = 8;
var active = false;

// select index style:
//   1. Standard
//      Selects an option if a substring match is found.
//      If no match is found, selects the closest match
//      from the bottom. For example, if the string is
//      "cz", it will match "cy...", "cx...", etc.
//   2. Explorer
//      Simulates the new index in Internet Explorer's
//      help. If the string is empty, selects the first
//      option. Otherwise, if no substring match is found
//      the selection doesn't change.
var style = 1;

start();

function site(name, url) {
  this.name = name;
  this.url = url;
}

function byName(a, b) {
  var anew = a.name.toLowerCase();
  var bnew = b.name.toLowerCase();
  if (anew < bnew) return -1;
  if (anew > bnew) return 1;
  return 0;
}

function display() {
  var list = document.organizer.list;
  if (list.selectedIndex > -1) // if an option is selected
    location.href = list.options[list.selectedIndex].value;
}

function update() {
  if ((!NS4 && !IE4) || NS4MAC) return;
  var form = document.organizer;
  var field = form.prefix;
  var list = form.list;
  field.value = list.options[list.selectedIndex].text;
}

function select(list, i) {
  if (list.selectedIndex != i) list.selectedIndex = i;
}

function checkKey() {
  if (!active) return;
  var form = document.organizer;
  var field = form.prefix;
  var list = form.list;
  var str = field.value.toLowerCase();
  if (str == "") {
    select(list, 0);
    return;
  }
  for (var i = 0; i < list.options.length; ++i) {
    if (list.options[i].text.toLowerCase().indexOf(str) == 0) {
      select(list, i);
      return;
    }
  }
  if (style == 1) {
    for (i = list.options.length - 1; i >= 0; --i) {
      if (str > list.options[i].text.toLowerCase()) {
        select(list, i);
        return;
      }
    }
    select(list, 0);
  }
}

function printList() {
  with (document) {
    write(
      '<FORM NAME="organizer" ',
      'onSubmit="display(); return false;">',
      '<TABLE BGCOLOR="#c0c0c0" CELLSPACING="0" ',
      'CELLPADDING="5" BORDER="2"><TR><TD>',
      '<TABLE BGCOLOR="#c0c0c0" CELLSPACING="0" ',
      'CELLPADDING="5" BORDER="1"><TR><TD>'
    );
    if ((NS4 || IE4) && !NS4MAC)
      write(
        '<INPUT TYPE="text" NAME="prefix" ',
        'SIZE="35" onFocus="active = true" ',
        'onBlur="active = false"><BR>'
      );
    write(
      '<SELECT NAME="list" SIZE="', size,
      '" onChange="update()">'
    );
    for (var i = 0; i < ar.length; ++i) {
      write('<OPTION VALUE="', ar[i].url, '">', ar[i].name);
    }
    write(
      '</SELECT><BR>',
      '<INPUT TYPE="submit" VALUE="Display">',
      '<INPUT TYPE="reset" VALUE="Cancel">',
      '</TD></TR></TABLE></TD></TR></TABLE></FORM>'
    );
  }
}

function start() {
  if (!window.Array) return;
  ar = new Array(); // define an array of sites

  // initialize the array
  ar[0] = new site("2ask Best of the Planet Awards",
          "http://208.5.2.150/botp/top/nomination/body.htm");
  ar[1] = new site("Ampersand",
          "http://www.arrowweb.com/amp/submit_body.html");
  ar[2] = new site("ASource",
          "http://www.asource.com/guide.html");
  ar[3] = new site("Benny Blue Web Wow Awards",
          "http://www.hsv.tis.net/~slmartin/WebWow.htm");
  ar[4] = new site("Cool Banana! Site of the Day",
          "http://www.coolbanana.com/Feedback/nominate.html");
  ar[5] = new site("Cool Site of the Day",
          "http://cool.infi.net/subscribe.html");
  ar[6] = new site("Cosmic Site of the Night",
          "http://www.adze.com/");
  ar[7] = new site("Cybersmith Site of the Day",
          "http://magneto.cybersmith.com/hotsites/suggestsite.html");
  ar[8] = new site("Dr. Webster Cool Web Site of the Day",
          "http://www.123go.com/drw/emaill.htm");
  ar[9] = new site("Dynamite Site of the Nite",
          "http://www.netzone.com/~tti/dsninfo.html");
  ar[10] = new site("Egotistical Site of the Week",
          "http://www.bibiana.com/ego.html");
  ar[11] = new site("Fred Langa's HotSpots (Windows Magazine)",
          "http://www.winmag.com/flanga/hotspots.htm");
  ar[12] = new site("Funky Site of the Day",
          "http://www.realitycom.com/cybstars/INDEXO.HTML");
  ar[13] = new site("Ground Zero",
          "http://www.ground.com/submit.html");
  ar[14] = new site("High Five",
          "http://www.highfive.com/core/submit.html");
  ar[15] = new site("Hot Rot Your Head",
          "http://www.botree.com/submit.htm");
  ar[16] = new site("iWorld Site of the Day",
          "http://www.internetnews.com/poweruser/sotd/");
  ar[17] = new site("Jayde Online",
          "http://www.jayde.com/cgi-bin/addurl.cgi");
  ar[18] = new site("Marketing Excellence",
          "http://www.focusa.com/me_award1.htm#mea");
  ar[19] = new site("NetGuide (Site of the Day or Top 100)",
          "http://www.netguide.com/aboutus/aboutreviews.html");
  ar[20] = new site("Netscape Guide by Yahoo!",
          "http://add.yahoo.com/fast/add?+Guide");
  ar[21] = new site("Netscream's UV Award",
          "http://www.netscream.com/award.htm");
  ar[22] = new site("Netsurfer Digest",
          "http://www.netsurf.com/nsd/pressroom.html");
  ar[23] = new site("New Zealand Net Awards",
          "http://www.netawards.co.nz/record.htm");
  ar[24] = new site("PC Magazine",
          "http://www8.zdnet.com/pcmag/insites/wgabout.htm");
  ar[25] = new site("Seven Wonders",
          "http://www.penncen.com/7wonders/7suggest.html");
  ar[26] = new site("The Internet RoadKill Award",
          "http://www.irk.pair.com/");
  ar[27] = new site("The Web 100",
          "http://www.web100.com/other/submit.html");
  ar[28] = new site("Too Cool",
          "http://www.toocool.com/guest/cool_add.htm");
  ar[29] = new site("USA TODAY Hot Sites",
          "http://www.usatoday.com/life/cyber/ch.htm");
  ar[30] = new site("Wave of the Day",
          "http://www.marketsquare.com/wave/");
  ar[31] = new site("WebScout",
          "http://www.webscout.com/nominate.html");
  ar[32] = new site("Wow! Web Wonders!",
          "http://www.bergen.org/AAST/Wow/submit.html");
  ar[34] = new site("Yahoo!'s Picks of the Week",
          "http://www.yahoo.com/picks/");
  ar[33] = new site("YPN Directory (and the Top 100)",
          "http://www.ypn.com/mm-bin/genobject/directory");

  if (ar.sort) ar.sort(byName);
  printList();
  if (NS4) document.captureEvents(Event.KEYUP);
  document.onkeyup = checkKey;
}
-->
</SCRIPT>

Ejemplo

Si quieren ver un ejemplo funcionando,tal vez para entender de que estamos hablando y como funciona la interfase pueden usar este link que abre un popup con un pequeño ejemplo listo para usar.

Richie Adler

You must be logged in to post a comment.

Buscar: