"Propagando Soluções"

Fazendo uma enquete com Xajax

Filed under: Informática, Desenvolvimento web, Ajax, PHPAdriano de Oliveira Gonçalves | 5 de Junho de 2006 @ 11:14:07 (Views: 1252)

Olá,
Tive uma idéia no trabalho para uma enquete em um site, usando Xajax. O resultado ficou legal, e achei que seria legal compartilhar a idéia. A medida que for dando tempo, pretendo ir postando tutoriais mais elaborados aqui sobre Xajax, mas vou começar com coisas simples. O objetivo deste post é mostrar como fazer, usando AJAX através do framework XAJAX, uma interface de enquete com o usuário final: o visitante do site. Então, as partes de cadastro, relatórios estatísticos, enquetes anteriores, etc, etc, etc. você pode terminar você mesmo…

Antes de continuar, se você não conhece nada de AJAX ainda, sugiro ler meu primeiro post sobre isso: “O que é AJAX e por onde começar”.

Estrutura de diretórios e banco de dados

Bem, vamos lá. Primeiro a estrutura das tabelas que eu usei. A nossa enquete vai funcionar em MySQL. É claro que no meu dia a dia eu não uso as funções de mysql comuns da API nativa do PHP. Uso algo como ADODB ou DB_DataObject do PEAR. Mas nesse exemplo, vamos usar as funções nativas pra ficar mais simples de entender. Clique aqui para baixar a nossa estrutura de tabelas, já com uma enquete padrão gravada. Também não ficaria jogando PHP no HTML direto, usaria Smarty (engine de templates), mas também vou tirar isso pra simplificar o exemplo…

Uma vez com o banco de dados montado, vamos manter um mínimo e básico de organização de arquivos pra trabalhar com uma aplicação, a qual vamos utilizar nos próximos tutoriais. Seguirei a seguinte estrutura de diretórios e arquivos:

|-libs -> diretórios de bibliotecas externas. Copie os arquivos do xajax para um diretório “xajax”, dentro deste diretório.
|-includes -> diretórios de arquivos de inclusão, como o de configurações e funções
|- config.php -> arquivo geral de configurações, que contém as informações de usuário e senha do banco de dados e outras configurações importantes
|-functions.php -> arquivos com todas as funções gerais dos sistemas
|-images -> caso seja necessário, um diretório para colocar nossas imagens
|-css -> diretório para arquivos css
|-js -> diretório para arquivos de Javascript
|-ajax -> servidores e arquivos adicionais utilizados somente para AJAX

Com isso já dá pra fazer bastante coisa. Caso precisemos mexer em alguma coisa nessa estrutura, falarei nisso mais tarde… Considerando que esses arquivos de configuração já estejam certos, com as configurações do banco de dados e uma função pra conectar a ele (você vai poder baixar o código completo no final), vamos logo ao foco deste post:

A tela padrão de enquete

Primeiro vamos buscar a última enquete do banco de dados e mostrar ela com as opções na página, que salvaremos como enquete.php:

<?
/* Enquete XAJAX
 * Adriano de Oliveira Gonçalves - http://adriano.ison.com.br/
 */

require_once("includes/config.php");
require_once("includes/functions.php");

session_start();            // Vou usar sessão apenas pra deixar a pessoa voltar apenas uma vez, pelo menos enquanto o browser estiver aberto
session_name(NOME_SESSAO);  // Isso é importante pra diferenciar sistemas que rodem no mesmo servidor

?>
<html>
<head>
<title>Enquete</title>
</head>
<body>
<h1>Enquete</h1>
<!-- Esse id na DIV é importante. É através dele que vamos trazer os resultado da enquete -->
<div id="div_enquete">
<?
// Faz a busca pela última enquete gravada do banco de dados
$conexao = conecta_bd();

$strQuery   = "SELECT * FROM enquetes ORDER BY enquete_id DESC LIMIT 1";
$query      = mysql_query($strQuery);

