Función Sugerir aplicación con PHP, MySQL y jQuery

Escuchar lo que sus visitantes tienen que decir siempre es beneficioso al planificar nuevas funciones o cambios en su sitio web. Durante mucho tiempo nos hemos limitado a simplemente configurar un formulario de contacto y esperar recibir comentarios de calidad, lo que desafortunadamente no siempre es el caso.

Hoy estamos llevando las cosas a un nivel superior:estamos aplicando los mismos principios sociales que han llevado al éxito a compartir sitios como Digg y delicious, y alentamos a los visitantes a sugerir y votar sobre las funciones que desean implementar en su sitio web.

El XHTML

Comenzando con el nuevo tipo de documento HTML5, definimos las etiquetas de encabezado y título de apertura y cierre, e incluimos la hoja de estilo principal de la aplicación:styles.css , en el documento.

sugerencias.php

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Feature Suggest w/ PHP, jQuery & MySQL | Tutorialzine Demo</title>

<link rel="stylesheet" type="text/css" href="styles.css" />

</head>

<body>

<div id="page">

    <div id="heading" class="rounded">
        <h1>Feature Suggest<i>for Tutorialzine.com</i></h1>
    </div>

    <!-- The generated suggestion list comes here -->

    <form id="suggest" action="" method="post">
        <p>
            <input type="text" id="suggestionText" class="rounded" />
            <input type="submit" value="Submit" id="submitSuggestion" />
        </p>
    </form>

</div>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="script.js"></script>
</body>
</html>

Después de esto viene la etiqueta del cuerpo y la #página div, que es el elemento contenedor principal. Contiene el encabezado, la lista desordenada con todas las sugerencias (que es generada por PHP, como verá en un momento) y el formulario de envío.

Por último, incluimos la biblioteca jQuery de la CDN de la biblioteca AJAX de Google y nuestro propio archivo script.js, que se analiza en detalle en la última sección de este tutorial.

El esquema de la tabla

La aplicación utiliza dos tablas MySQL para almacenar datos. Sugerencias y Sugerencias_votos. La primera tabla contiene el texto de la sugerencia y datos como la calificación y el número de votos que ha recibido el artículo. La segunda tabla lleva registro de las IPs de los votantes y evita que se emita más de un voto en un solo día por IP.

Para agilizar las consultas de selección, se define un índice sobre la puntuación campo. Esto ayuda a la hora de mostrar las sugerencias ordenadas por popularidad.

La tabla de sugerencias de votos tiene una clave principal que consta de tres campos:el suggestion_id , la IP del votante y la fecha del voto Y debido a que las claves primarias no permiten filas duplicadas, podemos estar seguros de que los usuarios pueden votar solo una vez al día simplemente verificando el valor de la variable de filas_afectadas después de la inserción.

El PHP

Antes de profundizar en la generación de los elementos de sugerencia y las interacciones de AJAX, primero tenemos que echar un vistazo a la clase PHP de sugerencia. Utiliza dos métodos mágicos de PHP (aparte del constructor) para proporcionar una rica funcionalidad a nuestro código. Al generar la página principal, PHP ejecuta una consulta de selección de MySQL en la base de datos y crea un objeto de esta clase para cada fila de la tabla. Las columnas de la fila se agregan como propiedades al objeto.

sugerencia.clase.php

class Suggestion
{
    private $data = array();

    public function __construct($arr = array())
    {
        if(!empty($arr)){

            // The $arr array is passed only when we manually
            // create an object of this class in ajax.php

            $this->data = $arr;
        }
    }

    public function __get($property){

        // This is a magic method that is called if we
        // access a property that does not exist.

        if(array_key_exists($property,$this->data)){
            return $this->data[$property];
        }

        return NULL;
    }

    public function __toString()
    {
        // This is a magic method which is called when
        // converting the object to string:

        return '
        <li id="s'.$this->id.'">
            <div class="vote '.($this->have_voted ? 'inactive' : 'active').'">
                <span class="up"></span>
                <span class="down"></span>
            </div>

            <div class="text">'.$this->suggestion.'</div>
            <div class="rating">'.(int)$this->rating.'</div>
        </li>';
    }
}

El __toString() El método se utiliza para crear una representación de cadena del objeto. Con su ayuda podemos construir el marcado HTML, completo con el título de la sugerencia y el número de votos.

