Autocomplete nativo do Cake (Ajax Helper)

Postado por Carlitos | Tags , , , , | Postado em 11:20

2

Eu já havia mostrado uma implementação de autocomplete no Cake usando o jQuery UI (http://cakenaveia.blogspot.com/2010/06/jqueryui-autocomplete-no-cakephp-agora.html), mas nunca havia usado o autocomplete nativo dele que utiliza o Prototype e o Scriptaculos. Até porque não tenho muita familiaridade com essas outras bibliotecas e gosto muito dos temas prontos do jQuery UI, o que dispensa dores de cabeça com o CSS.

Bom vamos nessa então...

Antes de começarmos veja nos posts anteriores como criar uma tabela de "cidades" (no meu caso, com os campos: id, estado_id, cidade) e crie para ela o model, views e controller usando o bake.

1º) Seguindo o book (http://book.cakephp.org/view/1370/autoComplete) crie a action autoComplete() no controller ...app/controllers/cidades_controller.php.

function autoComplete(){
        $this->layout = 'ajax';
        $cidades = $this->Cidade->find('all', array(
                'conditions' =>    array('Cidade.cidade LIKE' => $this->data['Cidade']['cidade'] . '%'),
                'order' => array('Cidade.cidade'),
                'fields' => array('cidade')
            )
        );
        $this->set('cidades', $cidades);
    }
Veja que estou listando todas as cidades cujo nome comece com o argumento recebido via POST e ordeno a lista de cidades ascendentemente.

2º) Crio a view ...app/views/cidades/auto_complete.ctp:
<ul>
<?php
    foreach($cidades as $cidade)
        echo '<li>' . $cidade['Cidade']['cidade'] . '</li>';
?>
</ul>
3º) E na view ...app/views/cidades/add.ctp adicionamos o autocomplete em si:
<?php
    $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);  
    
    echo $this->Html->css('autocomplete');
?>    
<div class="cidades form">
<?php echo $this->Form->create('Cidade');?>
    <fieldset>
         <legend><?php __('Add Cidade'); ?></legend>
    <?php
        echo $this->Form->label('Cidade');
        echo $ajax->autoComplete('Cidade.cidade', '/cidades/autoComplete');
    ?>
    </fieldset>
<?php echo $this->Form->end(__('Submit', true));?>
</div>
Veja que o $ajax->autoComplete('Cidade.cidade', '/cidades/autoComplete') cria um input com autocomplete, mas esse input vem sem label =/, portanto temos que gerar um label antes.

Veja também que adicionei um CSS (echo $this->Html->css('autocomplete'))  para estilizar a lista com os resultados do autocomplete. O arquivo ...app/webroot/css/autocomplete.css ficou assim:
div.auto_complete {
    background: #003d4c;
    color: #ffffff;
}
div.auto_complete ul {
    margin:0;
    padding:0;
    width: 100%;
    list-style-type:none;
}
div.auto_complete ul li.selected {
    background-color: #62AF56;
    font-weight: bold;
}
div.auto_complete ul li {
    margin:0;
    padding:5px;
    cursor:pointer;
}

Obs.: Eu dei uma garibada no CSS que achei aqui (http://webdomino.blogspot.com/2006/08/ajax-autocompletion.html). =]

Ao acessar a aplicação em: http://<seu_dominio>/cidades/add e entrar com o prefixo de alguma cidade já cadastrada no banco no campo cidade, após alguns segundos (é bem rápido, acho que não chega nem a isso) a lista é carregada automagicamente* hehe!

Veja o resultado final!



Espero que tenham gostado! Qualquer dúvida é só perguntar.

Abraço!

Helper Ajax com jQuery, ahan!

Postado por Carlitos | Tags , , , | Postado em 17:58

0

Estou numa empreitada testando o Helper Ajax nativo do CakePHP e ontem, inclusive, postei um exemplo legal de como usar o método observeField() para fazer um Select Combo de Estado e Cidade. Lembrando que o CakePHP traz esse helper que foi criado usando as bibliotecas Prototype e Scriptaculos. Daí eu pensei: - Poxa vida, bem que alguém poderia criar um Helper jQuery ou algo do tipo para o Cake... E não é que esse alguém existe, e se chama: Damian Jóźwiak. Ele disponibilizou a sua criação no Bakery com um link pro site onde tem os exemplos, download do código e até um teste de benchmark com outras bibliotecas (YUI, Dojo, etc).
Baixei o Helper Ajax do Damian e fui fazer uns testes, vejam:

Usei uma cópia da aplicação do Select Combo de Estado e Cidade, para isto basta copiar e colar a pasta app, renomeando-a, no meu caso chamei de "appjquery". A aplicação anterior eu acesso assim: "http://localhost/cake137/" e a nova eu acesso assim: "http://localhost/cake137/appjquery".

Feito isto eu extraí os arquivos: ajax.php e javascript.php para o diretório de helpers (/appjquery/views/helpers/).

Extraí também o jquery-1.4.2.min.js para o diretório /appjquery/webroot/js/.

Na minha view appjquery/views/users/add.ctp eu troquei o Prototype e o Scriptaculos pelo jQuery =P:

<?php
    //$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); 
    
    $javascript->link('jquery-1.4.2.min', false);
.
.
.

Pronto! Agora quem está fazendo o AJAX funcionar é o jQuery... e não precisei mudar nada as chamadas das funções.

Não conheço muito o Prototype, muito menos o Scriptaculos então pra mim foi muito vantajoso esse helper. Sem contar na facilidade de integração com o jQuery UI.

Bom, vi que esse helper permite muita coisa que ainda não testei. Vou começar a usá-lo a partir de agora e sempre que tiver novidades posto aqui!

Vejam as imagens dele funcionando! Ah, já ia me esquecendo... fiz um teste rápido com poucos registros e o helper nativo ainda ganha um pouco em desempenho =/... mas o jQuery lançou esta semana a sua versão 1.5 e os testes mostram uma melhora execelente principalmente para o uso do AJAX e para os navegadores Chrome e Firefox 4.



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! =]