1. Code
  2. JavaScript
  3. jQuery

Construyendo un Sistema de Calificación de 5 estrellas con jQuery, AJAX y PHP

En este tuturial usted aprenderá como hacer un sistema de calificación con AJAX, PHP y JQuery Los votos serán guardados y actualizados en tiempo real, con la magia de AJAX  y también vamos a usar el poder de PHP para que usted ni siquiera necesite una base de datos
Scroll to top

Spanish (Español) translation by Luis Chiabrera (you can also view the original English article)

En este tuturial usted aprenderá como hacer un sistema de calificación con AJAX, PHP y JQuery Los votos serán guardados y actualizados en tiempo real, con la magia de AJAX  y también vamos a usar el poder de PHP para que usted ni siquiera necesite una base de datos


Paso 1. Construyendo el HTML

Vamos a crear una página simple que muestra dos películas, y nos permite calificarlas. Esto significa que vamos a necesitar estrellas para mostrar la calificación actual y para permitir calificarlas. También queremos un área para mostrar el total de votos y la calificación actual con un espacio decimal.

Veamos el código HTML/CSS

1
        <div class='movie_choice'>
2
            Rate: Raiders of the Lost Ark
3
            <div id="r1" class="rate_widget">
4
                <div class="star_1 ratings_stars"></div>
5
                <div class="star_2 ratings_stars"></div>
6
                <div class="star_3 ratings_stars"></div>
7
                <div class="star_4 ratings_stars"></div>
8
                <div class="star_5 ratings_stars"></div>
9
                <div class="total_votes">vote data</div>
10
            </div>
11
        </div>
12
        
13
        <div class='movie_choice'>
14
            Rate: The Hunt for Red October
15
            <div id="r2" class="rate_widget">
16
                <div class="star_1 ratings_stars"></div>
17
                <div class="star_2 ratings_stars"></div>
18
                <div class="star_3 ratings_stars"></div>
19
                <div class="star_4 ratings_stars"></div>
20
                <div class="star_5 ratings_stars"></div>
21
                <div class="total_votes">vote data</div>
22
            </div>
23
        </div>

¿Porqué no hay imagenes en este HTML?  Las vamos a agregar despúes con CSS. Solo estamos usando HTML para crear la estructura donde trabajará el widget. Es hora de agregar código CSS.

1
        .rate_widget {
2
            border:     1px solid #CCC;
3
            overflow:   visible;
4
            padding:    10px;
5
            position:   relative;
6
            width:      180px;
7
            height:     32px;
8
        }
9
        .ratings_stars {
10
            background: url('star_empty.png') no-repeat;
11
            float:      left;
12
            height:     28px;
13
            padding:    2px;
14
            width:      32px;
15
        }
16
        .ratings_vote {
17
            background: url('star_full.png') no-repeat;
18
        }
19
        .ratings_over {
20
            background: url('star_highlight.png') no-repeat;
21
        }

Con esta parte del CSS logramos algunas cosas:

  • Darle el estado de "vacío" a cada espacio
  • Crear las clases para las estrellas llenas y las resaltadas
  • Definir y darle estilo a los contenedores de las estrellas

Usted puede usar las imágenes disponibles para descargar o puede hacer las suyas. Tiene que haber imágenes para cada uno de los 3 estados: vacío, lleno y resaltado.

Ahora agregamos un poco más de CSS para posicionar la caja del total de votos y centrar los widgets para que la página coincidad con la imágen al inicio de esta sección.

1
        .total_votes {
2
            background: #eaeaea;
3
            top: 58px;
4
            left: 0;
5
            padding: 5px;
6
            position:   absolute;  
7
        } 
8
        .movie_choice {
9
            font: 10px verdana, sans-serif;
10
            margin: 0 auto 40px auto;
11
            width: 180px;
12
        }

Paso 2. Agregar la interacción con la UI.

En este punto tenemos un poco de estrellas vacías con poco estilo, pero no hacemos mucho en este momento. Aquí es don jQuery entra al rescate.

Nuestro primer paso es agregar los eventos de mouseover y mouseout para las estrellas. Necesitamos resaltar la estrella cuando el cursor pasa por encima y todas las estrellas anteriores.

