Introduction - A Case for Manipulating an Array’s Contents

This post is essentially a tutorial about one of the more complex parts of a typing speed test web application I built in JavaScript. I felt it could be useful for people (like myself) who struggle with the concept of when it is necessary to manipulate strings so that the contents of the string are manipulatable.

Let’s say you have an array with set values you want to be able to check for spelling errors. The array looks like this:

var sentenceArray = new Array(); //declare new array object sentenceArray to individually store sentences. can't do that at array initialization.
sentenceArray[0] = "Much did had call new drew that kept. Limits expect wonder law she. Now has you views woman noisy match money rooms.";
sentenceArray[1] = "To up remark it eldest length oh passed. Off because yet mistake feeling has men.";
sentenceArray[2] = "Consulted disposing to moonlight ye extremity.";
sentenceArray[3] = "Engage piqued in on coming.";
sentenceArray[4] = "Text to test here. Click Change Test Text to change the text you wish to test your speed against.";

and you want the contents of the array to be split into individual characters so you can run a spellcheck to see if a user is typing the contents correctly. To accomplish this you would need to use the JavaScript split function (alongside forEach) to split the contents of sentenceArray into individual characters, and after, you would need to re-insert them into whatever display element you have in your HTML. For ease of reference (and so we don’t have to keep typing it repeatedly), I have created a constant and a variable (testText and originText, respectively) that uses the document method querySelector to target an ID in my HTML with their own respective IDs. Here’s what those looks like:

const testText = document.querySelector("#changeThisText");
var originText = document.querySelector("#origin-text p").innerHTML; //originText needs to be able to change if text changes, so made it into a var.

Explaining Array Manipulation Logic

Now that you know how these two values fit into the puzzle, here’s how you would manipulate one of the values in the array to become an array of characters able to be manipulated one character a time:

 originText = sentenceArray[0];
    sentence = sentenceArray[0];
    sentence.split("").forEach((character) => {
      const charSpan = document.createElement("span"); //creates an HTML <span> element charSpan to store all our characters from the forEach loop inside innerText.
      charSpan.innerText = character;
      testText.appendChild(charSpan); // after the function is called, testText's innerHTML is always cleared regardless of iteration. inside each if statement, new text is added (Appended) to child nodes directly from charSpan to testText's child nodes, one character at a time.
    });

I’ll break down the comments I added here a little bit. I am essentially setting the original text (originText) equal to the full value of the array at index 0, setting a pre-defined variable sentence equal to that sentence at index 0, and then splitting sentence one character at a time (without spaces). For each character that is “found,” (for lack of a better term) in this forEach loop, the locally defined constant charSpan, which is a span, (read more on HTML spans here) has its innerText changed to equal that forEach loop iteration’s version of the character variable’s contents.

Note that the => {} syntax after a loop declaration is just shorthand for doing something every time the forEach loop iterates through a new list item.

Now that I’ve successfully manipulated the original text to be an array of characters, I can now check that array of characters sequentially for errors if the user typed something incorrectly.

Error Checking an Array of Characters

This following section references constants defined in the above previous steps, so everything from here on should be fairly clear.

Let’s start off with what my user error checking function’s code looks like:

 if (isFirstRun){ // because spellCheck works off of spans of arrays of characters to check individual characters one by one, split sentence & replace testText
    testText.innerHTML = "";
    sentence.split("").forEach((character) => {
      const charSpan = document.createElement("span");
      charSpan.innerText = character;
      testText.appendChild(charSpan);
    });
    isFirstRun = false;
  }
  const displayedText = document.getElementById('changeThisText')
  const userInputText = document.getElementById("test-area-actual");
  
  const arrQuote = displayedText.querySelectorAll('span');
  const arrValue = userInputText.value.split('');
  let correct = true;

  arrQuote.forEach((characterSpan, index) => {
    var character = arrValue[index];
    if (character == null) {
      // if no characters have been typed inside array, remove all classes. else...
      characterSpan.classList.remove("correct");
      characterSpan.classList.remove("incorrect");
      correct = false; //havent typed anything yet so must be false
    } else if (character === characterSpan.innerText) {
      //if match, remove incorrect class simultaneously so that it can't be incorrect AND correct.
      characterSpan.classList.add("correct");
      characterSpan.classList.remove("incorrect");
    } else {
      // if not null but also not correct, obviously incorrect case.
      characterSpan.classList.remove("correct");
      characterSpan.classList.add("incorrect");
      correct = false; //if incorrect, obviously correct = false.
    }
  }); 

