Monday 3 June 2019

Web Tutorial: The Liverpool Quiz (Part 1/3)

Liverpool FC, the team I root for, has had a great season. In addition to scoring an unprecedented 97 points in the English Premier League, they won the season's UEFA Champion's League.

Thus, today, I would like to dedicate this web tutorial to them. For this, we will be creating a timed quiz using an AngularJS backbone.

Here's the setup...

Create three folders and name them js, css and img. js will hold the JavaScript file, while css will hold the stylesheets and img will hold images.

Inside the js folder, create main.js.

Inside the css folder, create styles.css.

In the main folder, create index.html.


We'll begin with the HTML...

Open up index.html and put in the basic HTML code. We'll include reference links to the AngularJS code and main.js.

index.html
<!DOCTYPE html>
<html>
    <head>
        <title>Liverpool Quiz</title>
        <link rel="stylesheet" type="text/css" href="css/styles.css">
        <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
        <script src="js/main.js"></script>
    </head>
   
    <body>

    </body>
</html>

Now, ensure that the body's ng-app attribute is quizApp. That's our application scope.

index.html
<!DOCTYPE html>
<html>
    <head>
        <title>Liverpool Quiz</title>
        <link rel="stylesheet" type="text/css" href="css/styles.css">
        <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
        <script src="js/main.js"></script>
    </head>
   
    <body ng-app="quizApp">

    </body>
</html>

Insert a div. That's controller-scoped, under quizCtrl. Give it an id of quizContainer, because we'll be styling this later. Much later.

index.html
<!DOCTYPE html>
<html>
    <head>
        <title>Liverpool Quiz</title>
        <link rel="stylesheet" type="text/css" href="css/styles.css">
        <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
        <script src="js/main.js"></script>
    </head>
   
    <body ng-app="quizApp">
        <div ng-controller="quizCtrl" id="quizContainer">

        </div>
    </body>
</html>

Now, insert three more divs. They will be styled using these classes respectively - pnlMain, pnlQuestions and pnlResults. From the names, you can probably guess what they'll be used to display.

index.html
<!DOCTYPE html>
<html>
    <head>
        <title>Liverpool Quiz</title>
        <link rel="stylesheet" type="text/css" href="css/styles.css">
        <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
        <script src="js/main.js"></script>
    </head>
   
    <body ng-app="quizApp">
        <div ng-controller="quizCtrl" id="quizContainer">
            <div class="pnlMain">

            </div>

            <div class="pnlQuestions">

            </div>

            <div class="pnlResults">

            </div>
        </div>
    </body>
</html>

There's nothing visible on the page yet, so don't bother refreshing...

But now we will add text to the divs. Each div has a div styled using CSS class content and a div styled using CSS class buttons. We will write the styles later.

index.html
        <div ng-controller="quizCtrl" id="quizContainer">
            <div class="pnlMain">
                <div class="content">

                </div>

                <div class="buttons">

                </div>
            </div>

            <div class="pnlQuestions">
                <div class="content">

                </div>

                <div class="buttons">

                </div>
            </div>

            <div class="pnlResults">
                <div class="content">

                </div>

                <div class="buttons">

                </div>
            </div>
        </div>

In the first div, put in some text. And in the second, put in a button. The button should have a class of btn and the id btnBegin.

index.html
            <div class="pnlMain">
                <div class="content">
                    <h1>Welcome to the Liverpool FC Quiz!</h1>
                    <p>Let's see how well you know your club.</p>
                </div>
               
                <div class="buttons">
                    <input type="button" id="btnBegin" class="btn" value="Begin">
                </div>
            </div>

Don't worry if the text doesn't make sense right now. Just refresh, and you'll see your page appear.

Now let's JavaScript this sucker

In main.js of the js folder, define the application scope as quizApp. Remember the first div in index.html?

js\main.js
var app = angular.module("quizApp", []);

Now create the first (and only) controller, quizCtrl.

js\main.js
var app = angular.module("quizApp", []);

