jueves, 25 de septiembre de 2008

Selects dependientes en CakePHP: la manera bruta, toma 1

Vaya por delante que soy malísimo con Javascript. Sé que hay mejores maneras de hacer esto de los selects dependientes (de hecho, ya se me está ocurriendo alguna).

Allá va:

Básicamente se trata de tener una acción en un Controller que obtenga los datos necesarios para el select "dependiente" y cuya vista sea (redoble de tambores)... el nuevo campo select con los valores adecuados y llamarla mediante Ajax.

Pondré unos ejemplos de un proyecto en que estoy ahora. (Para ponerte en contexto se trata de un sistema de publicación. Cuando estás editando un post puedes asignarlo a un Blog y, a su vez, puedes asignarlo a una Serie dentro de ese Blog (en ambos casos con selects). Cada Blog tiene distintas Series, por lo que al escoger un Blog en su select, el select de Series tiene que cambiar. Espero haberme explicado.)

Bien, en mi caso la acción que genera el select será

/series/select


La vista padre es el archivo /views/post/admin_edit.ctp en el que tengo...


echo $form->input('blog_id', array(
'label' => __('Blog to publish this post', true),
'empty' => __('select a blog', true),
)
);
if (isset($series)) {
echo $form->input('series_id', array(
'label' => __('Is this post part of a series?', true),
'empty' => __('No', true),
'div' => array(
'id' => 'PostSeriesDiv'
)
)
);
}
echo $ajax->observeField('PostBlogId', array(
'frequency' => '1',
'update' => 'PostSeriesDiv',
'url' => array(
Configure::read('Routing.admin') => false,
'controller' => 'series',
'action' => 'select'
)
)
);
En el fragmento anterior se pueden ver los dos selects implicados y el uso de $ajax->observeField para generar un script cuya función es controlar los cambios del primer select. Por supuesto, al principio de la vista me aseguro de cargar la biblioteca prototype.js

echo $javascript->link('prototype', false);

En la vista "padre" (el formulario donde están los selects) necesitas un poco de Javascript para llamar a esa acción. Yo lo he hecho con observeField. Este método del Ajax Helper pide como parámetros una frequency en segundos para observar el campo, una url de la acción destino, un update, para saber dónde colocar el nuevo contenido y un último parámetro with para indicar los parámetros que se han de pasar en la petición.

(aquí es dónde creo que se podría hacer algo mejor con un evento onChange, pero de momento no me pidas más, lo dejaré para la toma 2)

¿Cómo se pasa el valor del Select "padre" a la acción para poder obtener las opciones? Muy sencillo: observeField por defecto serializa el campo observado en el parámetro with y tú puedes recoger esos parámetros en la acción destino en el array $this->data['Model']['field']. Si, exactamente así. Esta es mi acción receptora, tal como la tengo en /controllers/series_controller.php

function select() {
$this->set('series', $this->Series->getByBlog($this->data['Post']['blog_id']));
}


Debido a la forma en que se actualizan los elementos, hay que tomar unas precauciones con la construcción de los selects.

En primer lugar al select en la vista maestra hay que ponerlo en una DIV con un id único pues el elemento select en sí no se puede actualizar con una petición Ajax. Suponiendo que uses $form->input() para generar el campo, basta con poner el parámetro "div" en las opciones y ponerle un "id".

En la vista devuelta por Ajax, tienes que poner el mismo parámetro "div" en null, para que no se nos duplique. Tal que así:

/views/series/select.ctp
<?php
echo $form->input('Post.series_id', array(
'options' => $series,
'div' => null, // Esto elimina el div contenedor
'label' => __('Is this post part of a series?', true),
'empty' => __('No', true)
)
);
?>

miércoles, 17 de septiembre de 2008

Encuesta chorra (o no)

Aprovechando que el Blogger ahora admite encuestas (ver arriba), se me ha ocurrido la siguiente pregunta. Supongamos que tienes un sistema de blogs con los modelos Blog y Post, asociados de modo que Blog hasMany Post.

Ahora supongamos que la raíz del sitio (/) saca una lista de todos los Post recientes, con independencia del Blog al que estén asociados. Es decir, una accion como /posts/index.

Por supuesto, cada Blog tiene su página principal, que muestra una lista de posts. Mi primera tendencia fue a crear una accion /blogs/view/blog_id, pero me dije: "Un momento, esto no es más que un /posts/index/blog_id. Puedo hacer que /posts/index maneje la situación de listar sólo los post de un blog determinado".

Así que en principio creé una acción que modificaba la búsqueda de datos en función de si se pasaba un parámetro para indicar un blog. Luego en la vista, incluí una lógica para actuar de manera ligeramente distinta si se listaban los post de todo el sitio o si eran sólo los del blog indicado. Como tampoco me gustaba, preparé dos vistas y dejé que el controller eligiese la adecuada.

Sin embargo, esta solución tampoco me convence. Finalmente he decidido crear dos acciones en el PostsController (index y blog), cada una con su vista. El Controller ahora no toma tantas decisiones, y la vista tampoco. Pero ¿Qué opinas tú?

Cache de todo

La clase Cache aún no parece muy documentada, pero hoy estaba buscando una manera sencilla de hacer cache y acabé encontrando una referencia indirecta.

La cosa es que con la clase estática Cache puedes almacenar lo que quieras usando cualquiera de los motores de Cache de CakePHP. Esto es útil para evitar cargas excesivas en los servidores pidiendo los mismos datos. CakePHP tiene sistemas integrados de cache para las vistas, y una función cache (deprecated).

¿Quieres guardar algo en la cache por defecto?

Pues simplemente es:

Cache::write('clave', $datos);

siendo clave un nombre que te permita identificar los datos guardados. Por supuesto, $datos son los datos que quieres guardar (cualquier cosa menos recursos). Opcionalmente puedes indicar una duración.

¿Necesitas recuperar algo de la cache?

Cache::read('clave');

¿Que lo que hay en cache ya no vale y hay que borrarlo?

Cache::delete('clave');

No he visto un método Cache::check() para comprobar si existe una clave en cache, pero lo puedes suplantar con Cache::read('clave') ya que si no existe esa clave, el método devuelve false.

jueves, 4 de septiembre de 2008

Validation CheatSheet 1.1

Acabo de hacer una chuleta (cheatsheet) sobre el tema de la validación en CakePHP. Puedes descargarla si crees que te puede resultar útil. Está basada en la página del manual correspondiente


29/8/2008: corregido el typo señalado por Kejk (Thanks)
4/9/2008: corregido y aumentado el apartado de Custom Validation
10/2/2014: Cambio del enlace