js/main.js
// ---------------------------------------------------------------------------
// TypingTest - JavaScript Application
// Copyright 2015, <Otakar Andrysek>
// Copyright 2016-2017, <SST CTF>
// Usage: launch locally or remotely on a client
// Revision history:
// 2015-02-15 Created by first build ALPHA .1
// 2015-02-16 Tested ALPHA .2
// 2015-02-16 Bug-Fixed ALPHA .23
// 2016-11-03 Cleaned up code again
// ---------------------------------------------------------------------------
//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 = 1;
var strToTest = new Array("ERROR 2");
function readTextFile(file, arrayData)
{
var rawFile = new XMLHttpRequest();
rawFile.open("GET", file, false);
rawFile.onreadystatechange = function ()
{
if(rawFile.readyState === 4)
{
if(rawFile.status == 200 || rawFile.status == 0)
{
var allText = rawFile.responseText;
strToTest = new Array(allText);
}
}
}
rawFile.send(null);
return arrayData;
}
// File to read the testing string from
readTextFile("content.txt");
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();
}
// Register onpaste on inputs and textareas in browsers that don't natively support it.
(function stopCP () {
var onload = window.onload;
window.onload = function () {
if (typeof onload == "function") {
onload.apply(this, arguments);
}
var fields = [];
var inputs = document.getElementsByTagName("input");
var textareas = document.getElementsByTagName("textarea");
for (var i = 0; i < inputs.length; i++) {
fields.push(inputs[i]);
}
for (var i = 0; i < textareas.length; i++) {
fields.push(textareas[i]);
}
for (var i = 0; i < fields.length; i++) {
var field = fields[i];
if (typeof field.onpaste !== "function" && !!field.getAttribute("onpaste")) {
field.onpaste = eval("(function () { " + field.getAttribute("onpaste") + " })");
}
if (typeof field.onpaste === "function") {
var oninput = field.oninput;
field.oninput = function () {
if (typeof oninput === "function") {
oninput.apply(this, arguments);
}
if (typeof this.previousValue == "undefined") {
this.previousValue = this.value;
}
var pasted = (Math.abs(this.previousValue.length - this.value.length) > 1 && this.value !== "");
if (pasted && !this.onpaste.apply(this, arguments)) {
this.value = this.previousValue;
}
this.previousValue = this.value;
};
if (field.addEventListener) {
field.addEventListener("input", field.oninput, false);
} else if (field.attachEvent) {
field.attachEvent("oninput", field.oninput);
}
}
}
}
})();
//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(3600 - totalTime) < 5) {
tTT.innerHTML = "<font color=\"Red\">" + String(totalTime.toFixed(2)) + " sec. / " + String(Number(3600 - totalTime).toFixed(2)) + " sec.</font>";
} else {
if (Number(3600 - totalTime) < 15) {
tTT.innerHTML = "<font color=\"Orange\">" + String(totalTime.toFixed(2)) + " sec. / " + String(Number(3600 - totalTime).toFixed(2)) + " sec.</font>";
} else {
tTT.innerHTML = String(totalTime.toFixed(2)) + " sec. / " + String(Number(3600 - 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 3600 seconds
if (totalTime >= 3600) {
endTest();
}
//Our handy error handling
} catch (e) {}
}
// Takes name from prompt, ready to store into MYSQL
function myFunction()
{
var person = prompt("Please enter your name", "");
// Profanity Filter (if there is a better way to do this LMK)
if (
person.search("ota") === - 1 &&
person.search("Ota") === - 1 &&
person.search("fruhwirth") === - 1 &&
person.search("Fruhwirth") === - 1 &&
person.search("morales") === - 1 &&
person.search("Morales") === - 1 &&
person.search("cena") === - 1 &&
person.search("Cena") === - 1
)
{
alert(person);
$.ajax(
{
//alert(person);
data: 'name=' + person,
url: 'http://sstctf.org/typing-test/backend.php',
method: 'POST', // or GET
success: function(msg)
{
alert(msg);
}
});
}
else
{
alert("Invalid option");
}
}
// 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!
}
}