Archivo Mensual de Mayo, 2009

Transformar URLs virtuales en absolutos con ASP.NET

Una de las ventajas que tiene ASP.NET sobre otras plataformas de desarrollo es que las aplicaciones son auto-contenidas en proyectos y al administrador del servidor se le facilita instalarlas en diferentes áreas de un sitio web.

Claro, esto solo funciona si la aplicación fue desarrollada teniendo en cuenta que puede quedar instalada en diferentes carpetas, porque si el desarrollador dejó URLs fijos en algún lado esa parte de la aplicación se va a dañar si no se instala en la misma carpeta.

Por ejemplo, si el logo de la aplicación está en "/Images/logo.png" y el desarrollador puso el URL fijo desde la raíz, cuando la aplicación se instale en otra carpeta el logo no va a aparecer.

¿Cómo evitamos este problema? Fácil: utilizando URLs virtuales que ASP.NET luego transforma en absolutos dependiendo de la carpeta donde se haya instalado la aplicación. Por ejemplo, si especificamos el logo de esta forma:

<asp:Image ID="iLogo" runat="server" ImageUrl="~/Images/Logo.png" />

ASP.NET lo convertirá automáticamente en una etiqueta <IMG> con el URL apropiado dependiendo de la carpeta donde se haya instalado la aplicación.

Los controles que necesitan URLs hacen esta conversión por nosotros cuando es apropiado. Pero ¿Cómo utilizar URLs virtuales cuando no estamos usando controles? Por ejemplo, en una etiqueta <LINK> o <SCRIPT>.

En el fichero .ASPX o en el code-behind podemos usar el método ResolveClientUrl() que realiza la misma función. Por ejemplo, en una etiqueta <SCRIPT> podemos hacer esto:

<script src="<%= ResolveClientUrl("~/Scripts/effects.js") %>" type="text/javascript">
</script>

Este método está definido en la clase Control y tanto Page como UserControl hereda en algún punto de Control.

Ahora, ¿Como transformar un URL virtual en una clase que no hereda de Control? Por ejemplo en una clase Presenter que debe transformar URLs virtuales almacenados en la base de datos.

En estos casos podemos utilizar los métodos estáticos de la clase VirtualPathUtility. Por ejemplo, suponga que tengo una lista con los URLs de documentos adjuntos que quiero pasar a la vista transformados en URLs absolutos. Lo puedo hacer de esta forma:

// En el Presenter
public string Attachments
{
    get
    {
        foreach (var url in m_attachments)
            yield return VirtualPathUtility.ToAbsolute(url);
    }
}

Utilizando URLs virtuales podemos asegurarnos que nuestra aplicación va a funcionar de la misma forma ya sea que la instalen en la raíz del sitio web como si la instalan en otra carpeta y como vieron ASP.NET nos facilita bastante el trabajo con los diferentes métodos para transformarlos.

Comentarios condicionales para mantener la cordura

Si pudiéramos convencer a nuestros clientes de que deben actualizar sus browsers esta página probablemente no sería necesaria, pero como Internet Explorer 6 (un programa que salió en el 2001) todavía es ampliamente utilizado hay que mantener los sitios compatibles con estos browsers antiguos.

Crear un sitio web que se vea igual en diferentes browsers puede hacer que un desarrollador web pierda la razón cuando llegue el momento de corregir los problemas que se presentan en Internet Explorer.

Mi método para crear un sitio web es armar el diseño en Firefox (porque con Firebug y Developer Toolbar tiene el mejor ambiente de desarrollo) hasta que se ve igual a la imagen que me entrega el diseñador. A continuación pruebo en Safari y Chrome con los cuales casi nunca tengo que cambiar nada.

Pero luego llega la hora de probar con Internet Explorer 6 y 7 y lo más probable es que el diseño esté completamente destrozado. Una columna debajo de la otra y con diferentes anchos, fondos y fuentes que no son los correctas, PNGs sin transparencia. Todo esto muy común en versiones anteriores de Internet Explorer.

Se puede probar agregando hacks al CSS (secuencias de caracteres que debido a bugs en los mismos browsers permiten entregar código diferente a algunos browsers) pero siempre me ha quedado un mal sabor de boca al usar estos hacks ya que el código CSS se convierte en un desorden y, claro, ya no valida.

La forma más sencilla que he encontrado es colocar comentarios condicionales al HTML para que diferentes versiones de Internet Explorer carguen hojas de estilo adicionales. Por ejemplo, en la cabecera de la página uno puede colocar:

<link href="general.css" media="screen" rel="stylesheet" type="text/css" />
<!--[if lte IE 7]><link href="ie7below.css" rel="stylesheet" type="text/css" /><![endif]-->
<!--[if lte IE 6]><link href="ie6below.css" rel="stylesheet" type="text/css" /><![endif]-->

En este caso, Firefox, Safari, Chrome e Internet Explorer 8 verán solo la primera hoja de estilos (general.css). Internet Explorer 7 verá general.css e ie7below.css y en esta última agregaremos lo que necesitemos para arreglar la página. Finalmente Internet Explorer 6 verá las tres hojas de estilo y en ie6below.css agregaremos las reglas específicas para IE6.

Veamos cómo funciona: Suponiendo que tenemos el siguiente código HTML:

<div id="wrapper">
	<div id="sidebar">
		<ul>
			<li><a href="/">Inicio</a></li>
		</ul>
	</div>

	<div id="content">
		<p>Bienvenido a mi sitio web.</p>
	</div>
</div>

… y queremos que se vea como dos columnas de contenido, podemos hacer algo como esto para los browsers modernos (Firefox, Safari, IE8):

/* general.css */
#wrapper {
	margin: 0 auto;
	overflow: hidden;
	width: 970px;
        margin
}
#sidebar {
	background-color: #CCC;
	float: left;
	padding: 10px 15px;
	width: 150px;
}
#content {
	background-color: #EFEFEF;
	margin-left: 180px;
	padding: 10px 15px;
}

En Internet Explorer 8 el diseño ya se ve como en esta imagen:

ie8

Ahora probemos en Internet Explorer 7 y por supuesto, el “margin: 0 auto” que utilizo para centrar las columnas en la página no funciona y los espacios en el sidebar están diferentes:

ie7

Para arreglarlo abrimos ie7below.css y agregamos el siguiente código:

body { text-align: center }
#wrapper { text-align: left }
#sidebar { zoom: 1 }

Ahora probamos en Internet Explorer 6 y vemos que las columnas tienen diferentes anchos:

ie6

Esto es porque en IE6 hay que incluir el padding en el ancho de la columna. Entonces cambiamos los anchos en el archivo ie6below.css:

#wrapper { width: 590px }
#sidebar { width: 130px }

Perfecto, ya se ve igual en estas tres versiones de Internet Explorer.

Utilizando comentarios condicionales mantenemos nuestra hoja de estilo general mucho más sencilla (la que funciona en browsers modernos) y aun así nos aseguramos que versiones anteriores de Internet Explorer funcionen correctamente.

Para mayor información sobre los comentarios condicionales les recomiendo visitar Quirks Mode – Conditional Comments

Paginación estilo Digg con MySQL y PHP

Cuando se tiene una buena cantidad de resultados es conveniente presentarlos al usuario página por página para facilitar la navegación. Esto es algo que se pregunta a menudo en los foros de PHP “Tengo una petición que me regresa muchos registros. ¿Cómo separo los resultados en varias páginas?”.

En este tutorial voy a mostrar un método para dividir los resultados en varias páginas y al final le daremos un estilo similar a Digg. Lo que queremos obtener es un listado de 12 películas por página y en la parte inferior enlaces a páginas similar al que usa Digg, que se ven así:

digg

Para efectos de demostración voy a utilizar la base de datos Sakila que es una base de datos de ejemplo desarrollada para MySQL. Específicamente queremos mostrar la información de las películas almacenadas en la tabla film.

A continuación voy a discutir este script de ejemplo de paginación estilo Digg con MySQL y PHP. Para probarlo en su equipo deberá tener instalado Sakila. Guárdelo en una carpeta de su servidor web y cambie los datos de la conexión a la base de datos que se encuentran al comienzo:

$hostname    = 'localhost';
$username    = 'root';
$password    = '123456';
$database    = 'sakila';

Las consultas

