Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

A Simple AJAX Quiz Using Atlas

4.08/5 (11 votes)
1 Aug 2006CPOL3 min read 1   1.4K  
A simple way to create a quiz using Atlas controls and Web Services.

Sample Image - SimpleAjaxQuizUsingAtlas.png

Introduction

As my first article on Code Project, I'd like to talk about a little proof of concept I created around AJAX/Atlas technologies. When I saw this screencast, I thought it could be easily used for a simple AJAX quiz system, so here I am :) The Code is really simple and there's no error management, but maybe later, I'll update this code to create a more complete solution.

Prerequisites

To be able to use this code, you'll have to do the following:

  • Install Atlas.
  • Create an SQL database called AjaxQuiz and execute the SQL script which comes with the source code.
  • Add a reference to the Atlas DLL (usually located in C:\Program Files\Microsoft ASP.NET\Atlas\v2.0.50727\Atlas).
  • Modify the AjaxQuiz connection string in web.config.

SQL code

The AjaxQuiz database contains three tables: t_Questions, t_Answers, and t_UserAnswers. There's also a single Stored Procedure called by our Web Method to process the data:

SQL
CREATE PROCEDURE dbo.ProcessNextQuestion 
    (
    @intQuestionID int = 0,
    @intAnswerID int = 0,
    @intUserID int = 0
    )
AS
    IF @intQuestionID > 0 AND @intAnswerID > 0 AND @intUserID > 0
    BEGIN
        INSERT INTO t_UserAnswers(UserID, AnswerID, QuestionID)
        VALUES(@intUserID, @intAnswerID, @intQuestionID)
    END

    SELECT TOP 1 QuestionID, QuestionText
    FROM t_Questions
    WHERE QuestionID > @intQuestionID

As you can see, if the SQL input parameter isn't null, the Stored Procedure inserts results of the previous question in the database. Then, it returns the text and ID for the next question. As the QuestionID is auto-incremented, the next question is always returned, but it supposes that IDs correspond to the desired question order.

Markup code

There's only one web page in this small project: Default.aspx. First, I have to describe the ScriptManager element:

HTML
<atlas:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="true">
 <Scripts>
  <atlas:ScriptReference Path="AjaxQuiz.js" />
 </Scripts>
 <Services>
  <atlas:ServiceReference Path="QuestionService.asmx" />
 </Services>
</atlas:ScriptManager>

That's where Atlas is really nice: we just need to reference our JS client script and our Web Service, and Atlas will do the rest (the client code to consume the WS, etc.) In our JS code, we will also be able to create instances of classes defined in the server code. Atlas will do the correspondence for us.

HTML
<div id="StartForm">
  <input id="btnStart" type="button" value="Start the Ajax Quiz !" onclick="Callback()" />
</div>
<div id="QuizForm" style="display: none;">
<div id="QuestionText"></div><br />
 <input type="radio" id="YesAnswer" name="Answer" checked="checked" /> Yes
 <input type="radio" id="NoAnswer" name="Answer" /> No
 <input type="radio" id="DontKnowAnswer" name="Answer" /> ?<br /><br />
 <input id="btnCallBack" type="button" value="Next" onclick="Callback()" /> 
 <img id="imgUpdate" src="Images/spinner.gif" alt="Updating data" style="display: none;" />
 <input id="QuestionID" type="hidden" value="0" />
</div>
<div id="EndForm" style="display: none;">
Thank you, this quiz is now finished ! 
</div>

Then, we add some HTML controls to create our form. First of all, there are three DIVs: one that shows on startup, another for quiz questions, and a last one displayed when the quiz is finished. The main section, QuizForm, contains three radio buttons for answers, a button to call our Web Service, and an image shown during AJAX calls.

Web Service

The Web Service contains only one WebMethod, StoreAnswer:

C#
/// <summary>
/// That's the only Web Method used. It both stores the answer to the current question 
/// and sends data for the next one.
/// </summary>
/// <param name="previousQuestion">A Question object containing user answer.</param>
/// <returns>Returns a Question object containing data 
///          for the next question (question ID, question Text)</returns>
[WebMethod]
public Question StoreAnswer(Question previousQuestion)
{
    // We initialize a Question object to null. It will be our return value.
    Question nextQuestion = null;
    if (previousQuestion == null)
    {
        // If no previous question is submitted, we create a new one with default values.
        previousQuestion = new Question(0, "", 0);
    }
    // SQL connection initialization (connection string is in web.config file)
    using (SqlConnection cn = new SqlConnection(
      ConfigurationManager.ConnectionStrings["AjaxQuizConnectionString"].ConnectionString))
    {
        try
        {
            // Then we call our stored procedure
            SqlCommand cmd = new SqlCommand("dbo.ProcessNextQuestion", cn);
            cmd.CommandType = CommandType.StoredProcedure;
            // First parameter for question ID.
            SqlParameter parm = new SqlParameter("@intQuestionID", SqlDbType.Int);
            parm.Value = previousQuestion.QuestionID;
            parm.Direction = ParameterDirection.Input;
            cmd.Parameters.Add(parm);
            // Second parameter for answer ID.
            SqlParameter parm2 = new SqlParameter("@intAnswerID", SqlDbType.Int);
            parm2.Value = previousQuestion.AnswerID;
            parm2.Direction = ParameterDirection.Input;
            cmd.Parameters.Add(parm2);
            // Third parameter for user ID.
            SqlParameter parm3 = new SqlParameter("@intUserID", SqlDbType.Int);
            parm3.Value = userID;
            parm3.Direction = ParameterDirection.Input;
            cmd.Parameters.Add(parm3);
            // Opening sql connection
            cn.Open();
            using (SqlDataReader rd = 
                     cmd.ExecuteReader(CommandBehavior.CloseConnection))
            {
                while (rd.Read())
                {
                    // We read data returned by our SP. It only returns one row.
                    nextQuestion = new Question(rd.GetInt32(0), rd.GetString(1));
                }
            }
            // We make the web service sleep for one second,
            // so that we can see the spinner image appear.
            // This line should be removed
            // if you seriously think about using this code ^^
            Thread.Sleep(1000);
        }
        finally
        {
            // Important : we always have to close the sql connection
            cn.Close();
        }
    }
    return nextQuestion;
}

The WebMethod calls a stored procedure, then returns the next question as a Question object. I made the thread sleep for one second, so that I can see the spinner image.

JavaScript code

The JavaScript code might be the hardest part, because we have to reference all our HTML objects and get/set their values. There must certainly be a simpler way to do the job, so don't hesitate to propose something in the comments section. The OnTimeout and OnError functions aren't included but are present in the Zip file.

JavaScript
// Called on 'Next' button click event.
function Callback()
{
    // Gets a reference to the hidden field containing the question ID

    var questionID = document.getElementById('QuestionID');
    // Gets references to the 3 possible answers

    var answer1 = document.getElementById('YesAnswer');
    var answer2 = document.getElementById('NoAnswer');
    var answer3 = document.getElementById('DontKnowAnswer');
    // Initializes a variable to hold the user answer

    var answerID = 0;
    // Gets user answer

    if(answer1.checked) answerID = 1;
    if(answer2.checked) answerID = 2;
    if(answer3.checked) answerID = 3;
    // Creates a new Question object. Atlas makes
    // the translation for us : JS and ASP.NET

    // know exactly the same class !

    var object = new Question();
    object.QuestionID = questionID.value;
    object.AnswerID = answerID;
    // Displays a image during the AJAX call

    DisplayUpdateImage(true);
    // Ajax call. QuestionService is the Web Service
    // we registered in Atlas ScriptManager
    // and as you can see, we can directly
    // call our WebMethod. Isn't it nice ? :)
    // We also add 3 events : one when process is completed,
    // another when a timeout occurs, 
    // and a last one if an error occurs.

    QuestionService.StoreAnswer(object, OnComplete, OnTimeout, OnError);
}

// Called when Ajax request is done
function OnComplete(response)
{
    // 3 references to our 3 DIVs

    var StartForm = document.getElementById('StartForm');
    var QuizForm = document.getElementById('QuizForm');
    var EndForm = document.getElementById('EndForm');

    // A reference to the hidden field used to hold the question ID

    var questionID = document.getElementById('QuestionID');
    // A reference to the DIV which will contain the next question text

    var questionText = document.getElementById('QuestionText');

    // If there is a next question

    if(response != null)
    {
        StartForm.style.display = 'none';
        EndForm.style.display = 'none';
        QuizForm.style.display = 'block';
        questionID.value = response.QuestionID;
        questionText.innerHTML = response.QuestionText;
    }
    // If there's no more questions, we display the EndForm div.
    else
    {
        EndForm.style.display = 'block';
        QuizForm.style.display = 'none';
    }
    // We hide the updating image

    DisplayUpdateImage(false);
}

Conclusion

And that's all! When the user clicks on the Start button, he sees the first question appear. No result is inserted in the database, because the input Question object is null. Then, when he clicks on the Next button, results are stored in the database and our stored procedure sends the next question ... and so on until the Stored Procedure doesn't return anything. In that case, finally, the user sees the ending message.

OK, I know this is a quite simple article, but I think it can illustrate what you can do with Atlas and how easy it is. I hope my English isn't too bad too :)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)