1
        $('.ratings_stars').hover(
2
            // Handles the mouseover
3
            function() {
4
                $(this).prevAll().andSelf().addClass('ratings_over');
5
                $(this).nextAll().removeClass('ratings_vote'); 
6
            },
7
            // Handles the mouseout
8
            function() {
9
                $(this).prevAll().andSelf().removeClass('ratings_over');
10
                set_votes($(this).parent());
11
            }
12
        );

Estamos usando el poder de los métodos .prevAll() y .nextAll() de jQuery para obtener las estrellas anteriores y siguientes a la actual.

El código de arriba luego agrega y quita las clases para resaltar las estrellas anteriores a donde está el cursos y no resaltar las que siguen.

¿Qué es el set_votes()?

Esta es una función que revisar que estrella debe estar en estado 'full', y se relaciona con el siguiente paso, dónde tomamos datos remotos del servidor.


Paso 3. Recopilando datos del servidor

Nuestras estrellas se destacan cuando mueves el mouse sobre ellas, y ese es un gran comienzo. ¿Pero qué pasa con las estrellas rojas que muestran el voto actual? Para alcanzar este paso, necesitamos obtener la información del servidor y escribir un poco JavaScript para manejar esos datos.

1
        $('.rate_widget').each(function(i) {
2
            var widget = this;
3
            var out_data = {
4
                widget_id : $(widget).attr('id'),
5
                fetch: 1
6
            };
7
            $.post(
8
                'ratings.php',
9
                out_data,
10
                function(INFO) {
11
                    $(widget).data( 'fsr', INFO );
12
                    set_votes(widget);
13
                },
14
                'json'
15
            );
16
        });

Este bloque de código, en realidad todo el JavaScript, va en un bloque document.ready. Este código en particular se ejecuta de inmediato. Consulta el servidor y obtiene información sobre cada widget de voto en la página.

Primero configuramos un objeto, out_data, para contener la información que enviamos al servidor. Nuestro script PHP espera ver 'fetch' cuando solo captura datos, por lo que los incluimos aquí. También incluimos la ID del widget, que permite que el script del lado del servidor sepa qué datos buscamos. Cuando se activa la función de devolución de llamada, contiene un objeto JavaScript que se ve así:

1
        {
2
            "widget_id"     : "r1",
3
            "number_votes"  : 129,
4
            "total_points"  : 344,
5
            "dec_avg"       : 2.7,
6
            "whole_avg"     : 3
7
        }

El método .data() es un poco de magia jQuery que te permite asociar datos arbitrarios con un objeto DOM.

Si observas de cerca el código, verás que estamos tomando ese objeto (almacenado en la variable INFO) y haciendo algo con él a través del método .data()

El método .data () es un poco de magia jQuery que te permite asociar datos arbitrarios con objeto DOM. En este caso, estamos almacenando los datos en el widget div. Se puede acceder más tarde así:

1
        $('#one_of_your_widgets).data('fsr').widget_id;

set_votes(), Finalmente.

Una vez que los datos han sido devueltos por el servidor, se transfieren indirectamente a set_votes().

1
        function set_votes(widget) {
2
        
3
            var avg = $(widget).data('fsr').whole_avg;
4
            var votes = $(widget).data('fsr').number_votes;
5
            var exact = $(widget).data('fsr').dec_avg;
6
            
7
            $(widget).find('.star_' + avg).prevAll().andSelf().addClass('ratings_vote');
8
            $(widget).find('.star_' + avg).nextAll().removeClass('ratings_vote'); 
9
            $(widget).find('.total_votes').text( votes + ' votes recorded (' + exact + ' rating)' );
10
        }

Las primeras tres líneas son de legibilidad, ya que esos nombres de variables son bastante largos. Así que echemos un vistazo a lo que está sucediendo aquí.

Línea 7: 'avg' es un número entero, que representa el promedio de voto redondeado de este widget. Porque es un número 1-5, podemos usarlo para encontrar la estrella adecuada en el widget, y girarlo, y a los anteriores a nuestro gráfico 'relleno'. Observa el uso de .andSelf() para incluir la estrella que hemos seleccionado.

Línea 8: Esto es bastante similar a la línea siete, pero estamos eliminando el gráfico relleno de las estrellas posteriores. Esto es necesario en caso de que el promedio de este widget haya bajado desde la última votación.

Línea 9: Aquí estamos actualizando el cuadro gris debajo del widget, que muestra una calificación más precisa, y le permite al visitante saber cuántos votos se han emitido.


Paso 4. Que comience la votación

El último paso para la interfaz de usuario es habilitar la votación. Vamos a agregar un controlador de clics a cada una de las estrellas. Este controlador de clics será responsable de enviar los datos de votación al servidor.

Aquí está el controlador de clics:

1
        $('.ratings_stars').bind('click', function() {
2
            var star = this;
3
            var widget = $(this).parent();
4
            
5
            var clicked_data = {
6
                clicked_on : $(star).attr('class'),
7
                widget_id : widget.attr('id')
8
            };
9
            $.post(
10
                'ratings.php',
11
                clicked_data,
12
                function(INFO) {
13
                    widget.data( 'fsr', INFO );
14
                    set_votes(widget);
15
                },
16
                'json'
17
            ); 
18
        });

En este bloque de código, comenzamos creando algunas variables no solo para mayor claridad, sino, en este caso, para que puedan usarse dentro de la devolución de llamada .post. Recuerda que el controlador de clics está asignado a las estrellas, por lo que también necesitamos esa segunda variable, el widget, para tener el objeto que contiene los datos.

Primero, configuramos nuestros datos salientes, los cuales hemos colocado en el objeto clicked_data. Tomamos la clase que incluye un nombre de clase en el formato de star_# diciéndonos qué voto se está dando, y nos preparamos para enviarlo al servidor, junto con la identificación del widget.

La ID del widget es la piedra angular en la que se basa este sistema de votación. Nos permite buscar nuestros datos almacenados y mostrarlos fácilmente al visitante.

Finalmente, en línea, enviamos esta información al servidor. El servidor agregará el voto a los totales actuales y enviará información al navegador que contiene los datos actualizados. Los valores que muestra el widget se actualizan con set_votes().


Paso 5. PHP: Creando la Clase

Ahora que la interfaz de usuario ha finalizado, necesitamos crear un script del lado del servidor para almacenar y recuperar datos de votación.

Vamos a crear una clase muy simple en PHP, llamada 'Calificaciones', y la usaremos para manejar las solicitudes del servidor para nuestro sistema de calificación. Solo habrá dos métodos, más la invocación. El uso de nuestra clase se verá así:

1
        # New Object

2
        $rating = new ratings($_POST['widget_id']);
3
    
4
        # either return ratings, or process a vote

5
        isset($_POST['fetch']) ? $rating->get_ratings() : $rating->vote();

Si vuelves a la sección cuatro, verás que cargamos los datos con el conjunto variable 'fetch'; eso es lo que estamos buscando aquí en la línea cinco. Si no está configurado, entonces estamos procesando un voto.

Lo primero que vamos a ver es el comienzo de la clase y, más específicamente, el constructor.

1
        class ratings {
2
            
3
            private $data_file = './ratings.data.txt';
4
            private $widget_id;
5
            private $data = array();
6
               
7
        function __construct($wid) {
8
            
9
            $this->widget_id = $wid;
10
        
11
            $all = file_get_contents($this->data_file);
12
            
13
            if($all) {
14
                $this->data = unserialize($all);
15
            }
16
        }

serialize() y unserialize son una excelente manera de almacenar fácilmente estructuras de datos PHP en disco.

Aquí están sucediendo muchas cosas en muy pocas líneas, así que voy a cubrir las partes importantes.

Línea 3: debe configurarse en un archivo de texto que te gustaría usar para almacenar tus datos. No estamos utilizando una base de datos para este proyecto, aunque fácilmente podrías hacerlo. Un archivo simple será suficiente para nuestras necesidades.

Línea 7: El constructor. Esto se llama cuando creamos nuestro objeto, e inmediatamente almacena la ID del widget.

Línea 11: Intentamos cargar el archivo de texto. Si el archivo no existe, está bien, pero en algunos sistemas necesitarás crearlo con anticipación y darle los permisos adecuados para que PHP pueda leerlo y escribirlo.

Línea 14: Esta línea es importante. Toma los datos del archivo de texto, si hay uno, y lo deserializa(). El archivo contiene una matriz PHP compleja que se ha convertido en una representación de texto sin formato, a través de serialize(), lo que nos permite almacenarlo y volver a leerlo como una matriz más adelante.


Paso 6. El método get_ratings().

Este método se llama solo o desde el método vote(). Encuentra los datos para un ID de widget en particular y los devuelve a la página solicitante, en formato JSON.

1
    public function get_ratings() {
2
        if($this->data[$this->widget_id]) {
3
            echo json_encode($this->data[$this->widget_id]);
4
        }
5
        else {
6
            $data['widget_id'] = $this->widget_id;
7
            $data['number_votes'] = 0;
8
            $data['total_points'] = 0;
9
            $data['dec_avg'] = 0;
10
            $data['whole_avg'] = 0;
11
            echo json_encode($data);
12
        } 
13
    }

Esto solo parece complicado, en realidad es bastante simple. Lo primero que hacemos es verificar si la matriz almacenada en $this->data tiene una clave que coincide con nuestra ID de widget. Si es así, simplemente devolvemos esa información, porque esa es la información del widget que la página estaba solicitando.

No tenemos que hacer nada con esos datos porque ya está en forma de matriz. $this->data es solo una matriz de matrices. Codificamos la matriz que queremos con json_encode() y la enviamos de vuelta al navegador.

Si no hay datos para la identificación del widget que hemos solicitado, creamos un registro con todos los valores cero y lo enviamos de vuelta al navegador.

Paso 7. El método vote()

Luego, necesitamos crear un método para manejar los votos entrantes. Cuando finaliza el método, debe llamar a get_ratings() para enviar la información actualizada al navegador web.

El Método Start

1
        public function vote() {
2
            # Get the value of the vote

3
            preg_match('/star_([1-5]{1})/', $_POST['clicked_on'], $match);
4
            $vote = $match[1];

Lo primero que hacemos es obtener el valor del voto. Recuerda que en algún lugar de 'clicked_on' hay un nombre de clase en el formato de star_#. "star_4", por ejemplo. Para obtener ese valor, estamos usando una expresión regular y capturando el valor del número para $match[1].

El método Middle

1
    
2
            $ID = $this->widget_id;
3
            # Update the record if it exists

4
            if($this->data[$ID]) {
5
                $this->data[$ID]['number_votes'] += 1;
6
                $this->data[$ID]['total_points'] += $vote;
7
            }
8
            # Create a new one if it does not

9
            else {
10
                $this->data[$ID]['number_votes'] = 1;
11
                $this->data[$ID]['total_points'] = $vote;
12
            }

Aquí almacenamos $this->widget_id en $ID para mayor claridad: el siguiente código se vuelve un poco duro para los ojos sin él.

Verificamos si existe información para esta ID y, de ser así, agregamos un voto al recuento total de votos y sumamos los puntos del voto recibido. Este es un total acumulado de todos los votos; así que si una persona da cinco estrellas, y otra, tres, son ocho puntos en total.

Si el registro no existe, creamos uno, con un voto, y solo los puntos del voto entrante.

Terminando

1
  
2
            $this->data[$ID]['dec_avg'] = round( $this->data[$ID]['total_points'] / $this->data[$ID]['number_votes'], 1 );
3
            $this->data[$ID]['whole_avg'] = round( $this->data[$ID]['dec_avg'] );
4
                  
5
            file_put_contents($this->data_file, serialize($this->data));
6
            $this->get_ratings();
7
        }

Una vez que hayamos actualizado el total de votos y puntos, tenemos que calcular tanto el promedio expresado como un número entero como un punto decimal. Para evitar tener que hacer los cálculos dos veces, primero calculamos el promedio a un decimal en la línea uno, y luego redondeamos eso a un número entero, en la línea dos.

En la línea cuatro, estamos almacenando la información modificada nuevamente en el disco después de procesarla con serialize(). Una vez que los datos se almacenan de forma segura, llamamos a $this->get_ratings() para enviar la información nueva y actualizada al navegador.


Conclusión

En aras de la simplicidad, esta no es una solución 100% completa. Para extender este proyecto, debemos almacenar una cookie para asegurarnos de que las personas solo voten una vez, o incluso registrar la dirección IP. También es posible que dos parejas de primer voto ocurran simultáneamente, y solo una puede ser registrada. Sin embargo, es un gran comienzo y es más que adecuado para realizar un seguimiento de los votos en unos pocos elementos en tu sitio web. Opiniones? ¡Gracias por leer!