Skip to content
This repository was archived by the owner on May 25, 2019. It is now read-only.

Commit 6080d0d

Browse files
committed
Fix a bunch of bugs
Remember original placeholder and maxlength attrs, properly unbind events, proper uninitialize func, proper clearing of value on blur with zero-length raw val.
1 parent a32e16c commit 6080d0d

2 files changed

Lines changed: 72 additions & 30 deletions

File tree

modules/directives/mask/demo/index.html

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,24 @@
55
angular.module('ui.directives',[]);
66
</script>
77
<script type="text/javascript" src="../mask.js"></script>
8+
<style type="text/css">
9+
input, button { font-size: 14px; font-family: helvetica, arial; width:300px; text-align: left }
10+
</style>
811
</head>
912
<body ng-app="ui.directives">
10-
<input ui-mask="'(9)9'" ng-model="x">
13+
14+
<label>
15+
Masked Input:<br>
16+
<input ui-mask="{{y}}" ng-model="x" placeholder="Specify a mask or click a button">
17+
</label><br>
18+
<label>
19+
Mask Defintion:<br>
20+
<input ng-model="y">
21+
</label><br><br>
22+
23+
<button ng-click="y = undefined">Set mask to undefined (uninitialize)</button><br>
24+
<button ng-click="y = '999-9999'">Set mask to 999-9999</button><br>
25+
<button ng-click="y = '(999) 999-9999'">Set mask to (999) 999-9999</button><br>
26+
<button ng-click="y = '(999) 999-9999 ext. 999'">Set mask to (999) 999-9999 ext. 999</button>
1127
</body>
1228
</html>

