diff --git a/README.md b/README.md index 4aa0805..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,7 +48,7 @@ $('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({ @@ -59,7 +59,7 @@ $('input').textcounter({ ## 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: %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) +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 @@ -156,4 +164,10 @@ init : function(el){} // Callback: function(element - [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 \ No newline at end of file +- [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 95d3a93..84748bf 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jquery-text-counter", - "version": "0.5.0", + "version": "0.9.1", "main": "textcounter.js", "license": "MIT", "ignore": [ diff --git a/package.json b/package.json index 4462f58..a481955 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,10 @@ "minimum", "maximum" ], - "version": "0.5.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 f598869..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.5.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 41b5d25..7db2a81 100644 --- a/textcounter.js +++ b/textcounter.js @@ -1,239 +1,358 @@ /*! -* jQuery Text Counter Plugin v0.5.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, - $formatted_counter_text = $('
').html(counterText.replace('%d', '' + counterNum + '')).contents(); + // if this is a countdown counter deduct from the max characters/words + textTotalCount = base.options.countDown ? base.options.max - textCount : textCount; - 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); + // 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, - errorText = ''; - - $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: %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) - - // 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 89d0557..f7831ef 100644 --- a/textcounter.min.js +++ b/textcounter.min.js @@ -1,8 +1,8 @@ /*! -* jQuery Text Counter Plugin v0.5.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",''+e+"")).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(g=d.options.countSpaces?f.replace(/[^\S\n|\r|\r\n]/g," ").length:f.replace(/\s/g,"").length,d.options.countExtendedCharacters){var j=f.match(/[^\x00-\xff]/gi);g=null==j?f.length:f.length+j.length}if("auto"==d.options.max){var k=d.$el.attr("maxlength");"undefined"!=typeof k&&k!==!1?d.options.max=k: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=d.options.min&&(d.options.mincount(d.el),d.clearErrors("min"))),d.options.max!==-1)if(g>=d.options.max&&0!=d.options.max)if(d.options.maxcount(d.el),d.options.stopInputAtMaximum){var l="";if("word"==d.options.type)for(var m=f.split(/[^\S\n]/g),n=0;n=d.options.max-1);)void 0!==m[n]&&(l+=m[n]+" ",n++);else if(d.options.countSpaces)l=f.substring(0,d.options.max);else for(var o=f.split(""),p=o.length,q=0,n=0;q'+e+"")}},d.clearErrors=function(a){var b=d.$el,c=d.$container;c.children(".error-text-"+a).remove(),0==c.children(".error-text").length&&(b.removeClass(d.options.inputErrorClass),c.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: %d",errorTextElement:"div",minimumErrorText:"Minimum not met",maximumErrorText:"Maximum exceeded",displayErrorText:!0,stopInputAtMaximum:!0,countSpaces:!1,countDown:!1,countDownText:"Remaining: %d",countExtendedCharacters:!1,maxunder:function(a){},minunder:function(a){},maxcount:function(a){},mincount:function(a){},init:function(a){}},a.fn.textcounter=function(b){return this.each(function(){new a.textcounter(this,b)})}}(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