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 * Return the length of the specified input. 55 * 56 * @param input 57 * @return {number} 58 */ 59 function inputLength(input) { 60 var text = input.val(); 61 62 if (options.twoCharLinebreak) { 63 // Count all line breaks as 2 characters 64 text = text.replace(/\r(?!\n)|\n(?!\r)/g, '\r\n'); 65 } else { 66 // Remove all double-character (\r\n) linebreaks, so they're counted only once. 67 text = text.replace(new RegExp('\r?\n', 'g'), '\n'); 68 } 69 70 var currentLength = 0; 71 72 if (options.utf8) { 73 currentLength = utf8Length(text); 74 } else { 75 currentLength = text.length; 76 } 77 return currentLength; 78 } 79 80 /** 81 * Truncate the text of the specified input. 82 * 83 * @param input 84 * @param limit 85 */ 86 function truncateChars(input, maxlength) { 87 var text = input.val(); 88 var newlines = 0; 89 90 if (options.twoCharLinebreak) { 91 text = text.replace(/\r(?!\n)|\n(?!\r)/g, '\r\n'); 92 93 if (text.substr(text.length - 1) === '\n' && text.length % 2 === 1) { 94 newlines = 1; 95 } 96 } 97 98 input.val(text.substr(0, maxlength - newlines)); 99 } 100 101 /** 102 * Return the length of the specified input in UTF8 encoding. 103 * 104 * @param input 105 * @return {number} 106 */ 107 function utf8Length(string) { 108 var utf8length = 0; 109 for (var n = 0; n < string.length; n++) { 110 var c = string.charCodeAt(n); 111 if (c < 128) { 112 utf8length++; 113 } 114 else if ((c > 127) && (c < 2048)) { 115 utf8length = utf8length + 2; 116 } 117 else { 118 utf8length = utf8length + 3; 119 } 120 } 121 return utf8length; 122 } 123 124 /** 125 * Return true if the indicator should be showing up. 126 * 127 * @param input 128 * @param thereshold 129 * @param maxlength 130 * @return {number} 131 */ 132 function charsLeftThreshold(input, thereshold, maxlength) { 133 var output = true; 134 if (!options.alwaysShow && (maxlength - inputLength(input) > thereshold)) { 135 output = false;
vendor: 3,686 bytes, lines 136-253
136 } 137 return output; 138 } 139 140 /** 141 * Returns how many chars are left to complete the fill up of the form. 142 * 143 * @param input 144 * @param maxlength 145 * @return {number} 146 */ 147 function remainingChars(input, maxlength) { 148 var length = maxlength - inputLength(input); 149 return length; 150 } 151 152 /** 153 * When called displays the indicator. 154 * 155 * @param indicator 156 */ 157 function showRemaining(currentInput, indicator) { 158 indicator.css({ 159 display: 'block' 160 }); 161 currentInput.trigger('maxlength.shown'); 162 } 163 164 /** 165 * When called shows the indicator. 166 * 167 * @param indicator 168 */ 169 function hideRemaining(currentInput, indicator) { 170 171 if (options.alwaysShow) { 172 return; 173 } 174 175 indicator.css({ 176 display: 'none' 177 }); 178 currentInput.trigger('maxlength.hidden'); 179 } 180 181 /** 182 * This function updates the value in the indicator 183 * 184 * @param maxLengthThisInput 185 * @param typedChars 186 * @return String 187 */ 188 function updateMaxLengthHTML(currentInputText, maxLengthThisInput, typedChars) { 189 var output = ''; 190 if (options.message) { 191 if (typeof options.message === 'function') { 192 output = options.message(currentInputText, maxLengthThisInput); 193 } else { 194 output = options.message.replace('%charsTyped%', typedChars) 195 .replace('%charsRemaining%', maxLengthThisInput - typedChars) 196 .replace('%charsTotal%', maxLengthThisInput); 197 } 198 } else { 199 if (options.preText) { 200 output += options.preText; 201 } 202 if (!options.showCharsTyped) { 203 output += maxLengthThisInput - typedChars; 204 } 205 else { 206 output += typedChars; 207 } 208 if (options.showMaxLength) { 209 output += options.separator + maxLengthThisInput; 210 } 211 if (options.postText) { 212 output += options.postText; 213 } 214 } 215 return output; 216 } 217 218 /** 219 * This function updates the value of the counter in the indicator. 220 * Wants as parameters: the number of remaining chars, the element currently managed, 221 * the maxLength for the current input and the indicator generated for it. 222 * 223 * @param remaining 224 * @param currentInput 225 * @param maxLengthCurrentInput 226 * @param maxLengthIndicator 227 */ 228 function manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator) { 229 if (maxLengthIndicator) { 230 maxLengthIndicator.html(updateMaxLengthHTML(currentInput.val(), maxLengthCurrentInput, (maxLengthCurrentInput - remaining))); 231 232 if (remaining > 0) { 233 if (charsLeftThreshold(currentInput, options.threshold, maxLengthCurrentInput)) { 234 showRemaining(currentInput, maxLengthIndicator.removeClass(options.limitReachedClass).addClass(options.warningClass)); 235 } else { 236 hideRemaining(currentInput, maxLengthIndicator); 237 } 238 } else { 239 showRemaining(currentInput, maxLengthIndicator.removeClass(options.warningClass).addClass(options.limitReachedClass)); 240 } 241 } 242 243 if (options.customMaxAttribute) { 244 // class to use for form validation on custom maxlength attribute 245 if (remaining < 0) { 246 currentInput.addClass('overmax'); 247 } else { 248 currentInput.removeClass('overmax'); 249 } 250 } 251 } 252 253 /**
254 * This function returns an object containing all the 255 * informations about the position of the current input 256 * 257 * @param currentInput 258 * @return object {bottom height left right top width} 259 * 260 */ 261 function getPosition(currentInput) { 262 var el = currentInput[0]; 263 return $.extend({}, (typeof el.getBoundingClientRect === 'function') ? el.getBoundingClientRect() : { 264 width: el.offsetWidth, 265 height: el.offsetHeight 266 }, currentInput.offset()); 267 } 268 269 /** 270 * This function places the maxLengthIndicator at the 271 * top / bottom / left / right of the currentInput 272 * 273 * @param currentInput 274 * @param maxLengthIndicator 275 * @return null 276 * 277 */ 278 function place(currentInput, maxLengthIndicator) { 279 var pos = getPosition(currentInput); 280 281 // Supports custom placement handler 282 if ($.type(options.placement) === 'function'){ 283 options.placement(currentInput, maxLengthIndicator, pos); 284 return; 285 } 286 287 // Supports custom placement via css positional properties 288 if ($.isPlainObject(options.placement)){ 289 placeWithCSS(options.placement, maxLengthIndicator); 290 return; 291 } 292 293 var inputOuter = currentInput.outerWidth(), 294 outerWidth = maxLengthIndicator.outerWidth(), 295 actualWidth = maxLengthIndicator.width(), 296 actualHeight = maxLengthIndicator.height(); 297 298 // get the right position if the indicator is appended to the input's parent 299 if (options.appendToParent) { 300 pos.top -= currentInput.parent().offset().top; 301 pos.left -= currentInput.parent().offset().left; 302 } 303 304 switch (options.placement) { 305 case 'bottom': 306 maxLengthIndicator.css({ top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 }); 307 break; 308 case 'top': 309 maxLengthIndicator.css({ top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 }); 310 break; 311 case 'left': 312 maxLengthIndicator.css({ top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth }); 313 break; 314 case 'right': 315 maxLengthIndicator.css({ top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }); 316 break; 317 case 'bottom-right': 318 maxLengthIndicator.css({ top: pos.top + pos.height, left: pos.left + pos.width }); 319 break; 320 case 'top-right': 321 maxLengthIndicator.css({ top: pos.top - actualHeight, left: pos.left + inputOuter }); 322 break; 323 case 'top-left': 324 maxLengthIndicator.css({ top: pos.top - actualHeight, left: pos.left - outerWidth }); 325 break; 326 case 'bottom-left': 327 maxLengthIndicator.css({ top: pos.top + currentInput.outerHeight(), left: pos.left - outerWidth }); 328 break; 329 case 'centered-right': 330 maxLengthIndicator.css({ top: pos.top + (actualHeight / 2), left: pos.left + inputOuter - outerWidth - 3 }); 331 break; 332 333 // Some more options for placements 334 case 'bottom-right-inside': 335 maxLengthIndicator.css({ top: pos.top + pos.height, left: pos.left + pos.width - outerWidth }); 336 break; 337 case 'top-right-inside': 338 maxLengthIndicator.css({ top: pos.top - actualHeight, left: pos.left + inputOuter - outerWidth }); 339 break; 340 case 'top-left-inside': 341 maxLengthIndicator.css({ top: pos.top - actualHeight, left: pos.left }); 342 break; 343 case 'bottom-left-inside': 344 maxLengthIndicator.css({ top: pos.top + currentInput.outerHeight(), left: pos.left }); 345 break; 346 } 347 } 348 349 /** 350 * This function places the maxLengthIndicator based on placement config object. 351 * 352 * @param {object} placement 353 * @param {$} maxLengthIndicator 354 * @return null 355 * 356 */ 357 function placeWithCSS(placement, maxLengthIndicator) { 358 if (!placement || !maxLengthIndicator){ 359 return; 360 } 361
362 var POSITION_KEYS = [ 363 'top', 364 'bottom', 365 'left', 366 'right', 367 'position' 368 ]; 369 370 var cssPos = {}; 371 372 // filter css properties to position 373 $.each(POSITION_KEYS, function (i, key) { 374 var val = options.placement[key]; 375 if (typeof val !== 'undefined'){ 376 cssPos[key] = val; 377 } 378 }); 379 380 maxLengthIndicator.css(cssPos); 381 382 return; 383 } 384 385 /** 386 * This function returns true if the indicator position needs to 387 * be recalculated when the currentInput changes 388 * 389 * @return {boolean} 390 * 391 */ 392 function isPlacementMutable() { 393 return options.placement === 'bottom-right-inside' || options.placement === 'top-right-inside' || typeof options.placement === 'function' || (options.message && typeof options.message === 'function'); 394 } 395 396 /** 397 * This function retrieves the maximum length of currentInput 398 * 399 * @param currentInput 400 * @return {number} 401 * 402 */ 403 function getMaxLength(currentInput) { 404 var max = currentInput.attr('maxlength') || options.customMaxAttribute; 405 406 if (options.customMaxAttribute && !options.allowOverMax) { 407 var custom = currentInput.attr(options.customMaxAttribute); 408 if (!max || custom < max) { 409 max = custom; 410 } 411 } 412 413 if (!max) { 414 max = currentInput.attr('size'); 415 } 416 return max; 417 } 418 419 return this.each(function () { 420 421 var currentInput = $(this), 422 maxLengthCurrentInput, 423 maxLengthIndicator; 424 425 $(window).resize(function () { 426 if (maxLengthIndicator) { 427 place(currentInput, maxLengthIndicator); 428 } 429 }); 430 431 function firstInit() { 432 var maxlengthContent = updateMaxLengthHTML(currentInput.val(), maxLengthCurrentInput, '0'); 433 maxLengthCurrentInput = getMaxLength(currentInput); 434 435 if (!maxLengthIndicator) { 436 maxLengthIndicator = $('<span class="bootstrap-maxlength"></span>').css({ 437 display: 'none', 438 position: 'absolute', 439 whiteSpace: 'nowrap', 440 zIndex: 1099 441 }).html(maxlengthContent); 442 } 443 444 // We need to detect resizes if we are dealing with a textarea: 445 if (currentInput.is('textarea')) { 446 currentInput.data('maxlenghtsizex', currentInput.outerWidth()); 447 currentInput.data('maxlenghtsizey', currentInput.outerHeight()); 448 449 currentInput.mouseup(function () { 450 if (currentInput.outerWidth() !== currentInput.data('maxlenghtsizex') || currentInput.outerHeight() !== currentInput.data('maxlenghtsizey')) { 451 place(currentInput, maxLengthIndicator); 452 } 453 454 currentInput.data('maxlenghtsizex', currentInput.outerWidth()); 455 currentInput.data('maxlenghtsizey', currentInput.outerHeight()); 456 }); 457 } 458 459 if (options.appendToParent) { 460 currentInput.parent().append(maxLengthIndicator); 461 currentInput.parent().css('position', 'relative'); 462 } else { 463 documentBody.append(maxLengthIndicator); 464 } 465 466 var remaining = remainingChars(currentInput, getMaxLength(currentInput)); 467 manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator); 468 place(currentInput, maxLengthIndicator); 469 } 470 471 if (options.showOnReady) { 472 currentInput.ready(function () { 473 firstInit(); 474 }); 475 } else { 476 currentInput.focus(function () { 477 firstInit(); 478 }); 479 } 480 481 currentInput.on('maxlength.reposition', function () { 482 place(currentInput, maxLengthIndicator); 483 }); 484 485 486 currentInput.on('destroyed', function () { 487 if (maxLengthIndicator) { 488 maxLengthIndicator.remove(); 489 } 490 }); 491 492 currentInput.on('blur', function () { 493 if (maxLengthIndicator && !options.showOnReady && !options.alwaysShow) { 494 maxLengthIndicator.remove(); 495 } 496 }); 497 498 currentInput.on('input', function () { 499 var maxlength = getMaxLength(currentInput), 500 remaining = remainingChars(currentInput, maxlength), 501 output = true; 502 503 if (options.validate && remaining < 0) { 504 truncateChars(currentInput, maxlength); 505 output = false;
506 } else { 507 manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator); 508 } 509 510 if (isPlacementMutable()) { 511 place(currentInput, maxLengthIndicator); 512 } 513 514 return output; 515 }); 516 }); 517 } 518 }); 519}(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.