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

Expressões Regulares, um dia você precisará delas....

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

0

Opa... Eu sempre menosprezei as expressões regulares sem entender o poder delas. Elas não servem apenas para a validação de dados, como eu achava, pois com poucas coisa você consegue realizar pesquisas/verificações complexas.

Uma expressão regular é um padrão formado por simples caracteres alfa-numéricos e especiais.Ou seja, a expressão: /[aeiou]/gi, encontraria todas as vogais maiúsculas ou minúsculas em um determinado texto.

Para saber mais sobre a sintaxe eu recomendo a leitura deste artigo do iMasters, http://imasters.com.br/artigo/2515/javascript/regular_expression/, entre outros que você encontra facilmente no Google. E se quiser "treinar" os seus conhecimentos poderá utilizar o http://gskinner.com/RegExr/ para testar seus padrões.

Mas o que isso tem haver com o CakePHP? Tem muito! Não só com o Cake, mas todo programador que se preze deve conhecer o básico de expressões regulares, pois ela poupa um precioso tempo. Veja um exemplo básico de como usar expressões regulares no Cake:

O $validate aceita regras de validação customizadas onde podemos utilizar: expressões regulares ou métodos (funções). No próprio book do Cake ele dá o exemplo abaixo:

var $validate = array(
    'login' => array(
        'rule' => array('custom', '/[a-z0-9]{3,}$/i'),  
        'message' => 'Apenas letras e números, mínimo de 3 caracteres'
    )
);

Pensando assim já podemos imaginar infinitas possibilidades de validação resolvidas com as benditas Regular Expressions.

Por hoje é só, flws

jQueryUI Autocomplete no CakePHP - Agora sim!

Postado por Carlitos | Tags , , , , | Postado em 15:55

3

Dias atrás postei como usar o Autocomplete do jQueryUI no cakePHP, porém tive que fazer uma "gambiarra" pra que a opção de obter os valores via data source "funcionasse".

Bom, então depois de um help do pessoal do jquery-br vi que o arquivo que gera os resultados recebe um valor via GET através da variável "term". Com isso basta verificar quais das chaves possui o "term" (ou até aqueles que começam com "term", basta algumas alterações no código).

Agora veja a solução pronta!

1º O Controller Users (./app/controllers/users_controller.php):

<?php
/**
* @author Carlitos Fioravante
*/

class UsersController extends AppController {
  var $name = 'Users';
  var $helpers = array('Javascript');

  function autocomplete(){
    $this->set('users', $this->User->find('list', array('fields' => 'User.username')));
  }

  function list_users(){
    $this->layout = false;
    $this->set('users', $this->User->find('list', array('fields' => 'User.username')));
  }
}
?>

2º A view "autocomplete.ctp" (./app/views/users/autocomplete.ctp):
<?php
/**
* @author Carlitos Fioravante
*/

$javascript->link(array('http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js','http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js'), false);
echo $html->css('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/flick/jquery-ui.css', null, array('inline' => false));

$javascript->codeBlock('
  jQuery(function($){
    $("#username").autocomplete({
      source: "' . $html->url(array('action' => 'list_users')) . '"
    });
  });
', array('inline' => false));

echo $form->create('Users');
echo $form->input('username', array('id' => 'username'));
echo $form->end('OK');
?>

3º A view "list_users.ctp" (./app/views/users/list_users.ctp):
<?php
/**
* @author Carlitos Fioravante
*/

header('Content-type: application/json; charset=UTF-8');

$users_selecteds = array();

if(!empty($_GET['term'])){
  foreach($users as $user){
    if(strripos($user, $_GET['term']) !== false){
      array_push($users_selecteds, $user);
    }
  }
}

echo json_encode($users_selecteds);

?>


Táh aê! Espero que seja proveitoso para alguém!

Abraço,

jQueryUI Autocomplete no CakePHP

Postado por Carlitos | Tags , , , | Postado em 15:08

0

Então... um colega nosso do grupo cakephp-br entrou em contato comigo via MP perguntando como utilizar o autocomplete do jQueryUI no CakePHP. Topei ajudá-lo e já aproveitei para conhecer as novidades do Cake 1.3.2.

Bom, depois de quebrar um pouco a cabeça com as mudanças na forma de invocar o JavaScript, entre outras mudanças neste novo cake, cheguei a uma solução que eu não recomendo para o caso em que o campo a ser listado do BD tenha muitos registros. E pensando bem um campo autocomplete é sempre mais indicado nestas circunstâncias mesmo, por exemplo para autocompletar um nome de um produto (de uma lista já predefinida, normalmente não muito grande), de uma cidade, etc.

Primeiramente vamos ver o que o jQueryUI diz a respeito do Autocomplete.

1) Na opção "default" o Autocomplete pode ser gerado a partir de uma lista de "tags" pré-definidas em um Array passado à variável "source" da função, assim:

$(function() {
  var availableTags = ["c++", "java", "php", "coldfusion", "javascript", "asp", "ruby", "python", "c", "scala", "groovy", "haskell", "perl"];
  $("#tags").autocomplete({
    source: availableTags
  });
});

2) Outra opção é obter este Array de tags através de uma página que retorne um array no formato JSON, assim:
$(function() {
  $("#tags").autocomplete({
    source: "search.php"
  });
});
Como os dados estão numa tabela do BD o ideal seria usar a 2ª opção, porém não consegui utilizá-la. Criei uma página que gerava o array no formato JSON (Ex.: ["c++", "java", "php", "coldfusion", "javascript", "asp", "ruby", "python", "c", "scala", "groovy", "haskell", "perl"]), porém o autocomplete "puxava" todos os elementos como se fossem um só, ou seja, se eu começava a digitar "a" ele me mostrava todas as opções do Array e não apenas aquelas que continham o caractere "a".

Bom, parti para a 1ª opção então. Dentro do meu controller eu criei a action "autocomplete" e nela eu busco todos os registros que quero para o meu campo "autocompletável" :D, assim:
function autocomplete(){
  $this->set('users', $this->User->find('list', array('fields' => 'User.username')));
}

Obs.: No meu caso estou usando uma tabela "Users" que possui um campo "username".

Já na view "autocomplete.ctp" eu estou recebendo a variável $users, que vem com um Array da seguinte forma:
Array
(
  [1] => a1_teste@teste.com
  [2] => a2_teste@teste.com
  [3] => outro_nome_de_teste@teste.com
   .
   .
   .
)

e então crio uma String com o formato array do JSON, desta forma:
$usernames = '[';

foreach($users as $user)
  $usernames .= '"' . $user . '",';

$usernames .= ']';

Então temos o seguinte código para a view:
<?php
/**
* @author Carlitos Fioravante
*/

$javascript->link(array('http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js','http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js'), false);
echo $html->css('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/flick/jquery-ui.css', null, array('inline' => false)); // Tema do Flickr, hehe :D

$usernames = '[';

foreach($users as $user)
  $usernames .= '"' . $user . '",';

$usernames .= ']';

$javascript->codeBlock('
  jQuery(function($){
    var usernames = ' . $usernames . ';
    $("#username").autocomplete({
      source: usernames
    });
  });
', array('inline' => false));

echo $form->create('Users');
echo $form->input('username', array('id' => 'username'));
echo $form->end('OK');

debug($users);
?>

Bom, não é a solução perfeita mas funcionou! Ainda estou a procura dela...

Veja a solução correta aqui nesse novo post!

Abraço,

Até que enfim uma logo!

Postado por Carlitos | Tags , , | Postado em 21:01

0

Logo nova (quer dizer... primeira logo) e postagem nova que é bom nada! Hehe!
Em breve trago novidades, estou estudando a API do Google Maps, que é fantástica e dá pra fazer um monte de coisa legal rapidinho. Assim que tiver algo realmente legal, posto aqui!

Flws!