1(function ($) { 2 'use strict'; 3 /** 4 * We need an event when the elements are destroyed 5 * because if an input is removed, we have to remove the 6 * maxlength object associated (if any). 7 * From: 8 * http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom 9 */ 10 if (!$.event.special.destroyed) { 11 $.event.special.destroyed = { 12 remove: function (o) { 13 if (o.handler) { 14 o.handler(); 15 } 16 } 17 }; 18 } 19 20 21 $.fn.extend({ 22 maxlength: function (options, callback) { 23 var documentBody = $('body'), 24 defaults = { 25 showOnReady: false, // true to always show when indicator is ready 26 alwaysShow: false, // if true the indicator it's always shown. 27 threshold: 10, // Represents how many chars left are needed to show up the counter 28 warningClass: 'label label-success', 29 limitReachedClass: 'label label-important label-danger', 30 separator: ' / ', 31 preText: '', 32 postText: '', 33 showMaxLength: true, 34 placement: 'bottom', 35 message: null, // an alternative way to provide the message text 36 showCharsTyped: true, // show the number of characters typed and not the number of characters remaining 37 validate: false, // if the browser doesn't support the maxlength attribute, attempt to type more than 38 // the indicated chars, will be prevented. 39 utf8: false, // counts using bytesize rather than length. eg: '£' is counted as 2 characters. 40 appendToParent: false, // append the indicator to the input field's parent instead of body 41 twoCharLinebreak: true, // count linebreak as 2 characters to match IE/Chrome textarea validation. As well as DB storage. 42 customMaxAttribute: null, // null = use maxlength attribute and browser functionality, string = use specified attribute instead. 43 allowOverMax: false 44 // Form submit validation is handled on your own. when maxlength has been exceeded 'overmax' class added to element 45 }; 46 47 if ($.isFunction(options) && !callback) { 48 callback = options; 49 options = {}; 50 } 51 options = $.extend(defaults, options); 52 53 54 /** 55 * Return the byte count of the specified character in UTF8 encoding. 56 * Note: This won't cover UTF-8 characters that are 4 bytes long. 57 * 58 * @param input 59 * @return {number} 60 */ 61 function utf8CharByteCount(character) { 62 var c = character.charCodeAt(); 63 // Not c then 0, else c < 128 then 1, else c < 2048 then 2, else 3 64 return !c ? 0 : c < 128 ? 1 : c < 2048 ? 2 : 3; 65 } 66 67 /** 68 * Return the length of the specified input in UTF8 encoding. 69 * 70 * @param input 71 * @return {number} 72 */ 73 function utf8Length(string) { 74 return string.split("") 75 .map(utf8CharByteCount) 76 // Prevent reduce from throwing an error if the string is empty. 77 .concat(0) 78 .reduce(function(sum, val) { return sum + val; }); 79 } 80 81 /** 82 * Return the length of the specified input. 83 * 84 * @param input 85 * @return {number} 86 */ 87 function inputLength(input) { 88 var text = input.val(); 89 90 if (options.twoCharLinebreak) { 91 // Count all line breaks as 2 characters 92 text = text.replace(/\r(?!\n)|\n(?!\r)/g, '\r\n'); 93 } else { 94 // Remove all double-character (\r\n) linebreaks, so they're counted only once. 95 text = text.replace(new RegExp('\r?\n', 'g'), '\n'); 96 } 97 98 var currentLength = 0; 99 100 if (options.utf8) { 101 currentLength = utf8Length(text); 102 } else { 103 currentLength = text.length; 104 } 105 return currentLength; 106 } 107 108 /** 109 * Truncate the text of the specified input. 110 * 111 * @param input 112 * @param limit 113 */ 114 function truncateChars(input, maxlength) { 115 var text = input.val(); 116 117 if (options.twoCharLinebreak) { 118 text = text.replace(/\r(?!\n)|\n(?!\r)/g, '\r\n'); 119 120 if (text[text.length - 1] === '\n') { 121 maxlength -= text.length % 2; 122 } 123 } 124 125 if (options.utf8) { 126 var indexedSize = text.split("").map(utf8CharByteCount); 127 for ( 128 var removedBytes = 0, 129 bytesPastMax = utf8Length(text) - maxlength 130 ;removedBytes < bytesPastMax 131 ;removedBytes += indexedSize.pop() 132 ); 133 maxlength -= (maxlength - indexedSize.length); 134 } 135 136 input.val(text.substr(0, maxlength)); 137 } 138 139 /** 140 * Return true if the indicator should be showing up. 141 * 142 * @param input 143 * @param threshold 144 * @param maxlength 145 * @return {number} 146 */ 147 function charsLeftThreshold(input, threshold, maxlength) { 148 var output = true; 149 if (!options.alwaysShow && (maxlength - inputLength(input) > threshold)) { 150 output = false;
vendor: 3,686 bytes, lines 151-268
151 } 152 return output; 153 } 154 155 /** 156 * Returns how many chars are left to complete the fill up of the form. 157 * 158 * @param input 159 * @param maxlength 160 * @return {number} 161 */ 162 function remainingChars(input, maxlength) { 163 var length = maxlength - inputLength(input); 164 return length; 165 } 166 167 /** 168 * When called displays the indicator. 169 * 170 * @param indicator 171 */ 172 function showRemaining(currentInput, indicator) { 173 indicator.css({ 174 display: 'block' 175 }); 176 currentInput.trigger('maxlength.shown'); 177 } 178 179 /** 180 * When called shows the indicator. 181 * 182 * @param indicator 183 */ 184 function hideRemaining(currentInput, indicator) { 185 186 if (options.alwaysShow) { 187 return; 188 } 189 190 indicator.css({ 191 display: 'none' 192 }); 193 currentInput.trigger('maxlength.hidden'); 194 } 195 196 /** 197 * This function updates the value in the indicator 198 * 199 * @param maxLengthThisInput 200 * @param typedChars 201 * @return String 202 */ 203 function updateMaxLengthHTML(currentInputText, maxLengthThisInput, typedChars) { 204 var output = ''; 205 if (options.message) { 206 if (typeof options.message === 'function') { 207 output = options.message(currentInputText, maxLengthThisInput); 208 } else { 209 output = options.message.replace('%charsTyped%', typedChars) 210 .replace('%charsRemaining%', maxLengthThisInput - typedChars) 211 .replace('%charsTotal%', maxLengthThisInput); 212 } 213 } else { 214 if (options.preText) { 215 output += options.preText; 216 } 217 if (!options.showCharsTyped) { 218 output += maxLengthThisInput - typedChars; 219 } 220 else { 221 output += typedChars; 222 } 223 if (options.showMaxLength) { 224 output += options.separator + maxLengthThisInput; 225 } 226 if (options.postText) { 227 output += options.postText; 228 } 229 } 230 return output; 231 } 232 233 /** 234 * This function updates the value of the counter in the indicator. 235 * Wants as parameters: the number of remaining chars, the element currently managed, 236 * the maxLength for the current input and the indicator generated for it. 237 * 238 * @param remaining 239 * @param currentInput 240 * @param maxLengthCurrentInput 241 * @param maxLengthIndicator 242 */ 243 function manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator) { 244 if (maxLengthIndicator) { 245 maxLengthIndicator.html(updateMaxLengthHTML(currentInput.val(), maxLengthCurrentInput, (maxLengthCurrentInput - remaining))); 246 247 if (remaining > 0) { 248 if (charsLeftThreshold(currentInput, options.threshold, maxLengthCurrentInput)) { 249 showRemaining(currentInput, maxLengthIndicator.removeClass(options.limitReachedClass).addClass(options.warningClass)); 250 } else { 251 hideRemaining(currentInput, maxLengthIndicator); 252 } 253 } else { 254 showRemaining(currentInput, maxLengthIndicator.removeClass(options.warningClass).addClass(options.limitReachedClass)); 255 } 256 } 257 258 if (options.customMaxAttribute) { 259 // class to use for form validation on custom maxlength attribute 260 if (remaining < 0) { 261 currentInput.addClass('overmax'); 262 } else { 263 currentInput.removeClass('overmax'); 264 } 265 } 266 } 267 268 /**
269 * This function returns an object containing all the 270 * informations about the position of the current input 271 * 272 * @param currentInput 273 * @return object {bottom height left right top width} 274 * 275 */ 276 function getPosition(currentInput) { 277 var el = currentInput[0]; 278 return $.extend({}, (typeof el.getBoundingClientRect === 'function') ? el.getBoundingClientRect() : { 279 width: el.offsetWidth, 280 height: el.offsetHeight 281 }, currentInput.offset()); 282 } 283 284 /** 285 * This function places the maxLengthIndicator based on placement config object. 286 * 287 * @param {object} placement 288 * @param {$} maxLengthIndicator 289 * @return null 290 * 291 */ 292 function placeWithCSS(placement, maxLengthIndicator) { 293 if (!placement || !maxLengthIndicator){ 294 return; 295 } 296 297 var POSITION_KEYS = [ 298 'top', 299 'bottom', 300 'left', 301 'right', 302 'position' 303 ]; 304 305 var cssPos = {}; 306 307 // filter css properties to position 308 $.each(POSITION_KEYS, function (i, key) { 309 var val = options.placement[key]; 310 if (typeof val !== 'undefined'){ 311 cssPos[key] = val; 312 } 313 }); 314 315 maxLengthIndicator.css(cssPos); 316 317 return; 318 } 319 320 321 /** 322 * This function places the maxLengthIndicator at the 323 * top / bottom / left / right of the currentInput 324 * 325 * @param currentInput 326 * @param maxLengthIndicator 327 * @return null 328 * 329 */ 330 function place(currentInput, maxLengthIndicator) { 331 var pos = getPosition(currentInput); 332 333 // Supports custom placement handler 334 if ($.type(options.placement) === 'function'){ 335 options.placement(currentInput, maxLengthIndicator, pos); 336 return; 337 } 338 339 // Supports custom placement via css positional properties 340 if ($.isPlainObject(options.placement)){ 341 placeWithCSS(options.placement, maxLengthIndicator); 342 return; 343 } 344 345 var inputOuter = currentInput.outerWidth(), 346 outerWidth = maxLengthIndicator.outerWidth(), 347 actualWidth = maxLengthIndicator.width(), 348 actualHeight = maxLengthIndicator.height(); 349 350 // get the right position if the indicator is appended to the input's parent 351 if (options.appendToParent) { 352 pos.top -= currentInput.parent().offset().top; 353 pos.left -= currentInput.parent().offset().left; 354 } 355 356 switch (options.placement) { 357 case 'bottom': 358 maxLengthIndicator.css({ top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 }); 359 break; 360 case 'top': 361 maxLengthIndicator.css({ top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 }); 362 break; 363 case 'left': 364 maxLengthIndicator.css({ top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth }); 365 break; 366 case 'right': 367 maxLengthIndicator.css({ top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }); 368 break; 369 case 'bottom-right': 370 maxLengthIndicator.css({ top: pos.top + pos.height, left: pos.left + pos.width }); 371 break; 372 case 'top-right': 373 maxLengthIndicator.css({ top: pos.top - actualHeight, left: pos.left + inputOuter }); 374 break; 375 case 'top-left': 376 maxLengthIndicator.css({ top: pos.top - actualHeight, left: pos.left - outerWidth }); 377 break; 378 case 'bottom-left': 379 maxLengthIndicator.css({ top: pos.top + currentInput.outerHeight(), left: pos.left - outerWidth }); 380 break; 381 case 'centered-right': 382 maxLengthIndicator.css({ top: pos.top + (actualHeight / 2), left: pos.left + inputOuter - outerWidth - 3 }); 383 break; 384 385 // Some more options for placements 386 case 'bottom-right-inside': 387 maxLengthIndicator.css({ top: pos.top + pos.height, left: pos.left + pos.width - outerWidth }); 388 break; 389 case 'top-right-inside': 390 maxLengthIndicator.css({ top: pos.top - actualHeight, left: pos.left + inputOuter - outerWidth }); 391 break; 392 case 'top-left-inside': 393 maxLengthIndicator.css({ top: pos.top - actualHeight, left: pos.left }); 394 break; 395 case 'bottom-left-inside': 396 maxLengthIndicator.css({ top: pos.top + currentInput.outerHeight(), left: pos.left }); 397 break; 398 } 399 } 400 401 /** 402 * This function returns true if the indicator position needs to 403 * be recalculated when the currentInput changes 404 * 405 * @return {boolean} 406 * 407 */ 408 function isPlacementMutable() { 409 return options.placement === 'bottom-right-inside' || options.placement === 'top-right-inside' || typeof options.placement === 'function' || (options.message && typeof options.message === 'function'); 410 } 411 412 /** 413 * This function retrieves the maximum length of currentInput 414 * 415 * @param currentInput 416 * @return {number} 417 * 418 */ 419 function getMaxLength(currentInput) { 420 var max = currentInput.attr('maxlength') || options.customMaxAttribute; 421 422 console.debug(max); 423 424 if (options.customMaxAttribute && !options.allowOverMax) { 425 var custom = currentInput.attr(options.customMaxAttribute); 426 if (!max || custom < max) { 427 max = custom; 428 } 429 } 430 431 if (!max) { 432 max = currentInput.attr('size'); 433 } 434 return max; 435 } 436 437 return this.each(function () { 438 439 var currentInput = $(this), 440 maxLengthCurrentInput, 441 maxLengthIndicator; 442 443 $(window).resize(function () { 444 if (maxLengthIndicator) { 445 place(currentInput, maxLengthIndicator); 446 } 447 }); 448 449 function firstInit() { 450 var maxlengthContent = updateMaxLengthHTML(currentInput.val(), maxLengthCurrentInput, '0'); 451 maxLengthCurrentInput = getMaxLength(currentInput); 452 453 if (!maxLengthIndicator) { 454 maxLengthIndicator = $('<span class="bootstrap-maxlength"></span>').css({ 455 display: 'none', 456 position: 'absolute', 457 whiteSpace: 'nowrap', 458 zIndex: 1099 459 }).html(maxlengthContent); 460 } 461 462 // We need to detect resizes if we are dealing with a textarea: 463 if (currentInput.is('textarea')) { 464 currentInput.data('maxlenghtsizex', currentInput.outerWidth()); 465 currentInput.data('maxlenghtsizey', currentInput.outerHeight()); 466 467 currentInput.mouseup(function () { 468 if (currentInput.outerWidth() !== currentInput.data('maxlenghtsizex') || currentInput.outerHeight() !== currentInput.data('maxlenghtsizey')) { 469 place(currentInput, maxLengthIndicator); 470 } 471 472 currentInput.data('maxlenghtsizex', currentInput.outerWidth()); 473 currentInput.data('maxlenghtsizey', currentInput.outerHeight()); 474 }); 475 } 476 477 if (options.appendToParent) { 478 currentInput.parent().append(maxLengthIndicator); 479 currentInput.parent().css('position', 'relative'); 480 } else { 481 documentBody.append(maxLengthIndicator); 482 } 483 484 var remaining = remainingChars(currentInput, getMaxLength(currentInput)); 485 manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator); 486 place(currentInput, maxLengthIndicator); 487 } 488 489 if (options.showOnReady) { 490 currentInput.ready(function () { 491 firstInit(); 492 }); 493 } else { 494 currentInput.focus(function () { 495 firstInit(); 496 }); 497 } 498 499 currentInput.on('maxlength.reposition', function () { 500 place(currentInput, maxLengthIndicator); 501 }); 502 503 504 currentInput.on('destroyed', function () { 505 if (maxLengthIndicator) { 506 maxLengthIndicator.remove(); 507 } 508 }); 509 510 currentInput.on('blur', function () { 511 if (maxLengthIndicator && !options.showOnReady) { 512 maxLengthIndicator.remove(); 513 } 514 }); 515 516 currentInput.on('input', function () { 517 var maxlength = getMaxLength(currentInput), 518 remaining = remainingChars(currentInput, maxlength), 519 output = true; 520 521 if (options.validate && remaining < 0) { 522 truncateChars(currentInput, maxlength); 523 output = false;
524 } else { 525 manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator); 526 } 527 528 if (isPlacementMutable()) { 529 place(currentInput, maxLengthIndicator); 530 } 531 532 return output; 533 }); 534 }); 535 } 536 }); 537}(jQuery));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.