Desenvolvimento - PHP

Aplicando AJAX com PHP, parte 2 - Combos e validação de formulário

Segunda parte da série sobre integração de AJAX com PHP utilizando a biblioteca CPAINT. Produziremos então um formulário de cadastro de classificado de carros, que verifica em tempo real se o e-mail fornecido já está cadastrado e uma caixa de seleção de fabricantes, que uma vez que um fabricante foi selecionado, ele carrega os modelos de carro.

por Alfred Reinold Baudisch



Um ano depois da parte 1, aqui seguirá a continuação dos artigos sobre AJAX e PHP (desculpem a demora :). Quando a parte 1 foi escrita, AJAX já era largamente utilizado por toda a WEB. Mas agora, com todo esse tempo que se passou, o uso está MONSTRUOSO. O que mais vemos, são sites, sites e mais sites que utilizam AJAX, seja só num formulário, seja em toda a estrutura. Por isso, está na hora de desenvolvermos um exemplo mais prático.

Produziremos então um formulário de cadastro de classificado de carros, que verifica em tempo real se o e-mail fornecido já está cadastrado e uma caixa de seleção de fabricantes, que uma vez que um fabricante foi selecionado, ele carrega os modelos de carro (uma das características mais utilizadas com AJAX!, o chamado "combo master"). E claro, o anunciante também dirá o preço, e nosso AJAX vai verificar se o preço está válido. No exemplo discutido, só pode haver um classificado cadastrado por e-mail.

Obs: Você já deve ter conhecimento da estrutura da classe CPAINT e como integrá-la com o PHP. Caso não conheça (ou não se lembre), veja a parte 1 dessa série de artigos.

1. O Banco de Dados
Com AJAX verá que é quase sempre necessário ter todo tipo de dado no banco de dados, para facilitar a interação e o usuário ter o menor trabalho possível. Segundo nossa definição acima, nenhum usuário digitará a marca e o modelo do carro a ser cadastrado, mas sim, ele apenas selecionará os dados com 2 cliques do mouse!

As tabelas necessárias são:

  • Fabricantes (c_fabricantes), que terá as fabricantes de automóveis;
  • Modelos (c_modelos), que terá modelos de automóveis. Cada modelo será vinculado a um ID de alguma fabricante cadastrada. Obviamente, uma fabricante pode ser vinculada a N automóveis.
  • Classificados (c_classificados), onde os classificados são armazenados, contendo e-mail do anunciante, preço e modelo anunciados (Não é inserido o fabricante, porque o próprio modelo já é vinculado a um. De qualquer forma, operações relacionais de banco de dados ficam fora do escopo desse artigo).

Que venham os dados!
-- c_fabricantes ------ CREATE TABLE c_fabricantes ( id int(11) NOT NULL auto_increment, fabricante varchar(50) default NULL, PRIMARY KEY (id) ) AUTO_INCREMENT=5 ; INSERT INTO c_fabricantes (id, fabricante) VALUES (1, "Volkswagen"), (2, "Chevrolet"), (3, "Fiat"), (4, "Ford"); -- c_modelos ------ CREATE TABLE c_modelos ( id int(11) NOT NULL auto_increment, fabricante_id int(11) default NULL, modelo varchar(50) default NULL, PRIMARY KEY (id) ) AUTO_INCREMENT=12 ; INSERT INTO c_modelos (id, fabricante_id, modelo) VALUES (1, 1, "Gol"), (2, 1, "Golf"), (3, 1, "Polo"), (4, 1, "Parati"), (5, 2, "Corsa Hatch"), (6, 2, "Corsa Sedan"), (7, 2, "Astra"), (8, 2, "Vectra"), (9, 3, "Palio"), (10, 3, "Stilo"); -- c_classificados ------ CREATE TABLE c_classificados ( id int(11) NOT NULL auto_increment, modelo_id int(11) default NULL, email varchar(50) default NULL, preco decimal(10,2) default NULL, PRIMARY KEY (id) ) AUTO_INCREMENT=2 ; INSERT INTO c_classificados (id, modelo_id, email, preco) VALUES (1, 7, "meu@email.com", 45000.00);
A estrutura e os dados SQL.

