(function($) { var MAXLENGTH_MIN = 1; var POSITION_BEFORE = 'before'; var POSITION_AFTER = 'after'; var POSITION_NONE = 'none'; /** * Returns filtered options map from a user supplied map * @param options * @return Object */ var filterSettings_ = function(options) { if(!$.isPlainObject(options)) { //must be an object return {}; } $.each(options, function(key, value) { if(key == 'maxLength') { //allows only integers greater than zero options.maxLength = parseInt(value); if(isNaN(options.maxLength)) { delete options.maxLength; } else if(options.maxLength < MAXLENGTH_MIN) { options.maxLength = MAXLENGTH_MIN; } else if(options.maxLength > $.charcount.config.max) { options.maxLength = $.charcount.config.max; } } else if(key == 'position') { //allows only the defined positions (before, after, none) options.position = $.trim(value.toString().toLowerCase()); if($.inArray(options.position, [POSITION_BEFORE, POSITION_AFTER, POSITION_NONE]) < 0) { delete options.position; } } else if(key == 'preventOverage') { //boolean value of whatever is provided options.preventOverage = Boolean(value); } else if(key == 'classPrefix') { //any string, lower case, no leading/trailing whitespace options.classPrefix = $.trim(value.toString().toLowerCase()); } else { //removes unused data provided delete options[key]; } }); return options; }; /** * Custom selector to choose which form field types can use this plugin * @param obj * @return Boolean */ $.expr[':'].charcountable = function(obj) { return $(obj).is('textarea,input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="search"]'); }; /** * Convenience function for setting/getting of the 'maxlength' attribute * @return jQuery|int */ $.fn.maxlength = function(/*value*/) { var element = $(this); if(arguments.length > 0) { var value = parseInt(arguments[0]); if(!isNaN(value)) { element.attr('maxlength', value); } return element; } else { var maxlength = parseInt(element.attr('maxlength')); return (maxlength < MAXLENGTH_MIN || maxlength > $.charcount.config.max || isNaN(maxlength)) ? undefined : maxlength; } }; /** * Configuration and default settings */ $.charcount = { util: { //returns pluralized form of a string based on a count pluralize: function(count, singular/*, plural*/) { var plural = (arguments.length > 2) ? arguments[2] : singular + 's'; return (count == 1) ? singular : plural; } }, config: { //maximum allowed value for $.fn.maxlength and maxLength setting max: 4000 }, defaults: { //maximum length of string length of value of element (TODO: word count support?) maxLength: 250, //where to position the count display element (before, after or none) position: POSITION_BEFORE, //should the element still not display characters if over the set maxlength preventOverage: true, //prefix for used css classes classPrefix: 'charcount' } }; /** * Initializes the plugin using the options (if any) provided by the user * @param options (optional) * @return jQuery */ $.fn.charcount = function(/*options*/) { var options = arguments.length > 0 ? filterSettings_(arguments[0]) : {}; var globalSettings = $.extend({}, $.charcount.defaults, options); return this.each(function(index) { var element = $(this); if(element.is(':charcountable')) { //settings from element attributes var elementSettings = { maxLength: element.maxlength() }; if(elementSettings.maxLength == undefined) { delete elementSettings.maxLength; } var settings = $.extend({}, globalSettings, filterSettings_(elementSettings)); element.removeAttr('maxlength'); if(settings.preventOverage) { //in HTML5, 'maxlength' attribute is allowed on textareas element.maxlength(settings.maxLength); } if(settings.position != POSITION_NONE) { element.wrap('').addClass(settings.classPrefix); var countDisplay = $('').addClass(settings.classPrefix + '-display'); if(settings.position == POSITION_BEFORE) { countDisplay.insertBefore(element).addClass(settings.classPrefix + '-position-' + POSITION_BEFORE); } else if(settings.position == POSITION_AFTER) { countDisplay.insertAfter(element).addClass(settings.classPrefix + '-position-' + POSITION_AFTER); } } element.bind('keyup keypress charcount', function(evt) { var length = $(evt.target).val().length; var remaining = settings.maxLength - length; if(settings.preventOverage && remaining < 1) { evt.preventDefault(); element.val(element.val().substr(0, settings.maxLength)); //trims text for elements that do not natively limit input } if(evt.type == 'keyup' || evt.type == 'charcount') { element.trigger('update', [length, remaining]); if(settings.position != POSITION_NONE) { var display = element.parent().find('.' + settings.classPrefix + '-display').removeClass(settings.classPrefix + '-overage'); if(remaining < 0) { display.addClass(settings.classPrefix + '-overage'); } display.text(remaining >= 0 ? remaining + ' ' + $.charcount.util.pluralize(remaining, 'character') + ' remaining' : Math.abs(remaining) + ' too many characters'); } } }).trigger('charcount'); } }); }; })(jQuery);