Barra de navegación de registros

Botones para navegación de registros
Figura. Barra de botones de navegación de registros: ir a inicio, atrás, adelante y fin.

Cuando estaba preparando la herramienta Probador de expresiones regulares necesitaba incorporar una barra de botones para navegación de registros. Se trataba de extraer las propiedades del objeto window.RegExp tras la ejecución de un patrón con modificador global y exponerlas en tantos registros como búsquedas resultaran. Se les podría definir también como botones para paginación. En el fondo lo que necesitaba eran botones para ir a inicio, atrás, adelante y fin (first, previous, next, last) o algo similar. Finalmente construí una barra de botones como los que aparecen en la Figura.

Botones para navegación en un slider de imágenes
Figura. Barra de botones para navegar un slider, carrusel o galería de imágenes.

Pero antes de llevarlo a cabo consideré varias posibilidades. Una de ellas pasaba por poner botones con esos títulos. O bien incorporarles iconos descriptivos de sus acciones y no incluir títulos visibles. Son las típicas flechas en forma de triángulos. Estos iconos podrían ser añadidos con imágenes, usando sprites CSS por ejemplo, tal como hago en el componente Slider de imágenes (ver Figura).

Uno de los problemas de esa barra es que las imágenes de las flechas resultan demasiado pequeñas para un dispositivo de menor tamaño, recomendándose botones de 50×50 píxeles de superficie interaccionable. Las imágenes de las flechas son de unos 23 píxeles mientras que el botón si tiene los 50 de ancho pero no así la altura necesaria. Cuando hice esa barra no pensaba en dispositivos de menor tamaño y en un futuro tendré que replantearme arreglarlo. Por eso decidí que no iba a usar esa barra para el nuevo cometido.

Además quería que esos botones tuviesen iconos pero evitando imágenes incluso del tipo Sprites CSS, pues no se escalan gráficamente y por lo tanto limitan la adaptabilidad a distintos casos de uso. En resumen, necesitaba un nuevo componente para una barra de botones de navegación de registros.

En este tema explicaré como implementarlo, pero antes apuntaré algo sobre los componentes y fuentes de iconos de interfaz de usuario disponibles en UNICODE. En el último apartado de este tema encontrará ejemplos de aplicación de la barra de navegación de registros. También puede ver una página de ejemplo en el desplegable de Ejemplos integrados al inicio de este tema. En su código fuente tendrá todos los recursos para usar el componente. En un tema siguiente se detalla más sobre el CSS necesario para realizar los iconos de los botones, explicándose como construir triángulos con CSS y como posicionarlos usando márgenes.

Componentes de código reutilizables

Uno de los objetivos de la programación es detectar código reutilizable. Separando este código en un componente bien definido no tendremos que estar rescribiéndolo si lo necesitáramos en un futuro para otro cometido. Si por ejemplo necesito un contenedor con pestañas para una página, ¿Por qué no crear un componente reutilizable por si lo necesito en otras páginas? A medida que adquirimos práctica en programación nos va resultando más fácil detectar cuando el código podría ser reutilizable.

Desde el punto de vista del desarrollo web podemos definir un componente como la separación e independencia del HTML, CSS y JS necesarios para replicar una determinada funcionalidad en diferentes páginas. Lo ideal es que el CSS y JS de un componente estén en sus propios archivos, incorporándolos a una página con <link rel="stylesheet" href="componente.css" /> y <script src="componente.js"></script>.

También sería ideal que el HTML estuviera en su propio archivo. De hecho existe un intento para poder importar partes de HTML en una página, tal como se explica en HTML Imports del sitio Html5rocks. Se trata de importar con <link rel="import" href="componente.html" />. Pero es algo que por ahora sólo lo soporta Chrome (por supuesto también Opera y Android con el mismo motor de renderizado), como se observa en la página de Caniuse a la fecha de escribir esta página.

Inicialmente había planteado separar los CSS y JS en archivos independientes. Pero muchos componentes son de escaso tamaño de código, lo que afecta a la optimización de la velocidad de carga. Es por eso que en una remodelación del sitio decidí incluir los más usados en los archivos base.css y general.js. Estos dos módulos, que contienen el estilo y JavaScript necesario para la mayor parte de las páginas de este sitio, separan su contenido precisamente en componentes. Esa separación también me sirve para inyectar sólo el CSS necesario a cada página tal como explico en el tema CSS-FOLD: Priorizar CSS above-the-fold. Y aunque por ahora no lo estoy haciendo, la misma técnica podría servir para inyectar sólo los componentes JS necesarios para cada página.