Primero necesitamos hacer una consulta para obtener el número de registros en la tabla y lo almacenamos en una variable $filmsCount:

select count(*) filmsCount
	from film

Ahora calculamos el número máximo de páginas que podemos mostrar. Para esto utilizamos el número de registros que acabamos de obtener y lo dividimos entre el número de registros por página. Aquí estoy usando la función ceil(), que redondea el numero hacia arriba, porque también tenemos que contar la última página que puede quedar con menos registros que las demás.

$pagesCount = (int)ceil($filmsCount / $rowCount);

Ahora averiguamos en qué página estamos. En el URL va a venir un parámetro pageIndex con el índice de la página. Si no está presente suponemos que estamos en la primer página y si sobrepasa la última página suponemos que estamos en la página actual.

$pageIndex = isset($_REQUEST['pageIndex']) ? (int)$_REQUEST['pageIndex'] : 0;
if ($pageIndex >= $pagesCount)
	$pageIndex = $pagesCount - 1;

Estamos listos para obtener los registros de la página. El SELECT de MySQL tiene unos parámetros justo para esto. Se pasan con la cláusula LIMIT de esta forma

$offset = $pageIndex * $rowCount;
$sql = "
select *
	from film
	order by title
	limit $offset, $rowCount
";

$rowCount es el número de registros por página y puede ser el número que usted quiera. En el ejemplo estoy usando 12. Los registros los almaceno como objetos en un arreglo $films y luego recorro el arreglo para mostrar cada registro.

Los enlaces de las páginas

Ahora la parte más delicada: Mostrar el listado de páginas con el estilo de Digg. Vamos a crear una lista <UL> donde cada elemento va a ser un enlace a la página correspondiente.

El enlace a la página anterior es el más sencillo. Simplemente, si estamos en una página mayor a la primera mostramos el enlace:

<ul id="pages">
<?php
// Página anterior.
if ($pageIndex > 0) {
?>
	<li class="page"><a href="?pageIndex=<?php echo $pageIndex - 1 ?>">&lquo;</a></li>
<?php } ?>

Ahora mostraríamos la página actual rodeada de N páginas adelante y atrás. El número de páginas a mostrar lo guardo en la variable $pagesToShow. Entonces calculamos las páginas que se van a mostrar así:

$start = $pageIndex - $pagesToShow;
if ($start < 0)
	$start = 0;

$end = $pageIndex + $pagesToShow;
if ($end >= $pagesCount)
	$end = $pagesCount - 1;

$start y $end los usaremos luego en un ciclo; sin embargo, hay que tener en cuenta la primera y segunda página. En Digg siempre se muestran los enlaces de la primera y segunda página. No importa si uno está en la página 500, siempre se muestran. Entonces los mostramos teniendo en cuenta que no se pueden repetir después:

if ($start > 0) {
	for ($i = 0; $i < 2 && $i < $start; ++$i) {
?>
	<li class="page"><a href="?pageIndex=<?php echo $i ?>"><?php echo $i + 1 ?></a></li>
<?php
	}
}

Ahora si podemos mostrar las páginas con toda tranquilidad. Lo único que tenemos que tener en cuenta es que a la página actual no le ponemos enlace:

for ($i = $start; $i <= $end; ++$i) {
	if ($pageIndex == $i) {
?>
	<li class="page current"><?php echo $i + 1 ?></li>
<?php
	} else {
?>
	<li class="page"><a href="?pageIndex=<?php echo $i ?>"><?php echo $i + 1 ?></a></li>
<?php
	}
}

Ya vamos terminando… De forma similar a la primera y segunda página, la penúltima y última siempre se muestran. Pero tenemos que evitar repetirlas así que usamos la función max() para evitarlo:

if ($end < $pagesCount - 1) {
	for ($i = max($pagesCount - 2, $end + 1); $i < $pagesCount; ++$i) {
?>
	<li class="page"><a href="?pageIndex=<?php echo $i ?>"><?php echo $i + 1 ?></a></li>
<?php
	}
}

Y para finalizar mostramos el enlace a la página siguiente que es bastante sencillo y cerramos el <UL>:

