diff --git a/README.md b/README.md index 2ee9318..d7e15c6 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Basic usage ([view editable code](http://jsfiddle.net/ractoon/p7x72La3/)): $('input').textcounter(); ``` -Define maximum words and allow further input ([view editable code](http://jsfiddle.net/ractoon/n4ufjo3b/)): +Define maximum words and allow further input ([view editable code](https://jsfiddle.net/ractoon/n4ufjo3b/)): ```javascript $('input').textcounter({ @@ -48,18 +48,18 @@ $('input').textcounter({ }); ``` -Define minimum characters and set custom `countDownText` ([view editable code](http://jsfiddle.net/ractoon/jx8awxbb/)): +Define minimum characters and set custom `countDownText` ([view editable code](https://jsfiddle.net/ractoon/jx8awxbb/)): ```javascript $('input').textcounter({ min: 20, - countDownText: "Characters Left: " + countDownText: "%d characters remaining" }); ``` ## Example -[View editable example](http://jsfiddle.net/ractoon/1xkuyp46/1/) +[View editable example](http://jsfiddle.net/ractoon/1xkuyp46/) ## Elements @@ -109,31 +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: ", // 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: ", // count down text -countExtendedCharacters : false, // count extended UTF-8 characters as 2 bytes (such as Chinese 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 @@ -155,4 +163,11 @@ init : function(el){} // Callback: function(element - [SammyB](https://github.com/SammyB) - countdown starting count fix - [eprincen2](https://github.com/eprincen2) - jQuery Validate compatibility fix - [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 \ No newline at end of file +- [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 +- [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 dd1b11c..84748bf 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jquery-text-counter", - "version": "0.4.0", + "version": "0.9.1", "main": "textcounter.js", "license": "MIT", "ignore": [ diff --git a/package.json b/package.json index c8acc96..a481955 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,31 @@ { - "name": "jquery-text-counter", - "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": [ - "jquery-plugin", - "ecosystem:jquery", - "words", - "characters", - "counter", - "minimum", - "maximum" - ], - "version": "0.4.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", - "repository": "https://github.com/ractoon/jQuery-Text-Counter", - "dependencies": { - "jquery": ">=1.5" - } -} \ No newline at end of file + "name": "jquery-text-counter", + "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": [ + "jquery-plugin", + "ecosystem:jquery", + "words", + "characters", + "counter", + "minimum", + "maximum" + ], + "version": "0.9.1", + "author": { + "name": "ractoon", + "url": "https://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", + "repository": "https://github.com/ractoon/jQuery-Text-Counter", + "dependencies": { + "jquery": ">=1.5" + } +} diff --git a/textcounter.jquery.json b/textcounter.jquery.json deleted file mode 100644 index 1666241..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.4.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 87393f4..7db2a81 100644 --- a/textcounter.js +++ b/textcounter.js @@ -1,238 +1,358 @@ /*! -* jQuery Text Counter Plugin v0.4.0 +* jQuery Text Counter Plugin v0.9.1 * https://github.com/ractoon/jQuery-Text-Counter * * Copyright 2014 ractoon * Released under the MIT license */ ;(function($) { - $.textcounter = function(el, options) { - // To avoid scope issues, use 'base' instead of 'this' - // to reference this class from internal events and functions. - var base = this; + $.textcounter = function(el, options) { + // To avoid scope issues, use 'base' instead of 'this' + // to reference this class from internal events and functions. + var base = this; + + // Access to jQuery and DOM versions of element + base.$el = $(el); + base.el = el; + + // Add a reverse reference to the DOM object + base.$el.data('textcounter', base); + + base.init = function() { + base.options = $.extend({}, $.textcounter.defaultOptions, options); + + // 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 + '')), + $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); + + // bind input events + base.$el.bind('keyup.textcounter click.textcounter blur.textcounter focus.textcounter change.textcounter paste.textcounter', base.checkLimits).trigger('click.textcounter'); + + // TextCounter: init(el) Callback + 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); + } - // Access to jQuery and DOM versions of element - base.$el = $(el); - base.el = el; + // if max is auto retrieve value + if (base.options.max == 'auto') { + var max = base.$el.attr('maxlength'); - // Add a reverse reference to the DOM object - base.$el.data("textcounter", base); + 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); - base.init = function() { - base.options = $.extend({}, $.textcounter.defaultOptions, options); + if (typeof max !== 'undefined' && max !== false) { + base.options.max = max; + } + else { + base.$container.text('error: [' + base.options.autoCustomAttr + '] attribute not set'); + } + } - // append the count element - var counterText = base.options.countDown ? base.options.countDownText : base.options.counterText, - counterNum = base.options.countDown ? base.options.max : 0; + // if this is a countdown counter deduct from the max characters/words + textTotalCount = base.options.countDown ? base.options.max - textCount : textCount; - base.$text_counter = $('').addClass(base.options.textCountClass).text(counterNum); - base.$container = $('<' + base.options.countContainerElement + '/>').addClass(base.options.countContainerClass).text(counterText).append(base.$text_counter); - base.$el.after(base.$container); + // set the current text count + base.setCount(textTotalCount); - // bind input events - base.$el.bind('keyup.textcounter click.textcounter blur.textcounter focus.textcounter change.textcounter paste.textcounter', base.checkLimits).trigger('click.textcounter'); + if (base.options.min > 0 && eventTriggered) { // if a minimum value has been set + if (textCount < base.options.min) { + base.setErrors('min'); - // TextCounter: init(el) Callback - base.options.init(base.el); - }; + // TextCounter: minunder(el) Callback + base.options.minunder(base.el); + } + else if (textCount >= base.options.min) { + // TextCounter: mincount(el) Callback + base.options.mincount(base.el); + + base.clearErrors('min'); + } + } + + if (base.options.max !== -1) { // if a maximum value has been set + 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 = ''; + + if (base.options.type == "word") { // word type + var wordArray = $text.split(/[^\S\n]/g); + var i = 0; + + // iterate over individual words + while (i < wordArray.length) { + // if over the maximum words allowed break; + if (i >= base.options.max) break; + + if (wordArray[i] !== undefined) { + trimmedString += wordArray[i] + ' '; + i++; + } + } + } + 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, maxLimit); + } + else { + var charArray = $text.split(''), + totalCharacters = charArray.length, + charCount = 0, + i = 0; + + while (charCount < maxLimit && i < totalCharacters) { + if (charArray[i] !== ' ') charCount++; + trimmedString += charArray[i++]; + } + } + } + + $this.val(trimmedString.trim()); + + textCount = base.textCount($this.val()); + textTotalCount = base.options.countDown ? base.options.max - textCount : textCount; + base.setCount(textTotalCount); + } else { + base.setErrors('max'); + } + } + else { + // TextCounter: maxunder(el) Callback + base.options.maxunder(base.el); + base.clearErrors('max'); + } + } - 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)) { - if (base.options.type == "word") { // word count - textCount = $text.trim().replace(/\s+/gi, ' ').split(' ').length; - } - else { // character count - 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; - } - - if (base.options.countExtendedCharacters) { - var extended = $text.match(/[^\x00-\xff]/gi); - - if (extended == null) { - textCount = $text.length; + // 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 { - textCount = $text.length + extended.length; - } - } - } - } - - // if max is auto retrieve value - 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'); - } - } - - // if this is a countdown counter deduct from the max characters/words - textTotalCount = base.options.countDown ? base.options.max - textCount : textCount; - - // set the current text count - base.setCount(textTotalCount); - - - if (base.options.min > 0 && eventTriggered) { // if a minimum value has been set - if (textCount < base.options.min) { - base.setErrors('min'); - - // TextCounter: minunder(el) Callback - base.options.minunder(base.el); - } - else if (textCount >= base.options.min) { - // TextCounter: mincount(el) Callback - base.options.mincount(base.el); - - base.clearErrors('min'); - } - } - - if (base.options.max !== -1) { // if a maximum value has been set - if (textCount >= base.options.max && base.options.max != 0) { - // TextCounter: maxcount(el) Callback - base.options.maxcount(base.el); - - if (base.options.stopInputAtMaximum) { // if the string should be trimmed at the maximum length - var trimmedString = ''; - - if (base.options.type == "word") { // word type - var wordArray = $text.split(/[^\S\n]/g); - var i = 0; - - // iterate over individual words - while (i < wordArray.length) { - // if over the maximum words allowed break; - if (i >= base.options.max - 1) break; - - if (wordArray[i] !== undefined) { - trimmedString += wordArray[i] + ' '; - i++; + 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; } - } - } - else { // character type - if (base.options.countSpaces) { // if spaces should be counted - trimmedString = $text.substring(0, base.options.max); - } - else { - var charArray = $text.split(''), - totalCharacters = charArray.length, - charCount = 0, - i = 0; - - while (charCount < base.options.max && i < totalCharacters) { - if (charArray[i] !== ' ') charCount++; - trimmedString += charArray[i++]; + } + + 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 + ''); } - } } + }; - $this.val(trimmedString.trim()); + base.setOverflowMessage = function () { + base.hideMessage(base.$container.find('.' + base.options.textCountMessageClass)); - textTotalCount = base.options.countDown ? 0 : base.options.max; - base.setCount(textTotalCount); - } else { - base.setErrors('max'); - } - } - else { - // TextCounter: maxunder(el) Callback - base.options.maxunder(base.el); - base.clearErrors('max'); - } - } - }; + base.removeOverflowMessage(); - base.setCount = function(count) { - base.$text_counter.text(count); - }; + var overflowText = base.options.countOverflowText + .replace('%d', base.textCount(base.$el.val()) - base.options.max) + .replace('%type', base.options.type + 's'); - base.setErrors = function(type) { - var $this = base.$el, - $countEl = $base.$container; - - $this.addClass(base.options.inputErrorClass); - $countEl.addClass(base.options.counterErrorClass); - - if (base.options.displayErrorText) { - switch(type) { - case 'min': - errorText = base.options.minimumErrorText; - break; - case 'max': - errorText = base.options.maximumErrorText; - break; - } - - if (!$countEl.children('.error-text-' + type).length) { - $countEl.append('<' + base.options.errorTextElement + ' class="error-text error-text-' + type + '">' + errorText + ''); - } - } - }; + var overflowDiv = base.$container.find('.' + base.options.countOverflowContainerClass).append(overflowText); + base.showMessage(overflowDiv); + }, + + base.removeOverflowMessage = function () { + base.$container.find('.' + base.options.countOverflowContainerClass).empty(); + }, - base.clearErrors = function(type) { - var $this = base.$el, - $countEl = base.$container; + base.showMessage = function ($selector) { + $selector.css('display', 'inline'); + }, - $countEl.children('.error-text-' + type).remove(); + base.hideMessage = function ($selector) { + $selector.css('display', 'none'); + }, - if ($countEl.children('.error-text').length == 0) { - $this.removeClass(base.options.inputErrorClass); - $countEl.removeClass(base.options.counterErrorClass); - } + 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); + } + }; + + // kick it off + base.init(); }; - // kick it off - base.init(); - }; - - $.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: ", // 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: ", // count down text - 'countExtendedCharacters' : false, // count extended UTF-8 characters as 2 bytes (such as Chinese characters) - - // 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 - }; - - $.fn.textcounter = function(options) { - return this.each(function() { - new $.textcounter(this, options); - }); - }; + $.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, '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 + }; + + $.fn.textcounter = function(options) { + return this.each(function() { + new $.textcounter(this, options); + }); + }; })(jQuery); diff --git a/textcounter.min.js b/textcounter.min.js index 5aef493..f7831ef 100644 --- a/textcounter.min.js +++ b/textcounter.min.js @@ -1,11 +1,8 @@ /*! -* jQuery Text Counter Plugin v0.4.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(c,b){var d=this;d.$el=a(c);d.el=c;d.$el.data("textcounter",d);d.init=function(){d.options=a.extend({},a.textcounter.defaultOptions,b);var f=d.options.countDown?d.options.countDownText:d.options.counterText,e=d.options.countDown?d.options.max:0;d.$text_counter=a("").addClass(d.options.textCountClass).text(e);d.$container=a("<"+d.options.countContainerElement+"/>").addClass(d.options.countContainerClass).text(f).append(d.$text_counter);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(o){var p=d.$el,t=d.$container,r=p.val(),h=0,u=0,s=o.originalEvent===undefined?false:true;if(!a.isEmptyObject(r)){if(d.options.type=="word"){h=r.trim().replace(/\s+/gi," ").split(" ").length}else{if(d.options.countSpaces){h=r.replace(/[^\S\n|\r|\r\n]/g," ").length}else{h=r.replace(/\s/g,"").length -}if(d.options.countExtendedCharacters){var l=r.match(/[^\x00-\xff]/gi);if(l==null){h=r.length}else{h=r.length+l.length}}}}if(d.options.max=="auto"){var q=d.$el.attr("maxlength");if(typeof q!=="undefined"&&q!==false){d.options.max=q}else{d.$container.text("error: [maxlength] attribute not set")}}u=d.options.countDown?d.options.max-h:h;d.setCount(u);if(d.options.min>0&&s){if(h=d.options.min){d.options.mincount(d.el);d.clearErrors("min")}}}if(d.options.max!==-1){if(h>=d.options.max&&d.options.max!=0){d.options.maxcount(d.el);if(d.options.stopInputAtMaximum){var k="";if(d.options.type=="word"){var m=r.split(/[^\S\n]/g);var j=0;while(j=d.options.max-1){break}if(m[j]!==undefined){k+=m[j]+" ";j++}}}else{if(d.options.countSpaces){k=r.substring(0,d.options.max)}else{var g=r.split(""),n=g.length,f=0,j=0;while(f'+errorText+"")}}};d.clearErrors=function(e){var g=d.$el,f=d.$container;f.children(".error-text-"+e).remove();if(f.children(".error-text").length==0){g.removeClass(d.options.inputErrorClass);f.removeClass(d.options.counterErrorClass)}};d.init()};a.textcounter.defaultOptions={type:"character",min:0,max:200,countContainerElement:"div",countContainerClass:"text-count-wrapper",textCountClass:"text-count",inputErrorClass:"error",counterErrorClass:"error",counterText:"Total Count: ",errorTextElement:"div",minimumErrorText:"Minimum not met",maximumErrorText:"Maximum exceeded",displayErrorText:true,stopInputAtMaximum:true,countSpaces:false,countDown:false,countDownText:"Remaining: ",countExtendedCharacters:false,maxunder:function(b){},minunder:function(b){},maxcount:function(b){},mincount:function(b){},init:function(b){}}; -a.fn.textcounter=function(b){return this.each(function(){new a.textcounter(this,b)})}})(jQuery); +!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.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