Esa presunción de que un componente podría ser utilizado en el futuro a veces no se cumple. En el botón Accesos de la barra superior se expone una lista de temas clasificados por materia. Uno de ellos contiene una lista de componentes creados en este sitio. Ahora que los reviso veo por ejemplo el componente Tabla de contenidos TOC que finalmente no uso en ninguna otra página. Pero otros como los contenedores de pestañas que mencioné antes o los formularios emergentes si que los vengo usando repetidamente.

Separar un componente es un trabajo adicional, pues hemos de plantearlo desde una perspectiva de unidad modular, esto es, una parte de código que sabemos como configurarla y ejecutarla desde la página donde lo usemos, sin importarnos cómo funciona por dentro el componente. En definitiva, conocemos su interfaz para utilizar el componente.

Al diseñar el componente tenemos que tratar de generalizarlo intentando cubrir la mayor cantidad de casos de uso posible. Esto alarga los tiempos de diseño y desarrollo del componente y, probablemente, cuando luego lo usemos en otra página nos veamos obligados a modificarlo bajo la premisa de que también debe seguir funcionando para las páginas donde ya esté implementado.

Todo este trabajo puede ser infructuoso si luego el componente no es usado en muchas otras páginas. Pero la experiencia me dice que son siempre más los casos de éxito que los de fracaso.

Iconos de interfaz de usuario

Botones para navegación de registros en una base de datos
Figura. Botones de navegación de registros en una base de datos.

Como dije antes, al final decidí construir un nuevo componente de una barra de navegación de registros. Usaría iconos para describir las acciones inicio, atrás, adelante y final. Algo como los que podemos ver en el acceso a registro de una base de datos (Figura), aunque excluyendo el botón de nuevo registro. Aparte de los Sprites CSS también cabe la posibilidad de usar iconos SVG y fuentes de símbolos. Estos son preferibles pues se escalan sin pérdida de calidad a distintos tamaños simplemente cambiando su propiedad de estilo font-size.

Pero el problema es que en UNICODE hay pocas posibilidades de encontrar símbolos de interfaz de usuario para navegación de registros que sean soportados por las fuentes más comunes. Los triángulos derecha e izquierda para simbolizar ir atrás y adelante si que están disponibles, como los que podemos ver en UNICODE Geometric Shapes, que reproducimos literalmente como carácteres de la fuente Arial/Helvética con un tamaño de 3em (unos 48px):

  • &#x25C0;
  • &#x25B6;
  • &#x25C4;
  • &#x25BA;

Esos si están ampliamente soportados por las fuentes más comunes. Pero ahí faltan los iconos de inicio y fin de registros. En las páginas de UNICODE Miscellaneous Technical (2300–23FF) y Geometric Shapes Extended (1F780–1F7FF) puedo encontrar algunos parecidos a lo que estoy buscando.

Iconos para navegacion de registros o reproductores de video o audio
Figura. UNICODE Miscellaneous Technical (2300-23FF) y Geometric Shapes Extended (1F780–1F7FF)

La imagen de la Figura es una reproducción de esos símbolos que me podrían servir, pero el problema es que no están soportados por las fuentes comunes, como podemos ver en esta página de FileFormat.info acerca de las fuentes que los soportan.

Iconos de interfaz de usuario para reproductores de video o audio
Figura. Iconos de FontAwesome del grupo Video Player Icons

Las posibilidades pasar por descargar fuentes como FontAwesome Icons, donde en su apartado de Video Player Icons hay algunos parecidos a los que busco y que se presentan en la Figura.

Iconos de interfaz de usuario previous, next, play y otros
Figura. Iconos de Google gratis

Y si quieres más iconos de esta clase gratis mira los de la página Iconos de Google. El grupo de la Figura los he compuesto descargándolos individualmente en PNG. El triángulo que apunta a la izquierda he tenido que replicarlo girándolo del que apunta a la derecha, pues no aparece en esa página de recursos. Están también disponibles en SVG y como font icon, explicándose en la página de Google Material Icons como usarlos.

