Creando un cuestionario con AngularJS – Parte 3

En este ejemplo vamos a crear un cuestionario con preguntas que deben ser respondidas por el usuario. Para ello partiremos de un set de preguntas que deben ser cargadas en el documento HTML. Además, según vaya respondiendo el usuario a cada pregunta, ésta se deberá actualizar de forma que sepa si ha respondido correctamente o no. Por último tendremos un indicador que muestre el rango del usuario en función de la cantidad de preguntas que va acertando.

Por tanto, nuestros requisitos son:

  • Queremos que las preguntas se carguen dinámicamente en el documento.
  • Por cada pregunta que responda el usuario se debe indicar si la respuesta es correcta o no.
  • Se debe actualizar el rango del usuario en función de las preguntas que vaya acertando.

Definiendo las preguntas.

En nuestro modelo de datos una pregunta se representaría de la siguiente forma:

{
id : 1,
text : 'Esto es una pregunta',
validAnswer : 1,
userAnswer : null,
status : '',
answers: [
{id : 1, text : 'Respuesta 1'},
{id : 2, text : 'Respuesta 2'},
{id : 3, text : 'Respuesta 3'}
]
}

Las propiedades de nuestra pregunta son:

id: identificador de la pregunta
text: enunciado de la pregunta
validAnswer: identificador de la respuesta correcta
userAnswer: identificador de la respuesta seleccionada por el usuario (null si no ha respondido)
status: indica si la pregunta se ha respondido de forma correcta o no
answers: listado de posibles respuestas a la pregunta. Cada pregunta está compuesta por un id y un texto


Definiendo nuestro modelo de datos.

Como explicamos anteriormente, AngularJS vincula nuestro modelo de datos con el documento HTML. Para conseguir esto, hace uso de un objeto scope donde se definen las propiedades del modelo.

function TestController($scope) {
    $scope.questions = [
        {
            id : 1,
            text:'Esto es una pregunta',
            validAnswer : 1,
            userAnswer : null,
            status : '',
            answers: [
                {id : 1, text : 'Respuesta 1.1'},
                {id : 2, text : 'Respuesta 1.2'},
                {id : 3, text : 'Respuesta 1.3'}
            ]
        },
        {
            id : 2,
            text:'Otra pregunta',
            validAnswer : 2,
            userAnswer : null,
            status : '',
            answers: [
                {id : 1, text : 'Respuesta 2.1'},
                {id : 2, text : 'Respuesta 2.2'}
            ]
        }
    ];

    $scope.userStatus = '';

    $scope.validAnswers = 0;

    $scope.validate = function (question) {
        if (question.validAnswer == question.userAnswer) {
            $scope.validAnswers ++;
            question.status = 'ok';
        } else {
            if (question.status == 'ok' && $scope.validAnswers > 0) {
                $scope.validAnswers --;
            }
            question.status = 'error';
        }

        updateUserStatus();
    };

    function updateUserStatus() {
        var avgValidAnswers = ($scope.validAnswers / $scope.questions.length) * 100;
        if (avgValidAnswers == 0) {
            $scope.userStatus = 'looser';
        } else if (avgValidAnswers == 100) {
            $scope.userStatus = 'guru';
        } else {
            $scope.userStatus = 'poor';
        }
    }

}

Como podemos ver, hemos definido tres propiedades además de un método en nuestro modelo de datos:

questions: Listado de preguntas con sus respectivas respuestas que serán cargadas dinámicamente en nuestro documento HTML.
userStatus: Rango del usuario en función de las preguntas que se van respondiendo.
validAnswers: Representa el número de respuestas acertadas por el usuario.
validate: Valida una pregunta en función de la respuesta del usuario. Actualiza userStatus y validAnswers además del status del objeto question.
Si nos fijamos, el método validate, que será invocado cuando el usuario responda a una pregunta (lo veremos en el próximo punto), actualiza las propiedades validAnswers, userStatus y question.status. Los valores asignados a question.status (ok o error) están representados en una hoja de estilos del mismo modo que los valores “looser”, “poor” y “guru” del estado del usuario. Esto hará que, cuando cambie el valor de estas propiedades, se cargue en el documento una regla distinta de la hoja de estilos con lo que el usuario apreciará un cambio en el documento.


Usuario con rango “looser”


Usuario con rango “poor”


Usuario con rango “guru”

Definiendo la vista y su vinculación con el modelo.

Pues ya lo único que queda es definir nuestro documento HTML como si fuese una plantilla, vincularla con nuestro modelo y hacer que AngularJS despligue su “magia”.


