Como usar "máscaras" em campos de formulário do Cake com plugin maskedinput pra jQuery

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

0

No post anterior mostrei como usar "máscaras" em campos de formulário do Cake com plugin meioMask pra jQuery. Hoje vou mostrar um outro plugin que faz basicamente a mesma coisa, porém ele mostra a "máscara", digamos assim, assim que damos um focus no campo. Por isso gosto e uso muito ele. Trata-se do maskedinput (http://digitalbush.com/projects/masked-input-plugin/).

Bom, antes de começar dá um play nesse som =]
1) Primeiramente fazemos o download do plugin maskedinput através do link http://digitalbush.com/projects/masked-input-plugin/. Eu baixei a versão 1.2.2 minified. Salvando o script em app/webroot/js;

2) Em uma view qualquer (escolhi a app/views/users/add.ctp) gerada via BAKE eu criei três campos: dtNasc, cpf e hora. Vejam:
<?php
//... app/views/users/add.ctp
...
  echo $this->Form->input('dtnasc',
   array(
    'label' => 'Data de Nascimento'
   ) 
  );
  echo $this->Form->input('cpf',
   array(
    'label' => 'CPF'
   ) 
  );
  echo $this->Form->input('hora',
   array(
    'label' => 'Hora'
   ) 
  );
...
?>

3) Adiciono os scripts (jQuery e o plugin) na view. Vejam:
<?php
//... app/views/users/add
 $javascript->link(
  array(
   'https://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js', 
   'jquery.maskedinput'
  ), 
 false);
...
?>

4) Conforme falei no post anterior usaremos os IDs dos campos para que o jQuery "os encontre" no DOM. Então setamos as máscaras para cada campo, para isto usamos a função mask(). Vejam:
<?php
//... app/views/users/add.ctp
...
 $javascript->codeBlock('
  jQuery(function($){
   $("#UserDtnasc").mask("99/99/9999");
   $("#UserCpf").mask("999.999.999-99");
   $("#UserHora").mask("99:99");
  });
 ', array('inline' => false));
...
?>

5) Pronto! Simples não? Repare que ao colocar o cursor do mouse sobre os campos a "máscara" já é mostrada. Isso facilita muito para o usuário, uma vez que ele já prevê como os valores serão formatados (mascarados). Vejam o código final da view:

<?php
//... app/views/users/add.ctp
 $javascript->link(
  array(
   'https://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js', 
   'jquery.maskedinput'
  ), 
 false); 
 $javascript->codeBlock('
  jQuery(function($){
   $("#UserDtnasc").mask("99/99/9999");
   $("#UserCpf").mask("999.999.999-99");
   $("#UserHora").mask("99:99");
  });
 ', array('inline' => false));
?>
<div class="users form">
<?php echo $this->Form->create('User');?>
 <fieldset>
   <legend><?php __('Add User'); ?></legend>
 <?php
  echo $this->Form->input('dtnasc', 
   array(
    'label' => 'Data de Nascimento'
   ) 
  );
  echo $this->Form->input('cpf', 
   array(
    'label' => 'CPF' 
   ) 
  );
  echo $this->Form->input('hora', 
   array(
    'label' => 'Hora' 
   ) 
  );
 ?>
 </fieldset>
<?php echo $this->Form->end(__('Submit', true));?>
</div>

Bom, espero que tenham gostado! Apesar de ser "perfumaria", muitas vezes os recursos de máscara facilitam a vida do usuário. Só não pode esquecer de tratar os dados no server-side, pois o usuário pode estar com o javascript desabilitado, ok? =P

Como usar "máscaras" em campos de formulário do Cake com plugin meioMask pra jQuery

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

0


Tá precisando colocar umas máscaras em campos de formulário? Não precisa ficar quebrando a cabeça com um monte de javascript. Existem muitos plugins prontos para jQuery e outras bibliotecas.
O plugin meioMask (http://plugins.jquery.com/project/meioMask) é uma excelente iniciativa brazuca do pessoal do MeioCódigo.com. Esse plugin para jQuery é bem flexível, veja como usá-lo em http://www.meiocodigo.com/projects/meiomask/.

Bom, mãos na massa!

1) O primeiro passo é entrar no site do meiomask e baixar a útltima versão estável do script e salvá-lo em: app/webroot/js. No meu caso baixei a versão 1.1.3 (comprimida) e salvei com o nome "jquery.meio.mask.min.js";


2) Peguei uma view qualquer (escolhi a app/views/users/add.ctp) previamente gerada pelo BAKE que possui um formulário com os seguintes campos: DtNasc, CPF e Hora (não me pergunte pra que raio de cadastro é esse... coloquei só pra exemplificar os casos =]), vejam:
<?php
// ...app/views/users/add.ctp  
...
...
...

  echo $this->Form->input('dtnasc',
   array(
    'label' => 'Data de Nascimento'
   ) 
  );
  echo $this->Form->input('cpf',
   array(
    'label' => 'CPF'
   ) 
  );
  echo $this->Form->input('hora',
   array(
    'label' => 'Hora'
   ) 
  );

