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

Advanced JavaScript Typing Test System

4.67/5 (2 votes)
17 Mar 2009CPOL2 min read 69.3K   3.9K  
Calculate the typing speed and accuracy of your users easily!

Typing Test Systep

Introduction

This is a somewhat advanced JavaScript Typing Test system that will allow you to place a table and a few blocks of code on your website (perhaps on a job application system as we did) and get a detailed statistical information on your user's typing ability, including:

  • Words Per Minute (WPM)
  • Accuracy Percentage Rating
  • Total Words Typed
  • Good Words / Bad Words Typed (Errors)
  • Time Taken to Complete
  • More as needed

To see the system in use, visit http://www.itsllc.us/TypingTest_1.asp; you may also download a lower quality version above, or follow along with the code below.

Background

There isn't much of a background to this code except for the fact that it uses JavaScript as a base to execute -- and yes, this is completely client side scripted. You could do the same thing in a server side scripting language or environment such as ASP; however, it would more than likely be less effective.

Using the code

To implement the code below, simply write (or copy and paste -- which is quicker, yeah) the code below into the <HEAD> section of your document -- or anywhere else as you wish, if you are using an include.

Shown below is the JavaScript code required to make the system work. It's fairly straightforward, and very high on the "commented" side -- so you shouldn't have any issues understanding; but if you do, don't hesitate to leave a comment or contact me.

JavaScript
<SCRIPT LANGUAGE="JavaScript">
//Holds whether or not we have already started the first typing test or now
// True = The test has already started
// False = The test hasn't started yet
var hasStarted = false;

//strToTest is an array object that holds various strings to be used as the base typing test
// - If you update the array, be sure to update the intToTestCnt
//   with the number of ACTIVE testing strings
var intToTestCnt = 4;
var strToTest = new Array("Innovative Technical Solutions, LLC (ITS) is a Native" + 
                " American owned business that was established in Paducah, " + 
                "Kentucky in April 2000. ITS is a certified and Small Disadvantaged " + 
                "Business by the U.S. Small Business Administration. Our headquarters " + 
                "are in Paducah, Kentucky and we have" + 
                " satellite offices located in Tennessee, " + 
                "Ohio, and Illinois. ITS is a leading" + 
                " edge Information Technology firm that " + 
                "is comprised of professionals with a broad" + 
                " range of experience in software " + 
                "development, high-speed imaging/scanning (TIFF, PDF, Text, " + 
                "and OCR capabilities), document management, records management," + 
                " relevance management, information security, environmental " + 
                "management, fire services management, fire protection " + 
                "engineering, and protective force expertise.",
                "The ITS Information Technology (IT) Team are experts " + 
                "in the identification, capture, indexing, microfilming, imaging, " + 
                "disposition, turnover, storage, and retrieval of records, " + 
                "and in the administration of records management databases. " + 
                "The types of records we have extensive experience in managing " + 
                "include waste management, hazardous waste, waste shipment, " + 
                "environmental compliance, environmental" + 
                " monitoring, feasibility studies, " + 
                "environmental work plans, cleanup actions, cemetery records, and " + 
                "various Federal laws such as CERCLA, Paper Reduction, " + 
                "Pollution Prevention, and Clean Water and Air.",
                "Collectively, the professional background of key ITS personnel " + 
                "demonstrates Fortune 100 Company experience that includes, " + 
                "but is not limited to, DOE, the Department of Defense, EPA, the " + 
                "Tennessee Valley Authority (TVA), Lockheed Martin Utility Services, " + 
                "Lockheed Martin Energy Systems, British Nuclear Fuels Limited, various " + 
                "state and local agencies, and USEC." + 
                " We consider the depth and magnitude " + 
                "of this experience as a proposition value " + 
                "to both our current and future customers.",
                "With our years of experience, we completely understand document " + 
                "management and technology. We know the importance of deadlines and " + 
                "we know the importance of production without error. We refuse to " + 
                "over-commit to deadlines that can not be met. Dedication to excellence " + 
                "in providing quality products and services through innovative ideas and " + 
                "processes. Steadfast resolve to a positive working environment that " + 
                "allows for the personal and professional development of all employees, " + 
                "while sustaining project service, and customer satisfaction. " + 
                "Commitment to the highest ethical management practices that " + 
                "recognize client satisfaction as a top priority.")
