Hoje vou dar uma pincelada sobre algo que eu queria abordar faz tempo, mas como me afastei do
PHP por conta da correria do trabalho no dia-a-dia, demorei... Mas nunca é tarde.
O exemplo é bem simples e clássico, solução pra um problema e desejo antigo: carregar dados numa página web sem refresh. Eu sempre ouvia falar do AJAX e, apesar de ter começado minha carreira fazendo sites, só fui vê-lo em 2006, mas a primeiras coisas que estou fazendo com ele pra testar foram só recentemente. E agora que entendi quero compartilhar - eu já até estudei o Adobe Flex e cada um (Flex e AJAX) tem suas vantagens, mas a principal do AJAX é ser mais leve, pois usa os próprios bons e velhos HTML, XML e JavaScript, sem precisar da instalação de plugins como o do Flash Player que o Flex precisa.
Como eu disse, é um exemplo simples e clássico de seleção de cidades, onde numa caixa você seleciona o estado desejado e na de baixo a cidade numa lista criada dinamicamente com as cidades de cada estado selecionado.
Pra começar, vamos à criação do nosso banco de dados no MySQL - eu peguei parte das tabelas de um banco no qual estou trabalhando, bem simples, foi um dump mesmo que fiz no phpMyAdmin do meu servidor on-line, já com os dados pro exemplo. Veja:
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
CREATE DATABASE `cidades` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `cidades`;
CREATE TABLE IF NOT EXISTS `cidades` (
`id_cidade` int(11) NOT NULL auto_increment,
`id_uf` int(11) NOT NULL,
`nome` varchar(35) NOT NULL,
`cep_principal` char(10) NOT NULL,
`is_capital` tinyint(1) NOT NULL default '0',
`sigla` varchar(3) NOT NULL,
PRIMARY KEY (`id_cidade`),
UNIQUE KEY `sigla` (`sigla`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=7 ;
INSERT INTO `cidades` (`id_cidade`, `id_uf`, `nome`, `cep_principal`, `is_capital`, `sigla`) VALUES
(1, 1, 'Cuiabá', '78000-000', 1, 'CBA'),
(2, 1, 'Sinop', '78550-000', 0, 'SNP'),
(3, 1, 'Alta Floresta', '78580-000', 0, 'AFL'),
(4, 1, 'Lucas do Rio Verde', '78455-000', 0, 'LRV'),
(5, 1, 'Rondonópolis', '78700-000', 0, 'ROO'),
(6, 2, 'Cascavel', '85800-000', 0, 'CVL');
CREATE TABLE IF NOT EXISTS `paises` (
`id_pais` int(11) NOT NULL auto_increment,
`sigla` varchar(5) NOT NULL,
`nome` varchar(30) NOT NULL,
PRIMARY KEY (`id_pais`),
UNIQUE KEY `sigla` (`sigla`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
INSERT INTO `paises` (`id_pais`, `sigla`, `nome`) VALUES
(1, 'BRA', 'Brasil');
CREATE TABLE IF NOT EXISTS `uf` (
`id_uf` int(11) NOT NULL auto_increment,
`id_pais` int(11) NOT NULL,
`sigla` char(2) NOT NULL,
`nome` varchar(25) NOT NULL,
PRIMARY KEY (`id_uf`),
UNIQUE KEY `sigla` (`sigla`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
INSERT INTO `uf` (`id_uf`, `id_pais`, `sigla`, `nome`) VALUES
(1, 1, 'MT', 'Mato Grosso'),
(2, 1, 'PR', 'Paraná'),
(3, 1, 'MS', 'Mato Grosso do Sul');
Criado o banco, vamos ao PHP. Eu usei o próprio Gedit do Ubuntu (se você usa o ambiente KDE do Kubuntu - o qual eu também tenho instalado por curiosidade, gostei, achei legal e muito bonito, embora não seja tão prático como o GNOME - use o Kate, mas tem que verificar a compatibilidade com UTF-8).
Voltando ao assunto, o primeiro arquivo que criamos é o dados.php, que contêm a classe de conexão ao banco e seleção da base, conforme abaixo:
<?php
class dados
{
//Propriedades/parâmetros do objeto de conexão
public $host = "localhost";
public $user = "cidades";
public $senha = "cidades";
public $db = "cidades";
public function get_conexao()
{
//Conecta ao MySQL e seleciona o banco de dados
$cnx = @mysql_connect($this->host, $this->user, $this->senha) or die("<pre>(!) Falha ao conectar ao banco de dados.</pre>");
@mysql_select_db($this->db, $cnx) or die("<pre>(!) Falha ao selecionar banco de dados. ".str_replace('..', '.', mysql_error().'.')."</pre>");
}
}
?>
Eu usei funções nativas e simples do PHP na conexão pra ficar mais legível. Ah, e se você notou, no PHP a classe deve ter o mesmo nome do arquivo que a contém.
Bem, agora vamos criar o arquivo cidades_obj.php, que será a nossa classe de objeto que representa e guarda os dados de cada cidade selecionada. Note que as propriedades do objeto são um espelho dos campos do banco de dados pra, também, ficar mais legível:
<?php
class cidades_obj
{
//Propriedades do objeto cidades
public $id_cidade;
public $id_uf;
public $nome;
public $cep_principal;
public $is_capital;
public $sigla;
}
?>
Ok, agora vamos à classe que pega os dados no banco e joga numa lista de objetos (ou seja, a lista com cada cidade representada pelo objeto que criamos acima). Lista de objetos é pra ficar mais bonitinho, mas é um array mesmo. O nosso arquivo é o cidades.php. No início dele temos as propriedades que usaremos como parâmetros e na função get_cidades usamos as funções clássicas pra trabalhar com querys do MySQL no PHP:
<?php
include_once("dados.php");
include_once("cidades_obj.php");
class cidades
{
//Propriedados/parâmetros do objeto de consulta de cidades
public $id_cidade = 0;
public $id_uf = 0;
public $nome = '';
public $cep_principal = '';
public $is_capital = false;
public $sigla = '';
//Contador de resultados
public $count = 0;
public function get_cidades()
{
//Seleciona cidades no banco
$sql = "SELECT * FROM cidades";
$sql .= " WHERE id_cidade > 0";
//Verifica parâmetros
if ($this->id_cidade > 0) $sql .= " AND id_cidade = ".$this->id_cidade;
if ($this->id_uf > 0) $sql .= " AND id_uf = ".$this->id_uf;
if (strlen($this->nome) > 0) $sql.= " AND nome LIKE '%".$this->nome."%'";
if (strlen($this->cep_principal) > 0) $sql.= " AND cep_principal = '".$this->cep_principal."'";
if ($this->is_capital == true) $sql .= " AND is_capital";
if (strlen($this->sigla) > 0) $sql.= " AND sigla = '".$this->sigla."'";
$sql .= " ORDER BY nome ASC";
//Conecta ao banco e abre a query
$dados = new dados();
$dados->get_conexao();
$rs = mysql_query($sql);
$this->count = 0;
//Passa resultados pra um array do objeto cidades
while ($reg = mysql_fetch_array($rs))
{
$cidade = new cidades_obj();
$cidade->id_cidade = $reg['id_cidade'];
$cidade->id_uf = $reg['id_uf'];
$cidade->nome = $reg['nome'];
$cidade->cep_principal = $reg['cep_principal'];
$cidade->is_capital = $reg['is_capital'];
$cidade->sigla = $reg['sigla'];
$a[] = $cidade;
$this->count++;
}
//Fecha conexão
mysql_close();
//Retorna lista de cidades
return $a;
}
}
?>
Já temos o objeto que trará nossos resultados, agora vamos ler o array gerado pela classe acima e escrever o nosso XML no arquivo cidades.xml.php. Note que no início do arquivo instanciamos o objeto da classe acima e mais abaixo usamos a biblioteca DOMDocument do PHP5 pra gerar o XML:
<?php
include_once("cidades.php");
//Chama objeto cidades passando parâmetro por UF
$cidades = new cidades();
$cidades->id_uf = $_POST["id_uf"];
$lst = $cidades->get_cidades();
//Verifica se o array tem resultados
if ($cidades->count > 0)
{
//Cria XML
$xml = new DOMDocument("1.0", "UTF-8");
$xml->preserveWhiteSpace = false;
$xml->formatOutput = true;
//Insere nó principal
$root = $xml->createElement("cidades");
//Varre o array
foreach($lst as $city)
{
//Atribui variáveis pra criar campos com o valor de cada registro
$id_uf = $xml->createElement("id_uf", $city->id_uf);
$id_cidade = $xml->createElement("id_cidade", $city->id_cidade);
$sigla = $xml->createElement("sigla", utf8_encode($city->sigla));
$nome = $xml->createElement("nome", utf8_encode($city->nome));
//Cria nó de registro
$cidade = $xml->createElement("cidade");
//Adiona campos com os valores
$cidade->appendChild($id_uf);
$cidade->appendChild($id_cidade);
$cidade->appendChild($sigla);
$cidade->appendChild($nome);
//Adiciona o registro ao nó prncipal
$root->appendChild($cidade);
}
//Fecha a TAG do nó principal
$xml->appendChild($root);
//Imprime o XML na tela
Header("Content-Type: text/xml");
echo $xml->saveXML();
}
?>
O resultado gerado pela função acima é um arquivo XML mesmo como este:
<?xml version="1.0" encoding="UTF-8"?>
<cidades>
<cidade>
<id_uf>1</id_uf>
<id_cidade>3</id_cidade>
<sigla>AFL</sigla>
<nome>Alta Floresta</nome>
</cidade>
<cidade>
<id_uf>2</id_uf>
<id_cidade>6</id_cidade>
<sigla>CVL</sigla>
<nome>Cascavel</nome>
</cidade>
<cidade>
<id_uf>1</id_uf>
<id_cidade>1</id_cidade>
<sigla>CBA</sigla>
<nome>Cuiabá</nome>
</cidade>
<cidade>
<id_uf>1</id_uf>
<id_cidade>4</id_cidade>
<sigla>LRV</sigla>
<nome>Lucas do Rio Verde</nome>
</cidade>
<cidade>
<id_uf>1</id_uf>
<id_cidade>5</id_cidade>
<sigla>ROO</sigla>
<nome>Rondonópolis</nome>
</cidade>
<cidade>
<id_uf>1</id_uf>
<id_cidade>2</id_cidade>
<sigla>SNP</sigla>
<nome>Sinop</nome>
</cidade>
</cidades>
Por fim, vamos à nossa página com a seleção das cidades por estado. Eu dei ao arquivo o nome de idades.html.php seguindo o padrão que você deve ter percebido acima: ".xml.php" pra XML, "_obj.php" pra objeto (só criei com "_" por causa no nome da classe do objeto), só ".php" pra classe que pega os dados, e agora .html.php" pro arquivo que será visível mesmo - no meu ponto de vista este padrão ajuda a identificar pelo nome o propósito de de cada arquivo.
Bem, este último arquivo é o maior e mais complexo, onde vamos trabalhar com o AJAX mesmo, isso significa muito JavaScript, pois como o próprio nome já diz é Asynchronous Javascript And XML. Temos as funções todas no JavaScript para instanciar o AJAX, ler, fazer a consulta no nosso XML e imprimir os resultados na tela. Eis o código completo:
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
<html>
<head>
<title>Selecione a cidade</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<!-- ESTILOS DA PÁGINA -->
<style type="text/css">
body
{
background-color: #FFFFFF;
font-family: arial, verdana, sans-serif;
font-size: 10pt;
}
.Fonte8
{
font-size: 8pt;
}
.Fonte12
{
font-size: 12pt;
}
</style>
<!-- CÓDIGOS DO AJAX -->
<script language="JavaScript">
function Dados(valor)
{
//Verifica se o navegador tem suporte a AJAX e qual o tipo de objeto AJAX ele usa
try
{
ajax = new ActiveXObject("Microsoft.XMLHTTP");
}
catch(e)
{
try
{
ajax = new ActiveXObject("Msxml2.XMLHTTP");
}
catch(ex)
{
try
{
ajax = new XMLHttpRequest();
}
catch(exc)
{
alert("Este navegador não tem recursos para uso do AJAX!");
ajax = null;
}
}
}
//Se tiver suporte a AJAX
if(ajax)
{
//Deixa apenas o elemento 1 no option, os outros são excluídos
document.forms[0].listCidades.options.length = 1;
idOpcao = document.getElementById("opcoes");
if (valor == -1)
{
//Desabilita a lista de cidades
document.forms[0].listCidades.disabled="disabled";
idOpcao.innerHTML = "Selecione um estado na lista.";
}
else
{
//Define a chamada via POST ao XML criado pelo PHP com as cidades
ajax.open("POST", "cidades.xml.php", true);
ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
ajax.onreadystatechange = function()
{
//Enquanto estiver processando emite a mensagem de aguarde
if(ajax.readyState == 1)
{
idOpcao.innerHTML = "Aguarde...";
}
//Após ser processado chama função processXML que vai varrer os dados
if(ajax.readyState == 4 )
{
if(ajax.responseXML)
{
processXML(ajax.responseXML);
}
else
{
//Caso não consiga ler o arquivo XML emite a mensagem e desabilita a lista de cidades
idOpcao.innerHTML = "Cidades não encontradas.";
document.forms[0].listCidades.disabled="disabled";
}
}
}
}
//Passa o código do estado escolhido como parâmetro pro POST e envia
var params = "id_uf=" + valor;
ajax.send(params);
}
function processXML(obj)
{
//Pega a TAG cidade
var dataArray = obj.getElementsByTagName("cidade");
//Verifica o total de elementos contidos na TAG cidade
if(dataArray.length > 0)
{
//Percorre o arquivo XML para extrair os dados
for(var i = 0 ; i < dataArray.length ; i++)
{
var item = dataArray[i];
//Contéudo dos campos no arquivo XML
var sigla = item.getElementsByTagName("sigla")[0].firstChild.nodeValue;
var nome = item.getElementsByTagName("nome")[0].firstChild.nodeValue;
idOpcao.innerHTML = "Selecione a cidade.";
//Cria um novo option dinamicamente
var novo = document.createElement("option");
//Atribui um ID a esse elemento
novo.setAttribute("id", "opcoes");
//Atribui valor e texto
novo.value = sigla;
novo.text = sigla + " - " + nome;
//Adiciona o novo elemento
document.forms[0].listCidades.options.add(novo);
}
//Habilita a lista de cidades
document.forms[0].listCidades.disabled="";
}
else
{
//Caso o XML volte vazio, exibe a mensagem abaixo e desabilita a lista de cidades
idOpcao.innerHTML = "Cidades não encontradas.";
document.forms[0].listCidades.disabled="disabled";
}
}
}
</script>
</head>
<body onload="document.forms[0].listCidades.disabled='disabled'">
<span class="Fonte12"><b>Selecione a cidade</b></span><br />
<span class="Fonte8">Tecnologia MySQL/OOP-PHP/AJAX</span>
<br /><br />
<form name="frmAjax">
UF:
<select name="listEstados" onChange="Dados(this.value);">
<option value="-1">Selecione o estado.</option>
<option value="1">Mato Grosso</option>
<option value="2">Paraná</option>
<option value="3">Mato Grosso do Sul</option>
</select>
<br><br>
Cidade:
<select name="listCidades" onChange="if (this.value != '0') { alert('Você selecionou ' + this.value + '.'); }">
<option id="opcoes" value="0">Primeiro selecione o estado.</option>
</select>
</form>
<a href="#" onclick="history.back()">Voltar</a>
</body>
</html>
O HTML é normal. Preste atenção aos nomes dos elementos pra poder chamá-los no JavaScript e às variáveis que armazenam os dados. Aquela verificação do suporte ao AJAX é por conta das diferenças de padrões - muita gente ainda não acordou pra vida e anida usa Microsoft Internet Explorer 6, e bem desatualizado, por isso nossa página pode não funcionar direito pra eles; além do que esta verificação é necessária porque o Windows Internet Explorer, me refiro já a algo mais considerável, que é o IE7 e o IE8, trabalha com AJAX via ActiveX, enquanto o Firefox, o Safari e o Chrome têm isso um pouco mais 'nativo' (não verifiquei o Opera).
Seguindo, nas últimas linhas da função Dados note que passamos o parâmetro com o código da UF que é verifiado la no $_POST do arquivo XML fazendo o nosso filtro. E na função proccessXML lemos o resultado retornado do XML chamado na função anterior, e preenchemos os valores da lista de cidades de acordo com eles.
Eu ignorei a tabela de países e criei a lista de estados fixa no HTML pra agilizar o exemplo.
Enfim, é bem resumido, mas espero que tenha sido proveitoso. Se você quiser como ficou o meu exemplo funcionando, clique aqui:
http://pedro-araujo.com/tarefas/cidades.html.php
Referências: