jgravois
jgravois

Reputation: 2579

jquery character countdown in textarea

I want to provide a visual to show users how many of the characters remain in a textarea as they type in their response. My code below WORKS! but I kinda pieced the logic together and have A LOT of repetition. Is there a better way to write the jquery below that is more maintainable and succinct?

HTML

<td colspan="4" style="text-align:center;">
     NOTES<br>
     <textarea class="sam_notes maxed" maxlength="750" name="sam_notes" style="height:100px;width:90%;margin:0 auto;"></textarea>
     <br>
     <span style="font:normal 11px sans-serif;color:#B00400;">
         <span class='counter_msg'></span>
     </span>
</td>

JQUERY

(function() {
     $(document).on('focus', '.sam_notes', function(e){
      var msgSpan = $(this).parents('td').find('.counter_msg');
      var ml     = parseInt( $(this).attr('maxlength') );
      var length = $(this).val().length;
      var msg = ml - length + ' characters of ' + ml + ' characters left';

      msgSpan.empty().html(msg);
    });

    $(document).on('keyup', '.sam_notes', function(e){
      var msgSpan = $(this).parents('td').find('.counter_msg');
      var ml     = parseInt( $(this).attr('maxlength') );
      var length = $(this).val().length;
      var msg = ml - length + ' characters of ' + ml + ' characters left';

      msgSpan.empty().html(msg);
    });
})();

Upvotes: 2

Views: 4736

Answers (3)

pgee70
pgee70

Reputation: 3984

i had a look at this: http://www.scriptiny.com/2012/09/jquery-input-textarea-limiter/ then extended it to a jquery widget.
yes, i know it is totally over-engineered. I just did it to learn more about widgets. but on the plus side, it is super simple to use. just call the widget on a text area with:

$(selector).limiter();

there are a bunch of parameters, you can send thru options as object, eg;

 $(selector).limiter({maxChars:1000,warningChars:990});

note, you can add the class stylings in your css. i assume here you have the jqueryui css installed for the warning text to change colour when it his the warningChars value. something like .ui-limiter-chars {position: absolute;padding-left: 1em;} could do the trick...

have a look at the fiddle... http://jsfiddle.net/DE5m9/2/

$.widget("ui.limiter", {
    options:{
        limiterClass:'ui-limiter-chars ui-widget-content',
        limiterTag:'small',
        maxChars:100,
        warningClass:'ui-state-error-text',
        warningChars:90,
        wrapperClass:'ui-limiter-wrapper ui-widget',
        tagName:'TEXTAREA'
    },
    _create: function(){
        // make sure that the widget can only be called once and it is only called on textareas
        var o = this.options;
        if (($(this).attr('aria-owns') === undefined ) && (this.element[0].tagName === o.tagName)) {
            var self = this;
            var id = Math.random().toString(16).slice(2, 10).replace(/(:|\.)/g, '');
            // ids = array of id of textarea, wrapper, and limiter chars ids.
            this.ids = [(this.element.attr('id') || 'ui-limiter-' + id),'ui-limiter-wrapper-'+id,'ui-limiter-chars-'+id];
            this.element[0].id = this.ids[0];
            var limiterWrapper = $('<div/>',{
                'class':o.wrapperClass,
                'id':this.ids[1]
            });
            // semantically, this seems to be a good fit for a small tag. ie not important.
            var limiterChars = $('<'+o.limiterTag+'/>', {
                'class': o.limiterClass,
                'id': this.ids[2]
            });

            $('#'+this.ids[0]).wrap(limiterWrapper);
            $('#'+this.ids[0]).after(limiterChars);
            $('#'+this.ids[0]).attr('aria-owns',this.ids[2]);
            $('#'+this.ids[0]).on("keyup focus", function() {
                self._setCount($('#'+self.ids[0]), $('#'+self.ids[2]));
            });
            this._setCount($('#'+this.ids[0]), $('#'+this.ids[2]));
        }
    },
    _setCount:function (src, elem) {
        var o = this.options;
        var chars = src.val().length;
        if (chars > o.maxChars) {
            src.val(src.val().substr(0, o.maxChars));
            chars = o.maxChars;
        }
        $('#'+this.ids[2]).text(o.maxChars - chars );
        if (chars > o.warningChars)
        {
            $('#'+this.ids[2]).addClass(o.warningClass);
        }
        else
        {
            $('#'+this.ids[2]).removeClass(o.warningClass);
        }
    },
    destroy: function(){
        $('#'+this.ids[2]).remove();
        $('#'+this.ids[0]).unwrap();
        $('#'+this.ids[0]).removeAttr('aria-owns');
        $.Widget.prototype.destroy.call(this);
    },

});

Upvotes: 1

krishwader
krishwader

Reputation: 11371

Here's a way :

$('td').on('focus keypress', '.sam_notes', function (e) {

    var $this = $(this);
    var msgSpan = $this.parents('td').find('.counter_msg');
    var ml = parseInt($this.attr('maxlength'), 10);
    var length = this.value.length;
    var msg = ml - length + ' characters of ' + ml + ' characters left';

    msgSpan.html(msg);
});

Here's a demo : http://jsfiddle.net/hungerpain/8gKs4/2/

Here's what I've changed in line order:

  1. removed document and changed it to td. This way the event wont bubble upto parent.
  2. Changed keyup to keypress. keyup wont work if you the user doesnt remove his finger from the keyboard. keypress captures keyup and keydown.
  3. since you're having the same stuff in focus and keypress, why not join them?
  4. you're using $(this) two times, so cached it for reuse later. (I know..I know.. I'm nitpicking)
  5. Added a radix to parseInt, which is a JSLINT rule. A radix is the extra number which you add to parseInt. Look at this answer for more info.
  6. Changed $(this).val() to this.value, which is more native.
  7. removed empty(). Since you're already using html(), this is not needed.

Hope this helps!

Upvotes: 6

I'm sure there are other improvements you could make, but the most obvious one to me is:

Refactor out the update count logic to prevent duplication

(function() {
     $(document).on('focus', '.sam_notes', function(e){
      UpdateCount($(this));
    });

    $(document).on('keyup', '.sam_notes', function(e){
      UpdateCount($(this));
    });

    function UpdateCount(notes) {
      var msgSpan = notes.parents('td').find('.counter_msg');
      var ml     = parseInt( notes.attr('maxlength') );
      var length = notes.val().length;
      var msg = ml - length + ' characters of ' + ml + ' characters left';

      msgSpan.html(msg);
    }
})();

Also, you don't need to empty() before html()

msgSpan.empty().html(msg); is the same as msgSpan.html(msg);

Upvotes: 4

Related Questions