var strToTestType = "";

var checkStatusInt;

//General functions to allow for left and right trimming / selection of a string
function Left(str, n){
 if (n <= 0)
     return "";
 else if (n > String(str).length)
     return str;
 else
     return String(str).substring(0,n);
}
function Right(str, n){
    if (n <= 0)
       return "";
    else if (n > String(str).length)
       return str;
    else {
       var iLen = String(str).length;
       return String(str).substring(iLen, iLen - n);
    }
}

//beginTest Function/Sub initializes the test and starts
//the timers to determine the WPM and Accuracy
function beginTest()
{
 //We're starting the test, so set the variable to true
 hasStarted = true;
 
 //Generate a date value for the current time as a baseline
 day = new Date();
 
 //Count the number of valid words in the testing baseline string
 cnt = strToTestType.split(" ").length;
 
 //Set the total word count to the number of valid words that need to be typed
 word = cnt;
 
 //Set the exact time of day that the testing has started
 startType = day.getTime();
 
 //Disable the printing button (if used, in this download it's not included)
 document.getElementById("printB").disabled = true;
 
 calcStat();
 
 //Initialize the testing objects by setting the values 
 //of the buttons, what to type, and what is typed
 document.JobOp.start.value = "-- Typing Test Started --";
 document.JobOp.start.disabled = true;
 document.JobOp.given.value = strToTestType;
 document.JobOp.typed.value = "";
 
 //Apply focus to the text box the user will type the test into
 document.JobOp.typed.focus();
 document.JobOp.typed.select();
}

//User to deter from Copy and Paste, also acting as a testing protection system
// Is fired when the user attempts to click or apply focus
// to the text box containing what needs to be typed
function deterCPProtect()
{
 document.JobOp.typed.focus();
}