app.controller("quizCtrl",
function($scope, $interval)
{   

}
);

Let's create some scope variables. questions is an array of objects which holds all the data for the questions. Leave questions as an empty array for now.

js\main.js
var app = angular.module("quizApp", []);

app.controller("quizCtrl",
function($scope, $interval)
{   
    $scope.questions = [];
}
);

For each question answered correctly, result is incremented. So obviously, the default is 0.

js\main.js
app.controller("quizCtrl",
function($scope, $interval)
{   
    $scope.questions = [];

    $scope.result = 0;
}
);

possible is the total number of questions. So naturally, it's set to the length of the questions array.

js\main.js
app.controller("quizCtrl",
function($scope, $interval)
{   
    $scope.questions = [];

    $scope.result = 0;
    $scope.possible = $scope.questions.length;
}
);

currentQuestion points to the questions array. It's defaulted to -1, which means the quiz has not begun.

js\main.js
app.controller("quizCtrl",
function($scope, $interval)
{   
    $scope.questions = [];

    $scope.result = 0;
    $scope.possible = $scope.questions.length;
    $scope.currentQuestion = -1;
}
);

secondsRemaining pertains to the timer function that we will write later. Since we'll be giving the user one minute to complete the quiz, set this to 60.

js\main.js
app.controller("quizCtrl",
function($scope, $interval)
{   
    $scope.questions = [];

    $scope.result = 0;
    $scope.possible = $scope.questions.length;
    $scope.currentQuestion = -1;
    $scope.secondsRemaining = 60;
}
);

proceed is button text. Set to to "Next" as a default and leave it alone for now. timer represents the state of the application timer. Set it to null, and again, forget about it. We'll be back later.

js\main.js
app.controller("quizCtrl",
function($scope, $interval)
{   
    $scope.questions = [];

    $scope.result = 0;
    $scope.possible = $scope.questions.length;
    $scope.currentQuestion = -1;
    $scope.secondsRemaining = 60;
    $scope.proceed = "Next";
    $scope.timer = null;
}
);

secondsRemaining, resultsMessage and resultsGrade are all strings. We won't be using them yet.

js\main.js
app.controller("quizCtrl",
function($scope, $interval)
{   
    $scope.questions = [];

    $scope.result = 0;
    $scope.possible = $scope.questions.length;
    $scope.currentQuestion = -1;
    $scope.secondsRemaining = 60;
    $scope.proceed = "Next";
    $scope.timer = null;

    $scope.secondsRemainingMessage = "";
    $scope.resultsMessage = "";
    $scope.resultsGrade = "";
}
);

initQuiz() is a function that resets all variables to default.

js\main.js
app.controller("quizCtrl",
function($scope, $interval)
{   
    $scope.questions = [];

    $scope.result = 0;
    $scope.possible = $scope.questions.length;
    $scope.currentQuestion = -1;
    $scope.secondsRemaining = 60;
    $scope.proceed = "Next";
    $scope.timer = null;

    $scope.secondsRemainingMessage = "";
    $scope.resultsMessage = "";
    $scope.resultsGrade = "";

    $scope.initQuiz =
    function()
    {   
        $scope.result = 0;
        $scope.currentQuestion = -1;
        $scope.secondsRemaining = 60;
        $scope.proceed = "Next";
        $scope.timer = null;
    };
}
);

We'll run the initQuiz() function when the page loads, so add this to your quizContainer div.

index.html
<div ng-controller="quizCtrl" ng-init="initQuiz()" id="quizContainer">

Now let's fill up the questions!

The questions array is an array of objects. Each object will look like this...

text - a string which is displayed as the question.

js\main.js
    $scope.questions =
    [
        {
            "text": "How many goals did Fernando Torres score in his first season?",           
        },
    ];

answer - an integer which points to the correct element of the options array (not yet created).

js\main.js
    $scope.questions =
    [
        {
            "text": "How many goals did Fernando Torres score in his first season?",
            "answer": 2,           
        },
    ];

explanation - a string which explains why the correct answer is correct.

