-
Notifications
You must be signed in to change notification settings - Fork 35
/
validations.js
248 lines (224 loc) · 9.51 KB
/
validations.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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
const Validator = Ember.Object.extend({
/**
@param {Object} target the target object
@param {String} key the target object property
@returns {Boolean} validation status
*/
validate: function(target, key) {
return true;
},
validateValue: function(value) {
return this.validate(Ember.Object.create({value: value}), 'value');
},
/**
@returns {String} the property which the validator will set the result of the validation.
*/
isValidProperty: function(key) {
return key + 'IsValid';
}
});
/**
Mix this in to your model object to perform on the fly validation.
You must provide a 'validations' hash, with the keys defining each property of your model to validate,
and the values the validation logic.
The validation logic should be defined either as a Flame validator singleton, an anonymous function, or a hash.
Validation is done on-demand, demand being the first call to foo.get("barIsValid") or foo.get("isValid").
Thus we don't validate stuff that just goes to DataStore but only the thing we use and about whose validity we're
interested in.
If you define 'Coupled properties' for a property foo, this means that when foo has changed, we need to revalidate not
just foo but also each coupled property. For example, if we have properties password and passwordCheck, when we
edit password we need to revalidate the validation for passwordCheck also.
Validations can only be set once to the object (this is usually done in the definition of the objects class).
*/
const Validatable = Ember.Mixin.create({
_propertyValidity: null,
_objectIsValid: null,
_validations: null,
isValidProperty: function(property) {
return property + 'IsValid';
},
// The observer calls this method with a value, so we have to add ignoreCoupledProperties afterwards
validateProperty: function(target, key, value, ignoreCoupledProperties) {
if (Ember.isNone(ignoreCoupledProperties)) {
ignoreCoupledProperties = false;
}
if (value === undefined) {
value = target.get(key);
}
var validationObj = target.get('validations')[key];
var coupledProperties = null;
if (jQuery.isPlainObject(validationObj)) {
var hash = validationObj;
validationObj = hash.validation;
coupledProperties = hash.coupledProperties;
}
var isValid;
if (!jQuery.isArray(validationObj)) {
validationObj = [validationObj];
}
for (var i = 0; i < validationObj.length; i++) {
if (!(isValid = this._validate(validationObj[i], target, key, value))) {
break;
}
}
var isValidProperty = this.isValidProperty(key);
target.beginPropertyChanges();
target.set(isValidProperty, isValid);
// Coupled properties are properties that should be revalidated if the original property changes
if (!ignoreCoupledProperties && coupledProperties) {
if (!jQuery.isArray(coupledProperties)) {
throw new Error('Hint: coupledProperties must be an array!');
}
for (var j = 0; j < coupledProperties.length; j++) {
var coupledProperty = coupledProperties[j];
if (coupledProperty !== key) {
this.validateProperty(this, coupledProperty, undefined, true);
}
}
}
target.set('isValid', target._checkValidity());
target.endPropertyChanges();
},
invalidProperties: function() {
var invalids = [];
var validations = this.get("validations");
for (var key in validations) {
if (this.get(this.isValidProperty(key)) !== true) {
invalids.push(key);
}
}
return invalids;
}.property().volatile(),
_validate: function(validator, target, key, value) {
var isValid = null;
if (validator instanceof Validator) {
isValid = validator.validate(target, key);
} else if (!Ember.isNone(validator)) {
// if not Validator, assume function
isValid = validator.call(this, value);
}
return isValid;
},
/**
@returns {Boolean} to indicate if all properties of model are valid.
*/
_checkValidity: function(forceRevalidation) {
var validations = this.get("validations");
for (var key in validations) {
if (forceRevalidation) {
this.validateProperty(this, key, this.get(key));
}
if (validations.hasOwnProperty(key) && this.get(this.isValidProperty(key)) !== true) {
return false;
}
}
return true;
},
isValid: function(key, val) {
if (typeof val !== "undefined") {
this._objectIsValid = val;
}
if (this._objectIsValid === null) { // If we haven't initialized this property yet.
this._objectIsValid = this._checkValidity();
}
return this._objectIsValid;
}.property().volatile(),
/**
Allow setting of validations only once. Validations set through this property are ignored after they've been
set once.
*/
validations: function(key, val) {
if (!Ember.isNone(val)) {
if (this._validations === null) {
this._validations = val;
} else {
Ember.Logger.info('Trying to set validations after the validations have already been set!');
}
}
return this._validations;
}.property().volatile(),
/**
Create all the *isValid properties this object should have based on its validations-property.
*/
_createIsValidProperties: function() {
var validations = this.get('validations');
var propertyName;
// TODO do this without setting computer properties, using only simple properties (i.e. the kind 'foo' is when
// defined like Ember.Object({foo: false}).
for (propertyName in validations) {
if (validations.hasOwnProperty(propertyName)) {
this._createIsValidProperty(propertyName);
}
}
for (propertyName in validations) {
if (validations.hasOwnProperty(propertyName)) {
this.addObserver(propertyName, this, 'validateProperty');
this.validateProperty(this, propertyName);
}
}
},
_createIsValidProperty: function(propertyName) {
if (this._propertyValidity === null) this._propertyValidity = {};
var self = this;
Ember.defineProperty(this, this.isValidProperty(propertyName), Ember.computed(function(propertyIsValidName, value) {
// Emulate common property behaviour where setting undefined value does nothing.
if (typeof value !== "undefined") {
self.propertyWillChange(propertyIsValidName);
self._propertyValidity[propertyIsValidName] = value;
self.propertyDidChange(propertyIsValidName);
}
return self._propertyValidity[propertyIsValidName];
}).property().volatile());
},
/**
Add validation for
@param {String} propertyName Name of the property we want to validate.
@param {Object} validator Validator or function that will handle the validation of this property.
*/
setValidationFor: function(propertyName, validator) {
// TODO do this without setting computed properties, using only simple properties (i.e. the kind 'foo' is when
// defined with Ember.Object({foo: false}).
var validations = this.get('validations');
if (validations === this.constructor.prototype.validations || validations === null) {
// ensure that setValidationFor does not mess with prototype-defined validations
validations = jQuery.extend({}, validations);
this.set('validations', validations);
}
validations[propertyName] = validator;
this._createIsValidProperty(propertyName);
this.removeObserver(propertyName, this, 'validateProperty'); // In case we're redefining the validation
this.addObserver(propertyName, this, 'validateProperty');
this.validateProperty(this, propertyName);
},
unknownProperty: function(key) {
var res = /^(.+)IsValid$/.exec(key);
var validations = this.get('validations');
if (res && validations) {
var propertyName = res[1];
if (validations[propertyName]) {
this._createIsValidProperties();
return this.get(key);
}
}
// Standard bailout, either the property wasn't of the form fooIsValid or we don't have property foo in
// this.validations.
if (this.__nextSuper) return this._super(key);
},
setUnknownProperty: function(key, value) {
var res = /^(.+)IsValid$/.exec(key);
var validations = this.get('validations');
if (res && validations) {
var propertyName = res[1];
if (validations[propertyName]) {
this._createIsValidProperties();
return this.set(key, value);
}
}
// Standard bailout, either the property wasn't of the form fooIsValid or we don't have property foo in
// this.validations.
if (this.__nextSuper) return this._super(key, value);
Ember.defineProperty(this, key);
return this.set(key, value);
}
});
export { Validator, Validatable };