Creating a jQuery Word Counter

On a project at work this week, I was creating a form that needed to have word limitations for each textarea. I found a ton of jQuery character counters, but not many word counters, so I decided to create my own.

Planning it all out

I really love how when you type more than 140 characters in Twitter; it doesn’t prevent you from typing anymore, it just warns you that you have too many characters.

Twitter Screenshot

I definitely wanted to add in that kind of functionality, and here were a couple of things I needed to account for when creating this for the project:

  • Ability to add in maximum number of words
  • Ability to add in minimum number of words
  • Ability to add in minimum and maximum number of words

I wanted it to be easy to apply the word count to the textarea, so I just wanted to be able to add a class to each textarea and the functionality would be applied.

Examples

  • Maximum of 50 words: class=”count[50]
  • Between 50 & 100 words: class=”count[50,100]
  • Minimum of 50 words (no maximum): class=”count[50,0]

That seemed easy and flexible enough to get the job done, so let’s get started on the script.

The jQuery

First, once the document is loaded, we will use an attribute selector to target each textarea that has a class that starts with count[

$(document).ready(function() {
	$("[class^='count[']").each(function() {
 
	});
});

Next, we are going to create some variables to use later on:

var elClass = $(this).attr('class');
var minWords = 0;
var maxWords = 0;
var countControl = elClass.substring((elClass.indexOf('['))+1, elClass.lastIndexOf(']')).split(',');

The first 3 lines should be pretty straightforward, just assigning the class value of the textarea to a variable, and defining our minimum and maximum variables.

The fourth line may look a little crazy, so let’s walk through that. First, we want to take a substring of our class variable. The start of that substring will be the location of the open bracket, [ (+1 so we don’t actually include the bracket). The end of the substring will be the location of the closing bracket, ]. Then, we want to split the substring on the comma and assign it to an array named countControl.

Next, we are going to assign values to our minWords and maxWords variables:

if(countControl.length > 1) {
	minWords = countControl[0];
	maxWords = countControl[1];
} else {
	maxWords = countControl[0];
}

Then, we want to add in the information about the number of words after the textarea, and if there is a minimum number of words required, assign a class of error:

$(this).after('<div class="wordCount"><strong>0</strong> Words</div>');
if(minWords > 0) {
	$(this).siblings('.wordCount').addClass('error');
}

In the stylesheet, we also need to add a a style for the error class. Here is what I used:

.error { color: #f00; }

Here is where get into the meat of the script, where we actually do the counting of the words:

$(this).bind('keyup click blur focus change paste', function() {
	var numWords = jQuery.trim($(this).val()).split(' ').length;
	if($(this).val() === '') {
		numWords = 0;
	}	
	$(this).siblings('.wordCount').children('strong').text(numWords);
 
	if(numWords < minWords || (numWords > maxWords && maxWords != 0)) {
		$(this).siblings('.wordCount').addClass('error');
	} else {
		$(this).siblings('.wordCount').removeClass('error');	
	}
});

Ok, let’s go through this piece by piece:

$(this).bind('keyup click blur focus change paste', function() {

This is where we are binding functions to the textarea. I didn’t know you could bind multiple functions in one statement like that, but I just thought of as many actions that a user could take that could trigger a change of words. So basically what this says is that anytime any of those events are triggered, we fill execute the function that we defined.

Next, let’s look at this section:

var numWords = jQuery.trim($(this).val()).split(' ').length;
if($(this).val() === '') {
	numWords = 0;
}	
$(this).siblings('.wordCount').children('strong').text(numWords);

Basically, we are just counting the number of spaces and updating our wordCount div that we appended as a sibling to the textarea. If there is nothing in the textarea, we assign a value of 0. This is necessary because if you type words and then go back and delete them all, it still thinks there is 1 word. I have no clue why.

Finally, let’s look at this section:

if(numWords < minWords || (numWords > maxWords && maxWords != 0)) {
	$(this).siblings('.wordCount').addClass('error');
} else {
	$(this).siblings('.wordCount').removeClass('error');	
}

If the number of words is less then the minimum number of words or greater then the maximum number of words (and the maximum number of words is not zero), then we add the class of error to our wordCount div. Otherwise, we remove the class of error.

Take a look at the demo to see it in action.

That’s it! Nothing too crazy going on and could be customized pretty easily. Anything you would want to add? Done differently? Let me know in the comments.


22

Comments
  • Mrn says:

    real nice :)

    like this, think i gonne use somting like this on my new site ;)

    grtz

  • M.A.Yoosuf says:

    its cool!

    @Trevor Davis, these days you are not writing in your blog :S

  • Brenelz says:

    Would be cool if you would implement this as a jQuery plugin

  • BillyG says:

    One problem: If someone types more than one space between words, then it counts wrong: Number of spaces in text + 1 = number of words!

  • Saro says:

    This can come in handy!

  • insic says:

    wow! very useful stuff.

  • Steward says:

    Script have an error… if I type more than one space – it count that as one more word.
    You should first replace “spacespace” to “space” in circle and than count words
    And also replace “spacecoma” to “coma”, and others

  • BillyG says:

    Deleting comments? Why? You do not agree with non-positive comments?
    As I said, it doesn’t work correct…
    Enter after a word 2 spaces and it counts one more – non existing – word!

  • saurabh shah says:

    very nice work …. very usefull … thanks for sharing …

  • parminder says:

    This is great and Easy to understand. Please keep great examples like this coming.

  • Willabee says:

    For client-side validation, a simple check from the onsubmit event handler, to see if the textarea/s had a class of error, would cause it to fail the validation test.

    Very often you see requests to enter, say, a competition, which requests minimum and maximum numbers of words to be entered. What a pain it is to count the words yourself. This would be a great solution for this process and would be well appreciated by the users.

    @Trevor – Nice idea and well presented. I also did not know about multiple event handler assignments in one binding. Very useful. I reckon all those events where we should consider adding the same functionality for keyboard users as we do mouse users, such as mouseover/focus and mouseout/blur, can all make use of multi-binding to help with accessibility concerns.

  • Willabee says:

    @BillyG – Assuming we have users who cannot write (adding more than one space between words) then you should have done your Regular Expressions tutorial with @Jeffery Way…. so modify @Trevor’s code and fix it; by changing one line of code into two…


    var text = jQuery.trim($(this).val()).replace(/\s+/g," ");
    var numWords = text.split(' ').length;

    Now, you can enter as much white space as you want between words and it will still do a correct word count.

  • Willabee says:

    It is easy to criticise the work that others do to try and help you become better at what you do.

    It is much better to be positive and offer solutions to an occasional bug or less than perfect solution.

    I just refactored my own solution above and got it down to a jQuery’s one liner chaining statement (Cascade Pattern)…


    var numWords = jQuery.trim($(this).val()).replace(/\s+/g," ").split(' ').length;

  • salih says:

    merhaba çok güzel bir çalışma ellerinize sağlık.

  • Tom says:

    You should check if there is string characters more than XX length, meaning that if you write more than 1 space, it is counting like words, so 50 spaces and just a letter, get “50 words” … WRONG.

    And too, “and” “to” “from” should not be counted, so I personally, recommend to make it if between space and space there are more or equal than 5 characters, count like word, else not.

  • BillyG says:

    @Willabee
    I apologize, the purpose of my first post was only to give you a hint about a “problem” you may overlooked.
    But as my post was not there the next day, I thought…
    Anyway, you gave me two good hints here: About binding a function to multiple events and the hint about the regular expressions tutorials.
    Thanks for this!

  • Jihad Aravassry says:

    Good work!!

  • Nykeri says:

    thanks man really nice

  • Maicon says:

    I learned more about JQuery with this. Good work!

  • Scott says:

    Very nice :D

  • check-one says:

    Modified to count words after line break and not to count multiple spaces as word:

    $(document).ready(function() {
    $(“[class^='count[']“).each(function() {
    var elClass = $(this).attr(‘class’);
    var minWords = 0;
    var maxWords = 0;
    var countControl = elClass.substring((elClass.indexOf(‘['))+1, elClass.lastIndexOf(']‘)).split(‘,’);

    if(countControl.length > 1) {
    minWords = countControl[0];
    maxWords = countControl[1];
    } else {
    maxWords = countControl[0];
    }

    $(this).after(‘0 Words’);
    if(minWords > 0) {
    $(this).siblings(‘.wordCount’).addClass(‘error’);
    }

    $(this).bind(‘keyup click blur focus change paste’, function() {
    var number = 0;
    var numWords = $(this).val().match(/\b/g);

    if(numWords) {
    number = numWords.length/2;
    }

    $(this).siblings(‘.wordCount’).children(’strong’).text(number);

    if(number maxWords && maxWords != 0)) {
    $(this).siblings(‘.wordCount’).addClass(‘error’);
    } else {
    $(this).siblings(‘.wordCount’).removeClass(‘error’);
    }
    });
    });
    });

  • ASG says:

    Great job. Love the counter. Thanks.