js\main.js
    $scope.questions =
    [
        {
            "text": "How many goals did Fernando Torres score in his first season?",
            "answer": 2,
            "explanation": "He scored 33 goals in all competitions.",           
        },
    ];

options - an array of objects. Each object has two properties - text and selected. text is basically the answer in the multiple-choice presentation, and selected is either "selected" (which means this answer has been selected) or an empty string, which means not.

js\main.js
    $scope.questions =
    [
        {
            "text": "How many goals did Fernando Torres score in his first season?",
            "answer": 2,
            "explanation": "He scored 33 goals in all competitions.",
            "options":
            [
                {
                    "text": "10 to 20",
                    "selected": ""
                },
                {
                    "text": "25 to 30",
                    "selected": ""
                },
                {
                    "text": "30 to 35",
                    "selected": ""
                },
                {
                    "text": "40 to 50",
                    "selected": ""
                }               
            ]           
        },
    ];

Here are the rest of the questions!

js\main.js
    $scope.questions =
    [
        {
            "text": "How many goals did Fernando Torres score in his first season?",
            "answer": 2,
            "explanation": "He scored 33 goals in all competitions.",
            "options":
            [
                {
                    "text": "10 to 20",
                    "selected": ""
                },
                {
                    "text": "25 to 30",
                    "selected": ""
                },
                {
                    "text": "30 to 35",
                    "selected": ""
                },
                {
                    "text": "40 to 50",
                    "selected": ""
                }               
            ]           
        },
        {
            "text": "Which of the following is a famous Bill Shankly quote?",
            "answer": 1,
            "explanation": "While he may have said the other things at some point, this quote remains one of the more famous ones.",
            "options":
            [
                {
                    "text": "A stitch in time saves nine.",
                    "selected": ""
                },
                {
                    "text": "If you are first, you are first. If you are second, you are nothing.",
                    "selected": ""
                },
                {
                    "text": "We'll be back.",
                    "selected": ""
                },
                {
                    "text": "Liverpool fans are the most amazing in the world.",
                    "selected": ""
                }               
            ]           
        },
        {
            "text": "Who was Liverpool Captain before Steven Gerrard?",
            "answer": 0,
            "explanation": "Steven Gerrard took over Captaincy from Sammi Hyppia in 2003.",
            "options":
            [
                {
                    "text": "Sammi Hyppia",
                    "selected": ""
                },
                {
                    "text": "Daniel Agger",
                    "selected": ""
                },
                {
                    "text": "Vladimir Smicer",
                    "selected": ""
                },
                {
                    "text": "Ian Rush",
                    "selected": ""
                }               
            ]           
        },
        {
            "text": "Which club did Michael Owen leave Liverpool for?",
            "answer": 0,
            "explanation": "Owen has played for all the other clubs, but he left Liverpool for Real Madrid in 2004.",
            "options":
            [
                {
                    "text": "Real Madrid",
                    "selected": ""
                },
                {
                    "text": "Newcastle",
                    "selected": ""
                },
                {
                    "text": "Manchester United",
                    "selected": ""
                },
                {
                    "text": "Stoke City",
                    "selected": ""
                }               
            ]           
        },
        {
            "text": "What was Robbie Fowler's nickname?",
            "answer": 3,
            "explanation": "Due to his natural killer instinct in front of goal, Fowler remains a Liverpool legend, and Anfield's God.",
            "options":
            [
                {
                    "text": "Robbs",
                    "selected": ""
                },
                {
                    "text": "Thunderstorm",
                    "selected": ""
                },
                {
                    "text": "Rocket",
                    "selected": ""
                },
                {
                    "text": "God",
                    "selected": ""
                }              
            ]          
        },
        {
            "text": "What position did John Arne Riise primarily play in?",
            "answer": 1,
            "explanation": "Riise was generally deployed as Left-back in this time in Liverpool.",
            "options":
            [
                {
                    "text": "Goal",
                    "selected": ""
                },
                {
                    "text": "Defence",
                    "selected": ""
                },
                {
                    "text": "Midfield",
                    "selected": ""
                },
                {
                    "text": "Attack",
                    "selected": ""
                }               
            ]           
        },
        {
            "text": "Luis Suarez reached the 30-goal mark for Liverpool in 2014. Before him, what other player achieved this?",
            "answer": 3,
            "explanation": "Ian Rush was the only other player in the list to achieve this.",
            "options":
            [
                {
                    "text": "Harry Kewell",
                    "selected": ""
                },
                {
                    "text": "Robbie Keane",
                    "selected": ""
                },
                {
                    "text": "Emile Heskey",
                    "selected": ""
                },
                {
                    "text": "Ian Rush",
                    "selected": ""
                }               
            ]           
        },
        {
            "text": "How many times was Kenny Daglish Liverpool Manager?",
            "answer": 0,
            "explanation": "King Kenny was Manager between 1985 and 1991, then again between 2011 and 2012.",
            "options":
            [
                {
                    "text": "2",
                    "selected": ""
                },
                {
                    "text": "3",
                    "selected": ""
                },
                {
                    "text": "1",
                    "selected": ""
                },
                {
                    "text": "5",
                    "selected": ""
                }               
            ]           
        },
        {
            "text": "While Dirk Kuyt was primarily a striker, what other position was he often played in?",
            "answer": 3,
            "explanation": "There were several occasions where Kuyt was deployed as Right Winger.",
            "options":
            [
                {
                    "text": "Goal",
                    "selected": ""
                },
                {
                    "text": "Central Defence",
                    "selected": ""
                },
                {
                    "text": "Left Midfield",
                    "selected": ""
                },
                {
                    "text": "Right Midfield",
                    "selected": ""
                }               
            ]           
        },
        {
            "text": "What did Philippe Coutinho and Michael Owen have in common?",
            "answer": 2,
            "explanation": "While it's certainly possible that they both loved ice-cream, both Coutinho and Owen were well-known for using the jersey number 10.",
            "options":
            [
                {
                    "text": "Love for ice-cream",
                    "selected": ""
                },
                {
                    "text": "Country of Birth",
                    "selected": ""
                },
                {
                    "text": "Jersey Numbers",
                    "selected": ""
                },
                {
                    "text": "Goal tallies",
                    "selected": ""
                }               
            ]           
        }
    ];

