Assignment: Quizzes

We regularly take multiple-choice quizzes using Sakai. In this assignment, you will write an application that formats quiz questions into a bunch of radio button sets and allows the person to answer the questions and "grade" their answers. We won't keep track of the score, but if we wanted to, we could use kind of code we did with Rock-Paper-Scissors.

Goal

In this assignment, you will use grid systems, you will dynamically create quiz questions from a list of information, and you will add an event handler to which you will delegate all the grading.

Dependencies

This assignment is based only on the material up to and including Dynamic DOM, in which we learned to dynamically add a checkbox to the page. It also relies on Bootstrap and Grids. It requires you to use the class syntax for OOP, like we did with bank accounts, but not the OOP with inheritance. It requires event delegation.

You'll have to create radio buttons, so look back at the lessons on that: radio buttons and class review

It also uses the grid HTML/CSS code that we learned in class: grids and the class review

I'm going to allow you to use either the new grid CSS or the Bootstrap grid system. There's a small difference in the HTML and CSS, and either solution is fine. The description below supposes the new grid CSS.

  • The JavaScript for this assignment is almost entirely based on the "Dynamic DOM" reading: dynamic DOM.
  • In particular, you should look at the addRow method from the Checklist Class section, and
  • and the makeRowElement function from the Helper Functions section.

Your code will not be precisely either of those; we've organized things a little differently. But those two functions explain a lot of the coding you will need to write.

When you get to the "grading" portion of the assignment, you'll have to use Event Delegation.

The only thing that isn't in that reading is the two-column grid-based layout, which is from the reading on Bootstrap

Reference Solution

Because the HTML and CSS is an important part of this assignment (though not the hardest part), I'm going to omit the obfuscated solution (since I don't know how to obfuscate HTML and CSS). Here's a

video of the solution

(That video is somewhat out of date, since I've added the media query feature. I'll demo the new solution in class.)

Operation

The app will work like this:

When the page loads, a global variable will be defined that contains a list of multiple-choice questions in JSON format. Here are some example questions based on the movies. You're welcome to use those, or write your own.

Each question looks like this:

var questions = [ 
    {Q: "Who starred in Casablanca?",
        A: "Charleton Heston",
        B: "Matt Damon",
        C: "Harrison Ford",
        D: "Humphrey Bogart",
        ANS: "D"
    },
...
];

Each question is represented by a single JS object literal (dictionary) with the following keys: there's the question (Q), four answers (A-D) and the answer (ANS).

Your code will iterate (use .forEach()) over that list of questions and format them onto the page. The page will have two sections of questions, using the grid system; either laid out horizontally (for wider screens) or vertically (for narrower screens). If the page has a vertical layout, the first half of the questions goes above the second half (obviously) and if the page has a horizontal layout, the first half of the questions go in the first column (to the left) and the second half of the questions in the second column (to the right).

Don't think that you'll be dynamically creating grid elements. That's probably possible, but I didn't. Instead, I just had two pre-specified (static) grid elements, and I dynamically added questions to those existing grid elements.

Each question will be nicely formatted as a paragraph and a group of four radio buttons for that question. There will also be a button to grade each question. Here's a screenshot:

formatted page of questions
A nicely formatted page of questions

Once the page is loaded, the user can choose radio buttons and click on the "grade" button. If their choice is correct, the question is marked correct by adding a "correct" CSS class (that is, a CSS class named "correct") and inserting an appropriate message. Similarly, if it's incorrect, it's marked by adding a "wrong" class and an appropriate message. My classes changed the border and background color, like this:

questions marked right or wrong using color
A nicely formatted page of answered questions, marked right or wrong using color.

High-Level Overview

Because I want you to write the grid CSS, you should copy the template file for your HTML, though you an adapt the HTML to use Bootstrap. Also copy the movie-questions.js, though of course you can create your own questions. (In fact, that's encouraged! Quiz me while I'm grading.)

cd ~/public_html/cs204-assignments
mkdir quizzes
cp ~cs204/pub/readings/template-jq.html quizzes/quiz.html
cp ~cs204/pub/assignments/quizzes/movie-questions.js quizzes/

My code has an HTML file with two empty containers for questions (the left column and the right column). My HTML file loads five JS files:

  1. jQuery; the template file already has this
  2. my bounds plugin; the template file has this, too
  3. the movie-questions.js that I gave you above. You'll copy this file and then write the script tag to load it.
  4. Question.js which defines the Question class. (Note the capital Q in the filename, because this file describes the Question class.) The class will have a constructor and one method named addToDOM. You'll write this file from scratch and also write the script tag to load it
  5. quiz.js which is the main driver. It builds the quiz questions and defines the event handler to do grading. You'll write this file from scratch and also write the script tag to load it.

The five .js files should probably be loaded in that order.

Implementation

We should separate our code into clear modules. But I am not asking for modules or IIFEs in this assignment.

Scaffolding

The following is a step-by-step implementation of a simplified version of the assignment: dynamic list

Question class

Question.js will define a new class of object called Question. It will have:

  • one instance variable to store the DOM element
  • a constructor that builds and stores that DOM element
  • a method that adds that DOM element to the page.

The constructor will take an object literal like the one above (the "who starred in Casablanca" question), describing just one question, and an integer (the question number). The constructor will create the HTML structure for the question, similar to the MakeRowElt function from CoffeeRun. The constructor will not add the structure to the DOM, but instead save it in an instance variable. The HTML structure will be what I've described as "off stage".

The constructor should not reference the questions array at all. In principle, we should be able to create questions from several arrays, or from a form that people fill out, or some other source. The fact that the second argument to the constructor happens to be an index into the questions array is a coincidence. Do not use that array anywhere in the Question.js file.

The Question class will also have an addToDOM method that adds the structure that was saved in an instance variable) to the document at the given destination. The destination is specified by the argument to the addToDOM method.

To add something to the document, we typically use the jQuery append() method:

$(somplace_in_the_document).append(new_thing_to_add);

There are some pictures of these steps later in this document.

The Main File

The quiz.js file puts the pieces together. It does two things:

It maps over the JSON array of questions, creating objects for each question and appending the the DOM element to the document in either the first or second column. The first half of the list should go in the first (left) column, and the second half of the list should go in the second (right) column. Use forEach() and a callback function. Remember that forEach invokes the callback function with (a) the element, (b) its index in the array, and (c) the array. You should use the two-arg version of the callback function.

The quiz.js file must also add an event handler to some appropriate ancestor of all the questions, delegating the click-handling for all the "grade" buttons. That click handler should determine whether the user got the question right, and add the appropriate class (and removing the other class, if any).

It's important that this be a delegated handler. See the reading on delegation

The Question Class Visualization

It might help to visualize what the constructor and the addToDOM method do.

First, the constructor:

the Question constructor

The Question constructor takes some JSON and builds some HTML, off-stage, keeping a pointer to the HTML in an instance variable.

Later, we add the off-stage HTML to the page:

the Question addToDOM method

The addToDOM(where) method takes as an argument an existing part of the page to which it will add the off-stage HTML. Here, it puts the question HTML in the right column.

Question Construction

Constructing the DOM element for a question is a fair amount of code, but not conceptually difficult. Look at the Row constructor for some inspiration.

I strongly suggest that you work backwards from the HTML. That is:

  1. Write out the HTML that your function needs to create. Actually code it up in your HTML file. Test that it works. You can leave it in there, and dynamically add other questions after it. Or you can delete it or comment it out, later.
  2. Identify the elements and their parent-child relationships.
  3. Identify which parts are static and which parts will depend on information from the question (object literal).
  4. Construct the elements and use .append() to establish the parent-child relationships.
  5. Store the branch in an instance variable for later.

Note that each set of radio buttons must have a shared name in order to work as a group. I suggest "Q"+i where i is the index of the question in the array. Therefore, the constructor will have two arguments: the object literal describing the question and the index.

You will have a non-radio button for triggering the grading behavior. It should be a regular button, not a submit button, so something like this:

<button type='button'>grade</button>

You can, of course, add classes and attributes if you'd like.

The Question object must also have a method called addToDOM that takes an argument that is a DOM element to append the question to. This is a slightly different interface than CoffeeRun uses.

Note: even though radio buttons are usually contained within a form element, we will not be sending these to a back-end server, so there's no use in creating a form. Furthermore, there are subtle and weird things that can happen if you do. (I'm happy to explain if you want, but I don't want to clutter this document with that explanation. See me to learn more!)

Testing the Constructor

Because of the way we've separated the role of the constructor and the addToDOM method, testing the constructor is a little difficult. Here's an approach that might help:

I suggest putting all/most of the code into a helper function that you can easily call in the JS console:

function helper(questionDescription, num) {
   ...
   return questionDiv;
}

That helper function will not reference any global variables or store into any global variables. It will be purely functional.

Then you can test it like this in the JS console:

elt = helper( questions[0], 0 );
$("body").append(elt);

and see what the result looks like by using your browser and the inspector.

