Deprecacation Warning: I currently recommend using simple schema for all validation as it integrates with your collections and allows you to fully secure and use client side operations in totality.
A dual client/server side form data validation, transformation, and aggregation package for Meteor
- Same validations logic on client and server sides
- Clean validation workflow with
- Transformations: trim, capitalize, clean...
- Formats: email, float, boolean, url, creditcard...
- Rules: min, max, equals...
- Field Aggregation: sum, avg, join
- Conditional required fields
- Frontend hooks
- Extensible via api
meteor add copleykj:shower
Shower invocation happens in two steps:
- First a configuration step
- Then runtime validation step
Shower(formDescriptionObject);
When invoked, Shower creates a new form object and stores it as a property of itself. You can then gain access to the form by referring to *Shower.*formName.
To validate a form, call the validate() method on the form object and it will return an object containing errors and validated/transformed form-data. Validation can happen client and server side.
var validationObject = Shower.loginForm.validate(rawFormData);
For instance, the above call could return:
validationObject:{
errors:{
username:{
required:true,
message:"*Required Field*"
}
},
formData:{
username:"",
password:"C00lPa$$w0rd"
}
}
If there are no errors, validationObject.errors will be empty;
You can also supply a callback as the second parameter of the validate() method. In this case the validation object will not be returned as it was in the above example, but split and passed as parameters to the callback. The call back will be passed the errors object as the first parameter and the formData object as the second.
Shower.loginForm.validate(rawFormData, function(errors, formData){
});
The validation workflow is processed as follows: first the fields are aggregated, checked for requirement, and transformed, They are then compared aginst their format and evaluated against each rule. Finally any fields specified by removeFields are removed.
Shower requires an object that describe the form and fields elements. This section describes the different elements which can compose that description.
name/id - The form must contain either a name or an id key which corresponds to the name or id of the form element in your html. This will create a new form object scoped to Shower.formName or Shower.formId. These accessors are the main access point to any validation checks.
template - (Optional) The name of the template that contains the form. If specified, Shower will attach the submit event to the template otherwise it will attache the submit using jQuery. Attaching via the template will cause the data context from the template to be passed to the validation call back. This is completely useless when combined with the disableSubmit option
disableSubmit - If true, Shower will not validate the form when it is submitted and it will be up to you to handle this. When using this option the Shower.Utils.getFormData() method can be useful.
method - The name of an optional remote method to call. If you pass in a string for the method, Shower will call the Meteor server method by this name, if you pass in a function then it will call the function. Either way it passes one argument to the method or function which is the raw data taken from the form.
aggregates - An object who's keys are the name of a new field to be created from a set of other form elements. The value for each key is an array with it's first element as the name of the aggregate function, an the second another array containing the names of the fields you would like to aggregate. A third element to the array can be added which will be passed as an argument to the aggregate function.
aggregates:{
birthDate:["join", ["month", "day", "year"], " "],
fullName:["join", ["firstName", "LastName"], " "]
}
Aggregation will be performed after validation and transformation. Any validation should happen on fields that the aggregation depends on to ensure valid data will be aggregated. If there is a need to transform aggregated data you can accomplish this by creating a custom aggregation function and return the transformed data.
Current aggregation functions are:
- sum - calculates the sum of the specified fields.
- avg - calculates the average of specified fields.
- join - concatenates the values of the specified fields. Takes optional separator argument.
- arraySet - creates new array of values of the specified fields.
- objectSet - creates new object of key:value pairs of specified fields.
removeFields - list of fields that should be removed from returned form values. Especially useful if you wish to remove fields that have been aggregated together into new fields.
removeFields["month", "day", "year", "firstName", "lastName"]
fields - This is a list of fields with each key being the name of a form field and the value being an object telling Shower about that field. See next chapter.
fields:{
firstname:{
//field description
},
userID:{
//field description
}
}
required - The required key tells Shower if this is a required field, if the field is required dependently of another field, or if the field is required only when the field is absent from the submitted fields. If required dependently the field can be conditionaly required based on another field value.
Let's see this in practice:
// field is required
required: true
or
// field is required when country is set
required:{
dependsOn:"country"
}
or
// field is required when country is set to "USA"
required:{
dependsOn:"country",
value:"USA"
}
or
// field is only required if other field is missing in form submission
required:{
whenFieldAbsent:"country"
}
message - The message key is the message added to the fields in the erroredFields object which is returned from the validate function and passed to the onFailure callback when validation fails. if message is not defined, the error message is set to *Invalid Input*
requiredMessage - The requiredMessage key is added to the fields in the erroredFields object when a required field is not present and is returned from the validate function and passed to the onFailure callback. If requiredMessage is not defined the error message is set to *Required Field*
Shower({
name: "testForm",
fields: {
firstname: {
required: true,
transforms: ["clean", "capitalize"],
format: "alphanumeric",
message: "That's a weird first name!"
}
}
});
Array telling Shower the transforms to perform on the field prior to any validation. Note that multiple transforms can be applied.
The following transforms are currently implemented:
- trim - Trims left & right spaces
- clean - Trims and removes extra spaces
- capitalize - Upper Case first letter
- stripTags - Removes any xml tags
- escapeHTML - Escapes HTML tags
- toUpperCase - all to UPPERCASE
- toLowerCase - all to lowercase
- slugify
- humanize
Please check the underscore.js documentation and underscore.string documentation for more details about these operations.
Element that defines the field data format. the format can be expressed as a predefined string, a regex or a function.
- boolean - 0, 1, true, false, yes, no
- integer - 65342, -1
- hex - FE7B, 1b83de
- float - 127.1, -57.3
- alphanumeric - that's it
- email dest@domain.ext
- money $100.00, $-200
- ipv4 127.0.0.1
- phone +1 (305) 1234567, +33 47 57 11 22
- url https://www.crionics.com, ftp://127.0.1.3
- creditcard 378282246310005, 4012-8888-8888-1881, 4012 8888 8888 1881
When using a regex, you could for instance use something as follows. Note how the format references a RegEx.
Shower({
name: "testForm",
fields: {
zipcode: {
required: true,
format: /^[0-9]{5}$/,
message: "bad zip code"
}
});
Using a function is equally straighforward. For instance in the exmaple below, we define a function that only accepts strings with a "Thank you" note. Format functions are typically used when a regular expression falls short.
Shower({
name: "testForm",
fields: {
msg: {
required: true,
format: function(val) { return val.indexOf("Thank you")},
message: "You didn't say thank you!"
}
});
List of rules for validating the field value. Note that this is different from a the format which acts more as a regular expression. Rules implement precise logic such as boundaries or edge case detection.
Shower({
name: "testForm",
fields: {
firstname: {
required: true,
format: "alphanumeric",
message: "bad zip code",
rules:{
maxLength:20,
minLength:4
}
}
}
});
The list of predefined rules is the following:
- maxLength
- minLength
- exactLength
- failIfFound
- minValue
- maxValue
- equalsValue
- maxFileSize
- acceptedFileTypes
In cases where you need to insert a default value when no value is provided (blank text boxes, unchecked checkboxes, etc.) there is the defaultValue
option on the field description. If this is specified as a function it will call the function and assign the return value as the default, otherwise the value will be assigned directly as is.
Shower({
name: "testForm",
fields: {
firstname: {
required: true,
format: "alpha",
message: "Only letters in a first name.",
rules:{
maxLength:20,
minLength:4
},
defaultValue: function(formFieldsObject){
switch(formFieldsObject["gender"]){
case "Male":
return "John";
case "Female":
return "jane";
}
}
}
}
});
onFailure - The client side callback when validation has failed. This gets passed an erroredFields object as the first parameter, which has each field that failed validation, which rules it failed and the message you provided. The second parmeter passed is a jQuery handle to the form.
By default this tries to insert the error message provided in an element with an id of fieldname-error
For example for a field with a name of username, this function will try to insert the message text in an element like this.
<span id='username-error'></span>
When overriding the default call back you can maintain default functionality, while building upon it, by calling the failureCallback function and passing in the erroredFields object that was passed to the onFailure callback.
onFailure: function(erroredFields, formHandle){
//custom code here
Shower.Utils.failureCallback(erroredFields, formHandle);
}
onSuccess - The client side callback when validation has succeeded. This is passed an object of validated and transformed form-data as key:value pairs as the first parameter, and a jQuery handle of the form as the second. By default this just clears all the displayed errors.
This, like the onFailure callback can maintain and build upon the default functionality by calling the successCallback. This however does not require you to pass in the form-data since it doesn't use it.
onSuccess: function(formData, formHandle){
//custom code here
Shower.Utils.successCallback(formData, formHandle);
}
To achieve dual client/server validation, pass in the name of the Meteor server method as a string to the method attribute of the formDescriptionObject. This method will be passed the unvalidated form data, and if a template was specified it will pass the template context as the second param. If data is available from the template context, this give access to it. e.g templateContext._id
.
On the client, Shower will then call the method when the form is submitted, passing in the form data. Then use the forms validate function in the server method and pass in the form data and specify the validation callback. This callback is passed an errors object as its first parameter which is empty if no fields errored. The second parameter passed is an object containing the validated and tranformed data for further manipulation or storage.
Meteor.methods({
login:function(rawFormData, templateData){
Shower.loginForm.validate(rawFormData, function(errors, formFieldsObject){
if(!errors){
//Do what we need to do here;
}
});
}
});
Next call Shower to wrap your form for validation. Shower takes one argument. This argument is an object describing the form, how to validate and transform the data, the Meteor method to use, an optional template name to attach the submit event to, and the client side callbacks.
Simple example:
Shower({
name:"loginForm",
method:"login",
template:"loginTemplate",
fields:{
username:{
required:true,
message:"letters or numbers, between 3 and 20 characters.",
format:/^[A-Z0-9]{3,20}$/i,
transform:["trim"]
},
password:{
required:true,
message:"Must be at least 6 characters.",
rules:{
minLength:6
}
}
}
});
###getFormData
The getFormData Method is exposed as Shower.Utils.getFormData(formReference)
. This is useful when handling form events yourself. You will need to pass in a reference to the form as the first parameter.
The successCallback method is exposed as `Shower.Utils.successCallback(formData, formHandle). This is the default client side success callback used to reset the form and clear the form errors. This is useful if you replace the onSuccess callback in a form description and wish to call this to retain default functionality while adding your own.
The failureCallback method is exposed as `Shower.Utils.failureCallback(erroredFields, formHandle). This is the default client side failure callback used to reset the form and clear the form errors. This is useful if you replace the onFailure callback in a form description and wish to call this to retain default functionality while adding your own.
You may add you own set of rules & transforms by using the following methods:
// fn takes the fieldValue and the defined ruleValue
Shower.registerFormat(name, fn);
// fn takes the fieldValue and the defined ruleValue
Shower.registerRule(name, fn);
// fn recieves the string to transform
Shower.registerTransform(name, fn);
//fn recieves an array of field names, an object containing the current submitted form values and optionally an argument passed to the function.
Shower.registerAggregate(name, fn);
For instance,
Shower.registerRule("force-zipcode", function(fieldValue, ruleValue){
return fieldValue === ruleValue;
});
Shower({
name:"myForm",
method:'checkZip',
fields:{
zipcode:{
required:true,
message:"Zip code 33178 is required",
rules:{
force-zipcode:"33178"
},
transform:["trim"]
}
}
});
A set of Shower snippets for Sublime Text are available here: https://github.com/copleykj/mesosphere-snippets
This is not a Sublime Text package so you will have to install it manually. If anyone would like to tackle the task of turning it onto a Sublime Text Package please feel free to fork and do so.
For now the example has been removed. Examples are coming shortly as a Meteor site available as a repository that can be cloned and ran or viewed on the meteor.com site.
Contributions are always welcome, please follow these steps to submit your changes via a github pull request.
Your code should include unit tests & documentation changes when applicable.
Ensure all unit tests pass sucessfully.
meteor test-packages ./