modules/directives/mask/mask.js

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,50 @@ angular.module('ui.directives').directive('uiMask', [
1414
restrict: 'A',
1515
link: function (scope, iElement, iAttrs, controller) {
1616
var maskProcessed = false, eventsBound = false,
17-
mask, maskCaretMap, maskPatterns, maskPlaceholder, characterCount,
17+
maskCaretMap, maskPatterns, maskPlaceholder,
1818
value, valueMasked, isValid,
19+
// Vars for initializing/uninitializing
20+
originalPlaceholder = iAttrs.placeholder,
21+
originalMaxlength = iAttrs.maxlength,
22+
// Vars used exclusively in eventHandler()
1923
oldValue, oldValueUnmasked, oldCaretPosition, oldSelectionLength;
2024

2125
iAttrs.$observe('uiMask', initialize);
2226
controller.$formatters.push(formatter);
2327
controller.$parsers.push(parser);
2428

2529
function initialize(maskAttr) {
26-
if (typeof maskAttr == 'undefined') {
27-
maskProcessed = false;
28-
unbindEventListeners();
29-
return false;
30-
}
30+
if (!angular.isDefined(maskAttr))
31+
return uninitialize();
3132
processRawMask(maskAttr);
32-
if (!maskProcessed) {
33-
unbindEventListeners();
34-
return false;
35-
}
33+
if (!maskProcessed)
34+
return uninitialize();
3635
initializeElement();
3736
bindEventListeners();
3837
}
3938

39+
function uninitialize() {
40+
maskProcessed = false;
41+
unbindEventListeners();
42+
43+
if (angular.isDefined(originalPlaceholder))
44+
iElement.attr('placeholder', originalPlaceholder);
45+
else
46+
iElement.removeAttr('placeholder');
47+
48+
if (angular.isDefined(originalMaxlength))
49+
iElement.attr('maxlength', originalMaxlength);
50+
else
51+
iElement.removeAttr('maxlength');
52+
53+
return false;
54+
}
55+
4056
function processRawMask(mask) {
41-
maskCaretMap = [];
42-
maskPatterns = [];
43-
maskPlaceholder = '';
44-
characterCount = 0;
57+
var characterCount = 0;
58+
maskCaretMap = [];
59+
maskPatterns = [];
60+
maskPlaceholder = '';
4561

4662
// If mask is an array, it's a complex mask!
4763
if (mask instanceof Array) {
@@ -77,13 +93,13 @@ angular.module('ui.directives').directive('uiMask', [
7793
}
7894

7995
function initializeElement() {
80-
value = oldValueUnmasked = unmaskValue(controller.$viewValue || '');
81-
valueMasked = oldValue = maskValue(value);
82-
isValid = validateValue(value);
83-
if (iAttrs.maxlength)
84-
iElement.attr('maxlength', maskCaretMap[maskCaretMap.length-1]*2); // Double maxlength to allow pasting at end of mask
96+
value = oldValueUnmasked = unmaskValue(controller.$viewValue || '');
97+
valueMasked = oldValue = maskValue(value);
98+
isValid = validateValue(value);
99+
if (iAttrs.maxlength) // Double maxlength to allow pasting new val at end of mask
100+
iElement.attr('maxlength', maskCaretMap[maskCaretMap.length-1]*2);
85101
iElement.attr('placeholder', maskPlaceholder);
86-
iElement.val(isValid ? valueMasked : '');
102+
iElement.val(isValid && value.length ? valueMasked : '');
87103
}
88104

89105
function bindEventListeners() {
@@ -95,7 +111,14 @@ angular.module('ui.directives').directive('uiMask', [
95111
}
96112

97113
function unbindEventListeners() {
98-
iElement.unbind('.uiMask');
114+
if (!eventsBound)
115+
return true;
116+
iElement.unbind('blur', blurHandler);
117+
iElement.unbind('input', eventHandler);
118+
iElement.unbind('propertychange', eventHandler);
119+
iElement.unbind('keyup', eventHandler);
120+
iElement.unbind('click', eventHandler);
121+
iElement.unbind('mouseout', eventHandler);
99122
eventsBound = false;
100123
}
101124

@@ -120,7 +143,7 @@ angular.module('ui.directives').directive('uiMask', [
120143
}
121144

122145
function validateValue(value) {
123-
// Allow zero-length values (this is required's responsibility)
146+
// Zero-length value validity is ngRequired's determination
124147
return value.length ? value.length === maskCaretMap.length - 1 : true;
125148
}
126149

@@ -153,7 +176,7 @@ angular.module('ui.directives').directive('uiMask', [
153176
function blurHandler(e) {
154177
oldCaretPosition = 0;
155178
oldSelectionLength = 0;
156-
if (!isValid) {
179+
if (!isValid || value.length === 0) {
157180
valueMasked = '';
158181
iElement.val('');
159182
scope.$apply(function() {
@@ -167,7 +190,7 @@ angular.module('ui.directives').directive('uiMask', [
167190
var eventWhich = e.which,
168191
eventType = e.type;
169192

170-
// Shift and ctrl aren't going to ruin our party.
193+
// Prevent shift and ctrl from mucking with old values
171194
if (eventWhich == 16 || eventWhich == 91) return true;
172195

173196
var elem = iElement,
@@ -205,25 +228,27 @@ angular.module('ui.directives').directive('uiMask', [
205228
// a character when clicking within a filled input.
206229
caretBumpBack = (isKeyLeftArrow || isKeyBackspace || eventType == 'click') && caretPos > caretPosMin;
207230

208-
oldSelectionLength = selectionLen;
231+
oldSelectionLength = selectionLen;
209232

210233
// These events don't require any action
211234
if (eventType == 'mouseout' || isSelection || (isSelected && (eventType == 'click' || eventType == 'keyup')))
212235
return true;
213236

214237
// Value Handling
215238
// ==============
239+
216240
// User attempted to delete but raw value was unaffected--correct this grievous offense
217241
if ((eventType == 'input' || eventType == 'propertychange') && isDeletion && !wasSelected && valUnmasked === valUnmaskedOld) {
218242
while (isKeyBackspace && caretPos > 0 && !isValidCaretPosition(caretPos))
219243
caretPos--;
220244
while (isKeyDelete && caretPos < maskPlaceholder.length && maskCaretMap.indexOf(caretPos) == -1)
221245
caretPos++;
222246
var charIndex = maskCaretMap.indexOf(caretPos);
223-
// Strip out character that user inteded to delete if mask hadn't been in the way.
247+
// Strip out non-mask character that user would have deleted if mask hadn't been in the way.
224248
valUnmasked = valUnmasked.substring(0, charIndex) + valUnmasked.substring(charIndex + 1);
225249
}
226250

251+
// Update values
227252
valMasked = maskValue(valUnmasked);
228253
oldValue = valMasked;
229254
oldValueUnmasked = valUnmasked;
@@ -232,17 +257,18 @@ angular.module('ui.directives').directive('uiMask', [
232257
// Caret Repositioning
233258
// ===================
234259

235-
// Ensure that typing always places caret ahead of typed character
260+
// Ensure that typing always places caret ahead of typed character in cases where the first char of
261+
// the input is a mask char and the caret is placed at the 0 position.
236262
if (isAddition && (caretPos <= caretPosMin))
237263
caretPos = caretPosMin + 1;
238264

239265
if (caretBumpBack)
240266
caretPos--;
241267

242-
// Make sure caret is within min and max positions
268+
// Make sure caret is within min and max position limits
243269
caretPos = caretPos > caretPosMax ? caretPosMax : caretPos < caretPosMin ? caretPosMin : caretPos;
244270

245-
// Scoot the caret around until it's in a valid position and within min/max limits
271+
// Scoot the caret back or forth until it's in a non-mask position and within min/max position limits
246272
while (!isValidCaretPosition(caretPos) && caretPos > caretPosMin && caretPos < caretPosMax)
247273
caretPos += caretBumpBack ? -1 : 1;
248274

0 commit comments

Comments
 (0)