2

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);
    });
})();
jgravois
  • 2,559
  • 8
  • 44
  • 65
  • 1
    Why don't you make use of any plugin such as this one: http://cssglobe.com/lab/charcount/01.html – Alvaro Jul 24 '13 at 15:54
  • Because if we all use plugins for everything, nobody will know how to code any more. I know what you mean, but for simple things I don't see why a plugin is always the answer – Fiona - myaccessible.website Jul 24 '13 at 15:58

3 Answers3

6

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!

Community
  • 1
  • 1
krishwader
  • 11,341
  • 1
  • 34
  • 51
  • I added a screenshot of your answer to my iPhone because your explanation of what you did (rather than just providing the demo ) teaches me best practices and will make me a better developer. Now I just have to figure out what a "radix" is since I figured it is NOT a spicy, red vegatable. – jgravois Jul 24 '13 at 16:23
  • That was where I first saw radix ... I am googling it now to find out what it is (I guess that explains why I hadn't used one before, eh? LOL ) – jgravois Jul 24 '13 at 16:27
  • Once I refreshed the page, I saw your edit and link to the radix explanation ... MUCH APPRECIATED! – jgravois Jul 24 '13 at 16:29
  • Nice, but beware of these two modest shortcomings: (1) if characters are deleted, the "max remaining" number does not correspondingly increase; and (2) pasted characters do not count against the maximum. – Mark Gavagan Jul 25 '16 at 20:18
4

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);

Fiona - myaccessible.website
  • 14,481
  • 16
  • 82
  • 117
  • I KNEW there was a better way! – jgravois Jul 24 '13 at 16:13
  • Thanks to you, I now know how to pass the $(this) to a function. That in itself will make my jQuery code SOOO much better since I won't have to next anonymous functions within anonymous functions like I used to. I gave you a +1 but had to choose another answer as the answer since that was the code I decided to use. Again THANK YOU for your time!!! – jgravois Jul 24 '13 at 16:25
1

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);
    },

});
pgee70
  • 3,707
  • 4
  • 35
  • 41
  • 1
    What i like about this and which the other examples don't have is when you delete, the counter goes back up – AdRock Nov 05 '14 at 15:17