...
...
...
?>

3) Agora adiciono o jQuery e o script do plugin meiomask na view, vejam:

<?php
// ...app/views/users/add.ctp
 $javascript->link(
  array(
   'https://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js',
   'jquery.meio.mask.min'
  ),
 false);
...
...
...
?>

4) Logo em seguida, adicionamos um "bloco de código" setando as máscaras para os campos do formulário usando o ID do campo para isso.
OBS: O Form Helper do Cake sempre gera um ID para cada campo criado seguindo o padrão id="ModelCampo". No nosso caso o model é o User e temos os campos Dtnasc, Cpf e Hora, o que gera os ids: UsersDtnasc, UserCpf e UserHora. O jQuery usa o '#' para referenciar IDs no DOM.

<?php
// ...app/views/users/add.ctp
...
...
...

 $javascript->codeBlock('
  jQuery(function($){
   $("#UserDtnasc").setMask("39/19/2999");
   $("#UserCpf").setMask("999.999.999-99");
   $("#UserHora").setMask("29:59");
  });
 ', array('inline' => false));
...
...
...

?>

5) Feito isto é só acessar o .../users/add e verificar o resultado ao entrar com números nos campos. Veja que ele permite apenas a entrada de números e adiciona automaticamente os pontos, barras e etc, que foram setados no molde da máscara. O código final ficou assim:

<?php // ...app/views/users/add.ctp $javascript->link( array( 'https://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js', 'jquery.meio.mask.min' ), false); $javascript->codeBlock(' jQuery(function($){ $("#UserDtnasc").setMask("39/19/2999"); $("#UserCpf").setMask("999.999.999-99"); $("#UserHora").setMask("29:59"); }); ', array('inline' => false)); ?> <div class="users form"> <?php echo $this->Form->create('User');?> <fieldset> <legend><?php __('Add User'); ?></legend> <?php echo $this->Form->input('dtnasc', array( 'label' => 'Data de Nascimento' ) ); echo $this->Form->input('cpf', array( 'label' => 'CPF' ) ); echo $this->Form->input('hora', array( 'label' => 'Hora' ) ); ?> </fieldset> <?php echo $this->Form->end(__('Submit', true));?> </div>
Eu particularmente uso outro plugin para máscaras (que mostrarei no próximo tópico), mas o meioMask é uma excelente alternativa além de ser brasileiro.

Espero que seja útil! Abração!

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!

Captcha usando o vendor PhpCaptcha

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

0

Bom, alguns dias sem postar nada e eu procurando algo interessante para compartilhar com vocês. Vamos lá então...

Estava quebrando a cabeça para implementar um Captcha (Isso mesmo! CAPTCHA, ou seja, aquelas letrinhas todas bagunçadas que impedem que um "não-humano" consiga submeter um formulário automaticamente... se bem que as vezes até um humano não consegue identificar as letras, mas vamos lá).

Baseado neste artigo que vi no Bakery vamos utilizar o PhpCaptcha como vendor. Segue os passos:

1) Faça o download do PhpCaptcha através do link http://www.ejeliot.com/pages/2 e descompacte o arquivo em "app/vendors/phpcaptcha" (crie este diretório caso não exista).

2) Faça o download dos arquivos de fontes Vera do GNOME através do link http://ftp.gnome.org/pub/GNOME/sources/ttf-bitstream-vera/1.10/ttf-bitstream-vera-1.10.zip e descompacte o arquivo em "app/vendors/phpcaptcha/fonts/".

3) Vamos criar o componente Captcha:

<?php

// Salve em app/controllers/componentes/captcha.php

App::import('Vendor','PhpCaptcha' ,array('file'=>'phpcaptcha'.DS.'php-captcha.inc.php'));

class CaptchaComponent extends Object
{
  var $controller;

  function startup( &$controller ) {
    $this->controller = &$controller;
  }