if(mysql_num_rows($query) == 0)
{
    echo "<br><br>Não há nenhuma enquete disponível no momento.";
}
else
{
    $enquete    = mysql_fetch_array($query);

    $strQuery   = "SELECT * FROM opcoes_enquetes WHERE enquetes_id=".$enquete["enquete_id"]." ORDER BY opcoes_ordem";
    $query      = mysql_query($strQuery);

    if(mysql_num_rows($query) == 0)
    {
        echo "<br><br>Não há nenhuma enquete disponível no momento."; // Query sem opções cadastradas, mas não vou dizer isso para o visitante do site, certo?
    }
    else
    {
        echo "<br><strong>".$enquete["enquete_titulo"]."</strong>";
        while($opcao = mysql_fetch_array($query))
        {
            echo "<br><input type='radio' name='enquete_".$enquete["enquete_id"]."' value='".$opcao["opcoes_id"]."' id='opcao_".$opcao["opcoes_id"]."'> <label for='opcao_".$opcao["opcoes_id"]."'>".$opcao["opcoes_titulo"]."</label>";
        }
    }
}
?>
</div>
</body>
</html>

Até aí tudo ok? (peraí, mas acho que está faltando uma coisa… cadê o botão de submit?). Bom, aí é que entra a parte divertida: nossa enquete não vai precisar de botão submit. A opção será gravada quando o usuário clicar na opção, sem submit, sem popup e sem refresh na tela. Mas antes de continuar, vamos falar um pouquinho sobre Xajax.

O Xajax

Xajax, pra quem não sabe, é um framework desenvolvido pra trabalhar com AJAX em PHP. Você pode baixá-lo gratuitamente em http://www.xajaxproject.org e utilizá-lo à vontade em seus projetos. Esse foi o framework AJAX pra PHP mais completo que eu já vi, e bem simples de usar. Nos exemplos do site deles, os autores sugerem usar a seguinte estrutura (não obrigadtória) de arquivos e códigos:

arquivo.php -> a sua página php, que terá interação com o usuário.
arquivo.server.php -> script ao qual serão feitas as requisições “por trás do pano”. Esse arquivo tem todas as suas funções que o XAJAX vai chamar.
arquivo.common.php -> script com comandos do Xajax comuns ao arquivo.php e arquivo.server.php, pra ter que escrever uma vez só e incluí-lo nos dois arquivos anteriores.

Para trabalhar com as funções no Xajax, você basicamente diz pra ele quais funções PHP usar, e ele vai disponibilizar em javascript na sua página as chamadas para essas funções como xajax_nomeDaFuncao(). Caso tenha dúvidas, dá uma olhada na seção “Examples”, em http://www.xajaxproject.org/.

Progamando o lado “servidor”

No nosso enquete.server.php, vamos fazer uma função que recebe o id da opção escolhida e soma mais um voto a ela no banco de dados. Aproveitando a oportunidade, abro um parêntesis aqui: quando for fazer uma função, pense antes de começar a escrevê-la. Pense por alguns minutos e pergunte-se:

- Quem mais na minha aplicação pode usar essa função?
- Que outras pessoas da minha equipe podem se beneficiar dessa minha função e em que tipos de casos?
- Que opções/parâmetros eu posso colocar nela pra facilitar o uso em outras situações?
- Tudo o que eu pensei em colocar nessa função precisa mesmo estar em uma só função? Não poderia quebrar isso em duas ou mais? Lembre-se: o ideal é que uma função cuide apenas de uma tarefa e nada mais. Se precisar de outra tarefa, crie outra função e chame essa outra função dentro da sua primeira. Mas tudo, claro, dentro do bom senso…
- Essa função pode vir a crescer em recursos e opções? O que eu posso fazer pra facilitar seu crescimento?
- Se essa função tem muitas opções/parâmetros, não seria mais interessante fazer uma classe só pra isso, ao invés de uma função?

E comente sempre suas funções! Agora, voltando à nossa enquete (já estou saindo do foco… :) ), nosso enquete.server.php vai ficar assim:

<?
/* Servidor Xajax para o arquivo enquete.php
 *
 */

// Pra ficar no mesmo nível de diretórios padrão
chdir("../");

require_once("includes/config.php");

session_start();            // Vou usar sessão apenas pra deixar a pessoa voltar apenas uma vez, pelo menos enquanto o browser estiver aberto
session_name(NOME_SESSAO);  // Isso é importante pra diferenciar sistemas que rodem no mesmo servidor

require_once("includes/functions.php");

/**
 * Função que grava um voto da enquete no banco de dados
 *
 * @author Adriano Gonçalves <adriano@ison.com.br>
 * @since 2006-06-04
 * @package tutoriais_blog
 * @subpackage enquetes_xajax
 */