2. Formulário
Vamos agora criar o formulário. Como o formulário terá campos select gerados com dados automáticos, será necessário um jeito prático de construí-los. É possível utilizar um for interno para cada select, mas obviamente, isso é muito "cavernoso". Logo, o que precisamos é de uma função auxiliar que crie campos select (ela não será dissecada, porque como disse, é apenas auxiliar, mas o próprio código é comentado e explica o funcionamento). Essa função será incluída num arquivo selectbox.php com o código:

"E-mail", "endereco" => "Endereço"); * Vira: * E-mail... etc * * @param array $itens Itens do select box * @param string $nome Nome e ID * @param string $selecionado Valor do item selecionado * @param string $extras Extra, por exemplo um código JS * @param string $class Classe CSS */ function SelectBox($itens, $nome, $selecionado = false, $extras = false, $class = false) { $select_box = "\n"; foreach($itens as $valor => $texto) { $select_box .= "" . $texto . "\n"; } $select_box .= "\n"; return $select_box; } ?>
selectbox.php

Tendo a função auxiliar é necessário primeiro criar as rotinas de conexão com banco de dados e inclusão de bibliotecas. No nosso caso, apenas conectamos com banco de dados, incluimos a cpaint2.inc.php (conforme explicado na parte 1) e a selectbox.php. Chamemos esse arquivo de utils.php:
<?
//ConectacomoMySQL
mysql_connect("localhost","root","");
mysql_select_db("exemplos");

//Auxiliar
require"lib/selectbox.php";
//C-Paint
require"lib/cpaint2.inc.php";
?>

E agora o código fonte do script com o formulário, formulario.php:
<?
require"utils.php";

//ObtémFabricantes,parainserirnoselectboxrelacionado.
$SQL="SELECTid,fabricante
FROMc_fabricantes
ORDERBYfabricanteASC"
;
$resId=mysql_query($SQL);

//Arrayqueserápassadoparaafunçãoselectbox.
//Oprimeiroitem,dechave0(primeiroíndicedoarray),
//indicaparaousuárioselecionar.Casoeleenviesemselecionar,
//irávalor0esaberemosquenãofoifeitaaseleção.
$Fabricantes=array("Selecione");

//Passaospares:IDdoFabricante=>NomedoFabricante
while($dados=mysql_fetch_array($resId))
$Fabricantes[$dados["id"]]=$dados["fabricante"];

//Criaoselectboxcomasfabricantes,passandoospares
//obtidosacima.
//Oquartoparâmetroéumachamadaaumafunção
//Javascriptdefinidaemvalidacao.js.Acadamudança
//nesseselectbox,elechamaráessafunção.
$selectFabricantes=SelectBox($Fabricantes,"fabricante","","onChange="carregaModelos(this);"");
?>

<!--INCLUIACLASSECPAINT-AJAX-->
<scripttype="text/javascript"src="lib/cpaint2.inc.compressed.js"></script>

<!--Rotinasdevalidaçãodoformulário-->
<scripttype="text/javascript"src="lib/validacao.js"></script>

<!--Formulário-->
<formaction="cadastro.php"method="post"onSubmit="returnverificaForm(this);">
<p>E-mail:<br/>
<inputtype="text"name="email"id="email"onblur="verificaEmail();"/>
<spanid="email_erro"> <?php

//Bancodedados,cpaint,selectbox
require"utils.php";

//InstanciaCpaint
$CPaint=newcpaint();

//Registrafunções
$CPaint->register(array("validaFormulario","verificaEmail"));
$CPaint->register(array("validaFormulario","obtemModelos"));
$CPaint->register(array("validaFormulario","verificaPreco"));

//IniciaCpainteretornodedados
$CPaint->start("ISO-8859-1");
$CPaint->return_data();

