Alters

Proyecto BSearch - Ayuda flotante

¡Buenas a todos!

Aquí estoy una vez más, dispuesto a avanzar el pequeño buscador que me propuse hacer.

De momento (en el momento en que escribo esto, el buscador aún está en desarrollo) tengo lista la ayuda flotante, y tengo casi listo el "kernel" (por llamarlo de alguna manera) que después hará de buscador.

De momento nos centramos en el primer aspecto.

¡Vamos allá!


Empezamos primero con una idea inicial, como siempre.

Esta ayuda será orientativa, de manera que no queremos que "consuma" demasiado. Por ello, nos saltaremos algunas comprobaciones (ya lo veremos más adelante).

Lo que queremos, básicamente, es que al pulsar un token (carácter especial), la ayuda flotante esté "al caso" y nos vaya sugiriendo lo que podemos estar buscando.

Para ello contaremos con sendas listas de proyectos y etiquetas, en las que la ayuda buscará (y luego el motor). Para que sea un poco más rápido, no tendremos cuenta si son proyectos o son etiquetas.

Simularemos el buscador con el siguiente HTML:

<html>
<head>
<title>Simulador Buscador</title>
</head>
<body>
<div id="t">
<input type="text" id="i" onkeyup="sch();"/>
</div>
<div id="x"></div>
<div id="y"></div>
</body>
</html>

Como vemos tenemos el cuadro de búsqueda (#i), y tenemos dos div (#x, #y) que servirán para los resultados y para debug, respectivamente.

Para darle un poco de glamour, usaremos los siguientes estilos CSS (no demasiado difíciles de comprender):

#x{
width: 300px;
}

#x div{
position: relative;
cursor: pointer;
width: 288px;
border: 1px solid;
border-bottom: 0px;
padding: 5px;
}

#x div:hover{
background-color: #EFEFEF;
}

#i{
width: 300px;
}

Como veis. simplemente es darle un poco de forma a la página... para poder diferenciarlo todo un poco.

Finalmente, vamos a proceder a explicar el código (100% JavaScript, porque los hay que somos masocas... jeje)

// Lista de proyectos
var prj = ["opr1", "pr2", "jpr3", "pjr4", "r5", "prj6", "proj7"];

// Lista de etiquetas
var tag = ["tag1", "pr2", "tag3", "tg4", "ag5", "ta6", "a7"];

/* Lista de caracteres de parada.
 * Es decir, se dejará de buscar texto cuando se encuentre
 * uno de estos caracteres
 */
var stp = [" ", "#", ".", "[", "&", "|", "(", "!"];

/* Lista de caracteres de reset.
 * Cada vez que se encuentre uno de estos caracteres
 * la ayuda se reiniciará
 */
var rst = [" ", "#", ".", "]", "&", "|", ")", "!"];

// Lista de elementos
var elem = [];

// Índice de elementos
var elem_i = 0;

// Variable para saber si se ha pulsado en un elemento
var ent = true;

/*
 * Función sch.
 * Si el último caracter no está en "rst", buscamos el
 * texto introducido. Para ello, vamos hacia atrás
 * desde la posición final hasta que nos encontremos
 * con un caracter de la lista "stp".
 * Cuando encontramos el caracter, llamamos a "qry".
 */
function sch(){
txt = g('i').value;
tlen = txt.length - 1;
sch_i = 0;
aux = '';

g('x').innerHTML = '';

if(!ent){
ent = true;
return false;
}

if(isIn(rst, txt.charAt(tlen))){
g('x').style.borderBottom = '0px';
return false;
}

for(sch_i = tlen;sch_i >= 0;sch_i--){
if(isIn(stp, txt.charAt(sch_i))){
qry(aux);
break;
}else{
aux = txt.charAt(sch_i) + aux;
}
}

sch_i = 0;
tlen = 0;
aux = "";

return false;
}

/* Función qry.
 * Mediante la función "mIsIn" sabemos si el texto
 * pasado está en una de las listas.
 * Para cada lista (prj y tag) se comprueba; en caso
 * de que esté, se añaden a "ret" mediante la función
 * "addCustomNode".
 */
function qry(txt){
elem_i = 0;
elem = [];
qry_i = 0;

if(mIsIn(prj, txt)){
for(qi = 0;qi < mIsIn_i_i;qi++){
qry_i = mIsIn_i[qi];
addCustomNode();
}
}

if(mIsIn(tag, txt)){
for(qi = 0;qi < mIsIn_i_i;qi++){
qry_i = mIsIn_i[qi];
addCustomNode();
}
}

gt();
}

/* Función mIsIn.
 * Recorre la lista dada en busca de un texto que
 * empieze por el mismo patrón que el texto dado.
 * En caso de coincidencia, se almacena la posición
 * en la variable "mIsIn_i", y se retorna un valor
 * positivo. En el caso de que no hayan coincidencias
 * se retorna un valor negativo.
 */
