UNPKG

jquery-text-counter

Version:

jQuery plugin to count text/characters in a textarea or input. Allows error notifications on minimum or maximum number of characters/words.

359 lines (293 loc) 16.5 kB
/*! * jQuery Text Counter Plugin v0.9.0 * 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; // 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 = $('<div/>').addClass(base.options.textCountMessageClass) .attr('aria-live', 'assertive').attr('aria-atomic', 'true') .html(counterText.replace('%d', '<span class="' + base.options.textCountClass + '">' + counterNum + '</span>')), $count_overflow_text = $('<div/>').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); } // 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'); // 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'); } } // hide the counter if it doesn't meet either the minimum or maximum display cutoff if (base.options.minDisplayCutoff == -1 && base.options.maxDisplayCutoff == -1) { base.$container.show(); } else if (textCount <= base.options.min + base.options.minDisplayCutoff) { base.$container.show(); } else if (base.options.max !== -1 && textCount >= base.options.max - base.options.maxDisplayCutoff) { base.$container.show(); } else { base.$container.hide(); } }; base.textCount = function(text) { var textCount = 0; if (base.options.type == "word") { // word count textCount = base.wordCount(text); } else { // character count textCount = base.characterCount(text); } return textCount; }; base.wordCount = function(text) { return text.trim().replace(/\s+/gi, ' ').split(' ').length; }; base.characterCount = function(text) { var textCount = 0, carriageReturnsCount = 0; // count carriage returns/newlines as 2 characters if (base.options.twoCharCarriageReturn) { carriageReturnsCount = base.twoCharCarriageReturnCount(text); } if (base.options.countSpaces) { // if need to count spaces textCount = text.replace(/[^\S\n|\r|\r\n]/g, ' ').length; } else { textCount = text.replace(/\s/g, '').length; } // count extended characters (e.g. Chinese) if (base.options.countExtendedCharacters) { var extended = text.match(/[^\x00-\xff]/gi); if (extended == null) { textCount = text.length; } else { textCount = text.length + extended.length; } } if (base.options.twoCharCarriageReturn) { textCount += carriageReturnsCount; } return textCount; }; base.twoCharCarriageReturnCount = function(text) { var carriageReturns = text.match(/(\r\n|\n|\r)/g), carriageReturnsCount = 0; if (carriageReturns !== null) { carriageReturnsCount = carriageReturns.length; } return carriageReturnsCount; }; base.setCount = function(count) { 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 + '</' + base.options.errorTextElement + '>'); } } }; base.setOverflowMessage = function () { base.hideMessage(base.$container.find('.' + base.options.textCountMessageClass)); base.removeOverflowMessage(); var overflowText = base.options.countOverflowText .replace('%d', base.textCount(base.$el.val()) - base.options.max) .replace('%type', base.options.type + 's'); var overflowDiv = base.$container.find('.' + base.options.countOverflowContainerClass).append(overflowText); base.showMessage(overflowDiv); }, base.removeOverflowMessage = function () { base.$container.find('.' + base.options.countOverflowContainerClass).empty(); }, base.showMessage = function ($selector) { $selector.css('display', 'inline'); }, base.hideMessage = function ($selector) { $selector.css('display', 'none'); }, base.clearErrors = function(type) { var $this = base.$el, $countEl = base.$container; $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 }; $.fn.textcounter = function(options) { return this.each(function() { new $.textcounter(this, options); }); }; })(jQuery);