//The final call to end the test -- used when the 
//user has completed their assignment
// This function/sub is responsible for calculating
// the accuracy, and setting post-test variables
function endTest()
{
 //Clear the timer that tracks the progress of the test, since it's complete
 clearTimeout(checkStatusInt);
 
 //Initialize an object with the current date/time
 //so we can calculate the difference 
 eDay = new Date();
 endType = eDay.getTime();
 totalTime = ((endType - startType) / 1000)
 
 //Calculate the typing speed by taking the number of valid words 
 //typed by the total time taken and multiplying it by one minute in seconds (60)
 //***** 1A ********************************************************** 1A *****
 //We also want to disregard if they used a double-space after 
 //a period, if we didn't then it would throw everything after the space off
 //Since we are using the space as the seperator for words; it's the 
 //difference between "Hey. This is me.
 //" versus "Hey. This is me." and
 //Having the last three words reporting as wrong/errors due 
 //to the double space after the first period, see?
 //****************************************************************************
 wpmType = Math.round(((document.JobOp.typed.value.replace(/  /g, 
                  " ").split(" ").length)/totalTime) * 60)
 
 //Set the start test button label and enabled state
 document.JobOp.start.value = ">> Re-Start Typing Test <<";
 document.JobOp.start.disabled = false;
 
 //Flip the starting and stopping buttons around since the test is complete
 document.JobOp.stop.style.display="none";
 document.JobOp.start.style.display="block";
 
 //Declare an array of valid words for what NEEDED to be typed and what WAS typed
 //Again, refer to the above statement on removing the double spaces globally (1A) 
 var typedValues = document.JobOp.typed.value.replace(/  /g, " ");
 var neededValues = Left(document.JobOp.given.value, 
         typedValues.length).replace(/  /g, " ").split(" ");
 typedValues = typedValues.split(" ");
  
 //Disable the area where the user types the test input
 document.JobOp.typed.disabled=true;
 
 //Declare variable references to various statistical layers
 var tErr = document.getElementById("stat_errors");
 var tscore = document.getElementById("stat_score");
 var tStat = document.getElementById("stat_wpm");
 var tTT = document.getElementById("stat_timeleft");
 
 var tArea = document.getElementById("TypeArea");
 var aArea = document.getElementById("AfterAction");
 var eArea = document.getElementById("expectedArea");
  
 //Initialize the counting variables for the good valid words and the bad valid words
 var goodWords = 0;
 var badWords = 0;
 
 //Declare a variable to hold the error words 
 //we found and also a detailed after action report
 var errWords = "";
 var aftReport = "<b>Detailed Summary:</b><br>" + 
                 "<font color=\"DarkGreen\">";
 
 //Enable the printing button
 document.getElementById("printB").disabled = false;
 
 //Loop through the valid words that were possible 
 //(those in the test baseline of needing to be typed)
 var str;
 var i = 0;
 for (var i = 0; i < word; i++)
 {
  //If there is a word the user typed that is 
  //in the spot of the expected word, process it
  if (typedValues.length > i)
  {
   //Declare the word we expect, and the word we recieved
   var neededWord = neededValues[i];
   var typedWord = typedValues[i];
   
   //Determine if the user typed the correct word or incorrect
   if (typedWord != neededWord)
   {
    //They typed it incorrectly, so increment the bad words counter
    badWords = badWords + 1;
    errWords += typedWord + " = " + neededWord + "\n";
    aftReport += "<font color=\"Red\"><u>" + 
                 neededWord + "</u></font> ";
   }
   else
   {
    //They typed it correctly, so increment the good words counter
    goodWords = goodWords + 1;
    aftReport += neededWord + " ";
   }
  }
  else
  {
   //They didn't even type this word, so increment the bad words counter
   //Update: We don't want to apply this penalty because they may have chosen to end the test
   //    and we only want to track what they DID type and score off of it.
   //badWords = badWords + 1;
  } 
 }
 
 //Finalize the after action report variable with the typing summary 
 //at the beginning (now that we have the final good and bad word counts)
 aftReport += "</font>";
 aftReport = "<b>Typing Summary:</b><br>You typed " + 
            (document.JobOp.typed.value.replace(/  /g, " ").split(" ").length) + 
            " words in " + totalTime + " seconds, a speed of about " + 
            wpmType + " words per minute.\n\nYou also had " + badWords + 
            " errors, and " + goodWords + " correct words, giving scoring of " + 
            ((goodWords / (goodWords+badWords)) * 100).toFixed(2) + 
            "%.<br><br>" + aftReport;
 
 //Set the statistical label variables with what 
 //we found (errors, words per minute, time taken, etc) 
 tErr.innerText = badWords + " Errors";
 tStat.innerText= (wpmType-badWords) + " WPM / " + wpmType + " WPM";
 tTT.innerText = totalTime.toFixed(2) + " sec. elapsed";
 
 //Calculate the accuracy score based on good words typed 
 //versus total expected words -- and only show the percentage as ###.##
 tscore.innerText = ((goodWords / (goodWords+badWords)) * 100).toFixed(2) + "%";
 
 //Flip the display of the typing area and the 
 //expected area with the after action display area
 aArea.style.display = "block";
 tArea.style.display = "none";
 eArea.style.display = "none";
 
 //Set the after action details report to the summary as 
 //we found; and in case there are more words found than typed
 //Set the undefined areas of the report to a space,
 //otherwise we may get un-needed word holders
 aArea.innerHTML = aftReport.replace(/undefined/g, " ");
 
 //Notify the user of their testing status via a JavaScript Alert
 //Update: There isn't any need in showing this popup now that 
 //we are hiding the typing area and showing a scoring area
 //alert("You typed " + (document.JobOp.typed.value.split(" ").length) + 
 //        " words in " + totalTime + " seconds, a speed of about " + 
 //        wpmType + " words per minute.\n\nYou also had " + badWords + 
 //        " errors, and " + goodWords + " correct words, giving scoring of " + 
 //       ((goodWords / (goodWords+badWords)) * 100).toFixed(2) + "%.");
}