function mIsIn(arr, txt){
  marrl = arr.length;
  msubl = txt.length;
  mI = 0;
  mIsIn_i = [];
  mIsIn_i_i = 0;
  ret = false;

  for(mI = 0;mI < marrl;mI++){
if(arr[mI].substring(0, msubl) == txt){
 mIsIn_i[mIsIn_i_i] = arr[mI];
 mIsIn_i_i++;
 ret = true;
}
  }

  return ret;
}

/* Función isIn.
 * Busca en la lista dada el texto dado.
 * En caso de coincidencia se retorna un valor
 * positivo y el índice del elemento queda
 * almacenado en "isIn_i"; en caso contrario
 * se retorna un valor negativo y el valor de
 * "isIn_i" pasa a valer -1.
 */
function isIn(arr, txt){
arrl = arr.length;
subl = txt.length;
isIn_i = 0;

for(isIn_i = 0;isIn_i < arrl;isIn_i++){
if(arr[isIn_i].substring(0, subl) == txt){
return true;
}
}

isIn_i = -1;

return false;
}

/* Función addCustomNode.
 * Se añade a "elem" el valor del
 * índice actual de "qry_i", y se
 * incrementa el valor de "elem_i".
 */
function addCustomNode(){
elem[elem_i] = qry_i;
elem_i++;
}

/* Función gt.
 * Limpia la lista "elem", y después obtiene
 * su valor para mostrar por pantalla.
 */
function gt(){
gt_i = 0;
ret = '';
elem = clean(elem);
elem_i = elem.length;

for(gt_i = 0;gt_i < elem_i;gt_i++){
 add(elem[gt_i]);
}

return ret;
}

/* Función add.
 * Retorna el texto para mostrar por
 * pantalla del elemento pasado.
 */
function add(txt){
g('x').innerHTML += '<div onclick="rplc(\'' + txt + '\');">' + txt + '</div>';
}

/* Función rplc.
 * Reemplaza el último texto buscado por el texto
 * pulsado en la ayuda, y añade un espacio adicional.
 */
function rplc(txt){
ent = false;
rtxt = g('i').value;
tlen = rtxt.length - 1;

g('x').innerHTML = '';

for(rplc_i = tlen;rplc_i >= 0;rplc_i--){
if(isIn(stp, rtxt.charAt(rplc_i))){
break;
}
}

g('i').value = g('i').value.substring(0, (rplc_i + 1)) + txt + ' ';
g('i').focus();
}

/* Función clean.
 * Limpia de duplicados la lista pasada mediante
 * un algoritmo simple con optimizaciones básicas
 * con la finalidad de preservar la velocidad
 * y la eficiencia del algoritmo general.
 */
function clean(arr){
cl = arr.length;
ret = [];
ret_i = 0;

for(ci = 0;ci < cl;ci++){
cc = false;

for(cj = (ci + 1);cj < cl;cj++){
if(arr[ci] == arr[cj]){
cc = true;
break;
}
}

if(!cc){
ret[ret_i] = arr[ci];
ret_i++;
}
}

return ret;
}

/* Función g.
 * Sinónimo de función "Document.getElementById".
 */
function g(id){
return document.getElementById(id);
}

El código está (creo) bien explicado en los comentarios. No obstante cualquier duda siempre la podéis comentar y haré lo posible para quitaros las dudas :-D

Ahora un par de comentarios: la búsqueda de texto en "sch()" se hace a la inversa porque si tenemos introducidos varias etiquetas o proyectos, buscar los espacios, caracteres especiales, etc sería costoso; por otra parte sabemos que el ítem que estemos tecleando será, en el 80% de los casos el último, por lo que tiene sentido empezar a buscar desde el final.

Sé perfectamente que el método más "correcto" sería: buscar dónde está el cursor en el momento de la llamada a "sch()". A partir de ahí podemos o bien ir hacia atrás hasta encontrar el carácter de "stop", o bien ir hacia delante hasta encontrar un carácter de "reset". En cualquiera de los dos casos, se tendrá que ir hacia atrás o hacia adelante hasta encontrar un carácter o bien de "reset" o bien de "stop".

Pero como comentaba, busco más bien velocidad en este aspecto, así que comprobar estas cosas para un 20% de los casos no tiene mucho sentido. No obstante se puede incluir para una segunda versión (mejorada) de este buscador. Solo sería cosa de cambiar la función "sch()" por una que aceptase el carácter de fin de texto, y a partir de ahí buscaría el inicio... y el resto se mantendría (excepto "rplc()")...

Bueno, me voy por las ramas amigos... os dejo un enlace en el que podéis probar la ayuda (aquí).

Como veréis es un pequeño host gratuito que me "compré" para hacer mis chapuzas...

Entonces, la próxima entrada será la del buscador.

Sed buenos, y como siempre...

¡Hasta la próxima!