bitcoin-atm
bitcoin atm for pyc inc.
git clone https://9o.is/git/bitcoin-atm.git
ui-mask.js
(19711B)
1 'use strict';
2
3 /*
4 Attaches input mask onto input element
5 */
6 angular.module('ui.mask', [])
7 .value('uiMaskConfig', {
8 'maskDefinitions': {
9 '9': /\d/,
10 'A': /[a-zA-Z]/,
11 '*': /[a-zA-Z0-9]/
12 }
13 })
14 .directive('uiMask', ['uiMaskConfig', '$parse', function (maskConfig, $parse) {
15 return {
16 priority: 100,
17 require: 'ngModel',
18 restrict: 'A',
19 compile: function uiMaskCompilingFunction(){
20 var options = maskConfig;
21
22 return function uiMaskLinkingFunction(scope, iElement, iAttrs, controller){
23 var maskProcessed = false, eventsBound = false,
24 maskCaretMap, maskPatterns, maskPlaceholder, maskComponents,
25 // Minimum required length of the value to be considered valid
26 minRequiredLength,
27 value, valueMasked, isValid,
28 // Vars for initializing/uninitializing
29 originalPlaceholder = iAttrs.placeholder,
30 originalMaxlength = iAttrs.maxlength,
31 // Vars used exclusively in eventHandler()
32 oldValue, oldValueUnmasked, oldCaretPosition, oldSelectionLength;
33
34 function initialize(maskAttr){
35 if (!angular.isDefined(maskAttr)) {
36 return uninitialize();
37 }
38 processRawMask(maskAttr);
39 if (!maskProcessed) {
40 return uninitialize();
41 }
42 initializeElement();
43 bindEventListeners();
44 return true;
45 }
46
47 function initPlaceholder(placeholderAttr) {
48 if(! angular.isDefined(placeholderAttr)) {
49 return;
50 }
51
52 maskPlaceholder = placeholderAttr;
53
54 // If the mask is processed, then we need to update the value
55 if (maskProcessed) {
56 eventHandler();
57 }
58 }
59
60 function formatter(fromModelValue){
61 if (!maskProcessed) {
62 return fromModelValue;
63 }
64 value = unmaskValue(fromModelValue || '');
65 isValid = validateValue(value);
66 controller.$setValidity('mask', isValid);
67 return isValid && value.length ? maskValue(value) : undefined;
68 }
69
70 function parser(fromViewValue){
71 if (!maskProcessed) {
72 return fromViewValue;
73 }
74 value = unmaskValue(fromViewValue || '');
75 isValid = validateValue(value);
76 // We have to set viewValue manually as the reformatting of the input
77 // value performed by eventHandler() doesn't happen until after
78 // this parser is called, which causes what the user sees in the input
79 // to be out-of-sync with what the controller's $viewValue is set to.
80 controller.$viewValue = value.length ? maskValue(value) : '';
81 controller.$setValidity('mask', isValid);
82 if (value === '' && iAttrs.required) {
83 controller.$setValidity('required', false);
84 }
85 return isValid ? value : undefined;
86 }
87
88 var linkOptions = {};
89
90 if (iAttrs.uiOptions) {
91 linkOptions = scope.$eval('[' + iAttrs.uiOptions + ']');
92 if (angular.isObject(linkOptions[0])) {
93 // we can't use angular.copy nor angular.extend, they lack the power to do a deep merge
94 linkOptions = (function(original, current){
95 for(var i in original) {
96 if (Object.prototype.hasOwnProperty.call(original, i)) {
97 if (!current[i]) {
98 current[i] = angular.copy(original[i]);
99 } else {
100 angular.extend(current[i], original[i]);
101 }
102 }
103 }
104 return current;
105 })(options, linkOptions[0]);
106 }
107 } else {
108 linkOptions = options;
109 }
110
111 iAttrs.$observe('uiMask', initialize);
112 iAttrs.$observe('placeholder', initPlaceholder);
113 var modelViewValue = false;
114 iAttrs.$observe('modelViewValue', function(val) {
115 if(val === 'true') {
116 modelViewValue = true;
117 }
118 });
119 scope.$watch(iAttrs.ngModel, function(val) {
120 if(modelViewValue && val) {
121 var model = $parse(iAttrs.ngModel);
122 model.assign(scope, controller.$viewValue);
123 }
124 });
125 controller.$formatters.push(formatter);
126 controller.$parsers.push(parser);
127
128 function uninitialize(){
129 maskProcessed = false;
130 unbindEventListeners();
131
132 if (angular.isDefined(originalPlaceholder)) {
133 iElement.attr('placeholder', originalPlaceholder);
134 } else {
135 iElement.removeAttr('placeholder');
136 }
137
138 if (angular.isDefined(originalMaxlength)) {
139 iElement.attr('maxlength', originalMaxlength);
140 } else {
141 iElement.removeAttr('maxlength');
142 }
143
144 iElement.val(controller.$modelValue);
145 controller.$viewValue = controller.$modelValue;
146 return false;
147 }
148
149 function initializeElement(){
150 value = oldValueUnmasked = unmaskValue(controller.$modelValue || '');
151 valueMasked = oldValue = maskValue(value);
152 isValid = validateValue(value);
153 var viewValue = isValid && value.length ? valueMasked : '';
154 if (iAttrs.maxlength) { // Double maxlength to allow pasting new val at end of mask
155 iElement.attr('maxlength', maskCaretMap[maskCaretMap.length - 1] * 2);
156 }
157 iElement.attr('placeholder', maskPlaceholder);
158 iElement.val(viewValue);
159 controller.$viewValue = viewValue;
160 // Not using $setViewValue so we don't clobber the model value and dirty the form
161 // without any kind of user interaction.
162 }
163
164 function bindEventListeners(){
165 if (eventsBound) {
166 return;
167 }
168 iElement.bind('blur', blurHandler);
169 iElement.bind('mousedown mouseup', mouseDownUpHandler);
170 iElement.bind('input keyup click focus', eventHandler);
171 eventsBound = true;
172 }
173
174 function unbindEventListeners(){
175 if (!eventsBound) {
176 return;
177 }
178 iElement.unbind('blur', blurHandler);
179 iElement.unbind('mousedown', mouseDownUpHandler);
180 iElement.unbind('mouseup', mouseDownUpHandler);
181 iElement.unbind('input', eventHandler);
182 iElement.unbind('keyup', eventHandler);
183 iElement.unbind('click', eventHandler);
184 iElement.unbind('focus', eventHandler);
185 eventsBound = false;
186 }
187
188 function validateValue(value){
189 // Zero-length value validity is ngRequired's determination
190 return value.length ? value.length >= minRequiredLength : true;
191 }
192
193 function unmaskValue(value){
194 var valueUnmasked = '',
195 maskPatternsCopy = maskPatterns.slice();
196 // Preprocess by stripping mask components from value
197 value = value.toString();
198 angular.forEach(maskComponents, function (component){
199 value = value.replace(component, '');
200 });
201 angular.forEach(value.split(''), function (chr){
202 if (maskPatternsCopy.length && maskPatternsCopy[0].test(chr)) {
203 valueUnmasked += chr;
204 maskPatternsCopy.shift();
205 }
206 });
207 return valueUnmasked;
208 }
209
210 function maskValue(unmaskedValue){
211 var valueMasked = '',
212 maskCaretMapCopy = maskCaretMap.slice();
213
214 angular.forEach(maskPlaceholder.split(''), function (chr, i){
215 if (unmaskedValue.length && i === maskCaretMapCopy[0]) {
216 valueMasked += unmaskedValue.charAt(0) || '_';
217 unmaskedValue = unmaskedValue.substr(1);
218 maskCaretMapCopy.shift();
219 }
220 else {
221 valueMasked += chr;
222 }
223 });
224 return valueMasked;
225 }
226
227 function getPlaceholderChar(i) {
228 var placeholder = iAttrs.placeholder;
229
230 if (typeof placeholder !== 'undefined' && placeholder[i]) {
231 return placeholder[i];
232 } else {
233 return '_';
234 }
235 }
236
237 // Generate array of mask components that will be stripped from a masked value
238 // before processing to prevent mask components from being added to the unmasked value.
239 // E.g., a mask pattern of '+7 9999' won't have the 7 bleed into the unmasked value.
240 // If a maskable char is followed by a mask char and has a mask
241 // char behind it, we'll split it into it's own component so if
242 // a user is aggressively deleting in the input and a char ahead
243 // of the maskable char gets deleted, we'll still be able to strip
244 // it in the unmaskValue() preprocessing.
245 function getMaskComponents() {
246 return maskPlaceholder.replace(/[_]+/g, '_').replace(/([^_]+)([a-zA-Z0-9])([^_])/g, '$1$2_$3').split('_');
247 }
248
249 function processRawMask(mask){
250 var characterCount = 0;
251
252 maskCaretMap = [];
253 maskPatterns = [];
254 maskPlaceholder = '';
255
256 if (typeof mask === 'string') {
257 minRequiredLength = 0;
258
259 var isOptional = false,
260 splitMask = mask.split('');
261
262 angular.forEach(splitMask, function (chr, i){
263 if (linkOptions.maskDefinitions[chr]) {
264
265 maskCaretMap.push(characterCount);
266
267 maskPlaceholder += getPlaceholderChar(i);
268 maskPatterns.push(linkOptions.maskDefinitions[chr]);
269
270 characterCount++;
271 if (!isOptional) {
272 minRequiredLength++;
273 }
274 }
275 else if (chr === '?') {
276 isOptional = true;
277 }
278 else {
279 maskPlaceholder += chr;
280 characterCount++;
281 }
282 });
283 }
284 // Caret position immediately following last position is valid.
285 maskCaretMap.push(maskCaretMap.slice().pop() + 1);
286
287 maskComponents = getMaskComponents();
288 maskProcessed = maskCaretMap.length > 1 ? true : false;
289 }
290
291 function blurHandler(){
292 oldCaretPosition = 0;
293 oldSelectionLength = 0;
294 if (!isValid || value.length === 0) {
295 valueMasked = '';
296 iElement.val('');
297 scope.$apply(function (){
298 controller.$setViewValue('');
299 });
300 }
301 }
302
303 function mouseDownUpHandler(e){
304 if (e.type === 'mousedown') {
305 iElement.bind('mouseout', mouseoutHandler);
306 } else {
307 iElement.unbind('mouseout', mouseoutHandler);
308 }
309 }
310
311 iElement.bind('mousedown mouseup', mouseDownUpHandler);
312
313 function mouseoutHandler(){
314 /*jshint validthis: true */
315 oldSelectionLength = getSelectionLength(this);
316 iElement.unbind('mouseout', mouseoutHandler);
317 }
318
319 function eventHandler(e){
320 /*jshint validthis: true */
321 e = e || {};
322 // Allows more efficient minification
323 var eventWhich = e.which,
324 eventType = e.type;
325
326 // Prevent shift and ctrl from mucking with old values
327 if (eventWhich === 16 || eventWhich === 91) { return;}
328
329 var val = iElement.val(),
330 valOld = oldValue,
331 valMasked,
332 valUnmasked = unmaskValue(val),
333 valUnmaskedOld = oldValueUnmasked,
334 valAltered = false,
335
336 caretPos = getCaretPosition(this) || 0,
337 caretPosOld = oldCaretPosition || 0,
338 caretPosDelta = caretPos - caretPosOld,
339 caretPosMin = maskCaretMap[0],
340 caretPosMax = maskCaretMap[valUnmasked.length] || maskCaretMap.slice().shift(),
341
342 selectionLenOld = oldSelectionLength || 0,
343 isSelected = getSelectionLength(this) > 0,
344 wasSelected = selectionLenOld > 0,
345
346 // Case: Typing a character to overwrite a selection
347 isAddition = (val.length > valOld.length) || (selectionLenOld && val.length > valOld.length - selectionLenOld),
348 // Case: Delete and backspace behave identically on a selection
349 isDeletion = (val.length < valOld.length) || (selectionLenOld && val.length === valOld.length - selectionLenOld),
350 isSelection = (eventWhich >= 37 && eventWhich <= 40) && e.shiftKey, // Arrow key codes
351
352 isKeyLeftArrow = eventWhich === 37,
353 // Necessary due to "input" event not providing a key code
354 isKeyBackspace = eventWhich === 8 || (eventType !== 'keyup' && isDeletion && (caretPosDelta === -1)),
355 isKeyDelete = eventWhich === 46 || (eventType !== 'keyup' && isDeletion && (caretPosDelta === 0 ) && !wasSelected),
356
357 // Handles cases where caret is moved and placed in front of invalid maskCaretMap position. Logic below
358 // ensures that, on click or leftward caret placement, caret is moved leftward until directly right of
359 // non-mask character. Also applied to click since users are (arguably) more likely to backspace
360 // a character when clicking within a filled input.
361 caretBumpBack = (isKeyLeftArrow || isKeyBackspace || eventType === 'click') && caretPos > caretPosMin;
362
363 oldSelectionLength = getSelectionLength(this);
364
365 // These events don't require any action
366 if (isSelection || (isSelected && (eventType === 'click' || eventType === 'keyup'))) {
367 return;
368 }
369
370 // Value Handling
371 // ==============
372
373 // User attempted to delete but raw value was unaffected--correct this grievous offense
374 if ((eventType === 'input') && isDeletion && !wasSelected && valUnmasked === valUnmaskedOld) {
375 while (isKeyBackspace && caretPos > caretPosMin && !isValidCaretPosition(caretPos)) {
376 caretPos--;
377 }
378 while (isKeyDelete && caretPos < caretPosMax && maskCaretMap.indexOf(caretPos) === -1) {
379 caretPos++;
380 }
381 var charIndex = maskCaretMap.indexOf(caretPos);
382 // Strip out non-mask character that user would have deleted if mask hadn't been in the way.
383 valUnmasked = valUnmasked.substring(0, charIndex) + valUnmasked.substring(charIndex + 1);
384 valAltered = true;
385 }
386
387 // Update values
388 valMasked = maskValue(valUnmasked);
389
390 oldValue = valMasked;
391 oldValueUnmasked = valUnmasked;
392 iElement.val(valMasked);
393 if (valAltered) {
394 // We've altered the raw value after it's been $digest'ed, we need to $apply the new value.
395 scope.$apply(function (){
396 controller.$setViewValue(valUnmasked);
397 });
398 }
399
400 // Caret Repositioning
401 // ===================
402
403 // Ensure that typing always places caret ahead of typed character in cases where the first char of
404 // the input is a mask char and the caret is placed at the 0 position.
405 if (isAddition && (caretPos <= caretPosMin)) {
406 caretPos = caretPosMin + 1;
407 }
408
409 if (caretBumpBack) {
410 caretPos--;
411 }
412
413 // Make sure caret is within min and max position limits
414 caretPos = caretPos > caretPosMax ? caretPosMax : caretPos < caretPosMin ? caretPosMin : caretPos;
415
416 // Scoot the caret back or forth until it's in a non-mask position and within min/max position limits
417 while (!isValidCaretPosition(caretPos) && caretPos > caretPosMin && caretPos < caretPosMax) {
418 caretPos += caretBumpBack ? -1 : 1;
419 }
420
421 if ((caretBumpBack && caretPos < caretPosMax) || (isAddition && !isValidCaretPosition(caretPosOld))) {
422 caretPos++;
423 }
424 oldCaretPosition = caretPos;
425 setCaretPosition(this, caretPos);
426 }
427
428 function isValidCaretPosition(pos){ return maskCaretMap.indexOf(pos) > -1; }
429
430 function getCaretPosition(input){
431 if (!input) return 0;
432 if (input.selectionStart !== undefined) {
433 return input.selectionStart;
434 } else if (document.selection) {
435 // Curse you IE
436 input.focus();
437 var selection = document.selection.createRange();
438 selection.moveStart('character', input.value ? -input.value.length : 0);
439 return selection.text.length;
440 }
441 return 0;
442 }
443
444 function setCaretPosition(input, pos){
445 if (!input) return 0;
446 if (input.offsetWidth === 0 || input.offsetHeight === 0) {
447 return; // Input's hidden
448 }
449 if (input.setSelectionRange) {
450 input.focus();
451 input.setSelectionRange(pos, pos);
452 }
453 else if (input.createTextRange) {
454 // Curse you IE
455 var range = input.createTextRange();
456 range.collapse(true);
457 range.moveEnd('character', pos);
458 range.moveStart('character', pos);
459 range.select();
460 }
461 }
462
463 function getSelectionLength(input){
464 if (!input) return 0;
465 if (input.selectionStart !== undefined) {
466 return (input.selectionEnd - input.selectionStart);
467 }
468 if (document.selection) {
469 return (document.selection.createRange().text.length);
470 }
471 return 0;
472 }
473
474 // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf
475 if (!Array.prototype.indexOf) {
476 Array.prototype.indexOf = function (searchElement /*, fromIndex */){
477 if (this === null) {
478 throw new TypeError();
479 }
480 var t = Object(this);
481 var len = t.length >>> 0;
482 if (len === 0) {
483 return -1;
484 }
485 var n = 0;
486 if (arguments.length > 1) {
487 n = Number(arguments[1]);
488 if (n !== n) { // shortcut for verifying if it's NaN
489 n = 0;
490 } else if (n !== 0 && n !== Infinity && n !== -Infinity) {
491 n = (n > 0 || -1) * Math.floor(Math.abs(n));
492 }
493 }
494 if (n >= len) {
495 return -1;
496 }
497 var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
498 for (; k < len; k++) {
499 if (k in t && t[k] === searchElement) {
500 return k;
501 }
502 }
503 return -1;
504 };
505 }
506
507 };
508 }
509 };
510 }
511 ]);