  function image(){

    $imagesPath = APP . 'vendors' . DS . 'phpcaptcha'.'/fonts/';

    $aFonts = array(
      $imagesPath.'VeraBd.ttf',
      $imagesPath.'VeraIt.ttf',
      $imagesPath.'Vera.ttf'
    );

    $oVisualCaptcha = new PhpCaptcha($aFonts, 200, 60);

    $oVisualCaptcha->UseColour(true);
    //$oVisualCaptcha->SetOwnerText('Source: '.FULL_BASE_URL);
    //$oVisualCaptcha->SetNumChars(6);
    $oVisualCaptcha->Create();
  }

  function audio(){
    $oAudioCaptcha = new AudioPhpCaptcha('/usr/bin/flite', '/tmp/');
    $oAudioCaptcha->Create();
  }

  function check($userCode, $caseInsensitive = true){
    if ($caseInsensitive) {
      $userCode = strtoupper($userCode);
    }

    if (!empty($_SESSION[CAPTCHA_SESSION_ID]) && $userCode == $_SESSION[CAPTCHA_SESSION_ID]) {
      // clear to prevent re-use
      unset($_SESSION[CAPTCHA_SESSION_ID]);

      return true;
    }
    else
      return false;
  }
}
?>

4) Adicione os métodos aos controllers que desejar. No caso vou adicionar ao controller Users:
<?php
class UsersController extends AppController
{
  ...
  // Adicione o componente Captcha ao controller
  var $components = array('Captcha');
  ...
  function captcha_image(){
    Configure::write('debug',0);
    $this->layout = null;
    $this->Captcha->image();
    $this->render();
  }

  // Não vou entrar no mérito do captcha com audio
  function captcha_audio()
  {
    $this->Captcha->audio();
  }
  ...
}
?>

5) Na view invoque o método captcha_image:
<img id="captcha" src="<?php echo $html->url('/users/captcha_image'); ?>" alt="" />

6) Agora para verificar se o usuário digitou corretamente os caracteres do Captcha usamos a função check, veja:
// Suponha que o campo texto onde o usuário preencheu os caracteres que leu
// no captcha seja "captcha", então temos
if(!$this->Captcha->check($this->data['User']['captcha'])){
  $this->Session->setFlash('Você não preencheu corretamente as letras da imagem!');
}


Veja como fica o resultado deste captcha colorido:


Bom, espero que funcione, pois aqui funcionou. Tem outros captchas para Cake e PHP também, veja aqui.

Abração!

Exportar dados - O príncipio...

Postado por Carlitos | Tags , , , | Postado em 19:23

1

Bom, sempre é preciso exportar dados (se vc ainda não fez isso, certamente num futuro próximo fará :P).

Missão: implementar um exportação de dados (para formatos: .xls - Excel, .doc - Word e .pdf, Adobe Reader) usando o CakePHP.

Colhendo dados: achei uma solução pronta para o .xls no Bakery - http://bakery.cakephp.org/articles/view/generate-excel-spreadsheets-from-your-database, li por cima mas não implementei. Achei também outra solução no CodigoFonte do UOL para implementar o .xls, essa eu testei e funcionou, falta apenas adicionar ao Cake pois esta em php simples. E achei também, neste blog aqui, uma implementação para .doc e para .xls, mas não testei ainda. Para o PDF usei uma solução pronta do Bakery usando o FPDF.

Cenas do próximo capítulo: Testar os scripts e ver qual possui o melhor desempenho. Verificar qual a melhor forma de acopla-los ao Cake (se via Helper ou Component).

Abraço,

Criando componentes

Postado por Carlitos | Tags , , , | Postado em 12:03

0

Bom dia povo! Ontem eu vi que havia um trecho de código que sempre se repetia ao longo de alguns controllers de um sistema em Cake que estou desenvolvendo e resolvi criar um componente com esta funcionalidade e aproveitar o reuso de código de forma limpa né ;)

Bom, vamos lá... mãos a obra!

O código é simples, trata-se de uma função que retorna o nome do navegador (browser) que o usuário está utilizando e qual a versão dele. Mas pra que usar isto, mano? Bom, é muito útil quando há alguma parte do seu site/sistema que "não funciona" em determinado navegador. Por exemplo, no meu caso existe uns getgads (firulas) na parte de validação de formulário que "não funciona" no IE 6 ou inferior.

Os componentes devem ficar dentro da pasta "./app/controllers/components/". O nome do meu componente é "Navegador", então criei o arquivo "./app/controllers/components/navegador.php", com o código

