#TECH

Meteor: Generating a Form Automatically, Validating and Saving it

It all started with the mandate by my organization to try out Meteor with some of my colleagues. During that time, I came across a package which does wonders for a developer. Yes! You heard it right. It does some magic and reduces the development effort. As a developer, I always want to do things fast and see the results reflected quickly. I’m sure there are a lot of people like me out there. Here comes Autoform. As the name suggests, it generates the form automatically for you. You need to add the aldeed:autoform, aldeed:simple-schema and aldeed:collection2 packages to your project.

I’m going to share how to generate and validate a form, then inserting/saving it using the aforementioned packages.

Run the below command from the root directory of your project.

 meteor add aldeed:autoform

 

This will add the aldeed:simple-schema package along with the autoform package. This validates the forms reactively while inserting or updating a MongoDB collection, which was attached to a particular form. The packages in Meteor are named as follows-

<author>:<package name>

 

Now, let’s add the aldeed:collection package. This package enables attaching of a validation schema to a MongoDB collection. Then it is validated while inserting or updating it on the client or server side.

meteor add aldeed:collection2

 

Now, let us get going and start doing something fantastic.

Create a file collection-schema.js in the /lib directory of your project. The files in the /lib directory are accessible from both client and server. Lets define the rules for our mongo collection in the schema as shown below. Then attach the schema to the collection.

In collection-schema.js

Schemas = {};

Schemas.UserProfile = new SimpleSchema({
   firstName: { //Key with which a field is identified and validated.
       label: "First Name", //Label of the field to be shown on the UI.
       type: String,
       regEx: /^[a-zA-Z]{2,25}$/ //Validation rule for this field.
   },
   lastName: {
       label: "Last Name",
       type: String,
       regEx: /^[a-zA-Z]{2,25}$/
   },
   email: {
       label: "Email",
       type: String,
       regEx: SimpleSchema.RegEx.Email,
       index: true, // This creates ascending index for this field.
       unique: true, //This makes sure that this field has a unique index in MongoDB.
       max: 50, //Maximum allowed length of this field.
       custom: function() { //Custom method could be used to do a server side check, 
  //like below.
//Here we’re making sure that this field is having a value and we want it to be      
//validated on the client side only.
           if (Meteor.isClient && this.isSet) {
               console.log("checking unique email");
               Meteor.call("isEmailExisting", this.value, function (error, result) {
                   if (result) {
                       console.log("Found duplicate email");   
                       UserProfile.simpleSchema().namedContext("userProfileForm")
.addInvalidKeys([{name: "email", type: "duplicateEmail"}]);
                   }
               });
           }
       }
   },
   password: {
       label: "Password",
       type: String,
       regEx: /^(?=^.{6,}$)(?=.*[0-9])(?=.*[A-Z])(?=.*[a-z])(?=.*[^A-Za-z0-9]).*$/
   },
   confirmPassword: {
       label: "Confirm Password",
       type: String,
       custom: function () {//Another usage of the custom function.
           if (this.value !== this.field('password').value) {
               return "passwordMismatch";
           }
       }
   },
   gender: {
       label: "Gender",
       type: String,
       autoform: {
           options: [
               {label: "Male", value: "Male"},
               {label: "Female", value: "Female"}
           ]
       }
   },
   tel: {
       label: "Phone",
       type: String,
       regEx: /^[\(\)\s\-\+\d]{10,17}$/,
       optional: true
   },
   address: {
       label: "Address",
       type: String,
       autoform: {
           afFieldInput: {
               type: 'textarea',
               rows: 4
           }
       }
   }
});

 

Here we’re defining the custom messages for the validation errors.

SimpleSchema.messages({
   'regEx firstName': "[label] can have alphabets only",
   'regEx lastName': "[label] can have alphabets only",
   passwordMismatch: 'Passwords do not match',
   duplicateEmail: 'Email already in use',
   notUnique: 'Please use another email, this is already in use',
   'regEx tel': 'Invalid phone number',
   'regEx password': "Password must be 8-20 characters long and contain a lowercase alphabet, an uppercase alphabet, a digit and a special character",
});

 

Now, we’ll attach the validation schema with the MongoDB collection. This will validate the UserProfile collection while inserting it.

UserProfile.attachSchema(Schemas.UserProfile);

 

On the server side, in the server/profile.js

Declare the collection-

UserProfile = new Meteor.Collection('userProfile');

 

Let’s add the methods that will be used for custom validation on the user profile collection and to save it.

Meteor.methods({
   isEmailExisting: function(emailToCheck) {
      var count = UserProfile.find({'email': emailToCheck}).count();
      console.log("Found emails-" + count);
      return count > 0;
   },
   saveProfile: function(profileDoc) {
      return UserProfile.insert(profileDoc);
   }
});

 

Now, we will write the code to generate the autoform. We’ll be using the autoform tag. This tag allows us to customize the fields that we need to generate. We could have also used the quickform tag. It would have required only 3 lines to generate the whole form. Seriously, not kidding! The id has to be unique for each and every autoform. Here, we are telling autoform to use the collection UserProfile to generate the form. The validations are fired on the blur event of the field. The form-horizontal is the bootstrap css class. The type=normal allows us to write custom form submission logic using the Autoform.hooks().

On the client side, in the client/views/profile.jsp

<template name="profileForm">
   <div class="container">
   <h3>Profile</h3>
   {{#autoForm collection="UserProfile" id="userProfileForm" type="normal" 
     validation="blur" class="form-horizontal"}}
       <fieldset>
           {{> afQuickField name='firstName'}}
           {{> afQuickField name='lastName'}}
           {{> afQuickField name='email'}}
           {{> afQuickField name='password' type='password'}}
           {{> afQuickField name='confirmPassword' type='password'}}
           {{> afQuickField name='gender'}}
           {{> afQuickField name='address'}}
           {{> afQuickField name='tel' type='tel'}}
       </fieldset>
       <div class="form-group">
           <button type="submit" class="btn btn-primary">Signup</button>
           <button type="reset" class="btn btn-default">Reset</button>
       </div>
   {{/autoForm}}
   </div>
</template>

 

On the client side, in the profile.js

We need to define the onSubmit method in the Autoform hooks to submit the form. The hooks are bound to the form with their ids. These hooks are provided by the autoform package. For further details, you can refer the https://github.com/aldeed/meteor-autoform page for the other methods. If we don’t return false from the onSubmit method, the form will still be posted after validating it. We need to call this.done() to notify the autoform, that we’re done with our custom submission logic.

AutoForm.hooks({
   userProfileForm: {
       onSubmit: function(insertDoc, updateDoc, currentDoc) {
           Meteor.call('saveProfile', insertDoc);//
           this.done();
           return false;
       },
       before: function () {
           //show loader
       },
       after: function() {
           //hide loader
       }
   }
});

 

The insertDoc parameter will have the validated form data.

That’s it folks!

You might also like