diff --git a/README.md b/README.md index bbb066a..d7e15c6 100644 --- a/README.md +++ b/README.md @@ -109,32 +109,39 @@ Fires when counter is under min limit. ## Options ```javascript -type : "character", // "character" or "word" -min : 0, // minimum number of characters/words -max : 200, // maximum number of characters/words, -1 for unlimited, 'auto' to use maxlength attribute -countContainerElement : "div", // HTML element to wrap the text count in -countContainerClass : "text-count-wrapper", // class applied to the countContainerElement -textCountClass : "text-count", // class applied to the counter length -inputErrorClass : "error", // error class appended to the input element if error occurs -counterErrorClass : "error", // error class appended to the countContainerElement if error occurs -counterText : "Total Count: %d", // counter text, %d replaced with count value -errorTextElement : "div", // error text element -minimumErrorText : "Minimum not met", // error message for minimum not met, -maximumErrorText : "Maximum exceeded", // error message for maximum range exceeded, -displayErrorText : true, // display error text messages for minimum/maximum values -stopInputAtMaximum : true, // stop further text input if maximum reached -countSpaces : false, // count spaces as character (only for "character" type) -countDown : false, // if the counter should deduct from maximum characters/words rather than counting up -countDownText : "Remaining: %d", // count down text, %d replaced with remaining value -countExtendedCharacters : false, // count extended UTF-8 characters as 2 bytes (such as Chinese characters) -twoCharCarriageReturn : false, // count carriage returns/newlines as 2 characters +type : "character", // "character" or "word" +min : 0, // minimum number of characters/words +max : 200, // maximum number of characters/words, -1 for unlimited, 'auto' to use maxlength attribute, , 'autocustom' to use a custom attribute for the length (must set "autoCustomAttr") +autoCustomAttr : "counterlimit", // custom attribute name with the counter limit if the max is 'autocustom' +countContainerElement : "div", // HTML element to wrap the text count in +countContainerClass : "text-count-wrapper", // class applied to the countContainerElement +textCountMessageClass : "text-count-message", // class applied to the counter message +textCountClass : "text-count", // class applied to the counter length (the count number) +inputErrorClass : "error", // error class appended to the input element if error occurs +counterErrorClass : "error", // error class appended to the countContainerElement if error occurs +counterText : "Total Count: %d", // counter text +errorTextElement : "div", // error text element +minimumErrorText : "Minimum not met", // error message for minimum not met, +maximumErrorText : "Maximum exceeded", // error message for maximum range exceeded, +displayErrorText : true, // display error text messages for minimum/maximum values +stopInputAtMaximum : true, // stop further text input if maximum reached +countSpaces : false, // count spaces as character (only for "character" type) +countDown : false, // if the counter should deduct from maximum characters/words rather than counting up +countDownText : "Remaining: %d", // count down text +countExtendedCharacters : false, // count extended UTF-8 characters as 2 bytes (such as Chinese characters) +twoCharCarriageReturn : false, // count carriage returns/newlines as 2 characters +countOverflow : false, // display text overflow element +countOverflowText : "Maximum %type exceeded by %d", // count overflow text +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 // 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 -maxcount : function(el){}, // Callback: function(element) - Fires when the counter hits the maximum word/character count -mincount : function(el){}, // Callback: function(element) - Fires when the counter hits the minimum word/character count -init : function(el){} // Callback: function(element) - Fires after the counter is initially setup +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 +maxcount : function(el){}, // Callback: function(element) - Fires when the counter hits the maximum word/character count +mincount : function(el){}, // Callback: function(element) - Fires when the counter hits the minimum word/character count +init : function(el){} // Callback: function(element) - Fires after the counter is initially setup ``` ## Development @@ -158,4 +165,9 @@ init : function(el){} // Callback: function(element - [Hexodus](https://github.com/Hexodus) - minunder/maxunder events - [juliovedovatto](https://github.com/juliovedovatto) / [alvaro-canepa](https://github.com/alvaro-canepa) - multiple classes support for counter container - [dtipson](https://github.com/dtipson) - multiple classes error fix -- [jmichalicek](https://github.com/jmichalicek) - count carriage returns/newlines as 2 characters \ No newline at end of file +- [jmichalicek](https://github.com/jmichalicek) - count carriage returns/newlines as 2 characters +- [diptopol](https://github.com/diptopol) - `stopInputAtMaximum` with `twoCharCarriageReturn` count fix, trimmed newline calculation fix, maximum text reached condition fix, text count overflow notification +- [trevorloflin](https://github.com/trevorloflin) - `minDisplayCutoff` and `maxDisplayCutoff` options +- [t3mujin](https://github.com/t3mujin) - autocustom support (maxlength workaround), text fixes +- [goranmiric ](https://github.com/goranmiric) - accessibility enhancements +- [ameshkin](https://github.com/ameshkin) - accessibility adjustments \ No newline at end of file diff --git a/bower.json b/bower.json index 5a2b2f4..84748bf 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jquery-text-counter", - "version": "0.6.0", + "version": "0.9.1", "main": "textcounter.js", "license": "MIT", "ignore": [ diff --git a/package.json b/package.json index 6d8d185..a481955 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,10 @@ "minimum", "maximum" ], - "version": "0.6.0", + "version": "0.9.1", "author": { "name": "ractoon", - "url": "http://www.ractoon.com" + "url": "https://www.ractoon.com" }, "licenses": [ { diff --git a/textcounter.jquery.json b/textcounter.jquery.json deleted file mode 100644 index 1f9668e..0000000 --- a/textcounter.jquery.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "textcounter", - "title": "jQuery Text Counter", - "description": "jQuery plugin to count text/characters in a textarea or input. Allows error notifications on minimum or maximum number of characters/words.", - "keywords": [ - "words", - "characters", - "counter", - "minimum", - "maximum" - ], - "version": "0.6.0", - "author": { - "name": "ractoon", - "url": "http://www.ractoon.com" - }, - "licenses": [ - { - "type": "MIT", - "url": "https://github.com/ractoon/jQuery-Text-Counter/blob/master/LICENSE.md" - } - ], - "bugs": "https://github.com/ractoon/jQuery-Text-Counter/issues", - "homepage": "https://github.com/ractoon/jQuery-Text-Counter", - "docs": "https://github.com/ractoon/jQuery-Text-Counter", - "dependencies": { - "jquery": ">=1.5" - } -} \ No newline at end of file diff --git a/textcounter.js b/textcounter.js index 5a9e6d1..7db2a81 100644 --- a/textcounter.js +++ b/textcounter.js @@ -1,5 +1,5 @@ /*! -* jQuery Text Counter Plugin v0.6.0 +* jQuery Text Counter Plugin v0.9.1 * https://github.com/ractoon/jQuery-Text-Counter * * Copyright 2014 ractoon @@ -24,9 +24,18 @@ // 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 = $('
').html(counterText.replace('%d', '')).contents(); + $formatted_counter_text = $('').addClass(base.options.textCountMessageClass) + .attr('aria-live', 'polite').attr('aria-atomic', 'true') + .html(counterText.replace('%d', '')), + $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.$container = $('<' + base.options.countContainerElement + '/>').addClass(base.options.countContainerClass).append($formatted_counter_text); base.$text_counter = base.$container.find('span'); base.$el.after(base.$container); @@ -46,42 +55,7 @@ eventTriggered = e.originalEvent === undefined ? false : true; if (!$.isEmptyObject($text)) { - if (base.options.type == "word") { // word count - textCount = $text.trim().replace(/\s+/gi, ' ').split(' ').length; - } - else { // character count - // count carriage returns/newlines as 2 characters - if (base.options.twoCharCarriageReturn) { - var carriageReturns = $text.match(/(\r\n|\n|\r)/g), - carriageReturnsCount = 0; - - if (carriageReturns !== null) { - carriageReturnsCount = carriageReturns.length; - } - } - - if (base.options.countSpaces) { // if need to count spaces - textCount = $text.replace(/[^\S\n|\r|\r\n]/g, ' ').length; - } - else { - textCount = $text.replace(/\s/g, '').length; - } - - // count extended characters (e.g. Chinese) - 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; - } - } + textCount = base.textCount($text); } // if max is auto retrieve value @@ -95,6 +69,16 @@ 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'); + } + } // if this is a countdown counter deduct from the max characters/words textTotalCount = base.options.countDown ? base.options.max - textCount : textCount; @@ -118,10 +102,12 @@ } if (base.options.max !== -1) { // if a maximum value has been set - if (textCount >= base.options.max && base.options.max != 0) { + if (textCount === base.options.max && base.options.max !== 0) { // TextCounter: maxcount(el) Callback base.options.maxcount(base.el); + base.clearErrors('max'); + } else if (textCount > base.options.max && base.options.max !== 0) { if (base.options.stopInputAtMaximum) { // if the string should be trimmed at the maximum length var trimmedString = ''; @@ -132,7 +118,7 @@ // iterate over individual words while (i < wordArray.length) { // if over the maximum words allowed break; - if (i >= base.options.max - 1) break; + if (i >= base.options.max) break; if (wordArray[i] !== undefined) { trimmedString += wordArray[i] + ' '; @@ -141,8 +127,12 @@ } } else { // character type + var maxLimit = (base.options.twoCharCarriageReturn) ? + base.options.max - base.twoCharCarriageReturnCount($text) + : base.options.max; + if (base.options.countSpaces) { // if spaces should be counted - trimmedString = $text.substring(0, base.options.max); + trimmedString = $text.substring(0, maxLimit); } else { var charArray = $text.split(''), @@ -150,7 +140,7 @@ charCount = 0, i = 0; - while (charCount < base.options.max && i < totalCharacters) { + while (charCount < maxLimit && i < totalCharacters) { if (charArray[i] !== ' ') charCount++; trimmedString += charArray[i++]; } @@ -159,7 +149,8 @@ $this.val(trimmedString.trim()); - textTotalCount = base.options.countDown ? 0 : base.options.max; + textCount = base.textCount($this.val()); + textTotalCount = base.options.countDown ? base.options.max - textCount : textCount; base.setCount(textTotalCount); } else { base.setErrors('max'); @@ -171,6 +162,79 @@ base.clearErrors('max'); } } + + // hide the counter if it doesn't meet either the minimum or maximum display cutoff + if (base.options.minDisplayCutoff == -1 && base.options.maxDisplayCutoff == -1) { + base.$container.show(); + } else if (textCount <= base.options.min + base.options.minDisplayCutoff) { + base.$container.show(); + } else if (base.options.max !== -1 && textCount >= 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") { // word count + textCount = base.wordCount(text); + } + else { // character count + 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; + + // count carriage returns/newlines as 2 characters + if (base.options.twoCharCarriageReturn) { + carriageReturnsCount = base.twoCharCarriageReturnCount(text); + } + + if (base.options.countSpaces) { // if need to count spaces + textCount = text.replace(/[^\S\n|\r|\r\n]/g, ' ').length; + } + else { + textCount = text.replace(/\s/g, '').length; + } + + // count extended characters (e.g. Chinese) + 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) { @@ -185,22 +249,52 @@ $this.addClass(base.options.inputErrorClass); $countEl.addClass(base.options.counterErrorClass); - if (base.options.displayErrorText) { - switch(type) { - case 'min': + switch(type) { + case 'min': errorText = base.options.minimumErrorText; break; - case 'max': + 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; @@ -208,6 +302,8 @@ $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); } @@ -218,32 +314,39 @@ }; $.textcounter.defaultOptions = { - 'type' : "character", // "character" or "word" - 'min' : 0, // minimum number of characters/words - 'max' : 200, // maximum number of characters/words, -1 for unlimited, 'auto' to use maxlength attribute - 'countContainerElement' : "div", // HTML element to wrap the text count in - 'countContainerClass' : "text-count-wrapper", // class applied to the countContainerElement - 'textCountClass' : "text-count", // class applied to the counter length - 'inputErrorClass' : "error", // error class appended to the input element if error occurs - 'counterErrorClass' : "error", // error class appended to the countContainerElement if error occurs - 'counterText' : "Total Count: %d", // counter text - 'errorTextElement' : "div", // error text element - 'minimumErrorText' : "Minimum not met", // error message for minimum not met, - 'maximumErrorText' : "Maximum exceeded", // error message for maximum range exceeded, - 'displayErrorText' : true, // display error text messages for minimum/maximum values - 'stopInputAtMaximum' : true, // stop further text input if maximum reached - 'countSpaces' : false, // count spaces as character (only for "character" type) - 'countDown' : false, // if the counter should deduct from maximum characters/words rather than counting up - 'countDownText' : "Remaining: %d", // count down text - 'countExtendedCharacters' : false, // count extended UTF-8 characters as 2 bytes (such as Chinese characters) - 'twoCharCarriageReturn' : false, // count carriage returns/newlines as 2 characters + 'type' : "character", // "character" or "word" + 'min' : 0, // minimum number of characters/words + 'max' : 200, // maximum number of characters/words, -1 for unlimited, 'auto' to use maxlength attribute, 'autocustom' to use a custom attribute for the length (must set "autoCustomAttr") + 'autoCustomAttr' : "counterlimit", // custom attribute name with the counter limit if the max is 'autocustom' + 'countContainerElement' : "div", // HTML element to wrap the text count in + 'countContainerClass' : "text-count-wrapper", // class applied to the countContainerElement + 'textCountMessageClass' : "text-count-message", // class applied to the counter message + 'textCountClass' : "text-count", // class applied to the counter length (the count number) + 'inputErrorClass' : "error", // error class appended to the input element if error occurs + 'counterErrorClass' : "error", // error class appended to the countContainerElement if error occurs + 'counterText' : "Total Count: %d", // counter text + 'errorTextElement' : "div", // error text element + 'minimumErrorText' : "Minimum not met", // error message for minimum not met, + 'maximumErrorText' : "Maximum exceeded", // error message for maximum range exceeded, + 'displayErrorText' : true, // display error text messages for minimum/maximum values + 'stopInputAtMaximum' : true, // stop further text input if maximum reached + 'countSpaces' : false, // count spaces as character (only for "character" type) + 'countDown' : false, // if the counter should deduct from maximum characters/words rather than counting up + 'countDownText' : "Remaining: %d", // count down text + 'countExtendedCharacters' : false, // count extended UTF-8 characters as 2 bytes (such as Chinese characters) + 'twoCharCarriageReturn' : false, // count carriage returns/newlines as 2 characters + 'countOverflow' : false, // display text overflow element + 'countOverflowText' : "Maximum %type exceeded by %d", // count overflow text + '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 // Callback API - 'maxunder' : function(el){}, // Callback: function(element) - Fires when counter under max limit - 'minunder' : function(el){}, // Callback: function(element) - Fires when counter under min limit - 'maxcount' : function(el){}, // Callback: function(element) - Fires when the counter hits the maximum word/character count - 'mincount' : function(el){}, // Callback: function(element) - Fires when the counter hits the minimum word/character count - 'init' : function(el){} // Callback: function(element) - Fires after the counter is initially setup + 'maxunder' : function(el){}, // Callback: function(element) - Fires when counter under max limit + 'minunder' : function(el){}, // Callback: function(element) - Fires when counter under min limit + 'maxcount' : function(el){}, // Callback: function(element) - Fires when the counter hits the maximum word/character count + 'mincount' : function(el){}, // Callback: function(element) - Fires when the counter hits the minimum word/character count + 'init' : function(el){} // Callback: function(element) - Fires after the counter is initially setup }; $.fn.textcounter = function(options) { diff --git a/textcounter.min.js b/textcounter.min.js index de126cb..f7831ef 100644 --- a/textcounter.min.js +++ b/textcounter.min.js @@ -1,8 +1,8 @@ /*! -* jQuery Text Counter Plugin v0.6.0 +* jQuery Text Counter Plugin v0.9.1 * https://github.com/ractoon/jQuery-Text-Counter * * Copyright 2014 ractoon * Released under the MIT license */ -!function(a){a.textcounter=function(b,c){var d=this;d.$el=a(b),d.el=b,d.$el.data("textcounter",d),d.init=function(){d.options=a.extend({},a.textcounter.defaultOptions,c);var b=d.options.countDown?d.options.countDownText:d.options.counterText,e=d.options.countDown?d.options.max:0,f=a("").html(b.replace("%d",'")).contents();d.$container=a("<"+d.options.countContainerElement+"/>").addClass(d.options.countContainerClass).append(f),d.$text_counter=d.$container.find("span"),d.$el.after(d.$container),d.$el.bind("keyup.textcounter click.textcounter blur.textcounter focus.textcounter change.textcounter paste.textcounter",d.checkLimits).trigger("click.textcounter"),d.options.init(d.el)},d.checkLimits=function(b){var c=d.$el,f=(d.$container,c.val()),g=0,h=0,i=void 0!==b.originalEvent;if(!a.isEmptyObject(f))if("word"==d.options.type)g=f.trim().replace(/\s+/gi," ").split(" ").length;else{if(d.options.twoCharCarriageReturn){var j=f.match(/(\r\n|\n|\r)/g),k=0;null!==j&&(k=j.length)}if(g=d.options.countSpaces?f.replace(/[^\S\n|\r|\r\n]/g," ").length:f.replace(/\s/g,"").length,d.options.countExtendedCharacters){var l=f.match(/[^\x00-\xff]/gi);g=null==l?f.length:f.length+l.length}d.options.twoCharCarriageReturn&&(g+=k)}if("auto"==d.options.max){var m=d.$el.attr("maxlength");"undefined"!=typeof m&&m!==!1?d.options.max=m:d.$container.text("error: [maxlength] attribute not set")}if(h=d.options.countDown?d.options.max-g:g,d.setCount(h),d.options.min>0&&i&&(g