I have a page that has fields that are validated using jquery-validate plugin, and wanted to include a twitter like character counter on the fields to see how many chars are left
Here is my demo
http://jsfiddle.net/4k1vokgv/1/
$(document).ready(function() {
$(".counter").characterCounter({
counterCssClass: 'text-counter',
limit: 1000,
counterFormat: 'Characters Remaining: %1',
});
var validatorStrat = $("#strategyForm").validate({
rules:{
exampleInputEmail1: {
required: true,
},
ZB_note: {
required: true,
maxlength: 140,
},
ZC_note: {
required: true,
maxlength: 140,
},
},
submitHandler: function(form) {}
});
});
Both character counters work fine until
the issue, when jquery-validate fires a validation error (required, maxlength, etc), the character counter then stops working on any element that has an error.
I do not believe this is an issue with the character counter plugin itself. I think the error generation that jquery validate does somehow causes this.
Anyways, included the full snippet below, any help is greatly appreciated
/**
* Character Counter v1.0
* ======================
*
* Character Counter is a simple, Twitter style character counter.
*
* https://github.com/dtisgodsson/jquery-character-counter
*
* @author Darren Taylor
* @author Email: [email protected]
* @author Twitter: darrentaytay
* @author Website: http://darrenonthe.net
*
*/
(function($) {
$.fn.characterCounter = function(options){
var defaults = {
exceeded: false,
limit: 150,
counterWrapper: 'span',
counterCssClass: 'help-block',
counterFormat: '%1',
counterExceededCssClass: 'exceeded',
onExceed: function(count) {},
onDeceed: function(count) {},
customFields: {},
};
var options = $.extend(defaults, options);
return this.each(function() {
$(this).after(generateCounter());
bindEvents(this);
checkCount(this);
});
function customFields(params)
{
var html='';
for (var i in params)
{
html += ' ' + i + '="' + params[i] + '"';
}
return html;
}
function generateCounter()
{
var classString = options.counterCssClass;
if(options.customFields.class)
{
classString += " " + options.customFields.class;
delete options.customFields['class'];
}
return '<'+ options.counterWrapper +customFields(options.customFields)+' class="' + classString + '"></'+ options.counterWrapper +'>';
}
function renderText(count)
{
return options.counterFormat.replace(/%1/, count);
}
function checkCount(element)
{
var characterCount = $(element).val().length;
var remaining = options.limit - characterCount;
if( remaining < 0 )
{
$(element).next("." + options.counterCssClass).addClass(options.counterExceededCssClass);
options.exceeded = true;
options.onExceed(characterCount);
}
else
{
if(options.exceeded) {
$(element).next("." + options.counterCssClass).removeClass(options.counterExceededCssClass);
options.onDeceed(characterCount);
options.exceeded = false;
}
}
$(element).next("." + options.counterCssClass).html(renderText(remaining));
};
function bindEvents(element)
{
$(element)
.bind("keyup", function () {
checkCount(element);
})
.bind("paste", function () {
var self = this;
setTimeout(function () { checkCount(self); }, 0);
});
}
};
})(jQuery);
$.validator.setDefaults({
errorElement: "span",
errorClass: "help-block",
// validClass: 'stay',
highlight: function (element, errorClass, validClass) {
$(element).addClass(errorClass); //.removeClass(errorClass);
$(element).closest('.form-group').removeClass('has-success').addClass('has-error');
},
unhighlight: function (element, errorClass, validClass) {
$(element).removeClass(errorClass); //.addClass(validClass);
$(element).closest('.form-group').removeClass('has-error').addClass('has-success');
},
errorPlacement: function(error, element) {
if(element.parent('.input-group').length) {
error.insertAfter(element.parent());
} else if (element.hasClass('select2')) {
error.insertAfter(element.next('span'));
} else {
error.insertAfter(element);
}
}
});
$(document).ready(function() {
$(".counter").characterCounter({
counterCssClass: 'text-counter',
limit: 140,
counterFormat: 'Characters Remaining: %1',
});
var validatorStrat = $("#strategyForm").validate({
rules:{
exampleInputEmail1: {
required: true,
},
ZB_note: {
required: true,
maxlength: 1000,
},
ZC_note: {
required: true,
maxlength: 1000,
},
},
submitHandler: function(form) {}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<script src="http://cdn.jsdelivr.net/jquery.validation/1.14.0/jquery.validate.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css" rel="stylesheet"/>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet"/>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<form role="form" id="strategyForm">
<div class="form-group">
<label for="exampleInputEmail1" class="control-label">Email address</label>
<input type="email" class="form-control" name="exampleInputEmail1" placeholder="Enter email" />
</div>
<div class="form-group">
<label class="control-label">What amount is to be solicited and when?</label>
<textarea class="form-control counter" rows="1" id="ZB_note" name="ZB_note" ></textarea>
</div>
<div class="form-group">
<label class="control-label">Who will be involved in the soliciation?</label>
<textarea class="form-control counter" rows="1" id="ZC_note" name="ZC_note" ></textarea>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
3
Answers
Problem is due to where the error element is being inserted compared to the traverse you use to set the counter text.
In your counter plugin you are looking for
next()
to set the count display using the following:That is based on structure being:
But the validator errorPlacment is doing:
So now the plugin
next(classSelector)
returns no matchesYou could simply use
nextAll()
instead ofnext()
in plugin…or change the errorPlacement to something like :Demo using
nextAll()
in pluginFinding the reason (Problem)
I believe I’ve found the problem.
When the a field doesn’t pass the validation rules it shows an error.
The error is being append to the DOM as:
This field is required.
When I removed it using the console the counter worked.
That made my wonder – maybe the checkCount function still works but the “output” (the
span
counter) doesn’t.So on line 72, I added:
Duplicated that scenario again – and it indeed printed the count.
So the problem is that from some reason – when the ” error” appears it conflicts with the ” text counter”. Please notice, that after start writing again – it seems that the ” error” is gone – but the truth is that it still in the DOM, it’s just hidden using CSS.
Then, I added the following code on Line 92:
Guess what was the output.
For the first debug line: text-counter (that’s good)
For the second debug line: undefined (not good)
How to solve it?
Solution 1: You’re using the
next()
function wrongly.When the ” error” element is being added, the text-counter field is no longer apply to the
next()
rule. Consider change it with something like:.parent().find('.text-counter')
in case each field+text-counter have a common parent.Solution 2: When a user starts typing, remove the ” error” element from the DOM.
I have made a code pen with exactly this functionality here.
Codepen
I will also add and discuss the code here, it really is not that hard.
The document.ready function insures the function loads when the DOM is ready. You then have a function that fires whenever a key is pressed on the text area. You then have the count variable that is set to the length of the value of the current text ID. You then set the count to plus one, because it is zero index. You then represent the value on the other text area.