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)
)
);
?>