//calcStat is a function called as the user types 
//to dynamically update the statistical information
function calcStat()
{
//If something goes wrong, we don't want to cancel the test -- so fallback 
//error proection (in a way, just standard error handling)
try {
 //Reset the timer to fire the statistical update function again in 250ms
 //We do this here so that if the test has ended (below) we can cancel and stop it
 checkStatusInt=setTimeout('calcStat();',250);
 
 //Declare reference variables to the statistical information labels
 var tStat = document.getElementById("stat_wpm");
 var tTT = document.getElementById("stat_timeleft");
 
 var tProg = document.getElementById("stProg");
 var tProgt = document.getElementById("thisProg");
 
 var tArea = document.getElementById("TypeArea");
 var aArea = document.getElementById("AfterAction");
 var eArea = document.getElementById("expectedArea");
   
 //Refer to 1A (above) for details on why we are removing the double space
 var thisTyped = document.JobOp.typed.value.replace(/  /g, " ");
 
 //Create a temp variable with the current time of day to calculate the WPM
 eDay = new Date();
 endType = eDay.getTime();
 totalTime = ((endType - startType) / 1000)

 //Calculate the typing speed by taking the number of valid words 
 //typed by the total time taken and multiplying it by one minute in seconds (60)
 wpmType = Math.round(((thisTyped.split(" ").length)/totalTime) * 60)

 //Set the words per minute variable on the statistical information block
 tStat.innerText=wpmType + " WPM";
 
 //The test has started apparantly, so disable the stop button
 document.JobOp.stop.disabled = false;
  
 //Flip the stop and start button display status
 document.JobOp.stop.style.display="block";
 document.JobOp.start.style.display="none";
 
 //Calculate and show the time taken to reach this point 
 //of the test and also the remaining time left in the test
 //Colorize it based on the time left (red if less 
 //than 5 seconds, orange if less than 15)
 if (Number(60-totalTime) < 5)
 {
  tTT.innerHTML="<font color=\"Red\">" + String(totalTime.toFixed(2)) + 
                " sec. / " + String(Number(60-totalTime).toFixed(2)) + 
                " sec.</font>";
 }
 else
 {
  if (Number(60-totalTime) < 15)
  {
   tTT.innerHTML="<font color=\"Orange\">" + 
                 String(totalTime.toFixed(2)) + " sec. / " + 
                 String(Number(60-totalTime).toFixed(2)) + " sec.</font>";
  }
  else
  {
   tTT.innerHTML=String(totalTime.toFixed(2)) + " sec. / " + 
                 String(Number(60-totalTime).toFixed(2)) + " sec.";
  }
 }
  
 //Determine if the user has typed all of the words expected
 if ((((thisTyped.split(" ").length)/word)*100).toFixed(2) >= 100)
 {
  tProg.width="100%";
  
  tProgt.innerText = "100%";
 }
 else
 {
  //Set the progress bar with the exact percentage of the test completed
  tProg.width=String((((thisTyped.split(" ").length)/word)*100).toFixed(2))+"%";
  
  tProgt.innerText = tProg.width;
 }
 
 //Determine if the test is complete based on them
 //having typed everything exactly as expected
 if (thisTyped.value == document.JobOp.given.value)
 {
  endTest();
 }
 
 //Determine if the test is complete based on whether or not they have 
 //typed exactly or exceeded the number of valid words (determined by a space)
 if (word <= (thisTyped.split(" ").length))
 {
  endTest();
 }
 
 //Check the timer; stop the test if we are at or exceeded 60 seconds
 if (totalTime >= 60)
 {
  endTest();
 }
 
//Our handy error handling
} catch(e){};
}

//Simply does a check on focus to determine if the test has started
function doCheck()
{
 if (hasStarted == false)
 {
  //The test has not started, but the user is typing already -- maybe we should start?
  beginTest(); //Yes, we should -- consider it done!
 }
}
</SCRIPT>

