sábado, 14 de julio de 2007

Acceder a valores de los campos de un formulario

Con html::tagValue ($campo) puedes acceder en la vista a los valores de los campos del modelo del que estás construyendo un formulario (son los que el Controller tenga en Controller::data).

O sea, cuando uno hace cosas como ésta:

$this->data = $this->Model->Read ()

puede acceder a esos datos mediante el método tagValue del HtmlHelper.

Muy útil para construir enlaces en la vista, sin tener que crear una variable de vista para guardarlos.

domingo, 8 de julio de 2007

Título de la página desde el controller

Conocía este consejillo de Armando Sosa, para definir el título de la página desde la vista. Pero buscando no sé qué en el API, encontré que si ajustas la variable 'title' en el controller para pasársela a la view, queda fijada como título de la página. O sea, en la acción deseada del controlador ponemos:

$this->set ('title', 'Lo que quieras que sea el título');

Actualización (8-7-07)

La parte "mala" es que $title no aparece como variable de la vista. Sin embargo, puedes usar $this->pageTitle en la vista para acceder a ella.

Diseño de Interfaz: barra de botones (2)

En el post anterior sobre este tema dejaba colgados un par de asuntos. Fundamentalmente, la solución al problema de tener que fijar una altura para el contenedor UL, a fin de que se muestre "envolviendo" los botones.

Por otro lado, tampoco está de más simplificar el ejemplo y reducirlo al mínimo necesario, de modo que los temas estéticos se resuelvan en cada caso particular.

El problema de la altura de un contenedor de elementos flotados

Veamos. Los elementos flotantes (que tienen propiedad float asignada) salen del flujo normal, por lo que se altera visualmente el tamaño o posición de sus elementos contenedores o de los que sigan el flujo normal a partir de ellos. Por esta razón, si en una lista (UL) todos los ítems (LI) son flotantes, la lista no obtiene altura, porque para el motor de dibujado no contiene elementos (que como tienen float, están fuera del flujo y no cuentan a la hora de calcular el tamaño del contenedor).

Un apaño es darle altura explícitamente, pero es problemático si no sabemos exactamente qué altura darle o si el usuario cambia el tamaño de la letra. Así que es una solución menos que óptima.

La solución que he adoptado finalmente ha sido meter todo el conjunto en una DIV, de modo que sea ésta la que se encargue de simular el fondo y de contener visualmente la lista. En principio, va a ocurrir lo mismo, es decir, no va a obtener altura porque el contenido de la lista sigue "flotando". Pero existe una solución sencilla:

Basta con añadir un BR después de la UL al que asignamos clear:both;. De este modo, el BR fuerza a la DIV a "cerrarse" abarcando el contenido aunque esté flotante. El resultado es válido para XHTML, HTML y CSS, o sea, que bien.

El código mostrado a continuación incluye la solución con los estilos simplificados.


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Barra de botones para registros</title>
<style type="text/css" media="screen">

/* Se eliminan los asuntos cosméticos y quedan sólo las reglas básicas para lograr la funcionalidad deseada */

div.barra_botones_registro {
display: block;
}

div.barra_botones_registro ul {
margin: 0;
padding: 0;
list-style: none;
}

div.barra_botones_registro ul li {
}

div.barra_botones_registro ul li a {
/* En esta regla se pondrían las características visuales de los botones */
display: block;
border: 1px solid #000;
padding: 2px 4px;
}

div.barra_botones_registro ul li.bloque1 {
float: left;
margin-right: 5px;
margin-left: 0px;
}

div.barra_botones_registro ul li.bloque2 {
float: left;
margin-right: 5px;
margin-left: 0px;
}

div.barra_botones_registro ul li.bloque3 {
float: right;
margin-right: 0;
margin-left: 5px;
}

div.barra_botones_registro ul li.bloque2.primer_boton_bloque2 {
margin-left: 10px;
}


</style>
</head>