function registraVoto($id_opcao, $retorna_resultados=true, $id_enquete=false, $id_html="")
{
    $conexao = conecta_bd();  

    $objResponse = new xajaxResponse();

    // Para executar esse comando o usuário do MySQL precisa ter permissão de LOCK TABLES
    mysql_query("LOCK TABLES opcoes_enquetes WRITE"); // Evita "condições de corrida" (não sabe o que é? Google this.)

    $strSQL = "UPDATE opcoes_enquetes SET opcoes_num_votos = opcoes_num_votos+1 WHERE opcoes_id=".intval($id_opcao); // O intval é questão de segurança
    mysql_query($strSQL);

    // Envia um alerta com o resultado da operação via AJAX
    if(mysql_affected_rows() > 0)
        $objResponse->addAlert("Houve erros durante o processamento do seu voto. Desculpe-nos o transtorno.");
    else
        $objResponse->addAlert("Obrigado pela sua participação!");

    mysql_query("UNLOCK TABLES");

    return $objResponse;
}

// Comandos comuns de Xajax
require("ajax/enquete.common.php");

// Comando obrigatório para o arquivo servidor
$xajax->processRequests();

?>

Note que eu já deixei três parâmetros na função registraVoto() que eu não estou utilizando, o $id_enquete, o $retorna_resultados e o $id_html. Eles servirão pra retornar de uma vez os resultados da enquete. Isso será feito chamando-se uma outra função que eu ainda vou escrever.

Agora, o nosso enquete.common.php:

<?
// Biblioteca XAJAX
require_once ("libs/xajax/xajax.inc.php");

// Cria um novo objeto XAJAX, dizento aonde está o servidor ("ajax/enquete.server.php")
$xajax = new xajax("ajax/enquete.server.php");

// Diz para o XAJAX para disponibilizar a função registraVoto() para o javascript, na página da enquete
$xajax->registerFunction("registraVoto");
?>

Configurando a página “cliente”

Agora, vamos fazer umas pequenas mudanças no nosso enquete.php para fazê-lo gravar o voto do visitante ao clicar em uma opção:

<?
/* Enquete XAJAX
 * Adriano de Oliveira Gonçalves - http://adriano.ison.com.br/
 */

require_once("includes/config.php");
require_once("includes/functions.php");

session_start();            // Vou usar sessão apenas pra deixar a pessoa voltar apenas uma vez, pelo menos enquanto o browser estiver aberto
session_name(NOME_SESSAO);  // Isso é importante pra diferenciar sistemas que rodem no mesmo servidor

// Comandos AJAX - MUDEI AQUI !!
require("ajax/enquete.common.php");

?>
<html>
<head>
<title>Enquete</title>
<?
// Imprime os códigos javscript do Xajax para a página  - MUDEI AQUI !!!
$xajax->printJavascript("libs/xajax/");
?>
</head>
<body>
<h1>Enquete</h1>
<!-- Esse id na DIV é importante. É através dele que vamos trazer os resultado da enquete -->
<div id="div_enquete">
<?
// Faz a busca pela última enquete gravada do banco de dados
$conexao = conecta_bd();

$strQuery   = "SELECT * FROM enquetes ORDER BY enquete_id DESC LIMIT 1";
$query      = mysql_query($strQuery);

if(mysql_num_rows($query) == 0)
{
    echo "<br><br>Não há nenhuma enquete disponível no momento.";
}
else
{
    $enquete    = mysql_fetch_array($query);

    $strQuery   = "SELECT * FROM opcoes_enquetes WHERE enquetes_id=".$enquete["enquete_id"]." ORDER BY opcoes_ordem";
    $query      = mysql_query($strQuery);

    if(mysql_num_rows($query) == 0)
    {
        echo "<br><br>Não há nenhuma enquete disponível no momento."; // Query sem opções cadastradas, mas não vou dizer isso para o visitante do site, certo?
    }
    else
    {
        echo "<br><strong>".$enquete["enquete_titulo"]."</strong>";
        while($opcao = mysql_fetch_array($query))
        {
            // MUDEI AQUI !!
            echo "<br><input type='radio' name='enquete_".$enquete["enquete_id"]."' value='".$opcao["opcoes_id"]."' id='opcao_".$opcao["opcoes_id"]."' onclick='xajax_registraVoto(this.value);'> <label for='opcao_".$opcao["opcoes_id"]."'>".$opcao["opcoes_titulo"]."</label>";
        }
    }
}
?>
</div>
</body>
</html>