class
validaFormulario
{
function
verificaEmail($Email)
{
//Primeiroverificaseestáemformatoválido
if(preg_match("/^[a-z0-9\.\-_\+]+@[a-z0-9\-_]+\.([a-z0-9\-_]+\.)*?[a-z]+$/is",$Email))
{
//E-mailok,verificaseelejáestácadastrado
$SQL="SELECTemail
FROMc_classificados
WHEREemail=\""
.$Email."\"";
$resId=mysql_query($SQL);

//E-mailencontrado
if(mysql_num_rows($resId))
validaFormulario::EnviaValor("Sóépermitidoumclassificadopore-mail!");
}
//E-mailinválido
else
validaFormulario::EnviaValor("E-mailemformatoinválido!");
}

function
obtemModelos($fabricanteId)
{
$SQL="SELECTid,modelo
FROMc_modelos
WHEREfabricante_id="
.$fabricanteId."
ORDERBYmodeloASC"
;
$resId=mysql_query($SQL);

//Temmodelosparaafabricanteescolhida
if(mysql_num_rows($resId))
{
//Mesmoesquemadacriaçãodoselectboxdefabricantes
//emformulario.php
$Modelos=array("Selecione");

while(
$dados=mysql_fetch_array($resId))
$Modelos[$dados["id"]]=$dados["modelo"];

$selectModelos=SelectBox($Modelos,"modelo");

//EnviaoselectparaoJavascript
validaFormulario::EnviaValor($selectModelos);
}
//Semmodelos
else
validaFormulario::EnviaValor("N");
}

function
verificaPreco($Preco)
{
//Verificaseestáemformatoválido
if(!preg_match("/^([0-9]+)(\.[0-9]{1,2})?$/",$Preco))
{
validaFormulario::EnviaValor("Preçoemformatoinválido!
Insirapontoemcasodecasadecimaleatéduascasasdecimais.
Nãoutilizevírgula.Exemplosdepreçosválidos:123.2,45000,455.35,etc."
);
}
}

/**
*EssafunçãoretornaumvalorparaoCPAINTqueporsua
*vezretornaráparaapágina
*/
functionEnviaValor($Total)
{
global
$CPaint;
$CPaint->set_data($Total);
}
}

?>

Dissecando validaFormulario.php:

  1. O arquivo possui exatamente a mesma lógica, estrutura e explicação do ajaxCalculadora.php (aquela simples calculadora do artigo parte 1). Logo eu não explicarei a lógica, e sim, o que cada função faz. Somente devo destacar que você sempre deve registrar as funções a ser usadas pelo JS + PHP, como na calculadora registrou as funções Somar, etc, aqui eu registro:
    //Registrafunções
    $CPaint->register(array("validaFormulario","verificaEmail"));
    $CPaint->register(array("validaFormulario","obtemModelos"));
    $CPaint->register(array("validaFormulario","verificaPreco"));
  2. verificaEmail recebe uma string, e faz um teste em expressão regular. Caso aprovado, é um e-mail válido. Em seguida, verifica se há algum classificado com esse e-mail, caso positivo, envia uma mensagem para a CPAINT. No caso de e-mail inválido, também envia uma mensagem à CPAINT. (seguindo o mesmo mecanismo da calculadora).
  3. obtemModelos recebe um id de fabricante, obtido pela seleção do combo de Fabricantes. Caso tenha modelos para esse fabricante, cria um combo com os modelos (seguindo o mesmo mecanismo da criação do combo dentro de formulario.php). Caso não tenha modelo para esse fabricante, retorna N, que será tratado em validacao.js.
  4. verificaPreco recebe uma string e faz um teste se está num formato de preço aceito. Só retorna algo caso esteja inválido.
  5. Veja que o funcionamento é o mesmo da simplíssima calculadora da parte 1, somente com funções fazendo tratamentos e operações diferentes. E como sempre, o retorno para o JavaScript, utilizando o CPAINT, é feito com a função set_data, que no caso aqui e como na calculadora, eu encapsulo na função EnviaValor.

4. Funções JavaScript de Validação e Tratamento de Erros
E agora, talvez a parte mais complexa, as funções JavaScript que recebem os dados do formulário, enviam para o PHP, recebem o retorno, processam este retorno e efetuam alguma ação.

Eis que finalmente, validacao.js:
<?
//CriaobjetoCPAINT
varcp=newcpaint();
cp.set_transfer_mode("POST");
cp.set_response_type("TEXT");

