-
Notifications
You must be signed in to change notification settings - Fork 3
/
index.js
375 lines (320 loc) · 12.4 KB
/
index.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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
var _ = require('lodash');
var Emitter = require('emitter');
/**
* Expose 'legit'
*/
module.exports = Legit;
// TODO: Make mixin component.
function mixin(obj) {
for (var key in Legit.prototype) {
obj[key] = Legit.prototype[key];
}
return obj;
}
/**
* Legit constructor. Mixes it's functions into the object.
*/
function Legit(obj) {
if (obj) {
Emitter(obj);
return mixin(obj);
}
}
Legit.prototype.invalidAttributes = [];
/*
* Hammer Dance!
*/
Legit.prototype.tooLegitToQuit = function() {
console.log('http://www.youtube.com/watch?v=wiyYozeOoKs');
};
/**
* Validates the entire model. Loops through all attributes
* in the "validation" attribute of the model and triggers the proper
* events depending on whether it the model is valid or not.
*
* @return {Array} The invalidAttributes array.
*
* @api public
*/
Legit.prototype.validate = function() {
_.each(this.validation, function (value, key) {
this.validateAttribute(key);
}, this);
this.triggerValidationEvents();
if (!this.isLegit()) {
return this.invalidAttributes;
}
};
/**
* Validates a single attribute for this model. Triggers proper events depending on
* whether the attribute is valid or not.
*
* Validating an attribute depends on there being a key in the "validation" attribute
* in this model that has the same name as the attribute.
*
* @param {String} attr The name of the attribute to validate.
*
* @api public
*/
Legit.prototype.validateAttribute = function(attr) {
var currentAttrVal = this.get(attr);
var attrValidation = this.validation[attr];
// If the defined validation for this model attribute is an array, it means
// there are multiple validations that need to be run for this attribute.
// So loop through and run each of them.
if (attrValidation instanceof Array) {
_.each(attrValidation, function (attrValidation) {
this.performValidation(attrValidation, currentAttrVal, attr, false);
}, this);
} else {
// Otherwise, there is only a single validation for this attribute.
this.performValidation(attrValidation, currentAttrVal, attr, false);
}
return this.isLegit(attr);
};
/**
* Returns whether this model is currently valid or not.
*
* @param {String} attr if you want to validate a specific attribute
* @return {Boolean} Whether the model is valid or not.
*
* @api public
*/
Legit.prototype.isLegit = function(attr) {
if (attr) {
return _.where(this.invalidAttributes, { attr: attr }).length === 0;
} else {
return this.invalidAttributes.length < 1;
}
};
/**
* Performs a single validation for a given attribute.
*
* @param {Object} attrValidation The validation entry which contains the validator, msg, etc.
* @param {?} currentAttrVal The value for this attribute at the time of validation.
* @param {String} attr The attribute name.
* @param {Boolean} silent Whether or not to trigger events. True to skip
* triggering of events.
* @api private
*/
Legit.prototype.performValidation = function(attrValidation, currentAttrVal, attr, silent) {
// Only validate if there is either no "onlyWhen" attribute
// or the "onlyWhen" function returns true.
if (this.shouldValidate(attrValidation)) {
var errorMsg;
// The validator failed.
if (!this.runValidator(attrValidation, currentAttrVal)) {
this.trackInvalidAttribute(attr, attrValidation.msg);
if (!silent) {
this.emit('validated:invalidAttribute', this, attr, attrValidation.msg);
}
// The validator passed.
} else {
this.trackValidAttribute(attr, attrValidation.msg);
if (!silent) {
this.emit('validated:validAttribute', this, attr);
}
}
}
};
/**
* Track that a particular attribute failed a validation by adding it to
* the invalidAttributes array.
*
* @param {String} attr The attribute name.
* @param {String} msg The error message.
*
* @api private
*/
Legit.prototype.trackInvalidAttribute = function(attr, msg) {
var invalidAttrObject = {
attr: attr,
msg: msg
};
// If this failed validation hasn't already been added to the
// invalidAttributes array, then add it now.
if (!_.findWhere(this.invalidAttributes, invalidAttrObject)) {
this.invalidAttributes.push(invalidAttrObject);
}
};
/**
* Track that a particular attribute passed a validation by removing it from
* invalidAttributes array.
*
* @param {String} attr The attribute name.
* @param {String} msg The error message.
*/
Legit.prototype.trackValidAttribute = function(attr, msg) {
var invalidAttrObject = {
attr: attr,
msg: msg
};
// If there is an attribute with this failed message in the invalidAttributes array, remove
// it now.
if (_.findWhere(this.invalidAttributes, invalidAttrObject)) {
this.invalidAttributes = _.reject(this.invalidAttributes, function(invalidAttr){
return _.isEqual(invalidAttrObject, invalidAttr);
});
}
};
/**
* Trigger validation events for this model.
*/
Legit.prototype.triggerValidationEvents = function() {
// Loop through each validation entry for this model.
_.each(this.validation, function (attrValidation, attr){
// Find any invalid entries for this attribute.
var invalidValidators = _.where(this.invalidAttributes, { attr: attr });
// There are invalid entries for this attribute.
if (invalidValidators.length > 0) {
// Grab the last invalid entry.
var invalidValidator = invalidValidators[invalidValidators.length-1];
// Trigger an "invalidAttribute" event for this attribute and pass it the model,
// attribute name and error message.
this.emit('validated:invalidAttribute', this, invalidValidator.attr, invalidValidator.msg);
// There are no invalid entrues for this attribute.
} else {
// Trigger a "validAttribute" event for this attribute.
this.emit('validated:validAttribute', this, attr);
}
}, this);
// Is this model valid right now?
if (!this.isLegit()) {
// Trigger 'invalid' event and pass all invalid entries.
this.emit('validated:invalid', this, this.invalidAttributes);
} else {
// Trigger 'valid' event.
this.emit('validated:valid', this);
}
};
/**
* Should we run this validation?
*
* @param {Object} attrValidation The validation.
*
* @return {Boolean} Whether or not this validator should be run.
*/
Legit.prototype.shouldValidate = function(attrValidation) {
// Does 'onlyWhen' the key exist in this validation?
if (attrValidation.onlyWhen) {
// Is the "onlyWhen" value a string? If so, it's a function on this model to
// be called.
if (typeof attrValidation.onlyWhen === 'string') {
return this[attrValidation.onlyWhen]();
// Otherwise it's a function, so run it.
} else {
return attrValidation.onlyWhen();
}
// The key doesn't exist, so just run the validation.
} else {
return true;
}
};
/**
* Run the validator function for the given validation.
*
* @param {Object} attrValidation The validation.
* @param {?} value The value of the attribute at the time of validation.
*
* @return {Boolean} Whether or not the validation passed.
*/
Legit.prototype.runValidator = function(attrValidation, value) {
if (typeof attrValidation.validator === 'string') {
return this.defaultValidators[attrValidation.validator].call(this, value, attrValidation);
} else {
return attrValidation.validator.call(this, value, attrValidation);
}
};
/**
* Check if this value is falsy or not.
*
* @param {?} value The value to check.
*
* @return {Boolean} False if the value is falsy, true if it's truthy.
*/
Legit.prototype.validateHasValue = function(value) {
if (value) {
if (_.isString(value) && value.length === 0) {
return false;
}
return true;
} else {
return false;
}
};
Legit.prototype.defaultValidators = {
/**
* Check if this value exists or not.
*
* @param {?} value The value to check.
*
* @return {Boolean} False if the value is falsy, true if it's truthy.
*/
required: function (value) {
return this.validateHasValue(value);
},
/**
* Check if this value looks like a email.
*
* @param {?} value The value to check.
*
* @return {Boolean} True if the value looks like a email, false if not.
*/
email: function (value) {
return this.validateHasValue(value) && value.toString().match(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i);
},
/**
* Check if this value looks like a url.
*
* @param {?} value The value to check.
*
* @return {Boolean} True if the value looks like a url, false if not.
*/
url: function (value) {
return this.validateHasValue(value) && value.toString().match(/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i);
},
/**
* Check if this value looks like a url.
*
* @param {?} value The value to check.
*
* @return {Boolean} True if the value matches any digit(s) (i.e. 0-9), false if not.
*/
digits: function (value) {
return this.validateHasValue(value) && value.toString().match(/^\d+$/);
},
/**
* Check if this value looks like a url.
*
* @param {?} value The value to check.
*
* @return {Boolean} True if the value matched any number (e.g. 100.000), false if not.
*/
number: function (value) {
return this.validateHasValue(value) && value.toString().match(/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/);
},
/**
* Check if this value is at least a certain length.
*
* @param {?} value The value to check.
* @param {Object} attrValidation The validation object.
*
* @return {Boolean} True if the value is at least as long as the
* threshhold value found in the attrValidation
* 'validationArg' key.
*/
minLength: function (value, attrValidation) {
return value.toString().length >= attrValidation.validatorArg;
},
/**
* Check if the value is equal to another model attrbute's value.
*
* @param {?} value The value to check.
* @param {Object} attrValidation The validation object.
*
* @return {Boolean} Whether or not the two attribute values are equal.
*/
equalTo: function (value, attrValidation) {
return value === this.get(attrValidation.validatorArg);
}
};