<?php
class NavegadorComponent extends Object {
}
?>

Depois adicionei a função que retorna as informações sobre o navegador
<?php
class NavegadorComponent extends Object {
  // Função que retorna o Brownser que está sendo usado
  function getBrowser(){
    $var = $_SERVER['HTTP_USER_AGENT'];
    $info['browser'] = "OTHER";
    // valid brosers array
    $browser = array ("MSIE", "OPERA", "FIREFOX", "MOZILLA", "NETSCAPE", "SAFARI", "LYNX", "KONQUEROR");
    // bots = ignore
    $bots = array('GOOGLEBOT', 'MSNBOT', 'SLURP');

    foreach ($bots as $bot)
    // if bot, returns OTHER
    if (strpos(strtoupper($var), $bot) !== FALSE)
      return $info;

    // loop the valid browsers
    foreach ($browser as $parent){
      $s = strpos(strtoupper($var), $parent);
      $f = $s + strlen($parent);
      $version = substr($var, $f, 5);
      $version = preg_replace('/[^0-9,.]/','',$version);
      if (strpos(strtoupper($var), $parent) !== FALSE){
        $info['browser'] = $parent;
        $info['version'] = $version;
        return $info;
      }
    }
    return $info;
  }
}
?>

Agora para poder ter acesso a esta nova funcionalidade você deverá adicionar o componente "Navegador" ao seu controller. Mas como? Veja exemplo abaixo
<?php
class TestesController extends AppController{
  $components = array('Navegador'); // Adiciono o componente "Navegador" ao  controller Testes

  function add(){
    /* guardo na variável 'browser' o array retornado pela função getBrowser do
    * meu componente Navegador com os dados do navegador do usuário 
    * como usei o $this->set, esta informação estará disponível para a view add
    * através da variável $browser, que é um array. */

    $this->set('browser', $this->Navegador->getBrowser());
  }
}
?>

Simples não? Espero que tenham entendido e que seja útil!
Consultem também a documentação oficial do CakePHP sobre o uso e criação de componentes aqui!

Abração,

Adicionar Syntax Highlight ao layout do Blogger/Blogspot

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

0

É muito ruim ler um código sem syntax highlight, não é? Pois é, passei alguns duas postando aqui no cakenaveia e resolvi procurar esse recurso para adicionar ao bloguezito... achei um muito legal que atende a diversas linguagens: PHP, HTML, CSS, JScript, Java, Perl e etc. O projeto é velhinho já (desde 2007) e muito bacana, veja aqui no site do script deles (mas só fiquei sabendo deles através do artigo deste camarada aqui {créditos}.

Então mãos a obra:
1º) baixe os scripts aqui, em seguida hospede eles em algum lugar.
2º) Acesse o painel de controle do seu blog (blogspot/blogger - mas vale para qualquer outro feito em Wordpress), vá até a aba Layout em seguida HTML.
3º) Dentro do HEAD do html do blog adicione os scripts do syntaxhighlight das linguagens que você vai precisar, veja:


<script language='javascript' src='http://...seusite.../shCore.js'/>

<script language='javascript' src='http://...seusite.../shBrushJava.js'/>

<script language='javascript' src='http://...seusite.../shBrushPhp.js'/>

<script language='javascript' src='http://...seusite.../shBrushJScript.js'/>

Adicione também o CSS do syntaxhighlight
<link href='http://...seusite.../SyntaxHighlighter.css' rel='stylesheet' type='text/css'/>

4º)Antes da </body> adicione o código
<script language='javascript'>
  dp.SyntaxHighlighter.ClipboardSwf = 'http://...seusite.../clipboard.swf';
  dp.SyntaxHighlighter.BloggerMode();
  dp.SyntaxHighlighter.HighlightAll('code');
</script>

5º) Pronto! Agora para usar basta escrevesr os códigos dentro de um tag <pre name='code' class='php ou css ou html.. (a linguagem que vc está escrevendo)> seus codigos <pre> e eles aparecerão bonitinhos para os leitores.

Abração pra todos!

Mensagem carregando... usando CSS e Javascript

Postado por Carlitos | Tags , , | Postado em 13:54

3

Corri atrás de uma forma simples de mostrar uma mensagem de "Carregando..." quando o usuário clica em um link e aguarda o processamento das informações. Bom, depois de muito camelar pela internet colhi algumas informações e fiz um bem simples usando o atributo FIXED do CSS. Mãos a obra!!!