<body>
<h1>Barra de botones para registro</h1>
<p>Ejemplo de una barra de botones para un registro. Está pensada para incluir tres grupos de botones, de forma que: </p>
<ul>
<li>Exista una separación lógica y visual</li>
<li>Se puedan prevenir pulsaciones erróneas</li>
</ul>

<div class="barra_botones_registro">
<ul>
<li class="bloque1"><a href="#">Borrar</a></li>
<li class="bloque1"><a href="#">Limpiar formulario</a></li>
<li class="bloque2 primer_boton_bloque2"><a href="#">Lista</a></li>
<li class="bloque2"><a href="http://some-site.com/">Imprimir</a></li>
<li class="bloque3"><a href="http://some-site.com/">Nuevo</a></li>
<li class="bloque3"><a href="http://some-site.com/">Duplicar</a></li>
</ul>
<br style="clear: both;" />
</div>

</body>
</html>

Diseño de interfaz: barra de botones para un registro

Lo mismo resulta que el título no es muy claro, lo que pretendo con esta nota es mostrar una prueba de concepto (qué bien suena eso) sobre cómo podría ser una barra de botones para operaciones con un registro de una tabla de base de datos.

No forma parte, estrictamente, del diseño del formulario, sino que más bien es un añadido en el que damos al usuario los medios para realizar algunas operaciones como volver a la lista de registros, borrar el objeto, crear uno nuevo, etc.

El problema que se trata de resolver es cómo agrupar los botones de una forma lógica, de forma que tengamos algunos de ellos fuera de la ruta de "escaneado visual" para evitar pulsaciones accidentales y que tengamos separados visualmente los bloques de botones.

La idea es que botones para acciones como crear un nuevo registro o editar el que se está viendo aparezcan en la parte inferior derecha, en el lugar donde termina el "escaneado de página", al menos para los lectores occidentales (aunque los principios creo que se pueden aplicar igual en sistemas de escritura con orientación diferente).

Los botones para eliminar registros o similares aparecerían en la esquina inferior izquierda, fuera de la ruta principal, de forma que normalmente el usuario tendría que moverse a propósito para buscarlos y pulsarlos.

En el centro, se pondrían otros botones. Algo más o menos como esta imagen que pego a continuación:


Usando HTML + CSS es posible acercarse bastante a esto. Básicamente se trata de que la barra de botones se asimila a una lista en la que cada ítem es un botón.

Para lograr que los diferentes botones se agrupen, a cada uno se le aplica una regla que lo identifica como propio de un grupo. De este modo, se pueden dar formatos diferentes a los botones.

El último problema sería definir puntos especiales en los puntos de separación de los botones.

Lo que he hecho ha sido empezar con la siguiente lista:


<ul class="barra_botones_registro">
<li class="bloque1">Borrar</li>
<li class="bloque1">Limpiar formulario</li>
<li class="bloque2 primer_boton_bloque2">Lista</li>
<li class="bloque2">Imprimir</li>
<li class="bloque3">Nuevo</li>
<li class="bloque3">Duplicar</li>
</ul>


Si no se especifica ninguna regla de formato, la barra se ve como una lista normal. A todos los ítems se les aplican al menos una regla, que define a qué grupo pertenece. En un caso, se asigna una regla especial para indicar que se trata de un botón que inicia un bloque.

A continuación, iré introduciendo las reglas css.

La lista como barra

Esta regla aplica un estilo a la lista, básicamente para se pueda ver como una barra horizontal que abarca a los demás botones. El punto más "controvertido" es asignar un height. El problema es que con CSS estándar, al estar el contenido "flotado" UL no toma "altura". Una solución sería meter todo en una DIV y que sea ésta la que se encargue de mostrar el "fondo" de la barra, dejando la UL "invisible". Sin embargo, de momento lo voy a dejar así porque me parece que el concepto queda más claro. Aparte de eso, la mayor parte de las reglas son de carácter cosmético.


ul.barra_botones_registro {
display: block;
margin: 0;
padding: 2px 4px;
border: 1px solid black;
background-color: #E1B250;
height: 20px;
}


Pintando botones