El __get() El método se utiliza para enrutar el acceso a las propiedades no definidas de la clase a los $datos formación. Esto quiere decir que si accedemos a $obj->suggestion , y esta propiedad no está definida, se obtendrá de la matriz $data y se nos devolverá como si existiera. De esta forma, podemos simplemente pasar una matriz al constructor, en lugar de configurar todas las propiedades. Estamos usando esto cuando creamos un objeto en ajax.php .

Ahora procedamos con la generación de la lista desordenada en la página principal.

sugerencias.php

require "connect.php";
require "suggestion.class.php";

// Converting the IP to a number. This is a more effective way
// to store it in the database:

$ip = sprintf('%u',ip2long($_SERVER['REMOTE_ADDR']));

// The following query uses a left join to select
// all the suggestions and in the same time determine
// whether the user has voted on them.

$result = $mysqli->query("
    SELECT s.*, if (v.ip IS NULL,0,1) AS have_voted
    FROM suggestions AS s
    LEFT JOIN suggestions_votes AS v
    ON(
        s.id = v.suggestion_id
        AND v.day = CURRENT_DATE
        AND v.ip = $ip
    )
    ORDER BY s.rating DESC, s.id DESC
");

$str = '';

if(!$mysqli->error)
{
    // Generating the UL

    $str = '<ul class="suggestions">';

    // Using MySQLi's fetch_object method to create a new
    // object and populate it with the columns of the result query:

    while($suggestion = $result->fetch_object('Suggestion')){

        $str.= $suggestion; // Uses the __toString() magic method.

    }

    $str .='</ul>';
}

Después de ejecutar la consulta, usamos fetch_object() método del $resultado objeto. Este método crea un objeto de la clase dada para cada fila del resultado y asigna las columnas de esa fila al objeto como propiedades públicas.

PHP también gestiona las solicitudes AJAX enviadas por jQuery. Esto se hace en ajax.php . Para distinguir una acción AJAX de otra, el script toma una $_GET['action'] parámetro, que puede tener uno de dos valores:'votar ' o 'enviar '.

ajax.php

require "connect.php";
require "suggestion.class.php";

// If the request did not come from AJAX, exit:
if($_SERVER['HTTP_X_REQUESTED_WITH'] !='XMLHttpRequest'){
    exit;
}

// Converting the IP to a number. This is a more effective way
// to store it in the database:

$ip = sprintf('%u',ip2long($_SERVER['REMOTE_ADDR']));

if($_GET['action'] == 'vote'){

    $v = (int)$_GET['vote'];
    $id = (int)$_GET['id'];

    if($v != -1 && $v != 1){
        exit;
    }

    // Checking to see whether such a suggest item id exists:
    if(!$mysqli->query("SELECT 1 FROM suggestions WHERE id = $id")->num_rows){
        exit;
    }

    // The id, ip and day fields are set as a primary key.
    // The query will fail if we try to insert a duplicate key,
    // which means that a visitor can vote only once per day.

    $mysqli->query("
        INSERT INTO suggestions_votes (suggestion_id,ip,day,vote)
        VALUES (
            $id,
            $ip,
            CURRENT_DATE,
            $v
        )
    ");

    if($mysqli->affected_rows == 1)
    {
        $mysqli->query("
            UPDATE suggestions SET
                ".($v == 1 ? 'votes_up = votes_up + 1' : 'votes_down = votes_down + 1').",
                rating = rating + $v
            WHERE id = $id
        ");
    }

}
else if($_GET['action'] == 'submit'){

    // Stripping the content
    $_GET['content'] = htmlspecialchars(strip_tags($_GET['content']));

    if(mb_strlen($_GET['content'],'utf-8')<3){
        exit;
    }

    $mysqli->query("INSERT INTO suggestions SET suggestion = '".$mysqli->real_escape_string($_GET['content'])."'");

    // Outputting the HTML of the newly created suggestion in a JSON format.
    // We are using (string) to trigger the magic __toString() method.

    echo json_encode(array(
        'html'  => (string)(new Suggestion(array(
            'id'            => $mysqli->insert_id,
            'suggestion'    => $_GET['content']
        )))
    ));
}

Cuando jQuery activa el 'voto ', no espera ningún valor de retorno, por lo que el script no genera ninguno. En el 'enviar Sin embargo, jQuery espera que se devuelva un objeto JSON que contenga el marcado HTML de la sugerencia que se acaba de insertar. Aquí es donde creamos una nueva Sugerencia objeto con el único propósito de usar su __toString() método mágico y convertirlo con el json_encode() incorporado función.

JQuery

Todo el código jQuery reside en script.js . Escucha eventos de clic en las flechas verde y roja. Pero como las sugerencias se pueden insertar en cualquier momento, estamos usando live() método jQuery, para que podamos escuchar el evento incluso en elementos que aún no se han creado.

secuencia de comandos.js

$(document).ready(function(){

    var ul = $('ul.suggestions');

    // Listening of a click on a UP or DOWN arrow:

    $('div.vote span').live('click',function(){

        var elem        = $(this),
            parent      = elem.parent(),
            li          = elem.closest('li'),
            ratingDiv   = li.find('.rating'),
            id          = li.attr('id').replace('s',''),
            v           = 1;

        // If the user's already voted:

        if(parent.hasClass('inactive')){
            return false;
        }

        parent.removeClass('active').addClass('inactive');

        if(elem.hasClass('down')){
            v = -1;
        }

        // Incrementing the counter on the right:
        ratingDiv.text(v + +ratingDiv.text());

        // Turning all the LI elements into an array
        // and sorting it on the number of votes:

        var arr = $.makeArray(ul.find('li')).sort(function(l,r){
            return +$('.rating',r).text() - +$('.rating',l).text();
        });

        // Adding the sorted LIs to the UL
        ul.html(arr);

        // Sending an AJAX request
        $.get('ajax.php',{action:'vote',vote:v,'id':id});
    });

    $('#suggest').submit(function(){

        var form        = $(this),
            textField   = $('#suggestionText');

        // Preventing double submits:
        if(form.hasClass('working') || textField.val().length<3){
            return false;
        }

        form.addClass('working');

        $.getJSON('ajax.php',{action:'submit',content:textField.val()},function(msg){
            textField.val('');
            form.removeClass('working');

            if(msg.html){
                // Appending the markup of the newly created LI to the page:
                $(msg.html).hide().appendTo(ul).slideDown();
            }
        });

        return false;
    });
});

Cuando se hace clic en una de esas flechas, jQuery determina si la clase 'inactiva' está presente en el elemento LI. Esta clase solo se asigna a la sugerencia, si el usuario votó durante el último día y, si está presente, el script ignorará cualquier evento de clic.

Observe cómo $.makeArray se utiliza para convertir los objetos jQuery, que contienen los elementos LI, en una verdadera matriz. Esto está hecho, por lo que podemos usar array.sort() y pásele una función de ordenación personalizada, que toma dos LI al mismo tiempo y genera un número entero negativo, cero o un número entero positivo, según cuál de los dos elementos tenga una calificación mayor. Esta matriz se inserta más tarde en la lista desordenada.

El CSS

Ahora que hemos generado todo el marcado, podemos continuar con el estilo. Como el estilo es bastante trivial, solo quiero mostrarle la clase que redondea las esquinas superior izquierda e inferior derecha de los elementos a los que se aplica. Puedes ver el resto de las reglas CSS en styles.css.

estilos.css

.rounded,
#suggest,
.suggestions li{
    -moz-border-radius-topleft:12px;
    -moz-border-radius-bottomright:12px;

    -webkit-border-top-left-radius:12px;
    -webkit-border-bottom-right-radius:12px;

    border-top-left-radius:12px;
    border-bottom-right-radius:12px;
}

Tenga en cuenta que la sintaxis de Mozilla difiere del estándar en la forma en que apunta a las diferentes esquinas del elemento. Teniendo eso en cuenta, podemos aplicar esta clase a casi todos los elementos, como puede ver en la demostración.

¡Con esto, nuestra aplicación de sugerencias de características está completa!

Conclusión

Si planea configurar este script en su propio servidor, deberá crear las dos tablas de sugerencias ejecutando el código que se encuentra en tables.sql. en la pestaña SQL de phpMyAdmin. También recuerde completar los detalles de conexión de su base de datos en connect.php .

Puede usar este script para recopilar valiosos comentarios de sus visitantes. También puede deshabilitar la opción para que los usuarios agreguen nuevas sugerencias y usarla como una especie de sistema de encuesta avanzado.

Asegúrese de compartir sus pensamientos en su sección de comentarios a continuación.