Once that's all working, your constructor can just use the helper function but instead of appending the return value to the body, it stores the element in an instance variable and the addToDOM method can add it to the page. Note that using the helper function this way will make the constructor very short. The addToDOM method is also very short, so the whole class definition is only a handful of lines of code. But that's okay.

Event Handling

The event handling is not a lot of code, but it's tricky. We learned about this kind of event handling in the Dynamic DOM reading.

Here are some sub-problems to solve:

  • How will the event handler know what the answer to this question is? Remember that the one handler is responsible for grading all the questions. I suggest storing the correct answer in an element, using a data-answer attribute. That will affect the HTML for the Question construction.
  • How will the event handler find the element that has the data-answer? This will undoubtedly require searching the tree, probably upward from the event.target using the jQuery .closest() method. See closest
  • What will the event target be? What click events will be delegated to this handler?
  • How will the handler know what answer the user gave? We saw how to use the .val() method in class. You might consider using .find() as well. See find

Once you've answer those questions, I think you'll find that the code is something like:

var correct = [code to find correct answer]
var given = [code to find answer user gave]
if( correct === given ) {
    [code for marking it correct]
} else {
    [code for marking it wrong]
}

Testing the Event Handler

Testing the event handler is difficult, but you can do it. The main idea is to have something that you can test in the JS console, so I strongly suggest keeping the actual event handler small, and punting most of the work to another function that you can easily test:

$(selector_for_some_static_ancestor).on('click',
                                        'selector_for_dynamic_descendant',
                                        function (event) {
                                            foo(event.target);
                                        });
function foo(some_button) {
    // code to process a button click
}

So, the function foo (clearly, you should name it better that that) is something you can test incrementally in the JS console.

The argument to foo will be one of the GRADE buttons. It will not be any radio button. The foo function will be invoked, via the event handler, when the user clicks on one of the GRADE buttons.

How can you get one of these buttons for testing purposes? Here's a way to get one of them:

var b = $("button")[i]; // where i is 0, 1, 2 or 3

That incantation works because "button" is a CSS selector that will match the four GRADE buttons (you should check that that's true), so the jQuery result set will have 4 elements in it, and then you just index into it to get one of them. So b will be one of the four buttons.

You can then test foo by evaluating foo(b).

Hints

I found I had to use all of the following jQuery methods:

  • .attr() to get an attribute
  • .closest() to search up the tree
  • .find() to search down the tree
  • .val() to get the value of an input
  • .text() to set the text in an element
  • .addClass() to add a CSS class to an element
  • .removeClass() to remove a CSS class to an element if it's there
  • .append() to add a child to a parent element

No dynamic element should have an ID. Some of your static elements might have an ID; that's your choice. I believe you should treat this as a general rule: IDs are only for static things where you can look at the HTML and say, "yeah, that thing is unique. Let me label it with a unique identifier."

Layout

I'd like you to learn a bit of grid systems, so I'd like you to achieve the following effects. First, here's a screenshot of my solution:

quiz layout

  • the container is 80% of the width of the page, centered
  • it has a gray background color
  • it has two columns with a 2em gutter between them
  • each column has a silver background color and a 2px solid green border. (One consequence of this is that you can only see the gray background color in the 2em gutter between columns.)
  • each question has a 2px solid black border, white background color and some reasonable nice margins and padding
  • the possible answers to each question are laid out vertically

Note that clicking on the actor name should choose that button. You'll achieve that with proper use of label

Media Query

Use a media query so that for narrow screens (less than 768px) it's a one-column layout, and for wider screens (greater than or equal to 768px) it's a two-column layout. Or, you can use Bootstrap's media queries.

Modularity

Making the questions and adding the event handler should be done with functions.

Each button of the radio button groups is identical in structure. You should define a function that creates one and invoke it four times. That function should not be global.

Checklist

  • Uses the grid system for layout (not flexbox)
  • dynamically switches from one-column to two-column when the browser is wide enough
  • Page with questions passes WAVE test
  • HTML and CSS are valid
  • Code will scale: the code will work exactly the same whether there are 4 questions or 400 questions
  • Just a single event handler attached to a static (non-generated) ancestor of all the question, rather than one for each question.
  • Functions and methods properly documented
  • Code neatly laid out and indented
  • Authors names in the files
  • "use strict"; at the top of all .js files

How to turn this in

Do all your work in a new folder called quizzes. Do the Gradescope submission.

Tutors/Graders

You can use the password to view the quiz

Time and Work

Finally, when you have completed the assignment, make sure you fill out the Time and Work report. That report is required.