Voilá! Damos uma espiada na tabela no banco de dados e… Temos a votação funcionando e gravando os votos! (Ei, mas a mensagem de confirmação está vindo com caracteres estranhos!…) Ih, é verdade. Isso é por causa de questões de codificação, uma das coisas que mais pertuba a vida dos desenvolvedores web. Bom, para resolver isso, basta incluir essa linha no seu config.php:

define ('XAJAX_DEFAULT_CHAR_ENCODING', 'ISO-8859-1' ); // Muda a codificação com a qual o XAJAX irá trabalhar

Retornando o resultado

Agora, vamos deixar nossa enquete melhor, retornando os resultados da votação para o usuário. Vamos primeiro escrever uma função que retorna os resultados em HTML e chamá-la na nossa registraVoto(). Então, o enquete.server.php vai ficar assim:

<?
/* Servidor Xajax para o arquivo enquete.php
 *
 */

// Pra ficar no mesmo nível de diretórios padrão
chdir("../");

require_once("includes/config.php");

session_start();            // Vou usar sessão apenas pra deixar a pessoa voltar apenas uma vez, pelo menos enquanto o browser estiver aberto
session_name(NOME_SESSAO);  // Isso é importante pra diferenciar sistemas que rodem no mesmo servidor

require_once("includes/functions.php");

/**
 * Função que grava um voto da enquete no banco de dados
 *
 * @author Adriano Gonçalves <adriano@ison.com.br>
 * @since 2006-06-04
 * @package tutoriais_blog
 * @subpackage enquetes_xajax
 */
function registraVoto($id_opcao, $retorna_resultados=true, $id_enquete=false, $id_html="")
{
    $conexao = conecta_bd();  

    $objResponse = new xajaxResponse();

    // Para executar esse comando o usuário do MySQL precisa ter permissão de LOCK TABLES
    mysql_query("LOCK TABLES enquetes READ, opcoes_enquetes WRITE"); // Evita "condições de corrida" (não sabe o que é? Google this.)

    $strSQL = "UPDATE opcoes_enquetes SET opcoes_num_votos = opcoes_num_votos+1 WHERE opcoes_id=".intval($id_opcao); // O intval é questão de segurança
    mysql_query($strSQL);

    if($retorna_resultados)
        $objResponse->addAssign($id_html,"innerHTML",retornaResultadoEnquete($id_enquete));  

    // Envia um alerta com o resultado da operação via AJAX
    if(mysql_affected_rows() == 0)
        $objResponse->addAlert("Houve erros durante o processamento do seu voto. Desculpe-nos o transtorno.");
    else
        $objResponse->addAlert("Obrigado pela sua participação!");

    mysql_query("UNLOCK TABLES");

    return $objResponse;
}

/**
 * Função que retorna os resultados de uma enquete em HTML
 *
 * @author Adriano Gonçalves <adriano@ison.com.br>
 * @since 2006-06-04
 * @package tutoriais_blog
 * @subpackage enquetes_xajax
 */
function retornaResultadoEnquete($id_enquete)
{
    $conexao = conecta_bd();

    // Pega daos da enquete
    $strSQL = "SELECT * FROM enquetes WHERE enquete_id=".intval($id_enquete);
    $query  = mysql_query($strSQL);

    if(mysql_num_rows($query) == 0)
        return false;
    else
    {
        $resultado = "";

        $enquete    = mysql_fetch_array($query);

        $resultado .= "<strong>".$enquete["enquete_titulo"]."</strong>";

        // Conta o total de votos
        $strQuery   = "SELECT SUM(opcoes_num_votos) AS 'total_votos' FROM opcoes_enquetes WHERE enquetes_id=".$enquete["enquete_id"];
        $query          = mysql_query($strQuery);
        $row            = mysql_fetch_array($query);
        $total_votos    = $row["total_votos"];

        // Pega opções
        $strQuery   = "SELECT * FROM opcoes_enquetes WHERE enquetes_id=".$enquete["enquete_id"]." ORDER BY opcoes_num_votos DESC";
        $query      = mysql_query($strQuery);

        $resultado .= "<table border='0'>"; // Nesse caso, to nem ligando pra tableless... :P
        while($opcao = mysql_fetch_array($query))
        {
            $resultado .= "<tr><td>".$opcao["opcoes_titulo"]."</td><td align='right'>".number_format((($opcao["opcoes_num_votos"]/$total_votos)*100),1,",",".")."%</td></tr>";
        }
        $resultado .= "</table>";
    }

    return $resultado;
}