El punto más importante aquí es el display: block de los botones. Luego los haremos "flotar" para colocarlos en el sitio adecuado mientras nos dejan el diseño fluido. Lo demás tiene que ver con la cosmética.


ul.barra_botones_registro li {
display: block;
padding: 2px 6px;
border: 2px outset #907130;
font-family: Verdana, "MS Trebuchet", sans-serif;
font-size: 10px;
background-color: #FC6;
}


Colocando los botones

Las siguientes tres reglas definen el comportamiento de los grupos de botones. En el planteamiento que esto haciendo, los dos primeros grupos de botones se colocan hacia la izquierda y el tercero hacia la derecha, lo que sugiere el "float" correspondiente.

En el caso del bloque3, hay que tener en cuenta que el botón que queramos tener más a la derecha ha de ser el primero en la lista.

Las propiedades margin-right y margin-left nos permiten fijar la separación entre los botones. Debería ser igual en todos los bloques (excepto en el primer botón del bloque2, que trataremos en el siguiente punto y que debería separarse más).

De momento, hemos conseguido una separación flexible entre los botones del bloque2 y bloque3.


ul.barra_botones_registro li.bloque1, ul.barra_botones_registro li.bloque2 {
float: left;
margin-right: 5px;
margin-left: 0px;
}

ul.barra_botones_registro li.bloque3 {
float: right;
margin-right: 0;
margin-left: 5px;
}


Separando el segundo bloque

Simplemente tenemos que identificar el primer botón de ese bloque y especificar que debe tener una separación mayor con respecto al bloque anterior.


.primer_boton_bloque2 {
margin-left: 10px;
}


Más allá

Una cosa que podríamos hacer es dar diferente formato visual a los bloques de botones para destacar algunos de ellos, los que tienen la "función primaria" frente a los que tienen funciones secundarias.

Ya que éste es un blog sobre aprendizaje de CakePHP. mencionar que probablemente escriba un helper de forma que sea fácil crear barras de botones que sigan este modelo. Al menos, una vez que consiga resolver el problema del fondo de la barra.

Una nota sobre las reglas

Como puedes ver, mis selectores css describen toda la ruta de la regla en algunos casos. Aparte de mejorar algo la comprensión de la regla (siempre sabes dónde se aplica), es una buena forma de asegurarse de que la regla se aplica sólo donde quieres que se aplique, definiendo su contexto.

miércoles, 4 de julio de 2007

Comprobar que algo es único

Tenía la impresión de haber escrito algo sobre esto, pero parece que no. Así que pego este pequeño método que comprueba si un modelo es único. En este caso, un usuario comprobando si su nombre es único.

La clase Model tiene un método isUnique () al que le podemos pasar varias condiciones, en formato de pares "campo" => "condición" (como los métodos Find) y un segundo parámetro para booleano para responder en cuanto una condición se cumpla. Si le pasas true, isUnique devuelve true con tal de que se cumple alguna de las condiciones. Si le pasas false, tienen que cumplirse todas las condiciones.

Un pequeño falloUna pequeña limitación de esta función es que no está pendiente automáticamente del id del modelo. Esto genera el problema de que cuando editas un modelo, la validación falla porque efectivamente existe un registro igual en la base de datos: justamente el que estás editando. Incluyendo el chequeo del id como condición en caso de que estemos editando un registro, funcionará correctamente. Para saber si un modelo está en "edición" no tenemos más que mirar si hay valor en model::id.

Cuando estamos añadiendo un modelo (todavía no está guardado en la base de datos) el problema no se presenta y valida correctamente.

Pienso que el código se explica bastante bien:

function usuarioEsUnico ($nombreUsuario) {
// Empezamos suponiendo que el nombre no va a validar
$esValido = false;
// La condición básica es que no coincidan los nombres de usuario
$condiciones['usuario'] = $nombreUsuario;
// Si estamos editando (hay valor en model::id, entonces comprobamos que no sea el mismo id
if ($this->id) {
$condiciones['id'] = '<> '.$this->id;
}
// Y ahora preguntamos al modelo si es único
$esValido = $this->isUnique ($condiciones, false);
return $esValido;
}