Select combo (estado + cidade) usando o Helper Ajax (observeField)
Autor: Carlitos | Publicado: 2011-02-02T18:33:00-03:00

Estou começando a utilizar o Helper Ajax do CakePHP (baixei a versão 1.3.7), que utiliza o Prototype (e o Scriptaculous). Antes eu usava o jQuery para lidar com AJAX no Cake, mas conversando com o Ananias no grupo resolvi implementar alguns métodos do Helper Ajax para conhecer melhor e para testar a sua performance.

Bom, neste post pretendo mostrar uma forma de implementar aquele velho select combo de "estado" e "cidade" (ou pais, estado e cidade), onde ao selecionar um determinado "estado" as "cidades" referentes a ele são carregadas no select abaixo. Veja como fica simples de implementar usando o Helper Ajax.

1º) Eu criei duas tabelas: "estados" e "cidades" e criei também os respectivos: Models, Controllers e Views para elas. O código para criação das tabelas é:
CREATE TABLE `estados` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `estado` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

CREATE TABLE `cidades` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `estado_id` int(11) NOT NULL,
  `cidade` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

para gerar o MVC usei o BAKE.

2º) Criei também a tabela "users" e seu MVC usando o bake. Veja o SQL para criação da tabela:
CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL,
  `password` char(40) NOT NULL,
  `created` datetime DEFAULT NULL,
  `modified` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 ;

Criei o "users" apenas para servir como ponto de partida para o formulário onde serão carregados os dados de outros modelos (veja que o modelo "User" não possui relacionamento com "Estado" e nem com "Cidade", e nem vice-versa).

3º) Bom, como tudo foi devidamente gerado via bake imagino que você já tenha todos os Models, Controllers e View devidamente criados (pelo menos o CRUD - add(), view(), delete() e edit()). 

4º) Em ..app/controllers/cidades_controller.php vamos criar a action "listar", veja:
function listar($estado_id = 1){
  $this-&gt;layout = 'ajax';
  $this-&gt;set('cidades', $this-&gt;Cidade-&gt;find('list', array(
    'conditions' =&gt; array(
      'Cidade.estado_id' =&gt; $estado_id      
     ),
    'fields' =&gt; array(
      'id', 'cidade'
     ),
   ))
  );
 }
