Reputation: 2579
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
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
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:
document
and changed it to td
. This way the event wont bubble upto parent.keyup
to keypress
. keyup
wont work if you the user doesnt remove his finger from the keyboard. keypress
captures keyup
and keydown
.focus
and keypress
, why not join them?$(this)
two times, so cached it for reuse later. (I know..I know.. I'm nitpicking)parseInt
, which is a JSLINT rule. A radix is the extra number which you add to parseInt
. Look at this answer for more info.$(this).val()
to this.value
, which is more native. empty()
. Since you're already using html()
, this is not needed.Hope this helps!
Upvotes: 6
Reputation: 14921
I'm sure there are other improvements you could make, but the most obvious one to me is:
(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);
}
})();
msgSpan.empty().html(msg);
is the same as msgSpan.html(msg);
Upvotes: 4