// Comandos comuns de Xajax
require("ajax/enquete.common.php");

// Comando obrigatório para o arquivo servidor
$xajax->processRequests();

?>

E vamos famos fazer uma pequena mudança no enquete.php, no loop que mostra as opções (linha 53-58); vai ficar assim:

        echo "<br><strong>".$enquete["enquete_titulo"]."</strong>";
        while($opcao = mysql_fetch_array($query))
        {
            echo "<br><input type='radio' name='enquete_".$enquete["enquete_id"]."' value='".$opcao["opcoes_id"]."' id='opcao_".$opcao["opcoes_id"]."' onclick='document.getElementById("div_enquete").innerHTML="Carregando, aguarde..."; xajax_registraVoto(this.value,true,".$enquete["enquete_id"].","div_enquete");'> <label for='opcao_".$opcao["opcoes_id"]."'>".$opcao["opcoes_titulo"]."</label>";
        }

Nessa altura a enquete já grava e mostra o resultado logo depois. Na nossa página aqui não faz taaanta diferença assim porque nela só tem isso, mas isso no cantinho do seu portal ou site com mais conteúdos e formatado direitinho com css vai ficar bem legal.

Ajustes finais

Vamos então fazer uns ajustes finais. Primeiro, uma vez que sabemos que as funções do enquete.server.php funcionam, vamos movê-las para o arquivo includes/functions.php, por questão de organização. Vamos também providenciar uma forma de, depois de ter votado uma vez, mostrar para o usuário apenas os resultados, utilizando uma variável de sessão. Você pode ver o resultado final baixando o código no link abaixo.

Para baixar o resultado final clique aqui.

É isso, podem testar! Se tiverem algum problema ou dúvida, podem me falar. Comentem, perguntem, dêem o feedback. Se acharem que algo pode ser melhorado no texto, podem sugerir. Me digam o que acham, pra que eu possa melhorar os conteúdos que eu posto. Me digam o que gostariam de ver aqui, que eu estudo a possibilidade de escrever sobre o assunto.

No mais, estudem, leiam, façam uma faculdade. Estudem análise, UML, Orientação a Objetos! Estudem Smarty, PEAR, ADODB. Experimentem, testem, perguntem, se aperfeiçoem; e não se conformem em ser o “garoto do 402 que faz site”…

Até a próxima…

3 Comentários

  1. Comentário by Adriano de Oliveira Gonçalves:

    Pessoal, uma conhecida minha encontrou um erro no meu functions.php, na função conecta_bd(). Já corrigi o erro e já atualizei o arquivo do código completo.

    Valeu, Flavinha!

  2. Comentário by Luis Carlos:

    Seria pedir muito pra vc fazer esse exemplo com smarty?
    Estou tendo algumas dificuldades em usar o Xajax com Smarty, se vc puder ajudar um pobre “programador” eu agradeceria…

  3. Comentário by Adriano de Oliveira Gonçalves:

    Olá, Luis! Obrigado pela sua visita… Trabalhar com Smarty na verdade é bem simples, e um bom caminho pra começar a aprender é estudando o manual, que também possui uma versão em Português (http://smarty.php.net/distributions/manual/pt_BR/Smarty-2.6.7-docs.chm). Entendendo como funciona, acho que você não teria muita dificuldade em montar esse exemplo em Smarty, lembrando que para jogar a saída do template para uma variável você usa o método fetch(), ao invés do display(). Se tiver alguma dúvida enquanto tenta fazer, pode me dar um toque. Quando eu estiver com um pouco mais tempo pretendo postar mais exemplos com Smarty…

    Um abraço e sucesso no aprendizado!

Deixe um comentário


:: Adriano de Oliveira Gonçalves, 2004-2008 - contato@adrianoweb.com.br ::

Xoops PhP MySql ApaChe FireFox RSS