Llevaba un tiempo bastante molesto para mi incapacidad para usar las herramientas de generación de CakePHP, la antigua utilidad Bake (actualmente cake bake). El problema es que siempre me salía con un error de conexión con la base de datos.
Yo utilizo en mi máquina de trabajo el paquete MAMP (Apache-MySQL-PHP para Mac OS X), instalado tal como viene por defecto.
El caso es que he podido entender el problema y solucionarlo.
bake busca comunicarse con la base de datos a través del socket
/var/mysql/mysql.sock
Pero si instalamos MAMP sin tocar su configuración para nada, el socket está en:
/Applications/MAMP/tmp/mysql/mysql.sock
La solución es crear un enlace simbólico:
ln -s /Applications/MAMP/tmp/mysql/mysql.sock /var/mysql/mysql.sock
Pero hay un par de requisitos previos. Primero tenemos que crear un directorio mysql bajo var:
cd /var
sudo mkdir mysql
y luego crear el enlace
sudo ln -s /Applications/MAMP/tmp/mysql/mysql.sock /var/mysql/mysql.sock
Y listo.
Nota final
Una cosilla... uso sudo porque al menos en Mac OS X mi usuario de trabajo no tiene permisos sobre la carpeta var para crear el directorio y el enlace simbólico, sin embargo, éste funciona sin ningún problema.
Diario de aprendizaje del framework CakePHP. Otras notas de desarrollo y diseño web, realizado sobre Mac.
lunes, 3 de septiembre de 2007
martes, 28 de agosto de 2007
Páginas estáticas en CakePHP (actualizado)
A raíz de un mensaje en el grupo Google de CakePHP me he puesto a probar el tema de las páginas estáticas.
No es nada difícil, aunque conviene tener en cuenta alguna cosilla para no extrañarse la primera vez.
Las páginas se ponen en la carpeta app/view/pages, con extensión .ctp. Por ejemplo, pagina.ctp
Además, lo que se pone en la página es el contenido (lo que va dentro del body, vamos) ya que la página se carga dentro del layout que toque. Es decir, no tienes que especificar tags body, ni mucho menos cabeceras html en la página, sino que van en el layout.
Para llamar a una página estática es tan simple como:
http://dominio.tld/pages/pagina
He investigado un poco sobre la pregunta de Pablo, y me encontrado una forma de fijar un título para la página estática. Consiste en añadir la siguiente línea
$this se refiere a la View que muestra esta página. No es muy "limpio" debido a que accedemos directamente a una propiedad de View, pero reesulta efectivo.
No es nada difícil, aunque conviene tener en cuenta alguna cosilla para no extrañarse la primera vez.
Las páginas se ponen en la carpeta app/view/pages, con extensión .ctp. Por ejemplo, pagina.ctp
Además, lo que se pone en la página es el contenido (lo que va dentro del body, vamos) ya que la página se carga dentro del layout que toque. Es decir, no tienes que especificar tags body, ni mucho menos cabeceras html en la página, sino que van en el layout.
Para llamar a una página estática es tan simple como:
http://dominio.tld/pages/pagina
He investigado un poco sobre la pregunta de Pablo, y me encontrado una forma de fijar un título para la página estática. Consiste en añadir la siguiente línea
<?php $this->pageTitle = 'Titulo para la página'; ?>
$this se refiere a la View que muestra esta página. No es muy "limpio" debido a que accedemos directamente a una propiedad de View, pero reesulta efectivo.
Capturar una URL (actualizado)
$redirectAfterLogin = array (
'controller' => $this->controller->params['controller'],
'action' => $this->controller->params['action'],
);
if (isset($this->controller->params['pass'])) {
foreach ($this->controller->params['pass'] as $pass) {
$redirectAfterLogin[] = $pass;
}
}
Esto lo hice para una redirección post-login, esto es, si la página solicitada requiere autentificación hay que anotar qué página es y recuperarla una vez que el usuario ha hecho login correctamente para llevarlo a dónde quería.
Es posible que haya una solución mejor en el propio CakePHP, pero ¿quién sabe dónde?
Claro que la hay
Está en el router:
$redirectAfterLogin = Router::parse ($this->controller->here)
Funciona aún mejor, pues respeta los parámetros con nombre.
lunes, 27 de agosto de 2007
La técnica de los argumentos como array
Una cosa que me llamó la atención al estudiar algunos ejemplos de código en CakePHP es el amplio uso de los arrays asociativos para pasar múltiples argumentos a los métodos, sobre todo conjuntos de ajustes para Behaviors, Helpers o Components.
La técnica básica es muy sencilla, simplemente es definir un array asociativo con las claves que nos interesen y sus valores. Por ejemplo:
La ventaja es que así resulta bastante fácil mantener la interfaz de un método y pasarle un número indeterminado de parámetros. Sólo tenemos que declarar que pasaremos un array y será problema del método decidir qué hacer con él.
Algo así:
Al ser $argumentos un array asociativo, en realidad le podemos pasar cualquier estructura de datos que se nos ocurra.
Valores por defecto
Para resolver el tema de los valores por defecto lo que haremos será definir una variable con una "plantilla" del array y luego mezclarla con el array que pasamos. De este modo, nos aseguramos de que el array final tenga todas las claves necesarias y, a la vez, los valores que hayamos pasado.
Por ejemplo:
Uso
Para utilizar los valores del array podemos optar por dos estrategias:
1. Utilizar el array, como en $array_final['repetir'].
2. Utilizar extract, para convertir las claves del array en variables y poder referirnos a ellas como $repetir y así.
El segundo método parece más legible, pero a veces me resulta más inteligible el primero. Una vez que hemos hecho "extract" de un array nos salen un montón de variables nuevas y puede ser fácil perder la pista de su origen, sin descontar el problema de que pueda haber conflictos de nombres. Pero es una elección personal, claro.
La técnica básica es muy sencilla, simplemente es definir un array asociativo con las claves que nos interesen y sus valores. Por ejemplo:
$ejemplo = array ( 'etiqueta' => 'La que sea', 'repetir' => 5 );
La ventaja es que así resulta bastante fácil mantener la interfaz de un método y pasarle un número indeterminado de parámetros. Sólo tenemos que declarar que pasaremos un array y será problema del método decidir qué hacer con él.
Algo así:
class ejemplo {
function metodo ($argumentos ) {
}
}
Al ser $argumentos un array asociativo, en realidad le podemos pasar cualquier estructura de datos que se nos ocurra.
Valores por defecto
Para resolver el tema de los valores por defecto lo que haremos será definir una variable con una "plantilla" del array y luego mezclarla con el array que pasamos. De este modo, nos aseguramos de que el array final tenga todas las claves necesarias y, a la vez, los valores que hayamos pasado.
Por ejemplo:
$porDefecto = array ( 'etiqueta' => '', 'repetir' => 10, 'plantilla' => 'Campo = %campo% ', ); $array_final = array_merge ($porDefecto, $ejemplo);
Uso
Para utilizar los valores del array podemos optar por dos estrategias:
1. Utilizar el array, como en $array_final['repetir'].
2. Utilizar extract, para convertir las claves del array en variables y poder referirnos a ellas como $repetir y así.
El segundo método parece más legible, pero a veces me resulta más inteligible el primero. Una vez que hemos hecho "extract" de un array nos salen un montón de variables nuevas y puede ser fácil perder la pista de su origen, sin descontar el problema de que pueda haber conflictos de nombres. Pero es una elección personal, claro.
Etiquetas:
aprendizaje,
array,
parámetros,
técnicas
martes, 21 de agosto de 2007
CakePHP-es
Se acaba de poner en marcha hace unos días CakePHP-es , un espacio comunitario sobre CakePHP en español. La iniciativa se ha iniciado con una wiki-traducción del manual actual de CakePHP, lo que va a suponer un alivio para buena parte de los que se están iniciando en este framework.
Esta traducción no es oficial, pero está autorizada por la CakePHP Software Foundation.
Esta web se une al grupo de google ya existente y bastante activo.
Además, se ha puesto en marcha un pastebin, que es una herramienta en línea para publicar y revisar código colectivamente.
O sea, que ya cuentas con unos cuantos recursos en español acerca de CakePHP. ¡A cocinar!
Esta traducción no es oficial, pero está autorizada por la CakePHP Software Foundation.
Esta web se une al grupo de google ya existente y bastante activo.
Además, se ha puesto en marcha un pastebin, que es una herramienta en línea para publicar y revisar código colectivamente.
O sea, que ya cuentas con unos cuantos recursos en español acerca de CakePHP. ¡A cocinar!
lunes, 6 de agosto de 2007
Curiosas propiedades de los behaviors
Recién vuelto (casi) de las vacaciones, hoy he retomado el proyecto Cake en el que estoy metido. Sigo dado toques a la fase de autentificación para mis aplicaciones web. Concretamente estoy trabajando en un behavior que me permita usar cualquier modelo para autentificar.
Esto es, en lugar de tener un Model Usuario con los métodos necesarios, lo que quiero es trasladar la funcionalidad a un Behavior de modo que el modelo quede "liberado" de la parte de autentificación y que ésta sea reutilizable. La cuestión es que las diferentes aplicaciones que tengo que poner en marcha o reescribir tienen distintos requisitos en lo que respecta al modelo de usuario.
Eso me ha permitido aprender unas cuantas cosas acerca del uso de Behaviors aparte de lo que ya había escrito anteriormente.
Una de las más importantes es la capacidad de añadir métodos al Model como si fueran propios, es decir, los puedes llamar con model->metodo (), aunque los hayas definido en el behavior.
Hay que tener una precaución según desde donde llames al método. Si es desde el propio modelo o behavior no ocurre nada especial. Pero si lo llamas desde un controlador, por ejemplo, CakePHP automágicamente añade una referencia al propio modelo como parámetro.
Por otra parte, la capacidad de integración de los behaviors con los models a mí me sigue pareciendo asombrosa. Así, aunque los behaviors no contemplan el callback beforeValidate, es posible, por ejemplo, invalidar campos de beforeSave, para realizar validaciones personalizadas.
El resultado puede parecer un poquito extraño ya que los formularios se validan entonces en dos etapas (la "normal" y la del "behavior") pero ciertamente funciona como debe. Es decir, la aplicación protesta primero por unos campos y luego por otros. Supongo que se puede mejorar esto, pero aún no he llegado tan lejos.
En cualquier caso, el resultado de este "subproyecto" me ha permitido sacar a un behavior todo el código relacionado con la autentificación, incluyendo el procesado de elementos "extraños", como todos los tejemanejes de contraseñas hasheadas procedentes del formulario pero que no son realmente parte del modelo.
Me explico. Al crear un usuario, por ejemplo, la contraseña es "hasheada" en javascript para que viaje encriptada desde del cliente al servidor. El campo hash contiene esta encriptación, mientras que el campo de la contraseña vuelve vacío. Si no hay javascript la contraseña viene desprotegida en su campo El modelo debe entonces controlar esta situación antes de guardar los datos y asegurarse de que el modelo los contiene tal y como los espera la base de datos. Y eso es justamente lo que hace el behavior en beforeSave.
Claro que el behavior forma parte de un paquete que incluye un helper y un component. La idea final es que este paquete sea una "caja negra" y que el Model no tenga que saber nada sobre ella, ocupándose sólo de sus cosas.
Este es el código que habría que poner en models/helpers/autentificacion.php.
Y esta es la forma de usarlo en un modelo cualquiera. El modelo tiene los campos id, usuario, clave, email.
Al asociar el behavior indicamos que utilice usuario como campo user y clave como campo password.
Esto es, en lugar de tener un Model Usuario con los métodos necesarios, lo que quiero es trasladar la funcionalidad a un Behavior de modo que el modelo quede "liberado" de la parte de autentificación y que ésta sea reutilizable. La cuestión es que las diferentes aplicaciones que tengo que poner en marcha o reescribir tienen distintos requisitos en lo que respecta al modelo de usuario.
Eso me ha permitido aprender unas cuantas cosas acerca del uso de Behaviors aparte de lo que ya había escrito anteriormente.
Una de las más importantes es la capacidad de añadir métodos al Model como si fueran propios, es decir, los puedes llamar con model->metodo (), aunque los hayas definido en el behavior.
Hay que tener una precaución según desde donde llames al método. Si es desde el propio modelo o behavior no ocurre nada especial. Pero si lo llamas desde un controlador, por ejemplo, CakePHP automágicamente añade una referencia al propio modelo como parámetro.
Por otra parte, la capacidad de integración de los behaviors con los models a mí me sigue pareciendo asombrosa. Así, aunque los behaviors no contemplan el callback beforeValidate, es posible, por ejemplo, invalidar campos de beforeSave, para realizar validaciones personalizadas.
El resultado puede parecer un poquito extraño ya que los formularios se validan entonces en dos etapas (la "normal" y la del "behavior") pero ciertamente funciona como debe. Es decir, la aplicación protesta primero por unos campos y luego por otros. Supongo que se puede mejorar esto, pero aún no he llegado tan lejos.
En cualquier caso, el resultado de este "subproyecto" me ha permitido sacar a un behavior todo el código relacionado con la autentificación, incluyendo el procesado de elementos "extraños", como todos los tejemanejes de contraseñas hasheadas procedentes del formulario pero que no son realmente parte del modelo.
Me explico. Al crear un usuario, por ejemplo, la contraseña es "hasheada" en javascript para que viaje encriptada desde del cliente al servidor. El campo hash contiene esta encriptación, mientras que el campo de la contraseña vuelve vacío. Si no hay javascript la contraseña viene desprotegida en su campo El modelo debe entonces controlar esta situación antes de guardar los datos y asegurarse de que el modelo los contiene tal y como los espera la base de datos. Y eso es justamente lo que hace el behavior en beforeSave.
Claro que el behavior forma parte de un paquete que incluye un helper y un component. La idea final es que este paquete sea una "caja negra" y que el Model no tenga que saber nada sobre ella, ocupándose sólo de sus cosas.
Este es el código que habría que poner en models/helpers/autentificacion.php.
<?php
/**
* autentificacion
*
* Created by Frankie on 2007-07-19.
* Copyright (c) 2007 Macintec. All rights reserved.
* 2007-08-06. Added abstraction to valid() method. The behavior now is model independant
* 2007-08-06. Added beforeSave() method to preprocess incoming data
**/
/**
* This behavioir adds Authentitfication abilities to a model, you can assign fields
* to act as user and password fields...
*
* @package autentificacion
* @author Frankie
**/
class AutentificacionBehavior extends ModelBehavior {
var $default_settings = array (
'user' => 'user',
'password' => 'password'
);
var $settings;
var $model;
function beforeSave (&$model) {
/**
* Si el campo hash contiene valores, significa que esa es la contraseña hasheada
* y por lo tanto, debemos poner ese valor en el campo de contraseña para guardar.
* El Javascript habrá borrado la contraseña en clave, por lo que este campo viene
* vaío.
* Por otro lado, si hash no contiene valores, entonces es que el cliente no tiene
* javascript y la contraseña viene abierta en el campo clave. Por último, en caso
* de que estemos editando el usuario, la clave puede venir vacía. En ese caso,
* se elimina el campo clave para no borrar la clave hash en la base de datos.
* El campo Hash se elimna de todas maneras porque no pertenece al modelo.
* TO DO: Establecer como preferencia que se permitan o no claves en abierto
*
* @author Frankie
*/
if (empty ($model->data[$model->name][$this->settings['password']]) && empty ($model->data[$model->name]['hash']) && empty ($model->id)) {
$model->invalidate ($this->settings['password'], __('Please, you must provide a password', true));
return false;
}
if (!empty ($model->data[$model->name]['hash'])) {
$model->data[$model->name]['clave'] = $model->data[$model->name]['hash'];
} elseif (!empty ($model->data[$model->name]['clave'])) {
$model->data[$model->name]['clave'] = sha1 ($model->data[$model->name]['clave']);
}
if (empty ($model->data[$model->name]['clave']) && !empty ($model->id)) {
unset ($model->data[$model->name]['clave']);
}
unset ($model->data[$model->name]['hash']);
return true;
}
function setup (&$model, $settings) {
// TODO: verificar que los campos pertenecen al modelo ???
$this->settings = am ($this->default_settings, $settings);
$this->model = $model;
}
/**
* Returns user record if the user is a registered user
*
* @return boolean true if user and password exists
* @author Frankie
**/
function valid (&$model, $userData, $salt = false) {
$user = $userData[$model->name][$this->settings['user']];
$password = $userData[$model->name][$this->settings['password']];
if ($salt) {
// User validation with salt
$conditions = "WHERE {$this->settings['user']} = '$user' AND sha1(concat({$this->settings['password']},'$salt')) = '$password'";
} else {
// User validation without salt, insecure login
$conditions = "WHERE {$this->settings['user']} = '$user' AND {$this->settings['password']} = '$password'";
}
if ($userFound = $this->model->find ($conditions)) {
return $userFound;
}
return false;
}
} // END class AutentifiacionBehavior extends ModelBehavior
?>
Y esta es la forma de usarlo en un modelo cualquiera. El modelo tiene los campos id, usuario, clave, email.
Al asociar el behavior indicamos que utilice usuario como campo user y clave como campo password.
<?php
/**
* usuario
*
* Created by Frankie on 2007-07-04.
* Copyright (c) 2007 Macintec. All rights reserved.
**/
/**
* Usuario
*
* @package /Applications/MAMP/htdocs/micake
* @author Fran Iglesias
* @copyright Copyright 2007 Fran Iglesias
* @version Revision: 0.1
* @modifiedby Fran Iglesias
* @lastmodified 2007-07-04
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
**/
class Usuario extends AppModel
{
var $name = "Usuario";
var $validate = array (
'usuario' => array (
'esUnico' => array (
'rule' => 'usuarioEsUnico',
'message' => 'User name exists. Please, choose another.'
),
'usuarioNoVacio' => array (
'rule' => VALID_NOT_EMPTY,
'message' => 'User name empty. Please, give it a name.'
)
),
'email' => array (
'emailValido' => array (
'rule' => VALID_EMAIL,
'message' => 'Please, type a valid email.'
)
)
);
var $actsAs = array ('Autentificacion' => array ('user' => 'usuario', 'password' => 'clave'));
/**
* Verifica que el nombre de usuario aportado no existe en la bd
*
* @return boolean true if unique
* @author Frankie
**/
function usuarioEsUnico ($nombreUsuario) {
$esValido = false;
$condiciones['usuario'] = $nombreUsuario;
if ($this->id) {
$condiciones['id'] = '<> '.$this->id;
}
$esValido = $this->isUnique ($condiciones, false);
return $esValido;
}
}
// END class Usuario
?>
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.
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.
$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.
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:
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.
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.
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.
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.
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.
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.
Suscribirse a:
Comentarios (Atom)