Back to the HTML...


Remember the div styled using pnlQuestions? There's another div within styled using content. Encase that in another div.

index.html
            <div class="pnlQuestions">
                <div>
                    <div class="content">

                    </div>
                </div>

                <div class="buttons">

                </div>
            </div>

And then apply some AngularJS magic. Iterate through the questions array using the ng-repeat attribute.

index.html
            <div class="pnlQuestions">
                <div ng-repeat="question in questions">
                    <div class="content">

                    </div>
                </div>

                <div class="buttons">

                </div>
            </div>

Insert yet another div. Style it using the CSS class question.

index.html
                <div ng-repeat="question in questions">
                    <div class="content">
                        <div class="question">

                        </div>
                    </div>
                </div>

Insert two more divs within. One will by styled using questionText and the other questionProgress. In the first div, use the template string for the current element's text property. The second div we'll leave for much later.

index.html
                <div ng-repeat="question in questions">
                    <div class="content">
                        <div class="question">
                            <div class="questionText">
                                {{question.text}}
                            </div>

                            <div class="questionProgress">
                               
                            </div>   
                        </div>
                    </div>
                </div>

Isn't this just awesome? All your questions are on display!

Of course, we'll need to display the option as well, So do this. Add a nice breaking line, then another div, styled using the CSS class answers. And another breaking line.

index.html
                <div ng-repeat="question in questions">
                    <div class="content">
                        <div class="question">
                            <div class="questionText">
                                {{question.text}}
                            </div>

                            <div class="questionProgress">
                               
                            </div>   
                        </div>

                        <br style="clear:both">

                        <div class="answers">

                        </div>

                        <br style="clear:both">
                    </div>
                </div>

