diff --git a/README.md b/README.md index 26dea5c..d7e15c6 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,21 @@ Include script after the jQuery library: ``` +or + +#### npm + +Install using [npm](https://www.npmjs.com/): + +``` +npm install jquery-text-counter +``` + +or + #### Bower -Alternatively install using [bower](http://bower.io/): +Install using [bower](http://bower.io/): ``` bower install jquery-text-counter @@ -20,13 +32,13 @@ bower install jquery-text-counter ## Usage -Basic usage: +Basic usage ([view editable code](http://jsfiddle.net/ractoon/p7x72La3/)): ```javascript $('input').textcounter(); ``` -Define maximum words and allow further input: +Define maximum words and allow further input ([view editable code](https://jsfiddle.net/ractoon/n4ufjo3b/)): ```javascript $('input').textcounter({ @@ -36,15 +48,19 @@ $('input').textcounter({ }); ``` -Define minimum characters and set custom `countDownText`: +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/) + ## Elements By default the plugin creates and appends the following element after the input: @@ -81,31 +97,51 @@ Fires when a counter reaches the minimum word/character count. Fires after the counter is initialized. +### maxunder(el){} + +Fires when counter is under max limit. + +### minunder(el){} + +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 -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 -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 @@ -124,4 +160,14 @@ init : function(el){} // Callback: function(element - [stgeneral](https://github.com/stgeneral) - count length newlines fix - [moinism](https://github.com/moinism) - callback API - [benr77](https://github.com/benr77) - bower support -- [SammyB](https://github.com/SammyB) - countdown starting count fix \ No newline at end of file +- [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 +- [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 6a201c0..84748bf 100644 --- a/bower.json +++ b/bower.json @@ -1,10 +1,11 @@ { "name": "jquery-text-counter", - "version": "0.3.3", + "version": "0.9.1", "main": "textcounter.js", "license": "MIT", "ignore": [ - "textcounter.jquery.json" + "textcounter.jquery.json", + "package.json" ], "authors": [ "ractoon (https://github.com/ractoon)" diff --git a/package.json b/package.json new file mode 100644 index 0000000..a481955 --- /dev/null +++ b/package.json @@ -0,0 +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.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 3bd2909..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.3.3", - "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 8819c45..7db2a81 100644 --- a/textcounter.js +++ b/textcounter.js @@ -1,230 +1,358 @@ /*! -* jQuery Text Counter Plugin v0.3.3 +* 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.$el.after('<' + base.options.countContainerElement + ' class="' + base.options.countContainerClass + '">' + counterText + '' + counterNum + ''); + // 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 = $this.next('.' + base.options.countContainerClass), - $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.$el.next('.' + base.options.countContainerClass).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'); - } - 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 { - base.clearErrors('max'); - } - } - }; + base.removeOverflowMessage(); - base.setCount = function(count) { - var $this = base.$el, - $countEl = $this.next('.' + base.options.countContainerClass); + var overflowText = base.options.countOverflowText + .replace('%d', base.textCount(base.$el.val()) - base.options.max) + .replace('%type', base.options.type + 's'); - $countEl.children('.text-count').text(count); - }; + var overflowDiv = base.$container.find('.' + base.options.countOverflowContainerClass).append(overflowText); + base.showMessage(overflowDiv); + }, - base.setErrors = function(type) { - var $this = base.$el, - $countEl = $this.next('.' + base.options.countContainerClass); - - $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 + ''); - } - } - }; + base.removeOverflowMessage = function () { + base.$container.find('.' + base.options.countOverflowContainerClass).empty(); + }, + + base.showMessage = function ($selector) { + $selector.css('display', 'inline'); + }, - base.clearErrors = function(type) { - var $this = base.$el, - $countEl = $this.next('.' + base.options.countContainerClass); + base.hideMessage = function ($selector) { + $selector.css('display', 'none'); + }, - $countEl.children('.error-text-' + type).remove(); + 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(); + }; + + $.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 + }; - if ($countEl.children('.error-text').length == 0) { - $this.removeClass(base.options.inputErrorClass); - $countEl.removeClass(base.options.counterErrorClass); - } + $.fn.textcounter = function(options) { + return this.each(function() { + new $.textcounter(this, options); + }); }; - // 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 - '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 - 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); \ No newline at end of file +})(jQuery); diff --git a/textcounter.min.js b/textcounter.min.js index 174fede..f7831ef 100644 --- a/textcounter.min.js +++ b/textcounter.min.js @@ -1,8 +1,8 @@ /*! -* jQuery Text Counter Plugin v0.3.3 +* jQuery Text Counter Plugin v0.9.1 * https://github.com/ractoon/jQuery-Text-Counter * * Copyright 2014 ractoon * Released under the MIT license */ -;(function(e){e.textcounter=function(t,n){var r=this;r.$el=e(t);r.el=t;r.$el.data("textcounter",r);r.init=function(){r.options=e.extend({},e.textcounter.defaultOptions,n);var t=r.options.countDown?r.options.countDownText:r.options.counterText,i=r.options.countDown?r.options.max:0;r.$el.after("<"+r.options.countContainerElement+' class="'+r.options.countContainerClass+'">'+t+''+i+"");r.$el.bind("keyup.textcounter click.textcounter blur.textcounter focus.textcounter change.textcounter paste.textcounter",r.checkLimits).trigger("click.textcounter");r.options.init(r.el)};r.checkLimits=function(t){var n=r.$el,i=n.next("."+r.options.countContainerClass),s=n.val(),o=0,u=0,a=t.originalEvent===undefined?false:true;if(!e.isEmptyObject(s)){if(r.options.type=="word"){o=s.trim().replace(/\s+/gi," ").split(" ").length}else{if(r.options.countSpaces){o=s.replace(/[^\S\n|\r|\r\n]/g," ").length}else{o=s.replace(/\s/g,"").length}if(r.options.countExtendedCharacters){var f=s.match(/[^\x00-\xff]/gi);if(f==null){o=s.length}else{o=s.length+f.length}}}}if(r.options.max=="auto"){var l=r.$el.attr("maxlength");if(typeof l!=="undefined"&&l!==false){r.options.max=l}else{r.$el.next("."+r.options.countContainerClass).text("error: [maxlength] attribute not set")}}u=r.options.countDown?r.options.max-o:o;r.setCount(u);if(r.options.min>0&&a){if(o=r.options.min){r.options.mincount(r.el);r.clearErrors("min")}}if(r.options.max!==-1){if(o>=r.options.max&&r.options.max!=0){r.options.maxcount(r.el);if(r.options.stopInputAtMaximum){var c="";if(r.options.type=="word"){var h=s.split(/[^\S\n]/g);var p=0;while(p=r.options.max-1)break;if(h[p]!==undefined){c+=h[p]+" ";p++}}}else{if(r.options.countSpaces){c=s.substring(0,r.options.max)}else{var d=s.split(""),v=d.length,m=0,p=0;while(m'+errorText+"")}}};r.clearErrors=function(e){var t=r.$el,n=t.next("."+r.options.countContainerClass);n.children(".error-text-"+e).remove();if(n.children(".error-text").length==0){t.removeClass(r.options.inputErrorClass);n.removeClass(r.options.counterErrorClass)}};r.init()};e.textcounter.defaultOptions={type:"character",min:0,max:200,countContainerElement:"div",countContainerClass:"text-count-wrapper",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,maxcount:function(e){},mincount:function(e){},init:function(e){}};e.fn.textcounter=function(t){return this.each(function(){new e.textcounter(this,t)})}})(jQuery) \ No newline at end of file +!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