I see this request sometimes, and it makes sense.

When it comes to grading Essay questions (sometimes known as “Open Answer” questions), you may not have time to read the equivalent of War and Peace, hundreds of times over.

This is where a word count limitation can come in handy.

The finished product: a LearnDash essay question with word count limitations.

In this “tutorial” I’ll basically just provide a massive snippet containing the PHP and JavaScript code necessary to enable a word count limiter for all of your LearnDash essays.

Then, I’ll explain what is going on, function by function.

The Snippet

See our guide on adding PHP code snippets to your WordPress site, if unfamiliar.

function lmscoder_learndash_essay_word_limit() { ?>
	<script>
		// Set constants
		const WORD_LIMIT = 100;
		const WORD_LIMIT_REACHED_ALERT = 'You have reached the word limit.';
		const WORD_COUNT_CLASS = 'lmscoder-word-count';
		const WORD_COUNT_ITEM_CLASS = WORD_COUNT_CLASS + '__item';
		const WORD_COUNT_LABEL_CLASS = WORD_COUNT_CLASS + '__label';
		const WORD_COUNT_VALUE_CLASS = WORD_COUNT_CLASS + '__value';
		const WORD_LIMIT_LABEL_CLASS = WORD_COUNT_CLASS + '__label';
		const WORD_LIMIT_VALUE_CLASS = WORD_COUNT_CLASS + '__value';
		
		// Check if something is a word or not
		function isWord( str ) {
			var alphaNumericFound = false;
			for ( var i = 0; i < str.length; i++ ) {
				var code = str.charCodeAt( i );
				if ( ( code > 47 && code < 58 ) || // numeric (0-9)
					( code > 64 && code < 91 ) || // upper alpha (A-Z)
					( code > 96 && code < 123 ) ) { // lower alpha (a-z)
					alphaNumericFound = true;
					return alphaNumericFound;
				}
			}
			return alphaNumericFound;
		}

		// Insert some HTML so we don't have to do template overrides
		function elementBuilder( element ) {
			const markup = `
			<div class="${ WORD_COUNT_CLASS }">
				<div class="${ WORD_COUNT_ITEM_CLASS }">
					<span class="${ WORD_COUNT_LABEL_CLASS }">Word count: </span>
					<span class="${ WORD_COUNT_VALUE_CLASS }">0</span>
				</div>
				<div class="${ WORD_COUNT_ITEM_CLASS }">
					<span class="${ WORD_LIMIT_LABEL_CLASS }">Word limit: </span>
					<span class="${ WORD_LIMIT_VALUE_CLASS }">${ WORD_LIMIT }</span>
				</div>
			</div>
			`;
			
			element.parentElement.insertAdjacentHTML( 'beforeend', markup );
		}

		// Set the word count
		function setWordCount( element ) {
			var text = element.value.split( ' ' );
			var wordCount = 0;

			for ( var i = 0; i < text.length; i++ ) {
				if ( ! text[i] == ' ' && isWord( text[i] ) ) {
					wordCount++;
				}
			}

			element.parentElement.querySelector( '.' + WORD_COUNT_VALUE_CLASS ).textContent = wordCount;
		}

		// Get the word count
		function getWordCount( element ) {
			return element.parentElement.querySelector( '.' + WORD_COUNT_VALUE_CLASS ).textContent;
		}
		
		// Wordcountify any textarea element passed to it
		function wordCountify( element ) {
			elementBuilder( element );
			
			element.addEventListener( 'keydown', function( e ) {
				if ( getWordCount( element ) >= WORD_LIMIT && e.code !== 'Backspace' ) {
					e.preventDefault();
					return alert( WORD_LIMIT_REACHED_ALERT );
				}
			} );
			
			element.addEventListener( 'keyup', function( e ) {
				setWordCount( element );
			} );
		}
		
		// Select all LearnDash essay textareas
		const essayTextareas = document.querySelectorAll( '.wpProQuiz_questionEssay' );
		
		// Wordcountify the selected essay textareas
		essayTextareas.forEach( element => wordCountify( element ) );
	</script>
<?php }
add_action( 'wp_footer', 'lmscoder_learndash_essay_word_limit' );

After implementing this, you should see a functional word counter beneath each of your LearnDash essay questions. You’ll also see the word limit, and run into an error should you reach the word limit.

The Explanation

Don’t want to just dump a code snippet on you without explaining it at all!

We’ve broken up the snippet into a number of comments.

Part #1: Set constants

Here is a nice and convenient place to change constants for your needs.

I used the “lmscoder-” CSS prefix just to make sure I didn’t conflict with another plugin that happened to use a generic class name like word-count.

Although feel free to rename it to any non-conflicting-on-your-site name you’d like, if you want to hide that you got this snippet from LMSCoder, or for any other reason. I don’t care!

This is also the place where you set the sitewide essay word count limit. The scope of this tutorial does not include setting the word count limit of individual essays.

Finally, this is the place where you can customize the message alert that appears when the student reaches the word limit.

Part #2: Insert some HTML so we don’t have to do template overrides

This is where we create and insert the HTML underneath each essay textarea.

For a fun bonus, I originally wrote this function as follows:

function elementBuilderOld( element ) {
	wordCountWrapperEl = document.createElement( 'p' );
	wordCountFirstChild = document.createElement( 'span' );
	wordCountSecondChild = document.createElement( 'span' );
	
	wordCountWrapperEl.classList.add( 'lmscoder-word-count' );
	wordCountFirstChild.classList.add( 'lmscoder-word-count__label' );
	wordCountSecondChild.classList.add( 'lmscoder-word-count__value' );
	
	wordCountFirstChild.textContent = 'Word count: ';
	wordCountSecondChild.textContent = '0';
	
	wordCountWrapperEl.prepend( wordCountFirstChild );
	wordCountWrapperEl.append( wordCountSecondChild );
	
	element.parentElement.append( wordCountWrapperEl );
}

Seemed a bit verbose just so I could make a couple lines of HTML. Then I remembered template literals!

Part #3: Set the word count

This function gets the textarea element passed to it, scans the value, and counts the number of words in it.

Then, it updates the value in the WORD_COUNT_VALUE_CLASS element.

Part #4: Get the word count

This function retrieves the value in the WORD_COUNT_VALUE_CLASS. It’s leveraged when checking to see if the student has reached the word limit.

Part #5: Wordcountify any textarea element passed to it

This function can be used to “wordcountify” every element passed to it.

It adds an event listener to the “keyup” event which checks to see how many words are in the textarea, and updates the count accordingly.

It adds an event listener to the “keyup” event, which checks to see if the word limit has been reached. If it has, an alert will pop up. Only the backspace key is allowed to be typed until the word count is reduced.

Pro Tip: You may wish to adjust the alert message set in WORD_LIMIT_REACHED_ALERT, to inform the user that the backspace key is allowed.

Otherwise they may find the word limit enforcement experience frustrating, as any other key pressed will trigger to an alert popup.

This is nice, because it means this will work even when a quiz contains multiple essay questions.

It can also be used for any textarea element on your WordPress site, such as comment forms.

Because of this flexibility, 99% of this snippet can be filed under not-LearnDash-specific.

Part #6: Select all LearnDash essay textareas

This is pretty much the only part of the snippet that specifically relates to LearnDash.

It selects all the LearnDash essay textarea elements on the page, which are selectable with the .wpProQuiz_questionEssay selector.

But I just want a word count displayed, no limit!

You’re in luck because all you have to do is take the exact same snippet from above, and delete some stuff.

Step #1: Remove constants related to word limits

That means delete the following lines:

const WORD_LIMIT = 100;
const WORD_LIMIT_REACHED_ALERT = 'You have reached the word limit.';
const WORD_LIMIT_LABEL_CLASS = WORD_COUNT_CLASS + '__label';
const WORD_LIMIT_VALUE_CLASS = WORD_COUNT_CLASS + '__value';

I guess keeping them won’t hurt although seems like a waste of space if you don’t want a word limit.

Step #2: Remove HTML related to word limits

That means, get rid of this part:

<div class="${ WORD_COUNT_ITEM_CLASS }">
	<span class="${ WORD_LIMIT_LABEL_CLASS }">Word limit: </span>
	<span class="${ WORD_LIMIT_VALUE_CLASS }">${ WORD_LIMIT }</span>
</div>

Probably don’t want to display anything related to “Word Limits” if you don’t have any. Also that WORD_LIMIT constant doesn’t exist anymore because we deleted it in the previous step.

Step #3: Remove keydown event listener

This is what we were using to check if the word limit had been reached.

If the student pressed a key down (get it? keydown events happen when a key is pressed down?) then there would be a check to see

That means, remove this part:

element.addEventListener( 'keydown', function( e ) {
	if ( getWordCount( element ) >= WORD_LIMIT && e.code !== 'Backspace' ) {
		e.preventDefault();
		return alert( WORD_LIMIT_REACHED_ALERT );
	}
} );

Also since we’re no longer using getWordCount function anywhere else in our code at this point, you can delete that as well.

This time I’m not going to tell you exactly what lines to delete. Try to find and delete the getWordCount function on your own as practice!

Credits

A big thanks to Kelvin Gobo whose word-counter-js code I refactored and adapted to make work for multiple textarea elements on the same page (as may be the case with a LearnDash quizzes with multiple essay questions).

For further explanation on how the isWord function determines what a “word” is, see Kelvin’s article on CodeSource titled Building a Word Counter in JavaScript.

Taking it further

I can think of a few ways to iterate take this concept further.

1) Another way of alerting, that doesn’t involve a JavaScript alert, although I guess that’s tough to miss.

2) Setting a word count minimum, instead of a maximum, or in conjunction with a maximum.

3) Setting a word count minimum/maximum per essay question, instead of a hardcoded sitewide setting.

4) Setting exclusion rules, like if one particular student really wants to exceed a word limit, then that can be allowed on a per-user and/or per-question basis.

5) A character limit, instead of a word limit. Maybe you’re running a course about how to write effective tweets?

6) Enhanced “security” because imposing word limits solely through client-side code means that a technically-savvy student could get around them. Perhaps there could be server-side code to reject essays beyond the word limit as well? Seems a bit overkill, to be honest.

The complexities can really compound depending on expectations, so if you need something beyond what this tutorial covers, feel free to contact me.