Select combo (estado + cidade) usando o Helper Ajax (observeField)

Postado por Carlitos | Tags , , | Postado em 18:33

3

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->layout = 'ajax';
  $this->set('cidades', $this->Cidade->find('list', array(
    'conditions' => array(
      'Cidade.estado_id' => $estado_id      
     ),
    'fields' => array(
      'id', 'cidade'
     ),
   ))
  );
 }
e criar a view ...app/views/cidades/listar.ctp:
<?php
// Recebo $cidades
echo '<option value="">Selecione a cidade</option>\n';       
foreach($cidades as $id => $cidade)
    echo "<option value='$id'>$cidade</option>\n";       
?> 

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 {

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

    function add() {
        if (!empty($this->data)) {
            $this->User->create();
            if ($this->User->save($this->data)) {
                $this->Session->setFlash(__('The user has been saved', true));
                $this->redirect(array('action' => 'index'));
            } else {
                $this->Session->setFlash(__('The user could not be saved. Please, try again.', true));
                // Se o usuário selecionou algum estado, carregamos a lista de cidades para a view
                if(!empty($this->data['User']['estado'])){
                    $this->loadModel('Cidade');
                    $cidades = $this->Cidade->find('list', array(
                            'conditions' => array(
                                    'Cidade.estado_id' => $this->data['User']['estado']
                                ),
                            'fields' => array('id', 'cidade'))
                        );
                    $cidades = array('' => 'Selecione a cidade', $cidades);    // Adiciono o "Selecione a cidade" na lista de cidades
                    $this->set('cidades', $cidades);
                }
            }
        }
        
        $this->loadModel('Estado');
        $estados = $this->Estado->find('list', array('fields' => array('id', 'estado')));
        $this->set('estados', $estados);    
    }
    
    function listar_cidades(){
        // Se foi informado o Estado via POST
        if(!empty($this->data['User']['estado'])){
            $this->redirect(array('controller' => 'cidades', 'action' => 'listar', $this->data['User']['estado']));
        }
    }
.
.
. 

 Veja o código do formulário criado no view add, em ...app/views/users/add.ctp:
<?php 
// Carrego o Prototype e o Scriptaculos
    $javascript->link('https://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js', false);
    $javascript->link('https://ajax.googleapis.com/ajax/libs/scriptaculous/1.8.3/scriptaculous.js', false); 
?>
<div class="users form">
<?php echo $this->Form->create('User');?>
    <fieldset>
         <legend><?php __('Add User'); ?></legend>
    <?php
        echo $this->Form->input('estado', 
                array(
                    'options' => $estados, 
                    'empty' => 'Selecione o estado'
                )
            ); 
// Crio um Loading... para que o usuário saiba o que está ocorrendo
        echo '<div id="loading" style="display: none"><b>' . $html->image('carregando.gif') . ' Aguarde, carregando cidades...</b></div>';                
        echo $this->Form->input('cidade', 
                array(
                    'type' => 'select',
                )
            );
        echo $this->Form->input('username');
        echo $this->Form->input('password');
    ?>
    </fieldset>
<?php echo $this->Form->end(__('Submit', true));?>
</div>
<div class="actions">
    <h3><?php __('Actions'); ?></h3>
    <ul>

        <li><?php echo $this->Html->link(__('List Users', true), array('action' => 'index'));?></li>
    </ul>
</div>
<?php
    echo $ajax->observeField('UserEstado', 
        array(
            'url' => array(
                    'action' => 'listar_cidades' 
                ),
            'frequency' => 0.2,
            'update' => 'UserCidade',
            'indicator' => 'loading',
        ) 
    ); 
?>
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! =]

Comentários postados (3)

Excelente post companheiro. muito bom mesmo.

Só pra acrescentar, uma pequena correção na codificação do cidades_controller:

function listar($estado_id = 1){
$this->layout = 'ajax';
$this->set('cidades', $this->Cidade->find('list', array(
'conditions' => array(
'Cidade.estado_id' => $estado_id
),
'fields' => array(
'id', 'cidade'
),
))
);
}


Obrigado.

Que bom que você gostou Carlilton!
Corrigi a codificação. Agora está OK!
Muito obrigado pelo toque!
Abraço,

kara,
muito bom o teu blog. parabens. ajudou-me muito.
100% de eficácia.
ficarei acompanhando.
abraços.

Postar um comentário