When you have the JavaScript inserted into the page, all you need then is the code for the components (which was done fairly quickly without the use of CSS -- as it is a little messy, you get the point and can re-write or organize as you wish).

HTML
<div align="center">
 <table border="0" cellpadding="0" cellspacing="0" width="100%">
  <tbody>
  <tr>
   <td style="border-bottom: 2px solid #354562; padding: 4px" class="titlec">
   <input disabled="" id="printB" onclick="window.print();" 
       type="button" value="Print Results" name="printB" 
       style="float: right; font-size: 8pt; font-family: Arial" />
   <input onclick="document.getElementById('AfterAction').style.display='none';
              document.getElementById('expectedArea').style.display='block';
              document.getElementById('typeArea').style.display='block';
              document.JobOp.typed.value='';document.JobOp.typed.disabled=false;
              randNum = Math.floor((Math.random() * 10)) % intToTestCnt;
              strToTestType = strToTest[randNum];
              document.JobOp.given.value = strToTestType;" 
         type="button" value="New Test" 
         name="newtest" 
         style="float: right; font-size: 8pt; font-family: Arial" />ITS 
   Typing Test System</td>
  </tr>
 </tbody>
 </table>
</div>
<table border="0" cellpadding="0" 
           cellspacing="0" width="100%">
 <tbody>
 <tr>
  <td style="border-bottom: 1px dotted #860E36; padding: 4px" 
     class="titlea" background="Images/Lt_Red_Back.gif" width="460">
  Accurately and precisely evaluate your typing speed and accuracy.</td>
  <td style="border-bottom: 1px dotted #860E36; padding: 4px" 
    class="titlea" background="Images/Lt_Red_Back.gif" width="190">
  <p align="right">v1.0</p>
  </td>
 </tr>
 <tr>
  <td style="padding: 4px" class="bodya" colspan="2">
  <form name="JobOp">
   <table border="0" cellpadding="5" width="100%">
    <tbody>
    <tr>
     <td>
     <table border="0" cellpadding="5" width="100%">
      <tbody>
      <tr>
       <td align="center" 
         style="border-left: 1px solid #344270; border-right: 2px solid #344270; 
                border-top: 1px solid #344270; border-bottom: 2px solid #344270; 
                padding: 5px; background-color: #CED3E8" 
         background="Images/Blue_Back.gif">
       <b><font face="Arial" size="2" color="#FFFFFF">Net / 
       Gross WPM</font></b></td>
       <td align="center" 
          style="border-left: 1px solid #344270; 
                 border-right: 2px solid #344270; border-top: 1px solid #344270; 
                 border-bottom: 2px solid #344270; padding: 5px; 
                 background-color: #CED3E8" 
          background="Images/Blue_Back.gif">
       <b><font face="Arial" size="2" color="#FFFFFF">Entry 
       Errors</font></b></td>
       <td align="center" 
           style="border-left: 1px solid #344270; border-right: 2px solid #344270; 
                  border-top: 1px solid #344270; border-bottom: 2px solid #344270; 
                  padding: 5px; background-color: #CED3E8" 
           background="Images/Blue_Back.gif">
       <b><font face="Arial" size="2" color="#FFFFFF">Accuracy</font></b></td>
       <td align="center" 
          style="border-left: 1px solid #344270; border-right: 2px solid #344270; 
                 border-top: 1px solid #344270; border-bottom: 2px solid #344270; 
                 padding: 5px; background-color: #CED3E8" 
          background="Images/Blue_Back.gif">
       <b><font face="Arial" size="2" color="#FFFFFF">Elapsed 
       / Remaining</font></b></td>
      </tr>
      <tr>
       <td align="center"><font size="2" face="Arial">
       <div id="stat_wpm">
        Not Started</div>
       </font></td>
       <td style="border-left: 1px dotted #8794C7; border-right: 1px dotted #8794C7; 
                  border-top-width: 1px; border-bottom-width: 1px" align="center">
       <font size="2" face="Arial">
       <div id="stat_errors">
        Waiting...</div>
       </font></td>
       <td style="border-left-width: 1px; border-right: 1px dotted #8794C7; 
                  border-top-width: 1px; border-bottom-width: 1px" align="center">
       <font size="2" face="Arial">
       <div id="stat_score">
        Waiting...</div>
       </font></td>
       <td align="center"><font size="2" face="Arial">
       <div id="stat_timeleft">
        0:00</div>
       </font></td>
      </tr>
     </tbody>
     </table>
     </td>
    </tr>
    <tr>
     <td style="border-left-width: 1px; border-right-width: 1px; 
                border-top: 1px solid #344270; border-bottom-width: 1px">
     <div id="expectedArea" style="display:block">
      <p style="margin-top: 0; margin-bottom: 0">
      <font color="#7A88C0" face="Arial" size="1">
      <textarea name="given" cols="53" rows="10" 
        wrap="on" onfocus="deterCPProtect();" 
        style="width: 100%; border: 1px solid #344270; padding: 2px; 
               font-family:Arial; font-size:9pt">
        Click on the button below to start the typing test. 
        What you will be expected to type will appear here.
      </textarea></font>
      </p>
     </div>
     </td>
    </tr>
    <tr>
     <td>
     <p align="center" style="margin-top: 0; margin-bottom: 2px">
     <input type="button" 
        value="&gt;&gt; Start Typing Test &lt;&lt;" name="start" 
        onclick="beginTest()" 
        style="display:block; border-left:1px solid #293358; border-right:2px 
           solid #293358; border-top:1px solid #293358; border-bottom:2px solid #293358; 
           width: 100%; background-color: #9BB892; color:#FFFFFF; 
           background-image:url('Images/Green_Back.gif')" /></p>
     <p align="center" style="margin-top: 0; margin-bottom: 0">
     <input disabled="" type="button" 
       value="&gt;&gt; End Typing Test &lt;&lt;" 
       name="stop" onclick="endTest()" 
       style="display:none; border-left:1px solid #293358; border-right:2px 
                solid #293358; border-top:1px solid #293358; border-bottom:2px 
                solid #293358; width: 100%; background-color: #F05959; 
                color:#FFFFFF; background-image:url('Images/Red_Back.gif')" /></p>
     </td>
    </tr>
    <tr>
     <td style="font-family: Arial; font-size: 9pt">
     <div id="typeArea" style="display:block">
      <table border="0" width="100%" cellspacing="1">
       <tbody>
       <tr>
        <td style="border: 1px solid #9CA8D1; background-color: #EAECF4">
        <div align="left">
         <table id="stProg" border="0" 
                     width="0%" cellspacing="1">
          <tbody>
          <tr>
           <td style="border: 1px solid #344270; background-color: #8F9BCB; 
                       font-family:Arial; font-size:8pt; color:#FFFFFF" 
                       align="right" background="Images/Blue_Back.gif">
           <div id="thisProg">
            0%</div>
           </td>
          </tr>
         </tbody>
         </table>
        </div>
        </td>
       </tr>
      </tbody>
      </table>
      <p style="margin-top: 0; margin-bottom: 0">
      <font color="#7A88C0" face="Arial" size="1">
      <textarea onkeypress="doCheck();" onkeydown="//calcStat()" 
         name="typed" cols="53" rows="10" wrap="on" 
         style="width: 100%; border: 1px solid #344270; padding: 2px; font-family:Arial; 
                 font-size:9pt"></textarea></font>
      </p>
     </div>
     <div id="afterAction" style="display:none">
     </div>
     </td>
    </tr>
    <script>
 randNum = Math.floor((Math.random() * 10)) % intToTestCnt;
 strToTestType = strToTest[randNum];
 
 document.JobOp.given.value = strToTestType;
 document.JobOp.typed.focus();
 </script>
   </tbody>
   </table>
  </form>
  </td>
 </tr>
</tbody>
</table>

History

  • 03/16/2009 - Initial release.
  • 03/17/2009 - Modified system for accuracy; added timer; and, added a detailed after-action report.

License

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