En definitiva, un montón de recursos gratis para iconos de interfaz de usurio. Pero si no quiero imágenes, SVG, fuentes, ni nada que necesite una descarga de recursos adicionales con la carga de la página ¿qué opciones me quedan? Pues crear uno mismo sus propios iconos con HTML y CSS. Y esto ya está inventado, como se observa en One-div.com. Y hasta una colección completa de fuentes pueden realizarse con CSS, como se observa en Yusugomori.com CSS Sans Fonts.

Ejemplos de aplicación de la barra de navegación de registros

Veámos un primer ejemplo donde tenemos cuatro registros a los que accedemos con los botones de la barra de navegación de registros:

Ejemplo: Barra de navegación de registros

Ejemplo A)
1 Contenido del primer registro.
2 Contenido del segundo registro.
3 Contenido del tercer registro.
4 Contenido del cuarto registro.
Registro 0 de 0

El HTML del ejemplo anterior es el siguiente:

<div class="registros">Ejemplo A)
    <div class="registro-bar-arrow-a"><span class="xxlarge">1</span> 
    Contenido del primer registro.</div>
    <div class="registro-bar-arrow-a"><span class="xxlarge">2</span> 
    Contenido del segundo registro.</div>
    <!-- ... más registros... -->
</div>
<div class="bar-arrow" id="bar-arrow-a" data-item="1"><button 
type="button" class="boton-arrow" data-mov="start" title="Inicio" 
    disabled><span>Inicio</span></button><button 
type="button" class="boton-arrow" data-mov="prev" title="Atrás" 
    disabled><span>Atrás</span></button><button 
type="button" class="boton-arrow" data-mov="next" title="Adelante" 
    disabled><span>Adelante</span></button><button
type="button" class="boton-arrow" data-mov="end" title="Fin" 
    disabled><span>Fin</span></button><span 
class="data-arrow">Registro <span class="arrow-cursor">0</span> de 
<span class="arrow-items">0</span></span></div>
    

El único elemento identificado es el contenedor exterior con ID bar-arrow-a. Esta identificación no tiene por objeto aplicarle CSS sino servir para ejecutar el siguiente JS, donde también se expone lo necesario para los dos ejemplos siguientes que se ejecutan en esta página.

<script>
/* El script a pie de página con el window.onload */
Wextensible.iniciarPagina = function() {
    wxL.adjudicarEventosBotonesNav();
    wxG.actualizarArrow("bar-arrow-a", 4, 1);
    wxG.actualizarArrow("bar-arrow-b", 4, 1);
    wxG.actualizarArrow("bar-arrow-c", wxL.listaImagenes.length, 1);
};
        window.onload = function() {
            if (Wextensible.generalJs){
                Wextensible.iniciarPagina();
            } else {
                var elemento = document.createElement("script");
                if (elemento.addEventListener){
                    elemento.addEventListener("load", Wextensible.iniciarPagina);
                } else if (elemento.readyState == "uninitialized") {
                    elemento.attachEvent("onreadystatechange", function(){
                        if (elemento.readyState == "loaded") Wextensible.iniciarPagina();
                    });
                }
                elemento.async = true;
                elemento.src = "/res/inc/general-old.js";
                var head = document.getElementsByTagName("head")[0];
                if (head) head.appendChild(elemento);
            }
        };//fin onload

/* wxL es un módulo local a cada página */
wxL.adjudicarEventosBotonesNav = function() {
    var botonArrow = document.getElementsByClassName("boton-arrow");
    for (var i=0, maxi=botonArrow.length; i<maxi; i++){
        botonArrow[i].addEventListener("click", function(event){
            var evt = event || window.event;
            //El item del cursor lo extraemos del componente. Esta ejecución también actualiza la 
            //referencia wxG.barArrow apuntando al contenedor de la botonera en la cual pulsamos 
            //en uno de sus botones. Así podremos obtener el ID del contenedor.
            var itemCursor = wxG.moverArrow(evt);
            //Pero la acción la ejecutamos aquí para todos los arrows de la página
            var id = wxG.barArrow.id;
            switch(id){
                case "bar-arrow-a":
                case "bar-arrow-b":
                    //Ejemplo bar-arrow-a y bar-arrow-b muestra y oculta DIV's'
                    var dives = document.getElementsByClassName("registro-" + id);
                    for (var j=0, maxj=dives.length; j<maxj; j++){
                        if (j == (itemCursor-1)){
                            dives[j].style.display = "block";
                        } else {
                            dives[j].style.display = "none";
                        }
                    }
                    break;
                case "bar-arrow-c":
                    //Ejemplo bar-arrow-c hace de pasa fotos
                    var pfotosImg = document.getElementById("pfotos-img");
                    var pfotosLegend = document.getElementById("pfotos-legend");
                    pfotosImg.src = "/temas/fractal/" + wxL.listaImagenes[itemCursor-1][0];
                    pfotosLegend.innerHTML = wxL.listaImagenes[itemCursor-1][1];
                    break;
            }

        });
    }
};

