Mind Mining Medium

Twitter style text counter in jQuery

October 27th, 2008
Punch Chart on Raphaƫl

I. Love. jQuery. I can’t say how much I love jQuery because some people might get jealous because jQuery might out-rank them in the scale of 0 to jAWESOME!

Enough drooling and leg humping. In a recent project, we needed a counter to display the number of characters the user had left in a text area. Nothing to hard or interesting. This sort of thing has been out there for years. The problem is, all the scripts I’ve found would automatically truncate the text. That is, if the user has 100 characters and they enter 105 characters, the 101 to 105th characters are gone. This is very annoying!

Rather than control the user and annoy them, we wanted to take the method Twitter does; Warn the user, don’t allow them to submit until the warning is fixed. That is, if the text counter is negative, they cannot submit, but once it’s 0 or greater, it’s fair game.

To add to the fun, we needed visual cues, i.e. colours! After some search and no luck, I sat down and wrote this little beauty. It’s version 1 so be nice.

You can always grab a copy here: http://karbassi.com/scripts/javascript/twitterCounter.js or http://plugins.jquery.com/project/twittercounter

/*
 * twitterCounter
 *
 * Displays a counter with the remaining text.
 *
 * Example:
 *  $('#description').twitterCounter(
 *  {
 *     limit: 140,
 *     counter: '#textcounter',
 *
 *     okSize: 140,
 *     okStyle: '.ok',
 *
 *     watchSize: 20,
 *     watchStyle: '.watch',
 *
 *     warningSize: 10,
 *     warningStyle: '.warning',
 *
 *     errorSize: 0,
 *     errorStyle: '.error',
 *  });
 *
 * $Version: 2008-10-24
 * Copyright (c) 2008 Ali Karbassi
 * ali.karbassi@gmail.com
 */
jQuery.fn.twitterCounter = function(options) {
   var curSize = $(this).val().length;
   var charsLeft = options['limit'] - curSize;
   var types = ['ok', 'watch', 'warning', 'error'];
   var x = {};
   $.each(types,
   function() {
      var el = this.toString();
      x[el] = {
         'Max': options[el + 'Size'],
         'Style': options[el + 'Style'].substring(0, 1) == '.' || options[el + 'Style'].substring(0, 1) == '#' ? options[el + 'Style'].substring(1, options[el + 'Style'].length) : options[el + 'Style'],
         'Type': options[el + 'Style'].substring(0, 1) == '.' ? 'class': 'id'
      }
   });
   for (var i = 0; i < types.length; i++) {
      var el = types[i].toString(); // Last Element check
      if (i + 1 < types.length) {
         var nextEl = types[i + 1].toString();
         if (charsLeft > x[nextEl]['Max'] && charsLeft < x[el]['Max'] + 1) {
            clean();
         }
      } else {
         if (charsLeft < x[el]['Max']) {
            clean();
         }
      }
   }
   $(options['counter']).text(charsLeft); // Add an event so the counter updates when the user types.
   $(this).one('keyup',
   function() {
      $(this).twitterCounter(options);
   });
   function clean() {
      if (x[el]['Type'] == 'class') {
         $.each(types,
         function() {
            var temp = this.toString();
            if ($(options['counter']).hasClass(temp)) {
               $(options['counter']).removeClass(temp);
            }
         });
         $(options['counter']).addClass(x[el]['Style']);
      } else {
         $(options['counter']).id(x[el]['Style']);
      }
   }
};

Photo credit: Dmitry Baranovskiy

