From bbde3a2a9b5a70ec51b2e3923c2b81e10148e1bf Mon Sep 17 00:00:00 2001 From: ractoon Date: Tue, 14 Feb 2017 09:51:16 -0700 Subject: [PATCH] Two character carriage return option --- README.md | 4 +- bower.json | 2 +- package.json | 2 +- textcounter.jquery.json | 2 +- textcounter.js | 436 +++++++++++++++++++++------------------- textcounter.min.js | 4 +- 6 files changed, 234 insertions(+), 216 deletions(-) diff --git a/README.md b/README.md index c47325d..bbb066a 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ countSpaces : false, // count spaces as character 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) +twoCharCarriageReturn : false, // count carriage returns/newlines as 2 characters // Callback API maxunder : function(el){}, // Callback: function(element) - Fires when counter is under max limit @@ -156,4 +157,5 @@ 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 \ No newline at end of file diff --git a/bower.json b/bower.json index 95d3a93..5a2b2f4 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jquery-text-counter", - "version": "0.5.0", + "version": "0.6.0", "main": "textcounter.js", "license": "MIT", "ignore": [ diff --git a/package.json b/package.json index 4462f58..6d8d185 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "minimum", "maximum" ], - "version": "0.5.0", + "version": "0.6.0", "author": { "name": "ractoon", "url": "http://www.ractoon.com" diff --git a/textcounter.jquery.json b/textcounter.jquery.json index f598869..1f9668e 100644 --- a/textcounter.jquery.json +++ b/textcounter.jquery.json @@ -9,7 +9,7 @@ "minimum", "maximum" ], - "version": "0.5.0", + "version": "0.6.0", "author": { "name": "ractoon", "url": "http://www.ractoon.com" diff --git a/textcounter.js b/textcounter.js index 41b5d25..5a9e6d1 100644 --- a/textcounter.js +++ b/textcounter.js @@ -1,239 +1,255 @@ /*! -* jQuery Text Counter Plugin v0.5.0 +* jQuery Text Counter Plugin v0.6.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; + $.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 = $('
').html(counterText.replace('%d', '' + counterNum + '')).contents(); + + 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); + + // 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)) { + if (base.options.type == "word") { // word count + textCount = $text.trim().replace(/\s+/gi, ' ').split(' ').length; + } + else { // character count + // count carriage returns/newlines as 2 characters + if (base.options.twoCharCarriageReturn) { + var carriageReturns = $text.match(/(\r\n|\n|\r)/g), + carriageReturnsCount = 0; + + if (carriageReturns !== null) { + carriageReturnsCount = carriageReturns.length; + } + } + + 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; + } + } + } - // Add a reverse reference to the DOM object - base.$el.data("textcounter", base); + // if max is auto retrieve value + if (base.options.max == 'auto') { + var max = base.$el.attr('maxlength'); - base.init = function() { - base.options = $.extend({}, $.textcounter.defaultOptions, options); + if (typeof max !== 'undefined' && max !== false) { + base.options.max = max; + } + else { + base.$container.text('error: [maxlength] 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.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; - } else { - textCount = $text.length + extended.length; + base.clearErrors('min'); + } } - } - } - } - - // 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++; + + 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++; + } + } + } + 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.trim()); + + 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'); } - } } - 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++]; + }; + + 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); + + 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 + ''); } - } } + }; - $this.val(trimmedString.trim()); + base.clearErrors = function(type) { + var $this = base.$el, + $countEl = base.$container; - 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'); - } - } - }; + $countEl.children('.error-text-' + type).remove(); - base.setCount = function(count) { - base.$text_counter.text(count); - }; + if ($countEl.children('.error-text').length == 0) { + $this.removeClass(base.options.inputErrorClass); + $countEl.removeClass(base.options.counterErrorClass); + } + }; - 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 + ''); - } - } + // kick it off + base.init(); }; - base.clearErrors = function(type) { - var $this = base.$el, - $countEl = base.$container; - - $countEl.children('.error-text-' + type).remove(); - - if ($countEl.children('.error-text').length == 0) { - $this.removeClass(base.options.inputErrorClass); - $countEl.removeClass(base.options.counterErrorClass); - } + $.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) + 'twoCharCarriageReturn' : false, // count carriage returns/newlines as 2 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 }; - // 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); - }); - }; + $.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..de126cb 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.6.0 * 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(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(d.options.twoCharCarriageReturn){var j=f.match(/(\r\n|\n|\r)/g),k=0;null!==j&&(k=j.length)}if(g=d.options.countSpaces?f.replace(/[^\S\n|\r|\r\n]/g," ").length:f.replace(/\s/g,"").length,d.options.countExtendedCharacters){var l=f.match(/[^\x00-\xff]/gi);g=null==l?f.length:f.length+l.length}d.options.twoCharCarriageReturn&&(g+=k)}if("auto"==d.options.max){var m=d.$el.attr("maxlength");"undefined"!=typeof m&&m!==!1?d.options.max=m: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 n="";if("word"==d.options.type)for(var o=f.split(/[^\S\n]/g),p=0;p=d.options.max-1);)void 0!==o[p]&&(n+=o[p]+" ",p++);else if(d.options.countSpaces)n=f.substring(0,d.options.max);else for(var q=f.split(""),r=q.length,s=0,p=0;s'+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,twoCharCarriageReturn:!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