diff --git a/LICENSE.md b/LICENSE.md index 6ae623c..f3a99e5 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # The MIT License (MIT) -Copyright (c) 2013 ractoon +Copyright (c) 2014 ractoon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 4fbbcb0..d7e15c6 100644 --- a/README.md +++ b/README.md @@ -10,33 +10,57 @@ Include script after the jQuery library: ``` +or + +#### npm + +Install using [npm](https://www.npmjs.com/): + +``` +npm install jquery-text-counter +``` + +or + +#### Bower + +Install using [bower](http://bower.io/): + +``` +bower install jquery-text-counter +``` + ## Usage -Basic usage: +Basic usage ([view editable code](http://jsfiddle.net/ractoon/p7x72La3/)): ```javascript -$(input).textcounter(); +$('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({ +$('input').textcounter({ type: "word", max: 15, stopInputAtMaximum: false }); ``` -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({ +$('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: @@ -59,26 +83,65 @@ If an error is present it is appended within the element. The input gains the `i ``` -### Options +## Callbacks + +### maxcount(el){} + +Fires when a counter reaches the maximum word/character count. + +### mincount(el){} + +Fires when a counter reaches the minimum word/character count. + +### init(el){} + +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 -'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 +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 @@ -89,4 +152,22 @@ If an error is present it is appended within the element. The input gains the `i ## Authors -[ractoon](http://www.ractoon.com) \ No newline at end of file +[ractoon](http://www.ractoon.com) + + +## Contributors + +- [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 +- [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 new file mode 100644 index 0000000..84748bf --- /dev/null +++ b/bower.json @@ -0,0 +1,17 @@ +{ + "name": "jquery-text-counter", + "version": "0.9.1", + "main": "textcounter.js", + "license": "MIT", + "ignore": [ + "textcounter.jquery.json", + "package.json" + ], + "authors": [ + "ractoon (https://github.com/ractoon)" + ], + "description": "A jQuery plugin for counting and limiting characters/words on text input, or textarea, elements.", + "dependencies": { + "jquery": ">=1.5.0" + } +} \ No newline at end of file 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 ed6d302..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.2.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 1b3fe83..7db2a81 100644 --- a/textcounter.js +++ b/textcounter.js @@ -1,8 +1,8 @@ /*! -* jQuery Text Counter Plugin v0.2.0 +* jQuery Text Counter Plugin v0.9.1 * https://github.com/ractoon/jQuery-Text-Counter * -* Copyright 2013 ractoon +* Copyright 2014 ractoon * Released under the MIT license */ ;(function($) { @@ -16,151 +16,294 @@ base.el = el; // Add a reverse reference to the DOM object - base.$el.data("textcounter", base); + base.$el.data('textcounter', base); base.init = function() { - base.options = $.extend({},$.textcounter.defaultOptions, options); + base.options = $.extend({}, $.textcounter.defaultOptions, options); // append the count element - var textcountID = base.$el.attr('id') + '-textcount', - counterText = base.options.countDown ? base.options.countDownText : base.options.counterText, - counterNum = base.options.countDown ? base.options.max : 0; + 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.$el.after('<' + base.options.countContainerElement + ' class="' + base.options.countContainerClass + '">' + counterText + '' + counterNum + ''); + 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 = $this.siblings('.' + base.options.countContainerClass), - $text = $this.val(), - textCount = 0, - textTotalCount = 0, - eventTriggered = e.originalEvent === undefined ? false : true; - - if (base.options.type == "word") { // word count - textCount = $text.replace(/\s+/g," ").split(' ').length; - - if ($this.val() === '') { - textCount = 0; - } - } - else { // character count - if (base.options.countSpaces) { // if need to count spaces - textCount = $text.length; - } - else { - textCount = $text.replace(/\s/g, '').length; - } - - if (base.options.countExtendedCharacters) { - var extended = $text.match(/[^\x00-\xff]/gi); - - if (extended == null) { - textCount = $text.length; - } else { - textCount = $text.length + extended.length; - } + var $this = base.$el, + $countEl = base.$container, + $text = $this.val(), + textCount = 0, + textTotalCount = 0, + eventTriggered = e.originalEvent === undefined ? false : true; + + if (!$.isEmptyObject($text)) { + textCount = base.textCount($text); + } + + // if 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'); } - } + } + else if (base.options.max == 'autocustom') { + var max = base.$el.attr(base.options.autoCustomAttr); + + if (typeof max !== 'undefined' && max !== false) { + base.options.max = max; + } + else { + base.$container.text('error: [' + base.options.autoCustomAttr + '] attribute not set'); + } + } // if this is a countdown counter deduct from the max characters/words textTotalCount = base.options.countDown ? base.options.max - textCount : textCount; - // 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) { - base.clearErrors('min'); - } - } - - if (base.options.max !== -1) { // if a maximum value has been set - 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\.\?]+/); - - for (var i = 0; i < base.options.max; i++) { - trimmedString += wordArray[i] + ' '; - } - } - 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++]; - } - } - } - - $this.val(trimmedString); - - textTotalCount = base.options.countDown ? 0 : base.options.max; - base.setCount(textTotalCount); - } else { + // 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); + 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 { - base.clearErrors('max'); - } - } + } + else { + // TextCounter: maxunder(el) Callback + base.options.maxunder(base.el); + base.clearErrors('max'); + } + } + + // hide the counter if it doesn't meet either the minimum or maximum display cutoff + if (base.options.minDisplayCutoff == -1 && base.options.maxDisplayCutoff == -1) { + base.$container.show(); + } else if (textCount <= base.options.min + base.options.minDisplayCutoff) { + base.$container.show(); + } else if (base.options.max !== -1 && textCount >= base.options.max - base.options.maxDisplayCutoff) { + base.$container.show(); + } else { + base.$container.hide(); + } }; - base.setCount = function(count) { - var $this = base.$el, - $countEl = $this.siblings('.' + base.options.countContainerClass); + base.textCount = function(text) { + var textCount = 0; + + if (base.options.type == "word") { // word count + textCount = base.wordCount(text); + } + else { // character count + textCount = base.characterCount(text); + } + + return textCount; + }; + + base.wordCount = function(text) { + return text.trim().replace(/\s+/gi, ' ').split(' ').length; + }; + + base.characterCount = function(text) { + var textCount = 0, + carriageReturnsCount = 0; + + // count carriage returns/newlines as 2 characters + if (base.options.twoCharCarriageReturn) { + carriageReturnsCount = base.twoCharCarriageReturnCount(text); + } + + if (base.options.countSpaces) { // if need to count spaces + textCount = text.replace(/[^\S\n|\r|\r\n]/g, ' ').length; + } + else { + textCount = text.replace(/\s/g, '').length; + } + + // count extended characters (e.g. Chinese) + if (base.options.countExtendedCharacters) { + var extended = text.match(/[^\x00-\xff]/gi); + + if (extended == null) { + textCount = text.length; + } else { + textCount = text.length + extended.length; + } + } + + if (base.options.twoCharCarriageReturn) { + textCount += carriageReturnsCount; + } + + return textCount; + }; + + base.twoCharCarriageReturnCount = function(text) { + var carriageReturns = text.match(/(\r\n|\n|\r)/g), + carriageReturnsCount = 0; + + if (carriageReturns !== null) { + carriageReturnsCount = carriageReturns.length; + } - $countEl.children('.text-count').text(count); + return carriageReturnsCount; + }; + + base.setCount = function(count) { + base.$text_counter.text(count); }; base.setErrors = function(type) { - var $this = base.$el, - $countEl = $this.siblings('.' + 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 + ''); - } - } + var $this = base.$el, + $countEl = base.$container, + errorText = ''; + + $this.addClass(base.options.inputErrorClass); + $countEl.addClass(base.options.counterErrorClass); + + switch(type) { + case 'min': + errorText = base.options.minimumErrorText; + break; + case 'max': + errorText = base.options.maximumErrorText; + + if (base.options.countOverflow) { + base.setOverflowMessage(); + } + + break; + } + + if (base.options.displayErrorText) { + if (!$countEl.children('.error-text-' + type).length) { + $countEl.append('<' + base.options.errorTextElement + ' class="error-text error-text-' + type + '">' + errorText + ''); + } + } }; + base.setOverflowMessage = function () { + base.hideMessage(base.$container.find('.' + base.options.textCountMessageClass)); + + base.removeOverflowMessage(); + + var overflowText = base.options.countOverflowText + .replace('%d', base.textCount(base.$el.val()) - base.options.max) + .replace('%type', base.options.type + 's'); + + var overflowDiv = base.$container.find('.' + base.options.countOverflowContainerClass).append(overflowText); + base.showMessage(overflowDiv); + }, + + base.removeOverflowMessage = function () { + base.$container.find('.' + base.options.countOverflowContainerClass).empty(); + }, + + base.showMessage = function ($selector) { + $selector.css('display', 'inline'); + }, + + base.hideMessage = function ($selector) { + $selector.css('display', 'none'); + }, + base.clearErrors = function(type) { - var $this = base.$el, - $countEl = $this.siblings('.' + base.options.countContainerClass); + var $this = base.$el, + $countEl = base.$container; - $countEl.children('.error-text-' + type).remove(); + $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); } @@ -171,29 +314,45 @@ }; $.textcounter.defaultOptions = { - 'type' : "character", // "character" or "word" - 'min' : 0, // minimum number of characters/words - 'max' : 200, // maximum number of characters/words, -1 for unlimited - '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 + '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); + new $.textcounter(this, options); }); }; -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/textcounter.min.js b/textcounter.min.js index 2222f1a..f7831ef 100644 --- a/textcounter.min.js +++ b/textcounter.min.js @@ -1,8 +1,8 @@ /*! -* jQuery Text Counter Plugin v0.2.0 +* jQuery Text Counter Plugin v0.9.1 * https://github.com/ractoon/jQuery-Text-Counter * -* Copyright 2013 ractoon +* Copyright 2014 ractoon * Released under the MIT license */ -;(function($){$.textcounter=function(el,options){var base=this;base.$el=$(el);base.el=el;base.$el.data("textcounter",base);base.init=function(){base.options=$.extend({},$.textcounter.defaultOptions,options);var textcountID=base.$el.attr("id")+"-textcount",counterText=base.options.countDown?base.options.countDownText:base.options.counterText,counterNum=base.options.countDown?base.options.max:0;base.$el.after("<"+base.options.countContainerElement+' class="'+base.options.countContainerClass+'">'+counterText+''+counterNum+"");base.$el.bind("keyup.textcounter click.textcounter blur.textcounter focus.textcounter change.textcounter paste.textcounter",base.checkLimits).trigger("click.textcounter")};base.checkLimits=function(e){var $this=base.$el,$countEl=$this.siblings("."+base.options.countContainerClass),$text=$this.val(),textCount=0,textTotalCount=0,eventTriggered=e.originalEvent===undefined?false:true;if(base.options.type=="word"){textCount=$text.replace(/\s+/g," ").split(" ").length;if($this.val()===""){textCount=0}}else{if(base.options.countSpaces){textCount=$text.length}else{textCount=$text.replace(/\s/g,"").length}if(base.options.countExtendedCharacters){var extended=$text.match(/[^\x00-\xff]/gi);if(extended==null){textCount=$text.length}else{textCount=$text.length+extended.length}}}textTotalCount=base.options.countDown?base.options.max-textCount:textCount;base.setCount(textTotalCount);if(base.options.min>0&&eventTriggered){if(textCount=base.options.min){base.clearErrors("min")}}}if(base.options.max!==-1){if(textCount>base.options.max&&base.options.max!=0){if(base.options.stopInputAtMaximum){var trimmedString="";if(base.options.type=="word"){var wordArray=$text.split(/[\s\.\?]+/);for(var i=0;i'+errorText+"")}}};base.clearErrors=function(type){var $this=base.$el,$countEl=$this.siblings("."+base.options.countContainerClass);$countEl.children(".error-text-"+type).remove();if($countEl.children(".error-text").length==0){$this.removeClass(base.options.inputErrorClass);$countEl.removeClass(base.options.counterErrorClass)}};base.init()};$.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};$.fn.textcounter=function(options){return this.each(function(){new $.textcounter(this,options)})}})(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