Yes, this looks massive. Thankfully it is not too hard to understand what it is we need to do.

First of all, we have a variable named isFirstRun defined outside the scope, initialized as true. The reason for this is that if the user decides to begin typing with the HTML pre-defined text, since this text is not yet split(), the if-else blocks in this function will not work correctly, as they rely on the testArea text being split to be checked character-by-character. So, if it’s the first run (meaning isFirstRun hasn’t been changed to false yet), then set testText’s inner HTML to be a blank string (so that when you run appendChild at the bottom you don’t add text unnecessarily),

(okay I lied here’s more proprietary code):

var sentence = originText;

and then for each sentence, split it with no spaces (“”), and for each value (character) in the object list, after creating a constant value charSpan that is simply a new HTML element of type span, change the inner text of charSpan (you can do this because it’s a child node of charSpan and not charSpan itself, which is a constant value) to be whatever the next character in sentence.split’s object list is. Finally, directly append the text that we are displaying to the user to be typed to where testText is pointing to. This will be done one character at a time, and since charSpan’s inner text is the next character of the true string, we successfully have replaced the original HTML string value with an array of char’s, essentially. After all that is complete in our forEach loop, I change isFirstRun to false, which ensures that that loop is never run again after the first time, because the user’s only option after completing the first initial typing challenge is to change the text to another array value. And the process of splitting that text simply continues.

Remember, the variable called “sentence” was initialized to be originText so that means we really are just taking the original string from HTML and running split(“”) so we can turn it into a list of individual characters.

The Nitty Gritty

Digging past the if isFirstRun check, we have four const’s: displayedText, userInputText, arrQuote, and arrValue. You’ve seen the way all of these function except for arrValue. arrValue runs a split() on any text inside the user input box of our page, which I made an ID for called test-area-actual. And then we have the boolean correct declaration being initialized to true. More on that shortly.

Using arrQuote, which selects any elements with the element type of ‘span’, we iterate through arrQuote using forEach and create two temporary values: characterSpan and index. This leads to our first function check: is character null? In other words, var character is initialized to arrValue at its first initial index. If that character is null, then clearly nothing exists inside test-area-actual, meaning the user hasn’t typed anything yet. So, remove class types incorrect and correct from characterSpan. Remember, in a forEach loop, the first value could be anything you want it to be. Here, characterSpan is each object inside arrQuote as the forEach loop goes character-by-character checking each object for content, and if characterSpan is null, that means there is nothing inside arrQuote, which holds any user input that is of element type “Span”. (see above section entitled Explaining Array Manipulation Logic if you forgot why we made a new element ‘span’.)

Else, if character EXACTLY matches the value stored in characterSpan’s inner text, this is a correct case. Add the class type correct and remove incorrect. Let me explain the ramifications of such a statement. This means that, say, if our user makes an error during the typing test, we apply a class type of incorrect to any element that is of type ‘span’. To visually demonstrate this, here’s what the CSS looks like for both the correct class and the incorrect class, and here’s what the incorrect text will look like on the live site.

.correct {
  color: green;
}

.incorrect {
  color: red;
  text-decoration: underline;
}

liveTypeTestSitePicture

See how the CSS is selectively applied? Characters that are correct have green coloring applied, while incorrect characters have a red color plus a red underline. This is all thanks to the work that we dd in splitting everything and correctly applying the “correct” class to characters the user inputted that are correct, and applying the “incorrect” class to user input that is, of course, incorrect.

This is just one example of many on why using the split() method on your array and storing all characters in a span element you create may be a good use of array manipulation. There is also mutation to consider, but perhaps that is a topic for another day.

Thank you for joining me on my beginner’s journey to JavaScript!

Happy text manipulating!