diff --git a/README.md b/README.md
index d7e15c6..4b5c560 100644
--- a/README.md
+++ b/README.md
@@ -135,7 +135,7 @@ countOverflowText : "Maximum %type exceeded by %d", // count overflow
countOverflowContainerClass : "text-count-overflow-wrapper", // class applied to the count overflow wrapper
minDisplayCutoff : -1, // maximum number of characters/words above the minimum to display a count
maxDisplayCutoff : -1, // maximum number of characters/words below the maximum to display a count
-
+counterId' : null, // custom ID for the counter element (e.g. to prevent conflicts). If one is not provided, a default ID will be assigned based on the ID of the element.
// Callback API
maxunder : function(el){}, // Callback: function(element) - Fires when counter is under max limit
minunder : function(el){}, // Callback: function(element) - Fires when counter is under min limit
diff --git a/textcounter.js b/textcounter.js
index 7db2a81..8a787bb 100644
--- a/textcounter.js
+++ b/textcounter.js
@@ -21,12 +21,14 @@
base.init = function() {
base.options = $.extend({}, $.textcounter.defaultOptions, options);
+ var counterElementId = base.options.counterId ?? base.el.id + '_counter';
+
// append the count element
var counterText = base.options.countDown ? base.options.countDownText : base.options.counterText,
counterNum = base.options.countDown ? base.options.max : 0,
$formatted_counter_text = $('
').addClass(base.options.textCountMessageClass)
- .attr('aria-live', 'polite').attr('aria-atomic', 'true')
- .html(counterText.replace('%d', '' + counterNum + '')),
+ .attr('aria-live', 'polite').attr('aria-atomic', 'true').attr('id', counterElementId)
+ .html(counterText.replace('%d', '' + counterNum + '')),
$count_overflow_text = $('').addClass(base.options.countOverflowContainerClass);
base.hideMessage($count_overflow_text);
@@ -39,6 +41,10 @@
base.$text_counter = base.$container.find('span');
base.$el.after(base.$container);
+ var curDescribedBy = base.$el.attr('aria-describedby') ?? '';
+ var describedByWithCount = (curDescribedBy + ' ' + counterElementId).trim();
+ base.$el.attr('aria-describedby', describedByWithCount);
+
// bind input events
base.$el.bind('keyup.textcounter click.textcounter blur.textcounter focus.textcounter change.textcounter paste.textcounter', base.checkLimits).trigger('click.textcounter');
@@ -340,6 +346,7 @@
'countOverflowContainerClass' : "text-count-overflow-wrapper", // class applied to the count overflow wrapper
'minDisplayCutoff' : -1, // maximum number of characters/words above the minimum to display a count
'maxDisplayCutoff' : -1, // maximum number of characters/words below the maximum to display a count
+ 'counterId' : null, // custom ID for the counter element (e.g. to prevent conflicts). If one is not provided, a default ID will be assigned based on the ID of the element.
// Callback API
'maxunder' : function(el){}, // Callback: function(element) - Fires when counter under max limit
diff --git a/textcounter.min.js b/textcounter.min.js
index f7831ef..fd20a7c 100644
--- a/textcounter.min.js
+++ b/textcounter.min.js
@@ -5,4 +5,4 @@
* Copyright 2014 ractoon
* Released under the MIT license
*/
-!function(t){t.textcounter=function(o,n){var e=this;e.$el=t(o),e.el=o,e.$el.data("textcounter",e),e.init=function(){e.options=t.extend({},t.textcounter.defaultOptions,n);var o=e.options.countDown?e.options.countDownText:e.options.counterText,r=e.options.countDown?e.options.max:0,i=t("").addClass(e.options.textCountMessageClass).attr("aria-live","polite").attr("aria-atomic","true").html(o.replace("%d",''+r+"")),s=t("").addClass(e.options.countOverflowContainerClass);e.hideMessage(s),e.$container=t("<"+e.options.countContainerElement+"/>").addClass(e.options.countContainerClass).append(i).append(s),e.$text_counter=e.$container.find("span"),e.$el.after(e.$container),e.$el.bind("keyup.textcounter click.textcounter blur.textcounter focus.textcounter change.textcounter paste.textcounter",e.checkLimits).trigger("click.textcounter"),e.options.init(e.el)},e.checkLimits=function(o){var n=e.$el,r=(e.$container,n.val()),i=0,s=0,a=void 0!==o.originalEvent;if(t.isEmptyObject(r)||(i=e.textCount(r)),"auto"==e.options.max)void 0!==(u=e.$el.attr("maxlength"))&&!1!==u?e.options.max=u:e.$container.text("error: [maxlength] attribute not set");else if("autocustom"==e.options.max){var u;void 0!==(u=e.$el.attr(e.options.autoCustomAttr))&&!1!==u?e.options.max=u:e.$container.text("error: ["+e.options.autoCustomAttr+"] attribute not set")}if(s=e.options.countDown?e.options.max-i:i,e.setCount(s),e.options.min>0&&a&&(i=e.options.min&&(e.options.mincount(e.el),e.clearErrors("min"))),-1!==e.options.max)if(i===e.options.max&&0!==e.options.max)e.options.maxcount(e.el),e.clearErrors("max");else if(i>e.options.max&&0!==e.options.max)if(e.options.stopInputAtMaximum){var c="";if("word"==e.options.type)for(var l=r.split(/[^\S\n]/g),p=0;p=e.options.max);)void 0!==l[p]&&(c+=l[p]+" ",p++);else{var m=e.options.twoCharCarriageReturn?e.options.max-e.twoCharCarriageReturnCount(r):e.options.max;if(e.options.countSpaces)c=r.substring(0,m);else{var x=r.split(""),C=x.length,f=0;for(p=0;f=e.options.max-e.options.maxDisplayCutoff?e.$container.show():e.$container.hide()},e.textCount=function(t){return"word"==e.options.type?e.wordCount(t):e.characterCount(t)},e.wordCount=function(t){return t.trim().replace(/\s+/gi," ").split(" ").length},e.characterCount=function(t){var o=0,n=0;if(e.options.twoCharCarriageReturn&&(n=e.twoCharCarriageReturnCount(t)),o=e.options.countSpaces?t.replace(/[^\S\n|\r|\r\n]/g," ").length:t.replace(/\s/g,"").length,e.options.countExtendedCharacters){var r=t.match(/[^\x00-\xff]/gi);o=null==r?t.length:t.length+r.length}return e.options.twoCharCarriageReturn&&(o+=n),o},e.twoCharCarriageReturnCount=function(t){var o=t.match(/(\r\n|\n|\r)/g),n=0;return null!==o&&(n=o.length),n},e.setCount=function(t){e.$text_counter.text(t)},e.setErrors=function(t){var o=e.$el,n=e.$container,r="";switch(o.addClass(e.options.inputErrorClass),n.addClass(e.options.counterErrorClass),t){case"min":r=e.options.minimumErrorText;break;case"max":r=e.options.maximumErrorText,e.options.countOverflow&&e.setOverflowMessage()}e.options.displayErrorText&&(n.children(".error-text-"+t).length||n.append("<"+e.options.errorTextElement+' class="error-text error-text-'+t+'">'+r+""+e.options.errorTextElement+">"))},e.setOverflowMessage=function(){e.hideMessage(e.$container.find("."+e.options.textCountMessageClass)),e.removeOverflowMessage();var t=e.options.countOverflowText.replace("%d",e.textCount(e.$el.val())-e.options.max).replace("%type",e.options.type+"s"),o=e.$container.find("."+e.options.countOverflowContainerClass).append(t);e.showMessage(o)},e.removeOverflowMessage=function(){e.$container.find("."+e.options.countOverflowContainerClass).empty()},e.showMessage=function(t){t.css("display","inline")},e.hideMessage=function(t){t.css("display","none")},e.clearErrors=function(t){var o=e.$el,n=e.$container;n.children(".error-text-"+t).remove(),0==n.children(".error-text").length&&(e.removeOverflowMessage(),e.showMessage(e.$container.find("."+e.options.textCountMessageClass)),o.removeClass(e.options.inputErrorClass),n.removeClass(e.options.counterErrorClass))},e.init()},t.textcounter.defaultOptions={type:"character",min:0,max:200,autoCustomAttr:"counterlimit",countContainerElement:"div",countContainerClass:"text-count-wrapper",textCountMessageClass:"text-count-message",textCountClass:"text-count",inputErrorClass:"error",counterErrorClass:"error",counterText:"Total Count: %d",errorTextElement:"div",minimumErrorText:"Minimum not met",maximumErrorText:"Maximum exceeded",displayErrorText:!0,stopInputAtMaximum:!0,countSpaces:!1,countDown:!1,countDownText:"Remaining: %d",countExtendedCharacters:!1,twoCharCarriageReturn:!1,countOverflow:!1,countOverflowText:"Maximum %type exceeded by %d",countOverflowContainerClass:"text-count-overflow-wrapper",minDisplayCutoff:-1,maxDisplayCutoff:-1,maxunder:function(t){},minunder:function(t){},maxcount:function(t){},mincount:function(t){},init:function(t){}},t.fn.textcounter=function(o){return this.each((function(){new t.textcounter(this,o)}))}}(jQuery);
\ No newline at end of file
+(function($){$.textcounter=function(el,options){var base=this;base.$el=$(el);base.el=el;base.$el.data("textcounter",base);base.init=function(){base.options=$.extend({},$.textcounter.defaultOptions,options);var counterElementId=base.options.counterId??base.el.id+"_counter";var counterText=base.options.countDown?base.options.countDownText:base.options.counterText,counterNum=base.options.countDown?base.options.max:0,$formatted_counter_text=$("").addClass(base.options.textCountMessageClass).attr("aria-live","polite").attr("aria-atomic","true").attr("id",counterElementId).html(counterText.replace("%d",''+counterNum+"")),$count_overflow_text=$("").addClass(base.options.countOverflowContainerClass);base.hideMessage($count_overflow_text);base.$container=$("<"+base.options.countContainerElement+"/>").addClass(base.options.countContainerClass).append($formatted_counter_text).append($count_overflow_text);base.$text_counter=base.$container.find("span");base.$el.after(base.$container);var curDescribedBy=base.$el.attr("aria-describedby")??"";var describedByWithCount=(curDescribedBy+" "+counterElementId).trim();base.$el.attr("aria-describedby",describedByWithCount);base.$el.bind("keyup.textcounter click.textcounter blur.textcounter focus.textcounter change.textcounter paste.textcounter",base.checkLimits).trigger("click.textcounter");base.options.init(base.el)};base.checkLimits=function(e){var $this=base.$el,$countEl=base.$container,$text=$this.val(),textCount=0,textTotalCount=0,eventTriggered=e.originalEvent===undefined?false:true;if(!$.isEmptyObject($text)){textCount=base.textCount($text)}if(base.options.max=="auto"){var max=base.$el.attr("maxlength");if(typeof max!=="undefined"&&max!==false){base.options.max=max}else{base.$container.text("error: [maxlength] attribute not set")}}else if(base.options.max=="autocustom"){var max=base.$el.attr(base.options.autoCustomAttr);if(typeof max!=="undefined"&&max!==false){base.options.max=max}else{base.$container.text("error: ["+base.options.autoCustomAttr+"] attribute not set")}}textTotalCount=base.options.countDown?base.options.max-textCount:textCount;base.setCount(textTotalCount);if(base.options.min>0&&eventTriggered){if(textCount=base.options.min){base.options.mincount(base.el);base.clearErrors("min")}}if(base.options.max!==-1){if(textCount===base.options.max&&base.options.max!==0){base.options.maxcount(base.el);base.clearErrors("max")}else if(textCount>base.options.max&&base.options.max!==0){if(base.options.stopInputAtMaximum){var trimmedString="";if(base.options.type=="word"){var wordArray=$text.split(/[^\S\n]/g);var i=0;while(i=base.options.max)break;if(wordArray[i]!==undefined){trimmedString+=wordArray[i]+" ";i++}}}else{var maxLimit=base.options.twoCharCarriageReturn?base.options.max-base.twoCharCarriageReturnCount($text):base.options.max;if(base.options.countSpaces){trimmedString=$text.substring(0,maxLimit)}else{var charArray=$text.split(""),totalCharacters=charArray.length,charCount=0,i=0;while(charCount=base.options.max-base.options.maxDisplayCutoff){base.$container.show()}else{base.$container.hide()}};base.textCount=function(text){var textCount=0;if(base.options.type=="word"){textCount=base.wordCount(text)}else{textCount=base.characterCount(text)}return textCount};base.wordCount=function(text){return text.trim().replace(/\s+/gi," ").split(" ").length};base.characterCount=function(text){var textCount=0,carriageReturnsCount=0;if(base.options.twoCharCarriageReturn){carriageReturnsCount=base.twoCharCarriageReturnCount(text)}if(base.options.countSpaces){textCount=text.replace(/[^\S\n|\r|\r\n]/g," ").length}else{textCount=text.replace(/\s/g,"").length}if(base.options.countExtendedCharacters){var extended=text.match(/[^\x00-\xff]/gi);if(extended==null){textCount=text.length}else{textCount=text.length+extended.length}}if(base.options.twoCharCarriageReturn){textCount+=carriageReturnsCount}return textCount};base.twoCharCarriageReturnCount=function(text){var carriageReturns=text.match(/(\r\n|\n|\r)/g),carriageReturnsCount=0;if(carriageReturns!==null){carriageReturnsCount=carriageReturns.length}return carriageReturnsCount};base.setCount=function(count){base.$text_counter.text(count)};base.setErrors=function(type){var $this=base.$el,$countEl=base.$container,errorText="";$this.addClass(base.options.inputErrorClass);$countEl.addClass(base.options.counterErrorClass);switch(type){case"min":errorText=base.options.minimumErrorText;break;case"max":errorText=base.options.maximumErrorText;if(base.options.countOverflow){base.setOverflowMessage()}break}if(base.options.displayErrorText){if(!$countEl.children(".error-text-"+type).length){$countEl.append("<"+base.options.errorTextElement+' class="error-text error-text-'+type+'">'+errorText+""+base.options.errorTextElement+">")}}};base.setOverflowMessage=function(){base.hideMessage(base.$container.find("."+base.options.textCountMessageClass));base.removeOverflowMessage();var overflowText=base.options.countOverflowText.replace("%d",base.textCount(base.$el.val())-base.options.max).replace("%type",base.options.type+"s");var overflowDiv=base.$container.find("."+base.options.countOverflowContainerClass).append(overflowText);base.showMessage(overflowDiv)},base.removeOverflowMessage=function(){base.$container.find("."+base.options.countOverflowContainerClass).empty()},base.showMessage=function($selector){$selector.css("display","inline")},base.hideMessage=function($selector){$selector.css("display","none")},base.clearErrors=function(type){var $this=base.$el,$countEl=base.$container;$countEl.children(".error-text-"+type).remove();if($countEl.children(".error-text").length==0){base.removeOverflowMessage();base.showMessage(base.$container.find("."+base.options.textCountMessageClass));$this.removeClass(base.options.inputErrorClass);$countEl.removeClass(base.options.counterErrorClass)}};base.init()};$.textcounter.defaultOptions={type:"character",min:0,max:200,autoCustomAttr:"counterlimit",countContainerElement:"div",countContainerClass:"text-count-wrapper",textCountMessageClass:"text-count-message",textCountClass:"text-count",inputErrorClass:"error",counterErrorClass:"error",counterText:"Total Count: %d",errorTextElement:"div",minimumErrorText:"Minimum not met",maximumErrorText:"Maximum exceeded",displayErrorText:true,stopInputAtMaximum:true,countSpaces:false,countDown:false,countDownText:"Remaining: %d",countExtendedCharacters:false,twoCharCarriageReturn:false,countOverflow:false,countOverflowText:"Maximum %type exceeded by %d",countOverflowContainerClass:"text-count-overflow-wrapper",minDisplayCutoff:-1,maxDisplayCutoff:-1,counterId:null,maxunder:function(el){},minunder:function(el){},maxcount:function(el){},mincount:function(el){},init:function(el){}};$.fn.textcounter=function(options){return this.each(function(){new $.textcounter(this,options)})}})(jQuery);
\ No newline at end of file