e criar a view ...app/views/cidades/listar.ctp:
&lt;?php
// Recebo $cidades
echo '&lt;option value=""&gt;Selecione a cidade&lt;/option&gt;\n';&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
foreach($cidades as $id =&gt; $cidade)
&nbsp;&nbsp; &nbsp;echo "&lt;option value='$id'&gt;$cidade&lt;/option&gt;\n";&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
?&gt;&nbsp;
Veja que essa view irá gerar as opções de cidades do nosso select Cidades.

 
5º) No ...app/controllers/users_controllers.php vamos: definir a var $helpers, criar a action "listar_cidades" e modificar a action add:
class UsersController extends AppController {

&nbsp;&nbsp;&nbsp; var $name = 'Users';
&nbsp;&nbsp;&nbsp; var $helpers = array('Ajax', 'Javascript');

&nbsp;&nbsp;&nbsp; function add() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (!empty($this-&gt;data)) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; $this-&gt;User-&gt;create();
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if ($this-&gt;User-&gt;save($this-&gt;data)) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; $this-&gt;Session-&gt;setFlash(__('The user has been saved', true));
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; $this-&gt;redirect(array('action' =&gt; 'index'));
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } else {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; $this-&gt;Session-&gt;setFlash(__('The user could not be saved. Please, try again.', true));
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Se o usuário selecionou algum estado, carregamos a lista de cidades para a view
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if(!empty($this-&gt;data['User']['estado'])){
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; $this-&gt;loadModel('Cidade');
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; $cidades = $this-&gt;Cidade-&gt;find('list', array(
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'conditions' =&gt; array(
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'Cidade.estado_id' =&gt; $this-&gt;data['User']['estado']
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ),
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'fields' =&gt; array('id', 'cidade'))
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; );
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; $cidades = array('' =&gt; 'Selecione a cidade', $cidades);&nbsp;&nbsp;&nbsp; // Adiciono o "Selecione a cidade" na lista de cidades
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; $this-&gt;set('cidades', $cidades);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; $this-&gt;loadModel('Estado');
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; $estados = $this-&gt;Estado-&gt;find('list', array('fields' =&gt; array('id', 'estado')));
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; $this-&gt;set('estados', $estados);&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; function listar_cidades(){
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Se foi informado o Estado via POST
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if(!empty($this-&gt;data['User']['estado'])){
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; $this-&gt;redirect(array('controller' =&gt; 'cidades', 'action' =&gt; 'listar', $this-&gt;data['User']['estado']));
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; }
.
.
.&nbsp;
&nbsp;Veja o código do formulário criado no view add, em ...app/views/users/add.ctp:
&lt;?php&nbsp;
// Carrego o Prototype e o Scriptaculos
&nbsp;&nbsp;&nbsp; $javascript-&gt;link('https://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js', false);
&nbsp;&nbsp;&nbsp; $javascript-&gt;link('https://ajax.googleapis.com/ajax/libs/scriptaculous/1.8.3/scriptaculous.js', false); 
?&gt;
&lt;div class="users form"&gt;
&lt;?php echo $this-&gt;Form-&gt;create('User');?&gt;
&nbsp;&nbsp;&nbsp; &lt;fieldset&gt;
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;legend&gt;&lt;?php __('Add User'); ?&gt;&lt;/legend&gt;
&nbsp;&nbsp;&nbsp; &lt;?php
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; echo $this-&gt;Form-&gt;input('estado', 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; array(
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'options' =&gt; $estados, 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'empty' =&gt; 'Selecione o estado'
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; )
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; );&nbsp;
// Crio um Loading... para que o usuário saiba o que está ocorrendo
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; echo '&lt;div id="loading" style="display: none"&gt;&lt;b&gt;' . $html-&gt;image('carregando.gif') . ' Aguarde, carregando cidades...&lt;/b&gt;&lt;/div&gt;';&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; echo $this-&gt;Form-&gt;input('cidade', 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; array(
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'type' =&gt; 'select',
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; )
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; );
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; echo $this-&gt;Form-&gt;input('username');
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; echo $this-&gt;Form-&gt;input('password');
&nbsp;&nbsp;&nbsp; ?&gt;
&nbsp;&nbsp;&nbsp; &lt;/fieldset&gt;
&lt;?php echo $this-&gt;Form-&gt;end(__('Submit', true));?&gt;
&lt;/div&gt;
&lt;div class="actions"&gt;
&nbsp;&nbsp;&nbsp; &lt;h3&gt;&lt;?php __('Actions'); ?&gt;&lt;/h3&gt;
&nbsp;&nbsp;&nbsp; &lt;ul&gt;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;li&gt;&lt;?php echo $this-&gt;Html-&gt;link(__('List Users', true), array('action' =&gt; 'index'));?&gt;&lt;/li&gt;
&nbsp;&nbsp;&nbsp; &lt;/ul&gt;
&lt;/div&gt;
&lt;?php
&nbsp;&nbsp;&nbsp; echo $ajax-&gt;observeField('UserEstado', 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; array(
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'url' =&gt; array(
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'action' =&gt; 'listar_cidades' 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ),
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'frequency' =&gt; 0.2,
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'update' =&gt; 'UserCidade',
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 'indicator' =&gt; 'loading',
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ) 
&nbsp;&nbsp;&nbsp; ); 
?&gt;
Bom. o resultado final é este!
Imagem 1 - O Form com o select Estado com os valores do banco e o select Cidade vazio, aguardando a seleção do estado.:

Imagem 2 - O usuário seleciona um estado:

Imagem 3 - Selecionado o estado a mensagem de carregando (atributo "indicator" do observeField()) aparece:

Imagem 4 - É carregada a lista de cidades!


Espero que o artigo seja útil! Caso fique com alguma dúvida ou tenha uma sugestão/crítica, fique a vontade para comentar! =]