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,