1/* ========================================================= 2 * bootstrap-pincode-input.js 3 * 4 * ========================================================= 5 * Created by Ferry Kranenburg 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 * ========================================================= */ 19 20; (function ($, window, document, undefined) { 21 22 "use strict"; 23 24 25 // Create the defaults once 26 var pluginName = "pincodeInput"; 27 var defaults = { 28 placeholders: undefined, // seperate with a " "(space) to set an placeholder for each input box 29 inputs: 4, // 4 input boxes = code of 4 digits long 30 hidedigits: true, // hide digits 31 pattern: '[0-9]*', 32 inputtype: 'number', 33 inputmode: 'numeric', 34 inputclass: '', // could be changed to form-control-lg or any other class 35 characterwidth: null, // em width of PIN character; defaults to 0.54 (em width of a typical digit), or 0.4 if digits are hidden 36 keydown: function (e) { 37 }, 38 change: function (input, value, inputnumber) { // callback on every input on change (keyup event) 39 //input = the input textbox DOM element 40 //value = the value entered by user (or removed) 41 //inputnumber = the position of the input box (in touch mode we only have 1 big input box, so always 1 is returned here) 42 }, 43 complete: function (value, e, errorElement) { // callback when all inputs are filled in (keyup event) 44 //value = the entered code 45 //e = last keyup event 46 //errorElement = error span next to to this, fill with html e.g. : $(errorElement).html("Code not correct"); 47 } 48 49 }; 50 51 // The actual plugin constructor 52 function Plugin(element, options) { 53 this.element = element; 54 this.settings = $.extend({}, defaults, options); 55 this._defaults = defaults; 56 this._name = pluginName; 57 this.init(); 58 } 59 60 // Avoid Plugin.prototype conflicts 61 $.extend(Plugin.prototype, { 62 init: function () { 63 this.buildInputBoxes(); 64 65 }, 66 updateOriginalInput: function () { 67 var newValue = ""; 68 $('.pincode-input-text', this._container).each(function (index, value) { 69 newValue += $(value).val().toString(); 70 }); 71 $(this.element).val(newValue); 72 }, 73 check: function () { 74 var isComplete = true; 75 var code = ""; 76 $('.pincode-input-text', this._container).each(function (index, value) { 77 code += $(value).val().toString(); 78 if (!$(value).val()) { 79 isComplete = false; 80 } 81 }); 82 83 if (this._isTouchDevice()) { 84 // check if single input has it all 85 if (code.length == this.settings.inputs) { 86 return true; 87 } 88 } else { 89 return isComplete; 90 } 91 92 93 }, 94 buildInputBoxes: function () {
95 this._container = $('<div />').addClass('pincode-input-container'); 96 97 98 var currentValue = []; 99 var placeholders = []; 100 var touchplaceholders = ""; // in touch mode we have just 1 big input box, and there is only 1 placeholder in this case 101 102 if (this.settings.placeholders) { 103 placeholders = this.settings.placeholders.split(" "); 104 touchplaceholders = this.settings.placeholders.replace(/ /g, ""); 105 } 106 107 // If we do not hide digits, we need to include the current value of the input box 108 // This will only work if the current value is not longer than the number of input boxes. 109 if (this.settings.hidedigits == false && $(this.element).val() != "") { 110 currentValue = $(this.element).val().split(""); 111 } 112 113 // make sure this is the first password field here 114 if (this.settings.hidedigits) { 115 this._pwcontainer = $('<div />').css("display", "none").appendTo(this._container); 116 this._pwfield = $('<input>').attr({ 117 'type': this.settings.inputtype, 118 'pattern': this.settings.pattern, 119 'inputmode': this.settings.inputmode, 120 'id': 'preventautofill', 121 'autocomplete': 'off' 122 }).addClass('pincode-input-text-masked').appendTo(this._pwcontainer); 123 } 124 125 if (this._isTouchDevice()) { 126 // set main class 127 $(this._container).addClass("touch"); 128 129 // For touch devices we build a html table directly under the pincode textbox. The textbox will become transparent 130 // This table is used for styling only, it will display how many 'digits' the user should fill in. 131 // With CSS letter-spacing we try to put every digit visually insize each table cell. 132 133 var wrapper = $('<div />').addClass('touchwrapper touch' + this.settings.inputs).appendTo(this._container); 134 var input = $('<input>').attr({ 135 'type': this.settings.inputtype, 136 'pattern': this.settings.pattern, 137 'inputmode': this.settings.inputmode, 138 'placeholder': touchplaceholders, 139 'maxlength': this.settings.inputs, 140 'autocomplete': 'off' 141 }).addClass(this.settings.inputclass + ' form-control pincode-input-text').appendTo(wrapper); 142 143 // calculate letter-spacing in Javascript since this isn't possible in CSS 144 var inputs = this.settings.inputs; 145 var digitEms = this.settings.characterwidth || (this.settings.hidedigits ? 0.4 : 0.54); 146 setTimeout(function () { 147 var digitWidth = parseFloat(getComputedStyle(input.get(0)).fontSize) * digitEms; 148 var spaceWidth = input.innerWidth() / inputs; 149 var spaceBetweenChars = spaceWidth - digitWidth; 150 $(input).css({ 151 "padding-left": spaceBetweenChars / 2 + "px", 152 "padding-right": "0", 153 "letter-spacing": spaceBetweenChars + "px" 154 }); 155 }, 0); 156 157 158 159 var touchtable = $('<div>').addClass('touch-flex').appendTo(wrapper); 160 // create touch background elements (for showing user how many digits must be entered) 161 for (var i = 0; i < this.settings.inputs; i++) { 162 if (i == (this.settings.inputs - 1)) { 163 $('<div/>').addClass('touch-flex-cell').addClass('last').appendTo(touchtable); 164 } else { 165 $('<div/>').addClass('touch-flex-cell').appendTo(touchtable); 166 } 167 } 168 if (this.settings.hidedigits) { 169 // hide digits 170 input.addClass('pincode-input-text-masked'); 171 } else { 172 // show digits, also include default value 173 input.val($(this.element).val()); 174 } 175 176 // add events 177 this._addEventsToInput(input, 1); 178 179 } else { 180 // for desktop mode we build one input for each digit 181 var width = (100 / this.settings.inputs) + '%'; 182 for (var i = 0; i < this.settings.inputs; i++) { 183 184 var input = $('<input>').attr({ 185 'type': 'text', 186 'maxlength': "1", 187 'autocomplete': 'off', 188 'placeholder': (placeholders[i] ? placeholders[i] : undefined) 189 }).addClass(this.settings.inputclass + ' form-control pincode-input-text').appendTo(this._container); 190 input.css({ width: width }) 191 if (this.settings.hidedigits) { 192 // hide digits 193 input.addClass('pincode-input-text-masked'); 194 } else { 195 // show digits, also include default value 196 input.val(currentValue[i]); 197 } 198 199 if (i == 0) { 200 input.addClass('first'); 201 } else if (i == (this.settings.inputs - 1)) { 202 input.addClass('last'); 203 } else { 204 input.addClass('mid'); 205 } 206 207 // add events 208 this._addEventsToInput(input, (i + 1)); 209 } 210 } 211 212 // hide original element and place this before it 213 $(this.element).css("display", "none"); 214 this._container.insertBefore(this.element); 215 216 // error box 217 this._error = $('<div />').addClass('text-danger pincode-input-error').insertBefore(this.element); 218 219 }, 220 enable: function () { 221 $('.pincode-input-text', this._container).each(function (index, value) { 222 $(value).prop('disabled', false); 223 }); 224 }, 225 disable: function () { 226 $('.pincode-input-text', this._container).each(function (index, value) { 227 $(value).prop('disabled', true); 228 }); 229 }, 230 focus: function () { 231 $('.pincode-input-text', this._container).first().select().focus(); 232 }, 233 clear: function () { 234 $('.pincode-input-text', this._container).each(function (index, value) { 235 $(value).val(""); 236 }); 237 this.updateOriginalInput(); 238 }, 239 _setValue: function (newValue, e) { 240 241 // slice value 242 var value = newValue.substring(0, this.settings.inputs); 243 244 if (this._isTouchDevice()) { 245 246 // update value 247 $('.pincode-input-text', this._container).each(function (index, input) { 248 $(input).val(value); 249 }); 250 251 252 // a hack to prevent visual issues on mobile devices 253 $(this.element).addClass("noletterspacing"); 254 $(this.element).select(); 255 $(this.element).blur(); 256 $(this.element).removeClass("noletterspacing"); 257 258 // update original input box 259 this.updateOriginalInput(); 260 261 // oncomplete check 262 if (this.check()) { 263 this.settings.complete($(this.element).val(), e, this._error); 264 } 265 266 } else { 267 268 var values = value.split(''); 269 270 $('.pincode-input-text', this._container).each(function (index, input) { 271 $(input).val(values[index]); 272 }); 273 274 // update original input box 275 this.updateOriginalInput(); 276 277 // oncomplete check 278 if (this.check()) { 279 this.settings.complete($(this.element).val(), e, this._error); 280 } 281 282 } 283 284 }, 285 _isTouchDevice: function () { 286 // I know, sniffing is a really bad idea, but it works 99% of the times 287 if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { 288 return false; 289 } 290 // iPad Pro pretends to be a Mac, but Macs don't have touch controls 291 if (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1) { 292 return false; 293 } 294 }, 295 _addEventsToInput: function (input, inputnumber) { 296 297 input.on('focus', function (e) { 298 this.select(); // automatically select current value 299 }); 300 301 // paste event should call onchange and oncomplete callbacks 302 input.on('paste', $.proxy(function (e) { 303 e.preventDefault(); 304 305 var clipboardData = (e.originalEvent || e).clipboardData || window.clipboardData; 306 var pastedData = clipboardData.getData('text/plain'); 307 308 this._setValue(pastedData, e); 309 310 }, this)); 311 312 input.on('keydown', $.proxy(function (e) { 313 if (this._pwfield) { 314 // Because we need to prevent password saving by browser 315 // remove the value here and change the type! 316 // we do this every time the user types 317 $(this._pwfield).attr({ 'type': 'text' }); 318 $(this._pwfield).val(""); 319 } 320 321 // prevent more input for touch device (we can't limit it) 322 if (this._isTouchDevice()) { 323 if (e.keyCode == 8 || e.keyCode == 46) { 324 // do nothing on backspace and delete 325 } else if ($(this.element).val().length == this.settings.inputs) { 326 e.preventDefault(); 327 e.stopPropagation(); 328 } 329 330 } else { 331 // in desktop mode, check if an number was entered 332 333 if (!(e.keyCode == 8 // backspace key 334 || e.keyCode == 9 // tab key 335 || e.keyCode == 46 // delete key 336 || (e.keyCode >= 48 && e.keyCode <= 57) // numbers on keyboard 337 || (e.keyCode >= 96 && e.keyCode <= 105) // number on keypad 338 || ((this.settings.inputtype != 'number' && this.settings.inputtype != 'tel') && e.keyCode >= 65 && e.keyCode <= 90)) // alphabet 339 ) { 340 e.preventDefault(); // Prevent character input 341 e.stopPropagation(); 342 } 343 344 } 345
346 this.settings.keydown(e); 347 }, this)); 348 349 input.on('keyup', $.proxy(function (e) { 350 // after every keystroke we check if all inputs have a value, if yes we call complete callback 351 if (!this._isTouchDevice()) { 352 // on backspace or delete go to previous input box 353 if (e.keyCode == 8 || e.keyCode == 46) { 354 // goto previous 355 // $(e.currentTarget).prev().select(); 356 // $(e.currentTarget).prev().focus(); 357 } else if ($(e.currentTarget).val() != "") { 358 $(e.currentTarget).next().select(); 359 $(e.currentTarget).next().focus(); 360 // if($(e.currentTarget).hasClass("last")){ 361 // console.log() 362 // $(e.currentTarget).blur(); 363 // } 364 } 365 } 366 367 // update original input box 368 this.updateOriginalInput(); 369 370 // oncomplete check 371 if (this.check()) { 372 this.settings.complete($(this.element).val(), e, this._error); 373 } 374 375 // onchange event for each input 376 if (this.settings.change) { 377 this.settings.change(e.currentTarget, $(e.currentTarget).val(), inputnumber); 378 } 379 380 381 // prevent more input for touch device (we can't limit it) 382 if (this._isTouchDevice()) { 383 if (e.keyCode == 8 || e.keyCode == 46) { 384 // do nothing on backspace and delete 385 } else if ($(this.element).val().length == this.settings.inputs) { 386 $(e.currentTarget).addClass("noletterspacing"); 387 388 $(e.currentTarget).select(); 389 $(e.currentTarget).blur(); 390 391 $(e.currentTarget).removeClass("noletterspacing"); 392 393 } 394 } 395 396 }, this)); 397 398 399 } 400 401 402 }); 403 404 // A really lightweight plugin wrapper around the constructor, 405 // preventing against multiple instantiations 406 $.fn[pluginName] = function (options) { 407 return this.each(function () { 408 if (!$.data(this, "plugin_" + pluginName)) { 409 $.data(this, "plugin_" + pluginName, new Plugin(this, options)); 410 } 411 }); 412 }; 413 414})(jQuery, window, document);
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.