//------------------------------------
//GERAL
//------------------------------------
varerroEmail=false;
var
erroFabricante=false;
var
erroPreco=false;

function
verificaForm(form)
{
if(
erroEmail||erroFabricante||erroPreco||
form.email.value==""||form.fabricante.value==0||
form.modelo.value==0||form.preco.value=="")
{
alert("Preenchatodososcamposcorretamente!");
return
false;
}

return
true;
}
//------------------------------------

//------------------------------------
//E-MAIL
//------------------------------------
//Validae-mailfornecido
functionverificaEmail()
{
//Obtémvalordigitado
valor=document.getElementById("email").value;

//E-mailembranco?FazvalidaçãodiretaemJavascript
if(valor==""){
document.getElementById("email_erro").innerHTML="PreenchaoE-mail";
erroEmail=true;
}
//OK,e-mailpreenchido,chamaPHPevalida
else
{
//PRINCIPALMÉTODO(call)=ChamaoPHPeobtémoretorno
cp.call("validaFormulario.php","verificaEmail",retornaEmail,valor);
}
}

//ObtémoretornodavalidaçãofeitaemAJAXeprocessa-o
functionretornaEmail(retorno)
{
//Setevealgumretornoapósverificaroe-mail,
//significaerro,portantoimprime-o.
if(retorno){
document.getElementById("email_erro").innerHTML=retorno;
erroEmail=true;
}
else{
document.getElementById("email_erro").innerHTML="";
erroEmail=false;
}
}

//------------------------------------

//------------------------------------
//FABRICANTES&MODELOS
//------------------------------------
functioncarregaModelos(campo)
{
fabricanteId=campo.value;

//Nenhumafabricantefoiselecionada.Casohouvessealgumaseleção
//anterior,limpa.Poisnãohámodeloparafabricante0.
if(fabricanteId==0){
document.getElementById("modelos_local").innerHTML="SelecioneumFabricante";
//Limpaoserros,casoocorreuantesdessaboaseleção
document.getElementById("fabricante_erro").innerHTML="";
}

//ChamaPHPparacarregarmodelosdafabricanteselecionada
else
cp.call("validaFormulario.php","obtemModelos",retornaModelos,fabricanteId);
}

function
retornaModelos(retorno)
{
//Nenhummodeloencontrado
if(retorno=="N"){
document.getElementById("fabricante_erro").innerHTML="NenhumModeloparaessefabricante.Selecioneoutro";
//Limpamodelosanteriores,jáqueagoraselecionouumafabricantevazia
document.getElementById("modelos_local").innerHTML="SelecioneumFabricante";

erroFabricante=true;
}

//Ok,hámodelosparaofabricante
else{
document.getElementById("modelos_local").innerHTML=retorno;
//Limpaoserros,casoocorreuantesdessaboaseleção
document.getElementById("fabricante_erro").innerHTML="";

erroFabricante=false;
}
}

//------------------------------------

//------------------------------------
//PREÇO
//------------------------------------
functionverificaPreco()
{
//Obtémvalordigitado
valor=document.getElementById("preco").value;

//Preçoembranco?FazvalidaçãodiretaemJavascript
if(valor==""){
document.getElementById("preco_erro").innerHTML="PreenchaoPreço";
erroPreco=true;
}
//OK,preçopreenchido,chamaPHPevalida
else
{
cp.call("validaFormulario.php","verificaPreco",retornaPreco,valor);
}
}

//ObtémoretornodavalidaçãofeitaemAJAXeprocessa-o
functionretornaPreco(retorno)
{
//Setevealgumretornoapósverificaropreço,
//significaerro,portantoimprime-o.
if(retorno){
document.getElementById("preco_erro").innerHTML=retorno;
erroPreco=true;
}
else{
document.getElementById("preco_erro").innerHTML="";
erroPreco=false;
}
}
//------------------------------------

?>

