PageSourceSearch

https://accounts.enago.com/lib/otp/js/bootstrap-pincode-input.js

js enago.com collected 2026-09-24 08:51:08 UTC 13,843 bytes, 414 lines download raw bytes

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.