Insert another div and style it using the CSS class option. Use the ng-repeat attribute to iterate through the options array of the current element in questions.

index.html
                <div ng-repeat="question in questions">
                    <div class="content">
                        <div class="question">
                            <div class="questionText">
                                {{question.text}}
                            </div>

                            <div class="questionProgress">
                               
                            </div>   
                        </div>

                        <br style="clear:both">

                        <div class="answers">
                            <div class="option" ng-repeat="option in question.options">

                            </div>
                        </div>

                        <br style="clear:both">
                    </div>
                </div>

Insert two more divs in that div. Style the first using the CSS class selector. Leave that alone for now; we'll get to it later on. Then style the second div using the CSS class text.

index.html
                <div ng-repeat="question in questions">
                    <div class="content">
                        <div class="question">
                            <div class="questionText">
                                {{question.text}}
                            </div>

                            <div class="questionProgress">
                               
                            </div>   
                        </div>

                        <br style="clear:both">

                        <div class="answers">
                            <div class="option" ng-repeat="option in question.options">
                                <div class="selector">

                                </div>

                                <div class="text">

                                </div>
                            </div>
                        </div>

                        <br style="clear:both">
                    </div>
                </div>

In that div, add two more divs. The first one is styled using optionText and contains the template string for the text. The second is styled using optionExplanation and contains the template string for the explanation.

index.html
                <div ng-repeat="question in questions">
                    <div class="content">
                        <div class="question">
                            <div class="questionText">
                                {{question.text}}
                            </div>

                            <div class="questionProgress">
                               
                            </div>   
                        </div>

                        <br style="clear:both">

                        <div class="answers">
                            <div class="option" ng-repeat="option in question.options">
                                <div class="selector">

                                </div>

                                <div class="text">
                                    <div class="optionText">{{option.text}}</div>
                                    <div class="optionExplanation">{{question.explanation}}</div>
                                </div>
                            </div>
                        </div>

                        <br style="clear:both">
                    </div>
                </div>

Now each question has a series of possible answers displayed!

Now add two buttons in the div styled using the CSS class buttons. One will have an id of btnNext but no value (we'll fill that in programatically) and the other is btnQuit. Fill in the value as "Quit".

index.html
            <div class="pnlQuestions">
                <div ng-repeat="question in questions">
                    <div class="content">
                        <div class="question">
                            <div class="questionText">
                                {{question.text}}
                            </div>

                            <div class="questionProgress">
                               
                            </div>   
                        </div>

                        <br style="clear:both">

                        <div class="answers">
                            <div class="option" ng-repeat="option in question.options">
                                <div class="selector">

                                </div>

                                <div class="text">
                                    <div class="optionText">{{option.text}}</div>
                                    <div class="optionExplanation">{{question.explanation}}</div>
                                </div>
                            </div>
                        </div>

                        <br style="clear:both">
                    </div>
                </div>

                <div class="buttons">
                    <input type="button" id="btnNext" class="btn" value="">
                    <input type="button" id="btnQuit" class="btn" value="Quit">
                </div>
            </div>

So at the end of the question section, here are your buttons.

Now for the last panel. Fill in the template strings as shown, and add a button.

index.html
            <div class="pnlResults">
                <div class="content">
                    <h1>{{secondsRemainingMessage}}</h1>
                    <p>Your score is <b>{{result}}</b> out of <b>{{possible}}</b>.</p>
                    <p>{{resultsMessage}}</p>
                </div>

                <div class="buttons">
                    <input type="button" id="btnRestart" class="btn" value="Restart">
                </div>
            </div>

Yes, that's right... your score is 0 out of 10. So far, anyway.

What we just did

We used some nesting iteration to loop through the list of questions and their associated multiple choice answers. And we did it right in the HTML. That's one of the many features that AngularJS (and many other front-end frameworks) provides for you.

Next

It all looks like a real mess right now. We'll clean it up a bit by writing the styles which we specified throughout this part, and make this page interactive.

No comments:

Post a Comment