Fecha de publicación: 2010/02/12
En las tablas de datos hechas con (X)HTML no es infrecuente encontrarse con que el encabezado de algunas columnas es un enlace que sirve para ordenar los datos en orden ascendente o descendente. Habitualmente se utiliza una imagen para indicar la ordenación que se ha seleccionado, pero, ¿es accesible?
Fecha de publicación: 2009/12/27
2.4.4 Link Purpose (In Context): The purpose of each link can be determined from the link text alone or from the link text together with its programmatically determined link context, except where the purpose of the link would be ambiguous to users in general. (Level A).
El propósito de cada enlace debe ser determinado unicamente por su propio texto. También puede ser determinado por su contexto salvo si resultase ambiguo (esto es mejor evitarlo si resulta posible).
Fecha de publicación: 2009/12/08
Recientemente estuve creando una clase para generar formularios sencillos en PHP mediante JSON.
Le faltan bastantes detalles, pero se puede utilizar en la generación de formularios que no tengan mucha complejidad.
Tipos de campo:
FORM.FIELDSET.<input type="text" />).<input type="password" />).<input type="checkbox" />).<input type="radio" />).<input type="file" />).<select>[...]</select>),<textarea>[...]</textarea>),Fecha de publicación: 2009/11/11
Suele pasar que cuando hay capas emergentes y animaciones flash la capa se queda por debajo del Flash.
Basta con añadir esto dentro del elemento OBJECT:
<param name="wmode" value="transparent" />
Un ejemplo completo:
<object type="application/x-shockwave-flash" data="fichero.swf">
<param name="wmode" value="transparent" />
<param name="movie" value="fichero.swf" />
<param name="quality" value="high" />
<p>No dispone del plugin Flash Player, si lo desea puede <a href="http://www.adobe.com/go/getflashplayer">descargar el plugin</a>. [Resto de contenido alternativo].</p>
</object>
Este método tiene una pega: no funciona en sistemas Linux.
Fecha de publicación: 2009/06/25
Actualizado 2009-06-30.
Según Jakob Nielsen Ocultar los caracteres de un campo de contraseña (mediante <input type="password" id="ejemplo" name="ejemplo" />) puede ocasionar algunos problemas. Considero que se pasa un poco, de modo que expongo los que creo que son más importantes:
He de aclarar que en bastantes dispositivos móviles en los campos de contraseña el caracter introducido está visible durante un breve espacio de tiempo, pero puede que no sea suficiente.
Nielsen también menciona que ocultar los caracteres de contraseña puede hacer que los usuarios se desanimen a entrar en un sitio web. Esto me parece un poco exagerado.
Puede que sea interesante dejar visibles los caracteres de los campos de contraseña, pero, si nos decantamos por esa opción habrá usuarios que no se sientan seguros, ya que la contraseña será visible por otras personas (imaginemos punto de acceso a Internet en un lugar público). Aquí Nielsen sugiere el uso de un checkbox para activar o desactivar la ocultación de los caracteres.
Capturas de la pantalla de encriptación de WinZip 12, en las que se puede apreciar la casilla que alterna la contraseña visible u oculta:


Fecha de publicación: 2009/05/11
Diez consejos útiles que mejorarán ostensiblemente la accesibilidad de un sitio web:
H1-H6) para estructurar el documento y utilizar adecuadamente los párrafos y listas. En los encabezados no se saltan niveles. Cuando una lista tiene un solo elemento no es una lista: es un párrafo.DIV), en lugar de con tablas.onclick, onmouseover, etc.) para eventos de Javascript.alt). El atributo irá vacío en caso contrario. Si es una imagen que se repite en diversas páginas, no es informativa, y no es publicable, debería ir como fondo mediante CSS.FIELDSET con sus correspondientes LEGEND, elementos INPUT, SELECT y TEXTAREA contenidos dentro del elemento LABEL (asociación implícita), y atributo for en el LABEL y atributo id en el elemento de campo con el mismo valor (asociación explícita).OBJECT para añadir contenido en Flash o en Java, incluyendo el contenido alternativo correspondiente para aquellos usuarios que no dispongan del plugin.SCRIPT (que solo ha de incluirse dentro del elemento HEAD y nunca fuera de él), ni código CSS en el elemento STYLE (de hecho a la CSS se la llama con el elemento LINK).Fecha de publicación: 2008/12/02
He aquí una función en PHP para generar un calendario con XHTML semántico y accesible, y con clases e identificadores adecuados para aplicarle rápidamente el CSS.
La función lleva cuatro parámetros, todos ellos opcionales, en el siguiente orden:
Ejemplo de llamada a la función con el mes de febrero de 2009, los fines de semana desactivados, los días nulos activados y un encabezado de nivel 3:
calendario(2009,2,0,1,3);
Descargar archivo con ejemplo funcional o verlo en acción (abre en ventana nueva).
Funciones para generar el calendario (para mayor seguridad, utilizar el código del ejemplo descargable):
function calendario ($year,$mes,$finDeSemana=1,$mostrarDiasNulos=1,$nivelH=2) {
if (strlen($year)!=4) {$year=date('Y');}
if (($mes<1 or $mes>12) or (strlen($mes)<1 or strlen($mes)>2)) {$year=date('n');}
// Listados: días de la semana, letra inicial de los días de la semana, y meses
$dias = array('Lunes','Martes','Miércoles','Jueves','Viernes','Sábado','Domingo');
$diasAbbr = array('L','M','M','J','V','S','D');
$meses = array('Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiempre','Octubre','Noviembre','Diciembre');
// Se sacan valores que se utilizarán más adelante
$diaInicial = gmmktime(0,0,0,$mes,1,$year); // Primer día del mes dado
$diasNulos = (date("N",$diaInicial))-1; // Con 'N' la semana empieza en Lunes. Con 'w', en domingo
if($diasNulos<0){$diasNulos = 7-abs($diasNulos);}
$diasEnMes = date("t",$diaInicial); // Número de días del mes dado
// Se abre la capa contenedora y se genera el encabezado del bloque de calendario
$html .= '<div id="calendario">';
$html .= '<h'.$nivelH.' class="encabezadoCalendario">Calendario</h'.$nivelH.'>';
// Párrafos con la fecha actual y la fecha seleccionada
$html .= '<p>Fecha actual: '.date('j').' de '.$meses[(intval(date('n'))-1)].' de '.date('Y').'</p>';
$html .= '<p>Fecha seleccionada: ';
if (isset($_GET['dia'])) {$html .= ''.$_GET['dia'].' de ';} // El día solo sale si se ha definido previamente en el parámetro 'dia' de la URL
$html .= ''.$meses[($mes-1)].' de '.$year.'</p>';
$html .= '<div class="tabla">';
// Enlaces al mes anterior y al siguiente
$html .= '<p>Navegación por meses:</p>';
$html .= '<ul id="calNavMeses">';
$enlaceAnterior1 = gmmktime(0,0,0,($mes-1),1,$year);
$mesAnterior = date('n',$enlaceAnterior1);
$yearMesAnterior = date('Y',$enlaceAnterior1);
$enlaceSiguiente1 = gmmktime(0,0,0,($mes+1),1,$year);
$mesSiguiente = date('n',$enlaceSiguiente1);
$yearMesSiguiente = date('Y',$enlaceSiguiente1);
$html .= '<li class="anterior"><a href="?mes='.$mesAnterior.'&ano='.$yearMesAnterior.'"><span>Mes anterior ('.$meses[($mesAnterior-1)].')</span></a></li>';
$html .= '<li class="siguiente"><a href="?mes='.$mesSiguiente.'&ano='.$yearMesSiguiente.'"><span>Mes siguiente ('.$meses[($mesSiguiente-1)].')</span></a></li>';
$html .= '</ul>';
// Enlaces al año anterior y al siguiente
$html .= '<p>Navegación por años:</p>';
$html .= '<ul id="calNavYears">';
$enlaceAnterior2 = gmmktime(0,0,0,$mes,1,($year-1));
$yearAnterior = date('Y',$enlaceAnterior2);
$enlaceSiguiente2 = gmmktime(0,0,0,$mes,1,($year+1));
$yearSiguiente = date('Y',$enlaceSiguiente2);
$html .= '<li class="anterior"><a href="?mes='.$mes.'&ano='.$yearAnterior.'"><span>Año anterior (</span>'.$yearAnterior.'<span>)</span></a></li>';
$html .= '<li class="siguiente"><a href="?mes='.$mes.'&ano='.$yearSiguiente.'"><span>Año siguiente (</span>'.$yearSiguiente.'<span>)</span></a></li>';
$html .= '</ul>';
// Se abre la tabla que contiene el calendario
$html .= '<table>';
// Título mes-año (elemento CAPTION)
$mesLista = $mes-1;
$html .= '<caption>'.$meses[$mesLista].'<span> de</span> '.$year.'</caption>';
// Se definen anchuras en elementos COL
$cl=0; $anchoCol=100/7; while ($cl<7) {$html .= '<col width="'.$anchoCol.'%" />'; $cl++;}
// Fila de los días de la semana (elemento THEAD)
$html .= '<thead><tr>';$d=0;
while ($d<7) {$html .= '<th scope="col" abbr="'.$dias[$d].'">'.$diasAbbr[$d].'</th>';$d++;}
$html .= '</tr></thead>';
// Se generan los días nulos (días del mes anterior o posterior) iniciales, el TBODY y su primer TR
$html .= '<tbody>';
if ($diasNulos>0) {$html .= '<tr>';} // Se abre el TR solo si hay días nulos
if ($diasNulos>0 and $mostrarDiasNulos==0) {$html .= '<td class="nulo" colspan="'.$diasNulos.'"></td>';} // Se hace un TD en blanco con el ancho según los día nulos que haya
if ($mostrarDiasNulos==1) { // Generación de los TD con días nulos si está activado que se muestren
$dni=$diasNulos;$i=0;
while ($i<$diasNulos) {
$enSegundosNulo = gmmktime(0,0,0,$mes,(1-$dni),$year);
$dmNulo = date('j',$enSegundosNulo);
$idFechaNulo = 'cal-'.date('Y-m-d',$enSegundosNulo);
$html .= '<td id="'.$idFechaNulo.'" class="diaNulo"><span class="dia"><span class="enlace">'.$dmNulo.'</span></span></td>';
$dni--;
$i++;
}
}
// Se generan los TD con los días del mes
$dm=1;$x=0;$ds=$diasNulos+1;
while ($dm<=$diasEnMes) {
if(($x+$diasNulos)%7==0 and $x!=0) {$html .= '</tr>';} // Se evita el cierre del TR si no hay días nulos iniciales
if(($x+$diasNulos)%7==0) {$html .= '<tr>';$ds=1;}
$enSegundosCalendario = gmmktime(0,0,0,$mes,$dm,$year); // Fecha del día generado en segundos
$enSegundosActual = gmmktime(0,0,0,date('n'),date('j'),date('Y')); // Fecha actual en segundos
$enSegundosSeleccionada = gmmktime(0,0,0,$_GET['mes'],$_GET['dia'],$_GET['ano']); // Fecha seleccionada, en segundos
$idFecha = 'cal-'.date('Y-m-d',$enSegundosCalendario);
// Se generan los parámetros de la URL para el enlace del día
$link_dia = date('j',$enSegundosCalendario);
$link_mes = date('n',$enSegundosCalendario);
$link_year = date('Y',$enSegundosCalendario);
// Clases y etiquetado general para los días, para día actual y para día seleccionado
$claseActual='';$tagDia='span';
if ($enSegundosCalendario==$enSegundosActual) {$claseActual=' fechaHoy';$tagDia='strong';}
if ($enSegundosCalendario==$enSegundosSeleccionada and isset($_GET['dia'])) {$claseActual=' fechaSeleccionada';$tagDia='em';}
if ($enSegundosCalendario==$enSegundosActual and $enSegundosCalendario==$enSegundosSeleccionada and isset($_GET['dia'])) {$claseActual=' fechaHoy fechaSeleccionada';$tagDia='strong';}
// Desactivación de los días del fin de semana
if (($ds<6 and $finDeSemana==0) or $finDeSemana!=0) { // Si el fin de semana está activado, o el día es de lunes a viernes
$tagEnlace='a';
$atribEnlace='href="?dia='.$link_dia.'&mes='.$link_mes.'&ano='.$link_year.'"';
} if ($ds>5 and $finDeSemana==0) { // Si el fin de semana está desactivado y el día es sábado o domingo
$tagEnlace='span';
$atribEnlace='';
$paramFinde='0';
}
// Con las variables ya definidas, se crea el HTML del TD
$html .= '<td id="'.$idFecha.'" class="'.calendarioClaseDia($ds).$claseActual.'"><'.$tagDia.' class="dia"><'.$tagEnlace.' class="enlace" '.$atribEnlace.'>'.$dm.'</'.$tagEnlace.'></'.$tagDia.'></td>';
$dm++;$x++;$ds++;
}
// Se generan los días nulos finales
$diasNulosFinales = 0;
while((($diasEnMes+$diasNulos)%7)!=0){$diasEnMes++;$diasNulosFinales++;}
if ($diasNulosFinales>0 and $mostrarDiasNulos==0) {$html .= '<td class="nulo" colspan="'.$diasNulosFinales.'"></td>';} // Se hace un TD en blanco con el ancho según los día nulos que haya (si no se activa mostrar los días nulos)
if ($mostrarDiasNulos==1) { // Generación de días nulos (si se activa mostrar los días nulos)
$dnf=0;
while ($dnf<$diasNulosFinales) {
$enSegundosNulo = gmmktime(0,0,0,($mes+1),($dnf+1),$year);
$dmNulo = date('j',$enSegundosNulo);
$idFechaNulo = 'cal-'.date('Y-m-d',$enSegundosNulo);
$html .= '<td id="'.$idFechaNulo.'" class="diaNulo"><span class="dia"><span class="enlace">'.$dmNulo.'</span></span></td>';
$dnf++;
}
}
// Se cierra el último TR y el TBODY
$html .= '</tr></tbody>';
// Se cierra la tabla
$html .= '</table>';
// Se cierran la capa de la tabla y la capa contenedora
$html .= '</div>';
$html .= '</div>';
// Se devuelve la variable que contiene el HTML del calendario
return $html;
}
function calendarioClaseDia ($dia) {
switch ($dia) {
case 1: $clase = 'lunes semana'; break;
case 2: $clase = 'martes semana'; break;
case 3: $clase = 'miercoles semana'; break;
case 4: $clase = 'jueves semana'; break;
case 5: $clase = 'viernes semana'; break;
case 6: $clase = 'sabado finDeSemana'; break;
case 7: $clase = 'domingo finDeSemana'; break;
}
return $clase;
}
Actualizado 2008-12-03: Se añade la posibilidad de desactivar los fines de semana.
Actualizado 2008-12-04: Se añade la posibilidad de desactivar o activar los días de la primera semana que son del mes anterior y los de la última que son del mes siguiente (denominados como días nulos). Se explican los parámetros a introducir al llamar a la función.
Actualizado 2008-12-05: Mejoras de accesibilidad.
Actualizado 2009-02-01: Corregido el fragmento de código expuesto en la página.
A diferencia de otros contenidos la licencia para este artículo y el ejemplo adjunto es Reconocimiento-Compartir bajo la misma licencia 3.0 España.
Fecha de publicación: 2008/11/30
Actualización 2010-03-10: Código de ejemplo corregido (faltaban algunos caracteres).
No es necesario copiar todos estos fragmentos de código: al final del artículo se muestra completo.
El primer paso es definir cuantos registros se quieren mostrar en cada página, en este caso se deja en 10:
// Registros a mostrar en cada página
$regVistos = 10;
Se define una consulta que recupere todos los registros que se vayan a mostrar, con los parámetros mínimos imprescindibles:
// Consulta que devuelve todos los registros
$lista0 = mysql_query(" SELECT * FROM registros");
Se cuentan los registros recuperados por la consulta MySQL:
// Se cuentan los registros devueltos por la consulta SQL $lista0
$totalSql = mysql_num_rows($lista0);
Con los registros que salen divididos por los que se quiere que se vean por cada página, se obtienen las páginas en las que se dividirá el listado de resultados. Se redondea siempre hacia arriba con ceil, ya que si, por ejemplo, salen 3,4 páginas, ese 0,4 que queda suelto, debe ocupar una página entera:
// Páginas que van a aparecer, redondeando los decimales siempre hacia arriba
$pagTotal = ceil($totalSql/$regVistos);
Son también necesarios los datos de la página actual, la anterior y la siguiente. En este ejemplo la página actual se recoge del parámetro de la URL pag. Si ese parámetro no está definido, se predetermina en 1. Las páginas anterior y siguiente se obtienen restando o sumando 1 según sea necesario:
// Se definen la página actual (desde el parámetro 'pag' de la URL) y las páginas anterior y siguiente
if (!isset($_GET['pag'])) {$pagActual=1;} else {$pagActual=$_GET['pag'];}
$pagAnterior = $pagActual-1;
$pagSiguiente = $pagActual+1;
Es el momento de realizar la consulta MySQL para mostrar los registros. En este caso la consulta devolverá los mismos registros que en la anterior, pero se le ha añadido ordenación descendente (es opcional) y el parámetro LIMIT. El parámetro LIMIT contiene dos números separados por comas: el primero es que el registro por el que se empieza el listado (la página actual menos 1 por los registros a mostrar en cada página), y el segundo el número de registros a mostrar. Ejemplo:
// Consulta SQL con la que se sacará el listado de registros
$lista1 = mysql_query(" SELECT * FROM registros ORDER BY campo DESC LIMIT ".(($pagActual-1)*$regVistos).",".$regVistos."");
// Bucle para generar el listado de registros
while($fila = mysql_fetch_assoc($lista1)) {
// Aquí irá el código PHP que escriba los registros
}
Finalmente se genera el listado de páginas mediante una lista desordenada (UL).
El primer elemento de la lista será el enlace a la página anterior, mostrándose con la condición de que la página actual no sea la primera.
Se mostrarán los números de página mediante un bucle, destacando la página actual mediante un elemento strong.
Y el último elemento de la lista será el enlace a la página siguiente.
Ejemplo:
// Se inicia el listado de páginas
echo '<ul>';
// Si la página actual no es la primera, se muestra el enlace a la página anterior
if ($pagAnterior>0) {echo '<li class="anterior"><a href="lista.php?pag='.$pagAnterior.'"><span class="oculto">Página </span>Anterior</a></li>';}
// Se saca el listado de páginas mediante un bucle
$pgIntervalo = 3; // Páginas que aparecen antes y después de la actual
$pgMaximo = ($pgIntervalo*2)+1; // Máximo de páginas en el listado
$pg=$pagActual-$pgIntervalo;$i=0;
while ($i<$pgMaximo) {
if ($pg==$pagActual) {$strong=array('<strong>','</strong>');} else {$strong=array('','');}
if ($pg>0 and $pg<=$pagTotal) {
echo '<li>'.$strong[0].'<a href="lista.php?p='.$_GET['p'].'&pag='.$pg.'"><span class="oculto">Página </span>'.$pg.'</a>'.$strong[1].'</li>';
$i++;
}
if ($pg>$pagTotal) {$i=$pgMaximo;} // Si la página que se va a mostrar se pasa de la cantidad de páginas definidas en $pagTotal se para la generación de elementos de lista
$pg++;
}
// Si la página actual no es la última, se muestra el enlace a la página siguiente
if ($pagSiguiente<=$pagTotal) {echo '<li class="siguiente"><a href="lista.php?p='.$_GET['p'].'&pag='.$pagSiguiente.'"><span class="oculto">Página </span>Siguiente</a></li>';}
// Se finaliza el listado de páginas
echo '</ul>';
Y de esta forma se obtiene una paginación en PHP.
<?
// Registros a mostrar en cada página
$regVistos = 10;
// Consulta que devuelve todos los registros
$lista0 = mysql_query(" SELECT * FROM registros");
// Se cuentan los registros devueltos por la consulta SQL $lista0
$totalSql = mysql_num_rows($lista0);
// Páginas que van a aparecer, redondeando los decimales siempre hacia arriba
$pagTotal = ceil($totalSql/$regVistos);
// Se definen la página actual (desde el parámetro 'pag' de la URL) y las páginas anterior y siguiente
if (!isset($_GET['pag'])) {$pagActual=1;} else {$pagActual=$_GET['pag'];}
$pagAnterior = $pagActual-1;
$pagSiguiente = $pagActual+1;
// Consulta SQL con la que se sacará el listado de registros
$lista1 = mysql_query(" SELECT * FROM registros ORDER BY campo DESC LIMIT ".(($pagActual-1)*$regVistos).",".$regVistos."");
// Bucle para generar el listado de registros
while($fila = mysql_fetch_assoc($lista1)) {
// Aquí irá el código PHP que escriba los registros
}
// Se inicia el listado de páginas
echo '<ul>';
// Si la página actual no es la primera, se muestra el enlace a la página anterior
if ($pagAnterior>0) {echo '<li class="anterior"><a href="lista.php?pag='.$pagAnterior.'"><span class="oculto">Página </span>Anterior</a></li>';}
// Se saca el listado de páginas mediante un bucle
$pgIntervalo = 3; // Páginas que aparecen antes y después de la actual
$pgMaximo = ($pgIntervalo*2)+1; // Máximo de páginas en el listado
$pg=$pagActual-$pgIntervalo;$i=0;
while ($i<$pgMaximo) {
if ($pg==$pagActual) {$strong=array('<strong>','</strong>');} else {$strong=array('','');}
if ($pg>0 and $pg<=$pagTotal) {
echo '<li>'.$strong[0].'<a href="lista.php?p='.$_GET['p'].'&pag='.$pg.'"><span class="oculto">Página </span>'.$pg.'</a>'.$strong[1].'</li>';
$i++;
}
if ($pg>$pagTotal) {$i=$pgMaximo;} // Si la página que se va a mostrar se pasa de la cantidad de páginas definidas en $pagTotal se para la generación de elementos de lista
$pg++;
}
// Si la página actual no es la última, se muestra el enlace a la página siguiente
if ($pagSiguiente<=$pagTotal) {echo '<li class="siguiente"><a href="lista.php?p='.$_GET['p'].'&pag='.$pagSiguiente.'"><span class="oculto">Página </span>Siguiente</a></li>';}
// Se finaliza el listado de páginas
echo '</ul>';
?>
Fecha de publicación: 2008/10/26
Hace más de un año saltó el escándalo del aberrante sitio web del Congreso de los Diputados (no por el aspecto, sino por el código y la accesibilidad).
En aquel momento se podían ver varias declaraciones de DTD a lo largo del código fuente, varias aperturas del elemento BODY y aberraciones similares.
En este momento parece que han solucionado algunos problemas. Pero está maquetada en tablas y tiene Javascript intrusivo. En el HEAD tiene elementos STYLE llenitos de reglas CSS (todo eso es mejor meterlo en un documento externo CSS y vincularlo al HTML con el elemento LINK). El sitio dispone de una página de accesibilidad, y al menos tienen la decencia de no colgarse medallas y decir que están en ello.
Han pasado 16 meses desde que se vio el asunto: ¿no es suficiente tiempo para arreglar un problema de maquetación?
Documento RTF (se abre con cualquier procesador de textos) con el HTML de la página de inicio del Congreso en el 26 de octubre de 2008 (este documento puede herir su sensibilidad y/o dañarle la vista: lo abre bajo su propia responsabilidad).
Otro detalle. Parece que el engendro ha sido puesto en marcha por Indra y por Telefónica. Son empresas mas o menos poderosas. ¿No tienen en sus filas ni un solo maquetador cualificado? Si no lo tienen, ¿no pueden subcontratarlo? ¿No hay nadie del Congreso supervisando la calidad del sitio web que han montado (y viendo si han tirado el dinero)?
Pagadores de impuestos, como bien dijo CCCP diputada en el Congreso:
Estamos manejando dinero público, y el dinero público no es de nadie.