Building a word and character count app in vanilla JavaScript

My second project for the Vanilla JS Academy was an application that counts the number of words and characters in a block of text. Here’s how I approached the challenge.

  1. Character count
  2. Character and word count
  3. Announcing the count

Character count

Here’s the HTML I started with:

<label for="text">Enter your text below.</label>
<textarea id="text"></textarea>

<p>You've written <strong><span id="character-count">0</span> characters</strong>.</p>

First things first, I created an immediately-invoked function expression (IIFE) to contain my code and keep it outside the global scope. I also have it set up so I can reference the document as d for short:

;(function(d) {

  "use strict";

  // Rest of code...

})(document);

Next, I declared my variables. I got a reference to the textarea and span elements:

// Get the text area and character count elements
var textArea = d.querySelector("#text");
var characterCount = d.querySelector("#character-count");

I then created two functions: one to get the character count, and another to update the displayed count:

/**
 * Get the character count of a form control's value
 * @param  {Node} element The form control
 * @return {Number}       The character count
 */
function getCharacterCount(element) {
  return element.value.length;
}

/**
 * Update the character count of an element
 * @param {Node} display The element that will display the count
 * @param {Node} count   The element whose characters will be counted
 */
function updateCharacterCount(display, count) {
  display.textContent = getCharacterCount(count);
}

Finally, I initialized my script. I updated the character count when the page loads, and also when the value of the textarea element changes:

// Update the character count when the page loads
updateCharacterCount(characterCount, textArea);

// Update the count when the value of the text area changes
textArea.addEventListener("input", function() {
  updateCharacterCount(characterCount, textArea);
}, false);

…And that’s it for part one. Here’s the complete script:

;(function(d) {

  "use strict";

  //
  // Variables
  //

  // Get the text area and character count elements
  var textArea = d.querySelector("#text");
  var characterCount = d.querySelector("#character-count");


  //
  // Functions
  //

  /**
   * Get the character count of a form control's value
   * @param  {Node} element The form control
   * @return {Number}       The character count
   */
  function getCharacterCount(element) {
    return element.value.length;
  }

  /**
   * Update the character count of an element
   * @param {Node} display The element that will display the count
   * @param {Node} count   The element whose characters will be counted
   */
  function updateCharacterCount(display, count) {
    display.textContent = getCharacterCount(count);
  }


  //
  // Init
  //

  // Update the character count when the page loads
  updateCharacterCount(characterCount, textArea);

  // Update the count when the value of the text area changes
  textArea.addEventListener("input", function() {
    updateCharacterCount(characterCount, textArea);
  }, false);

})(document);

Character and word count

To start, I altered the markup slightly. I created another span element to hold the word count:

<label for="text">Enter your text below.</label>
<textarea id="text"></textarea>

<p>You've written <strong><span id="word-count">0</span> words</strong> and <strong><span id="character-count">0</span> characters</strong>.</p>

My variables are the same as before, except I now have a new one called wordCount to reference the new span element:

// Get the text area, word count, and character count elements
var textArea = d.querySelector("#text");
var wordCount = d.querySelector("#word-count");
var characterCount = d.querySelector("#character-count");

I then created a single function, count, which can be used to count either the character count or the word count:

/**
 * Count the number of words/characters in a form control
 * @param   {Node}    element           The element whose words/characters should be counted
 * @param   {Boolean} countCharacters   Whether to count words (false) or characters (true)
 * @return  {Number}                    The word/character count
 */
function count(element, countCharacters) {
  // Bail if the element doesn't exist
  if (!element) return;

  // If counting charaters, get the count
  // Otherwise, trim any whitespace
  var count = countCharacters ? element.value.length : element.value.trim();

  // If no words/characters, return zero
  if (!count) return 0;

  // If counting words, get the count
  // (counting words as separated by 1 or more whitespace characters)
  if (!countCharacters) count = count.split(/\s+/).length;

  // Return the count
  return count;
}

I also created a function called updateCounts which I can use to update the character and word counts at the same time:

/**
 * Update the word and character counts
 */
function updateCounts() {
  // Update the word count
  wordCount.textContent = count(textArea, false);

  // Update the character count
  characterCount.textContent = count(textArea, true);
}

Finally, I initialized my script. I again updated the counts on page load and when the input event fires:

// Update the counts when the page loads
updateCounts();

// Update the counts when the value of the text area changes
textArea.addEventListener("input", updateCounts, false);

Here’s the complete script:

;(function(d) {

  "use strict";

  //
  // Variables
  //

  // Get the text area, word count, and character count elements
  var textArea = d.querySelector("#text");
  var wordCount = d.querySelector("#word-count");
  var characterCount = d.querySelector("#character-count");

 
  //
  // Functions
  //

  /**
   * Count the number of words/characters in a form control
   * @param {Node}    element           The element whose words/characters should be counted
   * @param {Boolean} countCharacters   Whether to count words (false) or characters (true)
   */
  function count(element, countCharacters) {
    // Bail if the element doesn't exist
    if (!element) return;

    // If counting charaters, get the count
    // Otherwise, trim any whitespace
    var count = countCharacters ? element.value.length : element.value.trim();

    // If no words/characters, return zero
    if (!count) return 0;

    // If counting words, get the count
    // (counting words as separated by 1 or more whitespace characters)
    if (!countCharacters) count = count.split(/\s+/).length;

    // Return the count
    return count;
  }

  /**
   * Update the word and character counts
   */
  function updateCounts() {
    // Update the word count
    wordCount.textContent = count(textArea, false);

    // Update the character count
    characterCount.textContent = count(textArea, true);
  }


  //
  // Init
  //

  // Update the counts when the page loads
  updateCounts();

  // Update the counts when the value of the text area changes
  textArea.addEventListener("input", updateCounts, false);

})(document);

Announcing the count

This final part is all about accessibility. The challenge was to make sure the updates to the wordCount and characterCount elements are actually announced to screen readers.

The first thing I did was update my HTML. I removed the span elements and put all the text inside a single p element with an ID of count. The important thing to note is that I added the aria-live attribute and set it to polite.

<label for="text">Enter your text below.</label>

<textarea id="text"></textarea>

<p id="count" aria-live="polite">You've written 0 words and 0 characters.</p>

As my good friend and mentor, Chris Ferdinandi (who you should absolutely check out), says in his article How and why to use aria-live:

The aria-live attribute lets screen readers know that content in a particular element is going to change dynamically, and that they should pay attention to it and announce those changes.

Its value can be set to off (the same as not using it at all), assertive (in which screen readers interrupt user actions to announce changes), and polite (which tells the screen reader to wait until the user is done to announce updates).

Generally speaking, we should always use polite.

I created two variables this time: one to hold the textarea element, and one to hold the single p element:

// Get the text area and count elements
var textArea = d.querySelector("#text");
var count = d.querySelector("#count");

My count function is the same as in the last section, but my updateCounts function is a little different:

/**
 * Update the word and character count
 * @param {Node} content The element whose value should be counted
 * @param {Node} count   The element to display the count
 */
function updateCounts(content, count) {
  // Bail if either element doesn't exist
  if (!content || !count) return;

  // Update the word count
  count.textContent = "You've written " + countWordsOrChars(content) + " words ";

  // Update the character count
  count.textContent += "and " + countWordsOrChars(content, true) + " characters."
}

Ultimately, I initialized my script…

// Update the counts when the page loads
updateCounts(textArea, count);

// Update the counts when the value of the text area changes
content.addEventListener("input", function() {
  updateCounts(this, count);
}, false);

…And that about wrapped it up for this project. Here’s the final script:

;(function(d) {
  
  "use strict";

  //
  // Variables
  //

  // Get the text area and count elements
  var textArea = d.querySelector("#text");
  var count = d.querySelector("#count");


  //
  // Functions
  //

  /**
   * Count the number of words/characters in a form control
   * @param {Node}    element           The element whose words/characters should be counted
   * @param {Boolean} countCharacters   Whether to count words (false) or characters (true)
   */
  function count(element, countCharacters) {
    // Bail if the element doesn't exist
    if (!element) return;

    // If counting charaters, get the count
    // Otherwise, trim any whitespace
    var count = countCharacters ? element.value.length : element.value.trim();

    // If no words/characters, return zero
    if (!count) return 0;

    // If counting words, get the count
    // (counting words as separated by 1 or more whitespace characters)
    if (!countCharacters) count = count.split(/\s+/).length;

    // Return the count
    return count;
  }

  /**
   * Update the word and character counts
   */
  function updateCounts() {
    // Update the word count
    wordCount.textContent = count(textArea, false);

    // Update the character count
    characterCount.textContent = count(textArea, true);
  }


  //
  // Init
  //

  // Update the counts when the page loads
  updateCounts(textArea, count);

  // Update the counts when the value of the text area changes
  content.addEventListener("input", function() {
    updateCounts(this, count);
  }, false);

})(document);

If you’d like, you can view the demo on GitHub Pages or have a look at the source code on GitHub.