1º) Bom, primeiro adicionamos o script abaixo no head do layout default. No meu caso é o default.ctp.

//Mostra o carregador
function __loadMostra(){
  var objLoader = document.getElementById("carregador_pai");
  objLoader.style.display = "block";
  objLoader.style.visibility = "visible";
}

Este código "mostra" (atribui 'block' e 'visible' para ele) o elemento carregando.

2º) Adicione a DIV que contém a mensagem de carregando.

Observe que declarei que a DIV deve ficar escondida (display: none; visibility: hidden;).
Como eu uso o CakePHP eu adicionei uma imagem (aqui neste site tem várias imagens de Carregando para escolher):
echo image('carregando.gif', array('alt' => 'Carregando...', 'border' => 0));

3º)Adicione o seguinte CSS:
#carregador_pai{
  width: 100%;
  height:100%;
  position: absolute;
  text-align: center;
  background-color:#414141;
  filter:alpha(opacity=90);
  opacity:0.9;
  overflow: auto;
  top: 0;
  left: 0;
  z-index:2;
}

#carregador_fundo{
  font-size: 1px;
  left: 8px;
  width: 113px;
  position: relative;
  top: 50px;
  height: 7px;
  background-color: #ebebe4
}
#carregador_posicao{
  padding-top:20%;
}
#carregador{
  border-right: #6a6a6a 1px solid;
  padding: 0px 10px 0px 16px;
  border-top: #6a6a6a 1px solid;
  display: block;
  font-size: 11px;
  z-index: 2;
  margin: 0px auto;
  border-left: #6a6a6a 1px solid;
  width: 250px;
  height:70px;
  color: #000000;
  border-bottom: #6a6a6a 1px solid;
  font-family: Tahoma, Helvetica, sans;
  background-color: #ffffff;
  text-align: left;
}

@media screen{
  body>div#carregador_pai{
    position: fixed;
  }
}
Esse CSS cria uma tela como na imagem abaixo.


Ah! Esse CSS não funciona no IE¨6 ou inferior. Mas quem liga né? ;) Desde já estou fazendo minha parte no movimento ATUALIZE SEU IE! E outra coisa, o único problema que os usuários do Internet Explorer 6 ou inferior terão é que não verão a tela de Carregando, mas o carregamento da página não é afetado.

4º) Guenta aí, tá quase acabando ;). Agora sempre que você tiver um link basta adicionar a função
onclick = "__loadMostra();";

Ou seja, ao clicar ele mostra a DIV do Carregando... e assim que a próxima página termina de ser carregada a DIV volta a ser ocultada. Dá para adicionar ele nos formulário também, excluindo o botão de submit e criando um INPUT type BUTTON e adicionando o __loadMostra() seguido do submit().
onclick = "__loadMostra(); submit();";

Com o CakePHP fica assim
// Cria o botão de Enviar que submete o formulário
echo $form->button('Enviar', array('onclick' => '__loadMostra(); submit();'));
// Cria a tag /FORM
echo $form->end();

Simples não? Dúvidas? Eu gostei do resultado final. Se tiver algo errado comentem aí!

Ah! Na imagem parece que estou usando o IE8 né? Não... esse é o Firefox "camuflado" com o thema "ie8fox".

Abração!

Confirmação de senha

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

0

Poxa, havia criado um formulário para criação de um novo usuário com os campos: username, password e email. Hoje precisei implementar um novo campo (Repita a senha)... como uso o "Auth" sempre que há um array contendo as chaves "username" e "password" ele automágicamente (como dizem a galerinha do CakeBook) encriptografa o campo password usando as configurações de Salt definidas no core.php (Blz, mas isso não vem ao caso).

Bom, o problema é que quando comparava

if($this->data['User']['password'] == $this->data['User']['confirm_password']) 

dava errado... depois de uns debugs no array $this->data percebi que o password já chegava ao controller encriptado, então a solução descobri aqui. Basta usar o metódo $this->Auth->password que encripta de acordo com as definições do Salt definidas no core.php, assim podemos comparar
$this->data['User']['password'] == $this->Auth->password($this->data['User']['confirm_password'])
Rá!

P.S.: Agora, sempre que o usuário retornava a tela do formulário (por erro em algum campo) o campo senha ficava enorme, pois estava preenchido com a senha encriptada. Para isso sempre que ocorre um erro de confirmação de senha eu limpo o password e o confirm_password, e quando ocorre erro em outro campo eu copio o valor do confirm_password para o password!

Abração,