This is the second part of the series on developing a multiple choice quiz plugin for WordPress. In the first part we created the backend of our plugin to capture the necessary data to store in the database.
In this final part, we will be creating the frontend of the plugin where the users can take quizzes and evaluate their knowledge.
The following topics will be covered in this part of completing our plugin:
- Create a Shortcode to Display a Quiz
- Integrating a jQuery Slider to Display Questions and Navigation
- Completing a Quiz and Generating Results
- Creating a Quiz Timer
We will need a considerable amount of questions in the backend to generate random quizzes. I hope that you already worked with the backend and stored enough questions for quizzes.
So let's get started.
Create a Shortcode to Display a Quiz
First we should have a post or page which loads the display elements of the quiz. This can be achieved by using either a shortcode or page template. In this plugin, a shortcode will be used to make it independent from the theme.
Initially, the shortcode should output the available quiz categories so that the users can select a category to generate the quiz. A shortcode will be added in the constructor using the add_shortcode
function as given in the following code.
add_shortcode( 'wpq_show_quiz', array( $this, 'wpq_show_quiz' ) );
Now, let's look at the implementation for the shortcode by retrieving available quiz categories from the database.
function wpq_show_quiz( $atts ) { global $post; $html = '<div id="quiz_panel"><form action="" method="POST">'; $html .= '<div class="toolbar">'; $html .= '<div class="toolbar_item"><select name="quiz_category" id="quiz_category">'; // Retrive the quiz categories from database $quiz_categories = get_terms( 'quiz_categories', 'hide_empty=1' ); foreach ( $quiz_categories as $quiz_category ) { $html .= '<option value="' . $quiz_category->term_id . '">' . $quiz_category->name . '</option>'; } $html .= '</select></div>'; $html .= '<input type="hidden" value="select_quiz_cat" name="wpq_action" />'; $html .= '<div class="toolbar_item"><input type="submit" value="Select Quiz Category" /></div>'; $html .= '</form>'; $html .= '<div class="complete toolbar_item" ><input type="button" id="completeQuiz" value="Get Results" /></div>'; // Implementation of Form Submission // Displaying the Quiz as unorderd list return $html; }
Our shortcode will generate the HTML form and necessary controls used for the quiz. We retrieve the list of available quiz categories into a drop down field to let the user select the prefered category. We can use the get_terms
function with hide_empty=1
to get the quiz categories which have at least one question.
A hidden field called wpq_action
is used to check the $_POST
values after submission.
After you insert the shortcode into a page or post, output will look like the following screen.
Now the user can select a quiz category and submit the form in order to get the quiz. So we are going to handle the form submission inside a shorcode to get the selected category and retrieve random questions for quizzes.
The following code contains the implementation of retrieving questions from a selected category.
$questions_str = ""; if ( isset( $_POST['wpq_action'] ) && 'select_quiz_cat' == $_POST['wpq_action'] ) { $html .= '<div id="timer" style="display: block;"></div>'; $html .= '<div style="clear: both;"></div></div>'; $quiz_category_id = $_POST['quiz_category']; $quiz_num = get_option( 'wpq_num_questions' ); $args = array( 'post_type' => 'wptuts_quiz', 'tax_query' => array( array( 'taxonomy' => 'quiz_categories', 'field' => 'id', 'terms' => $quiz_category_id ) ), 'orderby' => 'rand', 'post_status' => 'publish', 'posts_per_page' => $quiz_num ); $query = new WP_Query( $args ); $quiz_index = 1; while ( $query->have_posts() ) : $query->the_post(); // Generating the HTML for Questions endwhile; wp_reset_query(); // Embedding Slider } else { $html .= '<div id="timer" style="display: none;"></div>'; $html .= '<div style="clear: both;"></div></div>'; }
The code given should be included into the Implementation of Form Submission section of the previous code.
Once the form is submitted, we check whether the form contains the required action using the hidden field we generated earlier. Then we get the selected quiz category from the $_POST
array.
Then we query the database for wptuts_quiz
posts with the selected quiz category.
It's important to set orderby
as rand
to generate random questions for quizzes, which otherwise will generate the same set of questions each time. Also, make sure to set posts_per_page
to set the maximum number of questions per any given quiz.
Once the results are generated we need to add the questions into the necessary HTML elements and we will be implementing it in the next section.
Integrating a jQuery Slider to Display Questions and Navigation
Quizzes can be generated as a screen which contains all the questions at once, or screen which contains one question at a time with navigation controls. I believe that the latter technique is the favorite among most people. Therefore, we are going to display this quiz with a single question and navigation for traversing to previous and next questions.
Generating this functionality from scratch can be a time consuming task as well as reinventing the wheel. A jQuery slider will be the perfect solution for this situation and I'll be using RhinoSlider, as it's my favorite, so grab a copy.
Inside the downloaded folder, you will see three folders called img, js, and css. Copy the img and css folders into our plugin and copy the files inside the js folder to the existing js folder we have on our quiz plugin. Now we can get started on including the necessary scripts and styles for the slider.
Including Frontend Scripts
In the first part, we created the necessary scripts for the backend. In this part we are going to include the necessary scripts for RhinoSlider as well as quiz.js for custom functionality.
Consider the following code for including scripts and necessary configuration data.
function wpq_frontend_scripts() { wp_register_script( 'rhino', plugins_url( 'js/rhinoslider-1.05.min.js', __FILE__ ), array( 'jquery' ) ); wp_register_script( 'rhino-mousewheel', plugins_url( 'js/mousewheel.js', __FILE__ ), array( 'jquery' ) ); wp_register_script( 'rhino-easing', plugins_url( 'js/easing.js', __FILE__ ), array( 'jquery' ) ); wp_register_script( 'quiz', plugins_url( 'js/quiz.js', __FILE__ ), array( 'jquery', 'rhino', 'rhino-mousewheel', 'rhino-easing' ) ); wp_enqueue_script( 'quiz' ); $quiz_duration = get_option( 'wpq_duration' ); $quiz_duration = ( ! empty( $quiz_duration ) ) ? $quiz_duration : 300; $config_array = array( 'ajaxURL' => admin_url( 'admin-ajax.php' ), 'quizNonce' => wp_create_nonce( 'quiz-nonce' ), 'quizDuration' => $quiz_duration, 'plugin_url' => $this->plugin_url ); wp_localize_script( 'quiz', 'quiz', $config_array ); }
Here we have three JavaScript files used for the RhinoSlider and the quiz.js file for custom functionality. In the previous part, we configured the duration of the quiz. We can retrieve the duration using the get_option
function and assign it to the $config
array. Also, we have to include common configurations in the config array.
Finally we can use the wp_localize_script
function to assign the config data in the quiz.js file.
Including Frontend Styles
Similarly we can include the CSS files required for Rhino Slider using the following code.
function wpq_frontend_styles() { wp_register_style( 'rhino-base', plugins_url( 'css/rhinoslider-1.05.css', __FILE__ ) ); wp_enqueue_style( 'rhino-base' ); }
Finally we need to update the plugin constructor to add the necessary actions for including scripts and styles as given in the following code.
add_action( 'wp_enqueue_scripts', array( $this, 'wpq_frontend_scripts' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'wpq_frontend_styles' ) );
Everything is ready to integrate the slider with questions into the shortcode we created earlier. Let's move forward.
Embedding the Slider Into the Shortcode
We currently have two comments inside the shortcode function, mentioning "Generating HTML for Questions" and "Embedding Slider". Those sections need to be updated with respective code to generate a slider. First we need to update the while
loop as follows.
while ( $query->have_posts() ) : $query->the_post(); $question_id = get_the_ID(); $question = the_title( '', '', FALSE ) . ' ' . get_the_content(); $question_answers = json_decode( get_post_meta( $question_id, '_question_answers', true ) ); $questions_str .= '<li>'; $questions_str .= '<div class="ques_title"><span class="quiz_num">' . $quiz_index . '</span>' . $question . '</div>'; $questions_str .= '<div class="ques_answers" data-quiz-id="' . $question_id . '">'; $quiestion_index = 1; foreach ( $question_answers as $key => $value ) { if ( '' != $value ) { $questions_str .= $quiestion_index . ' <input type="radio" value="' . $quiestion_index . '" name="ans_' . $question_id . '[]" />' . $value . '<br/>'; } $quiestion_index++; } $questions_str .= '</div></li>'; $quiz_index++; endwhile;
Code Explanation
- Inside the loop, first we get the question by concatenating the title and content field for questions.
- Then we retrieve the answers of each question by using the
get_post_meta
function. - Inside the
foreach
loop, all the answers will be assigned to the radio buttons with necessary values. - Necessary list items will be assigned inside the loop including the HTML data attributes, which will come in handy in the next section.
- Final output of the while loop will be a string variable which contains the list of questions and their answers embedded in HTML.
Next we need to create the containers for the slider in the section commented as "Embedding Slider". The following code contains the HTML code for creating containers.
$html .= '<ul id="slider">' . $questions_str; $html .= '<li id="quiz_result_page"><div class="ques_title">Quiz Results <span id="score"></span></div>'; $html .= '<div id="quiz_result"></div>'; $html .= '</li></ul></div>';
We will be using an unordered list called slider
as the container for Rhino Slider. Initially we include the set of questions and answers generated inside the loop, using $questions_str
. It will contain a collection of list items.
Then we have to manually create another list item to show the results of the quiz and the score.
Now all the data and slides required for the quiz application are configured. We can initialize Rhino Slider in quiz.js to see the quiz in action.
jQuery(document).ready(function($) { $('#slider').rhinoslider( { controlsMousewheel: false, controlsPlayPause: false, showBullets: 'always', showControls: 'always' } ); } );
I have used some custom CSS styles to improve the look and feel. You can find all the modified CSS under the wp_quiz
section of the rhinoslider-1.05.css file. Now you should have something like the following image.
Completing the Quiz and Generating Results
Once the quiz is loaded, you can use the navigation controls to move between questions and select the answers. You should click the "Get Results" button once all questions are answered. Now we need to create the quiz results using an AJAX request.
Let's implement the jQuery code for making the AJAX request.
$("#completeQuiz").click(function() { wpq_quiz_results(); }); var wpq_quiz_results = function() { var selected_answers = {}; $(".ques_answers").each(function() { var question_id = $(this).attr("data-quiz-id"); var selected_answer = $(this).find('input[type=radio]:checked'); if ( selected_answer.length != 0 ) { var selected_answer = $(selected_answer).val(); selected_answers["qid_"+question_id] = selected_answer; } else { selected_answers["qid_"+question_id] = ''; } } ); // AJAX Request };
Once the "Get Results" button is clicked, we call the wpq_quiz_results
function using jQuery. Each question was added to the slider with a special CSS class called ques_answers
.
While traversing through each element with the ques_answers
class, we retrieve the question ID using the HTML data-attribute called data-quiz-id
and the selected radio button using jQuery.
Finally we assign all the questions and the selected answers into an array called selected_answers
, to be passed into the AJAX request.
Take a look at the following code for implementation of the AJAX request.
$.post(quiz.ajaxURL, { action: "get_quiz_results", nonce: quiz.quizNonce, data: selected_answers, }, function(data) { // AJAX result handling code }, "json" );
First we create the AJAX request using the configuration data assigned from the wpq_frontend_scripts
function. The answers list generated in the previous section will be sent as the data parameter. Before handling the result, we have to look at the implementation of server side code in the following section.
Creating an AJAX Handler on the Server Side
First we have to update the constructor with the actions necessary for using AJAX for both logged in users and normal users as shown in the following code.
add_action( 'wp_ajax_nopriv_get_quiz_results', array( $this, 'get_quiz_results' ) ); add_action( 'wp_ajax_get_quiz_results', array( $this, 'get_quiz_results' ) );
Then we can move into the implementation of the get_quiz_results
function as shown in the following code.
function get_quiz_results() { $score = 0; $question_answers = $_POST["data"]; $question_results = array(); foreach ( $question_answers as $ques_id => $answer ) { $question_id = trim( str_replace( 'qid_', '', $ques_id ) ) . ','; $correct_answer = get_post_meta( $question_id, '_question_correct_answer', true ); if ( $answer == $correct_answer ) { $score++; $question_results["$question_id"] = array( "answer" => $answer, "correct_answer" => $correct_answer, "mark" => "correct" ); } else { $question_results["$question_id"] = array( "answer" => $answer, "correct_answer" => $correct_answer, "mark" => "incorrect" ); } } $total_questions = count( $question_answers ); $quiz_result_data = array( "total_questions" => $total_questions, "score" => $score, "result" => $question_results ); echo json_encode( $quiz_result_data ); exit; }
Code Explanation
- Selected answers for all the questions will be received using the data parameter inside the
$_POST
array. - Then we retrieve the question ID of each question by replacing the
qid_
prefix. - Next we receive the correct answer of each question from the database using the
get_post_meta
function. - Next we check whether the provided answer matches the correct answer and create
$question_results
based on the status of the result. - While checking the answers, we need to update the quiz score using the
$score
variable. - Finally we assign the quiz results and scores to the
$quiz_result_data
array to be sent as the response.
Up to now, we created the AJAX request and implemented the server side response. In the next section we are going to complete the quiz results generation process by handling the AJAX response.
Handling AJAX Response Data
In the response handling part, we have quite a few tasks including the display of quiz results and scores. So I am going to separate the code into a few sections to make the explanation clear. Consider the following code which contains the complete AJAX request.
$.post( quiz.ajaxURL, { action: 'get_quiz_results', nonce: quiz.quizNonce, data: selected_answers }, function(data) { // Section 1 var total_questions = data.total_questions; $('#slider').data('rhinoslider').next($('#rhino-item' + total_questions)); $('#score').html( data.score + '/' + total_questions); // Section 2 var result_html = '<table>'; result_html += '<tr><td>Question</td><td>Answer</td><td>Correct Answer</td><td>Result</td></tr>'; var quiz_index = 1; $.each(data.result, function( key, ques ) { result_html += '<tr><td>' + quiz_index + '</td><td>' + ques.answer + '</td><td>' + ques.correct_answer + '</td>'; result_html += '<td><img src="' + quiz.plugin_url + 'img/' + ques.mark + '.png" /></td></tr>'; quiz_index++; }); result_html += '<tr><td> </td><td></td><td></td>'; result_html += '<td></td></tr>'; // Section 3 $('#quiz_result').parent().css('overflow-y','scroll'); $('#quiz_result').html(result_html); $('#timer').hide(); }, 'json' );
Explanation of Section 1
First, we retrieve the total questions of the quiz from the response received from the server. Then we use the next
function of Rhino Slider to redirect the user to the results slide. Then we set the user score with the total questions inside the #score
container.
Explanation of Section 2
The initial part of this code generates the HTML table with necessary headings to display the results. Then we assign the questions to the table inside the jQuery each
loop. We have used two images to display the success or failure status of the question.
Explanation of Section 3
Initially we have to allow scrolling in the results page as it can be quite long for quizzes with a large number of questions. The CSS overflow-y
attribute is used to allow scrolling. Finally we set the quiz results table into the #quiz_result
container and hide the timer, which we will be implementing in the next section.
Once the quiz is completed, your screen should look like something similar to the following image.
Creating a Quiz Timer
Usually any exam or quiz has a predefined time frame. So we are going to use the duration we configured in the settings page of our plugin to generate the quiz timer. We have already configured the timer to be hidden on initial page load, and to be visible on form submission, in the shortcode.
Let's focus on the dynamically changing timer using jQuery code as shown in the following.
var duration = quiz.quizDuration * 60; $(document).ready(function($) { setTimeout("startPuzzleCount()",1000); }); var startPuzzleCount = function() { duration--; $('#timer').html(duration+" Seconds Remaining"); if ( duration == '0' ) { $('#timer').html("Time Up"); wpq_quiz_results(); return; } setTimeout("startPuzzleCount()",1000); };
Quiz duration is retrieved using the configuration array passed using the wp_localize_script
function. Duration is then converted into seconds by multiplying by 60
.
Then we create a setTimeout
function to start the timer. Inside the function, we reduce the duration by 1
and assign to the #timer
container. When the time gets down to zero, we call the wpq_quiz_results
function to automatically complete the quiz and generate the results.
Finally, we call the setTimeout
function recursively to update the remaining time. We have completed the implementation of the timer and your quiz should look like the following image with the timer.
Wrap Up
Throughout this two-part series, we developed a simple and complete multiple choice quiz plugin for WordPress. You can extend the functionality of this plugin to suit the requirements of your application. I suggest you could improve the plugin by trying out the following:
- Create a way to assign questions to quizzes instead of generating random quizzes.
- Save the quiz results for logged in users.
- Create a quiz competition between users.
Let me know your suggestions and how it goes with the plugin extending process.
Looking forward to hearing from you.
Comments