<!DOCTYPE HTML>
<html ng-app>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>Cuestionario con AngularJS</title>
    <script src="js/angular-1.0.4.min.js"></script>
    <script src="js/script.js?09022013"></script>
    <link type="text/css" rel="stylesheet" href="css/test.css?09022013"/>
</head>
<body>
<div id="test" ng-controller="TestController">
    <h1>Ejemplo de cuestionario utilizando AngularJS</h1>
    <img src="img/AngularJS-logo.png" alt="AngularJS" class="logoAngularJS"/>
    <div class="questions">
        <div class="question" ng-repeat="question in questions">
            <p class="status-{{question.status}}">{{question.text}}</p>
            <div class="answers" ng-repeat="answer in question.answers">
                <input type="radio" ng-model="question.userAnswer" value="{{answer.id}}"
                       name="question-{{question.id}}" id="answer-{{question.id}}-{{answer.id}}"
                       ng-change="validate(question)"/>
                <label for="answer-{{question.id}}-{{answer.id}}">{{answer.text}}</label>
            </div>
        </div>
    </div>
    <div class="userStatus {{userStatus}}">
        <span>Has acertado {{validAnswers}} de {{questions.length}}</span>
    </div>
</div>
</body>
</html>

Pues lo que estamos viendo es todo lo que necesitamos para construir nuestra vista y que, además tenga un comportamiento dinámico. Expliquemos algunos puntos:

Línea 2:

vemos que el tag html está marcado con la directiva ng-app. Lo que se está haciendo es definir el ámbito sobre el que AngularJS actuará. En nuestro ejemplo sería en todo el documento HTML.

Línea 11:

vemos que el elemento div tiene una directiva ng-controller con valor igual a TestController. Con esto estamos definiendo el controlador que actuará sobre el elemento y sus nodos hijo. Si nos fijamos el valor “TestController” coincide con la función donde definimos las propiedades de nuestro modelo y su comportamiento. Para el que conozca JSF sería algo como configurar el Back Bean que hay detrás de la vista.

Línea 15:

vemos que hay un div con una directiva ng-repeat. Lo que hacemos es repetir ese elemento div (y sus nodos hijo) por cada “question” que tenemos definidas en el modelo (propiedad questions). Además define una variable question (la pregunta concreta) que podrá ser utilizada en ese ámbito. Si nos fijamos en la siguiente línea, observamos que podemos acceder directamente a las propiedades de question (recordemos que es un elemento del modelo) mediante la sintaxis {{}}. En concreto obtenemos los valores de las propiedades status y text.

Línea 18:

Observamos que se están pintando los elementos input (radio-buttons) que representan las posibles respuestas de un usuario a una pregunta. Con la directiva ng-model igual a question.userAnswer lo que hacemos es vincular el valor de este elemento, que si nos fijamos es el id de la respuesta, con la propiedad userAnswer que teníamos definida en las preguntas de nuestro modelo. Con esto, cuando el usario seleccione una pregunta, el id de la pregunta seleccionada se vinculará automáticamente con dicha propiedad.

Línea 20:

en ese mismo elemento input definimos una directiva ng-change con valor igual a validate(question). Lo que haremos será invocar al método validate que definimos en el modelo cuando se seleccione una respuesta. Con esto actualizaremos el número total de respuestas acertadas (validAnswers) y el rango del usuario (userStatus). Este cambio en el modelo se verá automáticamente reflejado en la vista.

5. Referencias.

6. Conclusiones.

En este tutorial hemos visto cómo dinamizar nuestras páginas HTML en el lado del cliente con ayuda del framework Javascript: AngularJS. Este tipo de frameworks (algunos los llaman MVC, otros MVVM) nos permiten separar la lógica de presentación de la vista mediante la vinculación de datos del modelo con componentes HTML.

Bajo mi punto de vista, AngularJS proporciona dos grandes ventajas:

  • Separación de la lógica de presentación de la vista, de forma que el código Javascript es independiente al código HTML por lo que podríamos rehacer la vista sin necesidad de tener que tocar ni una sola línea de nuestra lógica de negocio. Además, nuestro código es extremadamente fácil testear ya que no dependemos de ningún contexto externo (es lógica pura).
  • Facilidad a la hora de manipular los elementos del DOM y sus propiedades. Esta es una tarea que, por norma general, es bastante tediosa y a medida que la lógica de presentación crece suele ser complicado escribir código que pueda mantenerse con facilidad. AngularJS simplifica mucho la labor en este sentido.

Fuente:

Adictos al trabajo 

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *