/
validatable-input.js
211 lines (177 loc) · 5.96 KB
/
validatable-input.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
import Ember from 'ember';
export default Ember.Mixin.create({
/**
* @type {Array}
*/
attributeBindings: ['title', 'data-field'],
/**
* Check if the input has already been validated at least once
*
* @type {boolean}
*/
wasValidated: false,
/**
* Decide if we show the native browser error messages
*
* @type {boolean}
*/
useBrowserMessages: false,
/**
* Current error message for the field
*
* @type {string}
*/
errorMessage: null,
/**
* Allow to override error messages
*
* @type {Object}
*/
errorTemplates: {
// Errors when an input with "required" attribute has no value
valueMissing: {
defaultMessage: 'Value is required',
checkbox: 'You must check this box',
select: 'You must select at least an option',
radio: 'You must select an option'
},
// Errors when a value does not match a given type like "url" or "email"
typeMismatch: {
defaultMessage: 'Value is invalid',
email: 'Email address is invalid',
url: 'URL is invalid'
},
// Errors when a value does not follow the "pattern" regex
patternMismatch: {
defaultMessage: 'Value does not follow expected pattern'
},
// Errors when an input is too long
tooLong: {
defaultMessage: 'Enter at most %@ characters'
},
// Errors when an input is less than "min" value
rangeUnderflow: {
defaultMessage: 'Number must be more than %@'
},
// Errors when an input is more than "max" value
rangeOverflow: {
defaultMessage: 'Number must be less than %@'
},
// Errors when a value does not follow step (for instance for "range" type)
stepMismatch: {
defaultMessage: 'Value is invalid'
},
// Default message that is used when none is matched
defaultMessage: 'Value is invalid'
},
/**
* @returns {void}
*/
attachValidationListener: Ember.on('didInsertElement', function() {
if (this.get('inputTagName') === 'select') {
this.$().on('invalid.html5-validation change.html5-validation', Ember.run.bind(this, this.validate));
} else {
this.$().on('invalid.html5-validation focusout.html5-validation', Ember.run.bind(this, this.validate));
}
}),
/**
* @returns {void}
*/
detachValidationListener: Ember.on('willDestroyElement', function() {
this.$().off('.html5-validation');
}),
/**
* @returns {String}
*/
inputTagName: Ember.computed(function() {
return this.get('element').tagName.toLowerCase();
}),
/**
* Validate the input whenever it looses focus
*
* @returns {void}
*/
validate() {
let input = this.get('element');
// According to spec, inputs that have "formnovalidate" should bypass any validation
if (input.hasAttribute('formnovalidate')) {
return;
}
// Textareas do not support "pattern" attribute. As a consequence, if you set a "required" attribute
// and only add blank spaces or new lines, then it is considered as valid (although it makes little sense).
if(this.get('inputTagName') === 'textarea' && input.hasAttribute('required')) {
let content = Ember.$.trim(this.$().val());
if(content.length === 0) {
this.$().val('');
}
}
if (!input.validity.valid) {
this.set('errorMessage', this.getErrorMessage());
} else {
this.set('errorMessage', null);
}
// If the input was never validated, we attach an additional listener so that validation is
// run also on keyup. This makes the UX better as it removes error message as you type when
// you try to fix the errors
if (!this.get('wasValidated')) {
this.$().off('focusout.html5-validation').on('keyup.html5-validation', Ember.run.bind(this, this.validate));
this.set('wasValidated', true);
}
},
/**
* Notify whenever the error message for that input changes
*
* @returns {void}
*/
notifyError: Ember.observer('errorMessage', function() {
let fieldName = this.$().data('field') || this.get('elementId');
this.parentView.send('updateErrorMessage', fieldName, this.get('errorMessage'));
}),
/**
* Get the message error
*
* @returns {String}
*/
getErrorMessage() {
let target = this.get('element');
// If user want to use native browser error messages, we directly return. We also return the stored
// message in case of custom error
if (this.get('useBrowserMessages') || target.validity.customError) {
return target.validationMessage;
}
let errorTemplates = this.get('errorTemplates'),
type = target.getAttribute('type');
// We first check for the "required" case
if (target.validity.valueMissing) {
// For checkbox, we allow to have a title attribute that is shown instead of the
// required message. Very useful for things like "You must accept our terms"
if (type === 'checkbox' && target.hasAttribute('title')) {
return target.getAttribute('title');
}
return errorTemplates.valueMissing[type] || errorTemplates.valueMissing['defaultMessage'];
}
// If a "title" attribute has been set, according to the spec, we can use it as the message
if (target.hasAttribute('title')) {
return target.getAttribute('title');
}
let errorKeys = ['stepMismatch', 'rangeOverflow', 'rangeUnderflow', 'tooLong', 'patternMismatch', 'typeMismatch'];
for (let i = 0 ; i !== errorKeys.length ; ++i) {
let errorKey = errorKeys[i];
if (!target.validity[errorKey]) {
continue;
}
let message = errorTemplates[errorKey][type] || errorTemplates[errorKey]['defaultMessage'];
switch (errorKey) {
case 'tooLong':
return message.fmt(target.getAttribute('maxlength'));
case 'rangeUnderflow':
return message.fmt(target.getAttribute('min'));
case 'rangeOverflow':
return message.fmt(target.getAttribute('max'));
default:
return message;
}
}
return errorTemplates.defaultMessage;
}
});