Dissecando validacao.js:

  1. Cria-se o objeto CPAINT.
  2. Na seção GERAL, declara-se as variáveis que serão flags (indicadores) de que há um erro atualmente em algum campo que foi tratado via Ajax.
    varerroEmail=false;
    var
    erroFabricante=false;
    var
    erroPreco=false;

    Esses flags são ligados/desligados pelas funções relacionadas. Exemplo: a função que trata o e-mail, controla a erroEmail.
  3. A função verificaForm(form) é chamada quando o formulário é enviado (conforme vimos anteriormente, o formulário possui um onSubmit). Caso alguma flag de erro esteja ligada (com valor true, no caso), ou caso algum campo não tenha sido preenchido, a função retorna false, e o formulário não é enviado.
  4. A verificaEmail() é chamada quando o usuário sai do campo de e-mail (onBlur). Ela obtém o valor do campo e caso não preenchido, retorna uma mensagem de erro no span do campo e-mail, bem como ativa a flag de erro do e-mail: erroEmail=true;.
  5. Caso o e-mail for preenchido, a chamada AJAX é feita:
    cp.call("validaFormulario.php","verificaEmail",retornaEmail,valor); - em ordem de parâmetro: arquivo PHP que é chamado e possui integração com a CPAINT; chama a verificaEmail no PHP; no retorno a função JavaScript retornaEmail é chamada; e o valor (que contém o e-mail) é enviado ao PHP.
  6. Se retornaEmail(retorno) sreceber algo, quer dizer que houve erro (porque a função PHP chamda, verificaEmail() no caso realmente só retorna mensagens de erro). Nesse caso, ela cuida de exibir no span do campo e-mail, bem como de ativar a flag de erro. Veja que caso nada é retornado, ela desativa a flag. Desativar a flag ali é de extrema importância, pois algum erro pode ter ocorrido alteriormente.
  7. O funcionamento da verificaPreco() e retornaPreco(retorno) é o mesmo, logo não as explicarei.
  8. Caso carregaModelos(campo) receba um valor 0 (zero), ou seja, o usuário escolheu a opção "Selecione", deve-se limpar a antiga seleção de modelos, e mostrar a mensagem de seleção novamente. Caso alguma fabricante foi escolhida, chama o PHP. Como visto na dissecação do validaFormulario.php, a função obtemModelos retorna o HTML com o combo com os modelos.
  9. retornaModelos(retorno) verifica se o valor for N, quer dizer que não há modelos para a fabricante, logo ativa flag de erros, e mostra mensagem de acordo. Caso o retorno seja diferente, quer dizer o HTML com o combo de modelos, logo coloca-o no local certo no formulário e desativa a flag de erros.

5. Fim! E a parte 3?
Veja que a lógica e estrutura para AJAX é sempre a mesma:

  1. Biblioteca PHP que trata dados enviados e retorna para o CPAINT;
  2. Biblioteca JavaScript que obtém dados do usuário, envia para a CPAINT, obtém o retorno e o processa.

A maioria das suas funções agora será na base do Copiar e Colar, apenas com adaptação da validação/retorno de acordo com o dado sendo processado.

Ajax é só isso? Não. Vai muito muito muito além disso tudo. Mas, para a série de artigos, só falta mais uma parte: respostas complexas e completas com XML. Esse é o tema da parte 3, será publicado exclusivamente no meu blog O Desenvolvedor PHP, portanto fique antenado!

Meu muito obrigado pela leitura desse artigo. Saí da toca e resolvi escrever!

OBS 1: Cuidado, AJAX vicia. :)OBS 2: Faça download do SQL e códigos-fonte do artigo, aqui.

Abraços,
Alfred Reinold Baudisch

Blogs:
Jornada Imperial
www.auriumsoft.com.br/blog

O Desenvolvedor PHP
www.auriumsoft.com.br/desenvolvedorphp

THE Mobile Developer
http://mobiledeveloper.auriumsoft.com

Alfred Reinold Baudisch

Alfred Reinold Baudisch - Desenvolvedor web freelance, com atuação na área há 7 anos. Experiência avançada em PHP, SQL e modelagem de sistemas multi-camadas. Atualmente dedicado ao aprendizado em desenvolvimento mobile, especificamente mobile games, com J2ME. Apaixonado e conhecedor do mercado financeiro, gestão e estratégias de novos negócios, visão constantemente empreendedora. Editor dos blogs Jornada Imperial e O Desenvolvedor PHP.