bootstrap-maxlength.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. (function ($) {
  2. 'use strict';
  3. $.fn.extend({
  4. maxlength: function (options, callback) {
  5. var documentBody = $('body'),
  6. defaults = {
  7. alwaysShow: false, // if true the indicator it's always shown.
  8. threshold: 10, // Represents how many chars left are needed to show up the counter
  9. warningClass: 'label label-success',
  10. limitReachedClass: 'label label-important',
  11. separator: ' / ',
  12. preText: '',
  13. postText: '',
  14. showMaxLength : true,
  15. placement: 'bottom',
  16. showCharsTyped: true, // show the number of characters typed and not the number of characters remaining
  17. validate: false, // if the browser doesn't support the maxlength attribute, attempt to type more than
  18. // the indicated chars, will be prevented.
  19. utf8: false // counts using bytesize rather than length. eg: '£' is counted as 2 characters.
  20. };
  21. if ($.isFunction(options) && !callback) {
  22. callback = options;
  23. options = {};
  24. }
  25. options = $.extend(defaults, options);
  26. /**
  27. * Return the length of the specified input.
  28. *
  29. * @param input
  30. * @return {number}
  31. */
  32. function inputLength(input) {
  33. var text = input.val();
  34. // Remove all double-character (\r\n) linebreaks, so they're counted only once.
  35. text = text.replace(new RegExp('\r?\n','g'), '\n');
  36. // var matches = text.match(/\n/g);
  37. var currentLength = 0;
  38. if (options.utf8) {
  39. currentLength = utf8Length(input.val());
  40. } else {
  41. currentLength = input.val().length;
  42. }
  43. return currentLength;
  44. }
  45. /**
  46. * Return the length of the specified input in UTF8 encoding.
  47. *
  48. * @param input
  49. * @return {number}
  50. */
  51. function utf8Length(string) {
  52. var utf8length = 0;
  53. for (var n = 0; n < string.length; n++) {
  54. var c = string.charCodeAt(n);
  55. if (c < 128) {
  56. utf8length++;
  57. }
  58. else if((c > 127) && (c < 2048)) {
  59. utf8length = utf8length+2;
  60. }
  61. else {
  62. utf8length = utf8length+3;
  63. }
  64. }
  65. return utf8length;
  66. }
  67. /**
  68. * Return true if the indicator should be showing up.
  69. *
  70. * @param input
  71. * @param thereshold
  72. * @param maxlength
  73. * @return {number}
  74. */
  75. function charsLeftThreshold(input, thereshold, maxlength) {
  76. var output = true;
  77. if (!options.alwaysShow && (maxlength - inputLength(input) > thereshold)) {
  78. output = false;
  79. }
  80. return output;
  81. }
  82. /**
  83. * Returns how many chars are left to complete the fill up of the form.
  84. *
  85. * @param input
  86. * @param maxlength
  87. * @return {number}
  88. */
  89. function remainingChars(input, maxlength) {
  90. var length = maxlength - inputLength(input);
  91. return length;
  92. }
  93. /**
  94. * When called displays the indicator.
  95. *
  96. * @param indicator
  97. */
  98. function showRemaining(indicator) {
  99. indicator.css({
  100. display: 'block'
  101. });
  102. }
  103. /**
  104. * When called shows the indicator.
  105. *
  106. * @param indicator
  107. */
  108. function hideRemaining(indicator) {
  109. indicator.css({
  110. display: 'none'
  111. });
  112. }
  113. /**
  114. * This function updates the value in the indicator
  115. *
  116. * @param maxLengthThisInput
  117. * @param typedChars
  118. * @return String
  119. */
  120. function updateMaxLengthHTML(maxLengthThisInput, typedChars) {
  121. var output = '';
  122. if (options.message){
  123. output = options.message.replace('%charsTyped%', typedChars)
  124. .replace('%charsRemaining%', maxLengthThisInput - typedChars)
  125. .replace('%charsTotal%', maxLengthThisInput);
  126. } else {
  127. if (options.preText) {
  128. output += options.preText;
  129. }
  130. if (!options.showCharsTyped) {
  131. output += maxLengthThisInput - typedChars;
  132. }
  133. else {
  134. output += typedChars;
  135. }
  136. if (options.showMaxLength) {
  137. output += options.separator + maxLengthThisInput;
  138. }
  139. if (options.postText) {
  140. output += options.postText;
  141. }
  142. }
  143. return output;
  144. }
  145. /**
  146. * This function updates the value of the counter in the indicator.
  147. * Wants as parameters: the number of remaining chars, the element currently managed,
  148. * the maxLength for the current input and the indicator generated for it.
  149. *
  150. * @param remaining
  151. * @param currentInput
  152. * @param maxLengthCurrentInput
  153. * @param maxLengthIndicator
  154. */
  155. function manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator) {
  156. maxLengthIndicator.html(updateMaxLengthHTML(maxLengthCurrentInput, (maxLengthCurrentInput - remaining)));
  157. if (remaining > 0) {
  158. if (charsLeftThreshold(currentInput, options.threshold, maxLengthCurrentInput)) {
  159. showRemaining(maxLengthIndicator.removeClass(options.limitReachedClass).addClass(options.warningClass));
  160. } else {
  161. hideRemaining(maxLengthIndicator);
  162. }
  163. } else {
  164. showRemaining(maxLengthIndicator.removeClass(options.warningClass).addClass(options.limitReachedClass));
  165. }
  166. }
  167. /**
  168. * This function returns an object containing all the
  169. * informations about the position of the current input
  170. *
  171. * @param currentInput
  172. * @return object {bottom height left right top width}
  173. *
  174. */
  175. function getPosition(currentInput) {
  176. var el = currentInput[0];
  177. return $.extend({}, (typeof el.getBoundingClientRect === 'function') ? el.getBoundingClientRect() : {
  178. width: el.offsetWidth,
  179. height: el.offsetHeight
  180. }, currentInput.offset());
  181. }
  182. /**
  183. * This function places the maxLengthIndicator at the
  184. * top / bottom / left / right of the currentInput
  185. *
  186. * @param currentInput
  187. * @param maxLengthIndicator
  188. * @return null
  189. *
  190. */
  191. function place(currentInput, maxLengthIndicator) {
  192. var pos = getPosition(currentInput),
  193. inputOuter = currentInput.outerWidth(),
  194. outerWidth = maxLengthIndicator.outerWidth(),
  195. actualWidth = maxLengthIndicator.width(),
  196. actualHeight = maxLengthIndicator.height();
  197. switch (options.placement) {
  198. case 'bottom':
  199. maxLengthIndicator.css({top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2});
  200. break;
  201. case 'top':
  202. maxLengthIndicator.css({top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2});
  203. break;
  204. case 'left':
  205. maxLengthIndicator.css({top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth});
  206. break;
  207. case 'right':
  208. maxLengthIndicator.css({top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width});
  209. break;
  210. case 'bottom-right':
  211. maxLengthIndicator.css({top: pos.top + pos.height, left: pos.left + pos.width});
  212. break;
  213. case 'top-right':
  214. maxLengthIndicator.css({top: pos.top - actualHeight, left: pos.left + inputOuter});
  215. break;
  216. case 'top-left':
  217. maxLengthIndicator.css({top: pos.top - actualHeight, left: pos.left - outerWidth});
  218. break;
  219. case 'bottom-left':
  220. maxLengthIndicator.css({top: pos.top + currentInput.outerHeight(), left: pos.left - outerWidth});
  221. break;
  222. case 'centered-right':
  223. maxLengthIndicator.css({top: pos.top + (actualHeight / 2), left: pos.left + inputOuter - outerWidth - 3});
  224. break;
  225. }
  226. }
  227. /**
  228. * This function retrieves the maximum length of currentInput
  229. *
  230. * @param currentInput
  231. * @return {number}
  232. *
  233. */
  234. function getMaxLength(currentInput) {
  235. return currentInput.attr('maxlength') || currentInput.attr('size');
  236. }
  237. return this.each(function() {
  238. var currentInput = $(this),
  239. maxLengthCurrentInput,
  240. maxLengthIndicator;
  241. $(window).resize(function() {
  242. if(maxLengthIndicator) {
  243. place(currentInput, maxLengthIndicator);
  244. }
  245. });
  246. currentInput.focus(function () {
  247. var maxlengthContent = updateMaxLengthHTML(maxLengthCurrentInput, '0');
  248. maxLengthCurrentInput = getMaxLength(currentInput);
  249. if (!maxLengthIndicator) {
  250. maxLengthIndicator = $('<span class="bootstrap-maxlength"></span>').css({
  251. display: 'none',
  252. position: 'absolute',
  253. whiteSpace: 'nowrap',
  254. zIndex: 1099
  255. }).html(maxlengthContent);
  256. }
  257. // We need to detect resizes if we are dealing with a textarea:
  258. if (currentInput.is('textarea')) {
  259. currentInput.data('maxlenghtsizex', currentInput.outerWidth());
  260. currentInput.data('maxlenghtsizey', currentInput.outerHeight());
  261. currentInput.mouseup(function() {
  262. if (currentInput.outerWidth() !== currentInput.data('maxlenghtsizex') || currentInput.outerHeight() !== currentInput.data('maxlenghtsizey')) {
  263. place(currentInput, maxLengthIndicator);
  264. }
  265. currentInput.data('maxlenghtsizex', currentInput.outerWidth());
  266. currentInput.data('maxlenghtsizey', currentInput.outerHeight());
  267. });
  268. }
  269. documentBody.append(maxLengthIndicator);
  270. var remaining = remainingChars(currentInput, getMaxLength(currentInput));
  271. manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator);
  272. place(currentInput, maxLengthIndicator);
  273. });
  274. currentInput.blur(function() {
  275. maxLengthIndicator.remove();
  276. });
  277. currentInput.keyup(function() {
  278. var remaining = remainingChars(currentInput, getMaxLength(currentInput)),
  279. output = true;
  280. if (options.validate && remaining < 0) {
  281. output = false;
  282. } else {
  283. manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator);
  284. }
  285. return output;
  286. });
  287. });
  288. }
  289. });
  290. }(jQuery));