<?php
// Siguiente página
if ($pageIndex < $pagesCount) {
?>
	<li class="page"><a href="?pageIndex=<?php echo $pageIndex + 1 ?>">&gt;</a></li>
<?php } ?>
</ul>

Ahora necesitamos algo de CSS para que se parezca al de Digg. Lo que tenemos que hacer cambiar la lista de dirección vertical a horizontal con un display: inline; para los elementos, agregar los bordes, unos paddings y estamos listos. Este es el código CSS que yo utilicé en el ejemplo:

#pages {
	clear: both;
	text-align: center;
}
#pages li {
	display: inline;
	padding: 3px 6px 4px;
}
#pages li.page {
	border: 1px solid #CCC;
}
#pages li.current {
	background-color: #CCC;
}

El resultado final es una página como la de la captura de pantalla en la que podemos ver 12 películas por páginas y usar los enlaces de la parte inferior para navegar por las páginas.

paginacion-estilo-digg

En una aplicación más grande, la lógica necesaria para mostrar los enlaces de las páginas debería encapsularse en una clase para evitar errores cuando se desee reutilizar en otro listado y el HTML podría guardarse en una plantilla separada. Este ejercicio se lo dejo quienes lean este blog… si, cualquiera de ustedes tres.

ACTUALIZACIÓN 2009/05/29: Como correctamente apunta Jairo, a los enlaces de la paginación hay que agregarle cualquier parámetro que modifique los resultados del SELECT. Por ejemplo, si hay que filtrar los films por el precio deberíamos pasarlo al final del URL así:

<?php
// Siguiente página
if ($pageIndex < $pagesCount) {
?>
	<li class="page"><a href="?pageIndex=<?php echo $pageIndex + 1,
		'&price=', $price ?>">&gt;</a></li>
<?php } ?>
</ul>

Eliminando el borde gris de Flash

Debido a que EOLAS demandó a Microsoft por una patente… Pausa para apreciar la ironía… Internet Explorer comenzó a agregar un borde gris alrededor de cualquier película flash y solo se elimina al hacerle clic para activarlas.

Este es el método más sencillo para eliminar ese molesto borde gris en las animaciones flash. Fué inventado por Tohle Býval y no podría ser más sencillo: Solo hay que incluir un script en la cabecera de la página.

IMPORTANTE: Antes de continuar, debo aclarar que este método solo funciona si se está abriendo la página desde un servidor web. Puede ser Apache o IIS montado en el mismo equipo o también el servidor de hosting que usted tenga. Pero si prueban a abrir el HTML diréctamente desde el disco no va a funcionar.

Bueno, no más preámbulos. Justo antes de cerrar la etiqueta <head> hay que agregar el siguiente código:

<!--[if IE]>
<script type="text/javascript" src="fix_eolas.js"
	defer="defer"></script>
<![endif]-->
</head>

Es de vital importancia que mantengan el defer=”defer” para que el script se ejecute cuando todas las películas ya hayan cargado. Además usamos comentarios condicionales para que solo Internet Explorer vea el script.

En esa etiqueta nos referimos a este archivo fix_eolas. El código es muy sencillo:

var objects = document.getElementsByTagName("object");

for (var i=0; i<objects.length; i++)
	objects[i].outerHTML = objects[i].outerHTML;

Lo que ese código hace es re-insertar todas las etiquetas <object>. Con eso es suficiente para eliminar el borde gris de activación.

Para recapitular, si ud. desea eliminar el borde gris solo tiene que hacer lo siguiente:

  1. Agregar la etiqueta a la cabecera.
  2. Guardar el archivo fix_eolas junto con el HTML.
  3. Asegurarse de que está probando desde un servidor web y no desde el disco duro.
  4. ???
  5. ¡Hacerse rico!

Ctrl + Alt + Del

Decidí reiniciar mi blog con el cambio de diseño. Ahh y ahora tiene nombre: “Punto Flotante”.

No se preocupen, el contenido que estaba antes lo iré publicando poco a poco a medida que lo vay re-editando. Esta vez quiero dedicarme más a publicar artículos técnicos que a los personales ya los tutoriales siempre fueron los más populares en este blog. Creo que el nuevo nombre también muestra ese objetivo.

Espero que no se olviden del blog y sigan visitándome.