17 Responses to “Twitter style text counter in jQuery”

  1. Hey, thanks for publishing this, it’s exactly what I was after for a current side project!

    My only suggestion is a simple demo page might help a little :)

    Thanks again!

  2. Rick Penticoff

    Thanks for making the script available. I can get it to work in FF 3 & 2 (Win & Mac) and Safari 3 (Mac), but it doesn’t work in IE 7 or 6 (Win). Any suggestions? Thanks!

  3. Rick Penticoff

    Just to follow up: none of the classes (.ok, .watch, .warning, .error) appear to be getting applied to the #textcounter element in IE. They are applied in FF and Safari. Coding it as:

    counter: “P#textcounter”, or
    counter: “#textcounter”,

    (where ) makes no difference – it’s the same result either way.

  4. Can you help me figure out how to use this? Is there a demo page where I can see the source code and how it is applied with your creation?

  5. kevotheclone

    This is very nice. Thanks for publishing it.

    I got it to work in IE7 right away. I manage an intranet site where the company uses IE7 only so that’s important for me. I just added a ; then CSS styles such as .ok {background-color: lightgreen;} and it worked!

    The only problem I had was that when the counter reaches zero the error style is not applied. I do have a maxlength attribute on the input field and I see that when I remove the maxlength attribute and type more than the limit the .error style is applied. It’s not until the counter reaches -1 that the .error style is applied. watchSize and warningSize styles are applied when the # of chars = the set number, but errorSize doesn’t work the same.

    Here’s some suggestions for version #2:

    1) Give it a jquery style name. When it’s sitting in a folder with a bunch of jquery plugins it’ll be easier to find.Something like: jquery.charcounter.js

    2) I’d also remove “twitter” from the name; it makes it sound like it’s a tweat counter. You can still mention that you were inspired by twitter in your comments. And maybe the twitter people might not like you using their name.

    3) Provide optional support for fields with the maxlength attribute (as described above). I realize this was not your original design goal, but if there is ever a version #2 you might consider it.

    4) You might allow an additonal option field name “msg”, that would be appended to the character count so the message could be displayed like “20 remaining characters”

    Thanks again!

  6. kevotheclone

    Opps I forget to escape my <span> element in the previous post.

    It should have read:

    I just added a <span id=”textcounter” />; then CSS styles such as .ok {background-color: lightgreen;} and it worked!

  7. Mario Vargas

    Hello,

    I am using your plug-in in one of my applications and made a modification to it. I thought I should share it so others could take advantage of it. I thought it was counter-productive to have to write the HTML for the text counter every time I needed to use it. My modification allows for the possibility of not providing the “counter” ID in the plug-in’s options. When this isn’t available, the plug-in wraps the text input control with the necessary HTML to continue normal operation.

    I also modified the name of the plug-in to “charCounter”, as suggested by kevotheclone.

    I hope I can type in some HTML here, so here’s the modified jQuery plug-in and corresponding CSS:

    File: jquery.charCounter.js

    /*
     * charCounter
     *
     * Displays a counter with the remaining text.
     *
     * Example:
     *   $('#description').charCounter(
     *   {
     *     limit: 140,
     *     counter: '#textcounter',
     *
     *     okSize: 140,
     *     okStyle: '.ok',
     *
     *     watchSize: 20,
     *     watchStyle: '.watch',
     *
     *     warningSize: 10,
     *     warningStyle: '.warning',
     *
     *     errorSize: 0,
     *     errorStyle: '.error',
     *   });
     *
     * $Version: 2008-10-24
     * Copyright (c) 2008 Ali Karbassi
     * ali.karbassi@gmail.com
     * Source: http://tech.karbassi.com/2008/10/27/twitter-style-text-counter-in-jquery/
     *
     * Modified by: Mario J Vargas (angstrey@hotmail.com)
     * $Version: 2009-04-15
     *
     * Example:
     *   // When counter ID not supplied (i.e. counter: '#textcounter')
     *   // text input control is automatically wrapped with text counter's HTML.
     *   $('#description').charCounter(
     *   {
     *		limit: 140,
     *
     *		okSize: 140,
     *		okStyle: '.ok',
     *
     *		watchSize: 20,
     *		watchStyle: '.watch',
     *
     *		warningSize: 10,
     *		warningStyle: '.warning',
     *
     *		errorSize: 0,
     *		errorStyle: '.error',
     *   });
    *
     */
    jQuery.fn.charCounter = function(options) {
       var curSize = $(this).val().length;
       var charsLeft = options['limit'] - curSize;
       var types = ['ok', 'watch', 'warning', 'error'];
       var x = {};
    
    	// If a counter's ID hasn't been specified,
    	// automatically wrap the input control with the
    	// HTML for the text counter.
    	if( typeof( options['counter'] ) == 'undefined' )
    	{
    		var curElm = $(this);	// text input control
    
    		var counterBlockID = "textcounter-block-" +  curElm.attr( "id" );
    		var elmWrapper = ""
    		// Identity text input control as text counter's input control
    		// and wrap the control with the HTML block. This allows us to
    		// adjust the width of the input control via CSS.
    		curElm.addClass( "textcounter-input" ).wrap( elmWrapper );
    
    		// Now that the input control block has been wrapped with
    		// a "textcounter block", inject the actual text counter's HTML
    		var counterID = "textcounter-" + curElm.attr( "id" );
    		var counterHtml = "Characters Left: ";
    		$( '#textcounter-block-' +  curElm.attr( "id" ) ).prepend( counterHtml );
    
    		// Finally, add the missing counter ID to continue
    		// normal execution of the charCounter
    		options['counter'] = '#' + counterID;
    	}
    
       $.each(types, function(){
          var el = this.toString();
          x[el] = {'Max' : options[el + 'Size'],
          'Style' : options[el + 'Style'].substring(0, 1) == '.' || options[el + 'Style'].substring(0, 1) == '#' ? options[el + 'Style'].substring(1, options[el + 'Style'].length) : options[el + 'Style'],
          'Type' : options[el + 'Style'].substring(0, 1) == '.' ? 'class' : 'id'}
       });
    
       for( var i=0; i < types.length; i++)
       {
          var el = types[i].toString();
    
          // Last Element check
          if( i+1  x[nextEl]['Max'] && charsLeft  x[nextEl]['Max'] && charsLeft < x[el]['Max'] + 1) {
                clean();
             }
          } else {
             // console.debug(charsLeft, el, x[el]['Max'], x[nextEl]['Max']+1, charsLeft < x[el]['Max'] + 1);
             if( charsLeft < x[el]['Max']  ) {
                clean();
             }
          }
       }
    
       $(options['counter']).text(charsLeft);
    
       // Add an event so the counter updates when the user types.
       $(this).one('keyup', function(){
          $(this).charCounter(options);
       });
    
       function clean() {
          if( x[el]['Type'] == 'class' ) {
             $.each(types, function(){
                var temp = this.toString();
                if( $(options['counter']).hasClass(temp) ) {
                   $(options['counter']).removeClass(temp);
                }
             });
             $(options['counter']).addClass(x[el]['Style']);
          } else {
             $(options['counter']).id(x[el]['Style']);
          }
       }
    };
    

    File: jquery.charCounter.css

    .textcounter-block
    {
    	width: 550px;
    	/* Border used to highlight wrapper. Remove this line. */
    	border: solid 1px red;
    }
    	.textcounter-input
    	{
    		width: 550px;
    	}
    	.textcounter-display
    	{
    		display: block;
    		text-align: right;
    	}
    		.textcounter-display .textcounter-count
    		{
    			font-size: 1.5em;
    			font-weight: bold;
    		}
    			.textcounter-count.ok
    			{
    				color: Green;
    			}
    			.textcounter-count.watch
    			{
    				color: Purple;
    			}
    			.textcounter-count.warning
    			{
    				color: Orange;
    			}
    			.textcounter-count.error
    			{
    				color: Red;
    			}
    
  8. Mario Vargas

    Here’s how I am using it:

    HTML (note the “rel” attribute)

    
    

    JavaScript:

    jQuery(document).ready(function() {
    	jQuery.each( jQuery('textarea[rel=textcounter]'), function() {
    		jQuery(this).charCounter({
    			limit: 600,
    
    			okSize: 600,
    			okStyle: '.ok',
    
    			watchSize: 20,
    			watchStyle: '.watch',
    
    			warningSize: 10,
    			warningStyle: '.warning',
    
    			errorSize: -1,
    			errorStyle: '.error'
    		});
    	});
    });
    
  9. Can you help me figure out how to use this? Is there a demo page where I can see the source code and how it is applied with your creation?

  10. Hi, this looks like promising script, but could you be so kind and show us some demo and instructions how to install it?

    Thank you!

  11. Hi, this looks like promising script, but could you be so kind and show us some demo and instructions how to install it?

    Thank you!

  12. Hi, this looks like promising script, but could you be so kind and show us some demo and instructions how to install it?

    Thank you!

  13. Hi, this looks like promising script, but could you be so kind and show us some demo and instructions how to install it?

    Thank you!

  14. Hi, this looks like promising script, but could you be so kind and show us some demo and instructions how to install it?

    Thank you!

  15. thanks for posting this ,very helpfull……

  16. thanks for posting this ,very helpfull……

  17. There's a bug in the script in the when classes are removed around line 66-67. The script assumes that the 'type' name is the same as the class name, even though the class name can be configured using the '***Style' options.

    Otherwise, it's a great script :-)

Leave a Reply