/* Lista de imágenes para el tercer ejemplo del pasa-fotos */
wxL.listaImagenes  = [
["ejemplos/trazador/fractal0.jpg", 
  "<b>Conjunto de Mandelbrot..."], 
...
];
</script>
    

Por un lado wxL es un módulo local a la página y wxG es la referencia al módulo general.js. En su componente arrows, que he replicado en el desplegable con codigo previo a este párrafo, están las siguientes funciones y variables:

  • barArrow: Variable temporal para guardar una referencia al contenedor que alberga los botones, único elemento que tiene un ID y por el cual podemos obtener una referencia a una barra de botones cuando hay varias en una página.
  • actualizarArrow(idBarArrow, totalItems, item): Permite actualizar los elementos con clases arrow-items y arrow-cursor con los argumentos totalItems e item.
  • moverArrow(): Mueve el cursor, tras lo cual se actualiza la variable barArrow, una referencia al elemento contenedor de los botones. Esta función nos devuelve el nuevo número de posición del registro.

De estas variables y funciones sólo necesitamos conocer su interfaz para utilizarlas en el módulo local. Para el primer ejemplo primero adjudicamos eventos con wxL.adjudicarEventosBotonesNav() y luego actualizamos el número de registros de la barra así como activar el primer registro con wxG.actualizarArrow("bar-arrow-a", 4, 1).

Para adjudicar eventos iteramos por todos los botones de clase boton-arrow de la página. El manejador del evento mueve el registro con var itemCursor = wxG.moverArrow(evt); pasándole el evento para algunos navegadores que lo necesitan así. Pero esta función sólo tiene por objeto cambiar el atributo data-item del contenedor de botones y controlando los límites de registros. Para el exterior se encarga de actualizar la referencia barArrow que apunta a la barra de botones y finaliza devolviendo el número de la nueva posición de registro.

Con la actualización de la referencia barArrow sabremos en que barra de botones estamos navegando, lo que nos permitirá completar´la acción de cambio de posición de registro en el manejador del evento. Para los dos primeros ejemplos se trata de ocultar todos los DIV y presentar el item al que nos movemos. En el último ejemplo cambia el atributo src de un elemento imagen descargándose la correspondiente imagen.

El segundo ejemplo es como el anterior pero modificando el estilo de los botones:

Ejemplo: Barra de navegación de registros personalizada

Ejemplo B)
1 Contenido del primer registro.
2 Contenido del segundo registro.
3 Contenido del tercer registro.
4 Contenido del cuarto registro.
Registro 0 de 0
 #bar-arrow-b {
     font-size: 10px;
     color: maroon;
 }

Sólo tenemos que actuar sobre el contenedor que alberga los botones, especificando un tamaño de fuente y un color. Ambas propiedades se heredan y por lo tanto se trasladan a los botones. Usando la propiedad currentColor transferimos el color de primer plano al color de fondo y de bordes para los iconos de los botones.

El último ejemplo nos sirve como pasa-fotos. En la variable wxL.listaImagenes del módulo local tenemos un array de arrays de imágenes, con la URL y el comentario para poner al pie.

Ejemplo: Barra de navegación de registros para un pasa-fotos

Fractales generados con aplicación trazadora de gráficas matemáticas
Registro 0 de 0
Conjunto de Mandelbrot. Una muestra de hasta donde podemos llegar con el trazador de gráficas matemáticas para generar fractales.

Como verá en el código más arriba expuesto, al mover el registro cambiamos el atributo src de un elemento <img id="pfotos-img"> y agregamos el comentario en un contenedor al pie de la imagen.