Introduction
Having job application forms, or "employment forms", readily available on your corporate website saves time and money for job seekers and recruiters alike, raises company visibility online, and is an integral part of the hiring process overall.
In a typical job application form, the applicant is asked to complete an online application form containing fields for filling in their personal information, their educational qualifications, previous work experience, and uploading their resume.
Such an application form can be created using specific online tools which are usually paid tools, but guess what, you could create a job application form from scratch without using any paid software.
In this article, we'll explore a free and open-source JavaScript library that can be leveraged to build a job application form which you can connect easily to your website. But before we head into the details, let's take a look at the technologies that we'll use:
- Angular: An application-design framework and development platform for creating single-page applications.
- SurveyJS: A free and open-source JavaScript form builder (Github link), which makes creating self-hosted forms much simple and easier.
Now, if you're wondering why you should bother with yet another JavaScript library for building a job application form, instead of using only Angular, you can read this guide. It covers in detail the benefits of this library and how it makes your work faster and easier.
Next, let's see the basic outline of our job application form including the number of pages the form will have and the fields on each page.
Structure of the Job Application Form
The job application form that we will create in this article is composed of 4 different pages:
Page 1: Personal information
- Name
- Address
- Country
- Phone
Page 2: Education and training (a maximum of 3 education diplomas)
- Diploma (degree)
- School
- Year of graduation
- Description
Page 3: Experience (a maximum of 3 previous work experiences)
- Position
- Company
- Start date
- End date
- Description
Page 4: Resume and cover letter
- Download resume
- Write a cover letter (why you are suitable for the position)
Finally, we'll begin our step-by-step process of building the above job application form.
Let's start.
Steps for Creating the Job Application Form
Step 1: Create an Angular Application and Configure SurveyJS
Before starting, we should ensure that we have installed NPM and the latest version of Angular CLI.
Then, let's create an Angular project using CLI and install SurveyJS:
ng new job-application-form-app
cd job-application-form-app
npm install survey-angular-ui - save
This command will generate a project with the following structure:
After that, we'll configure the SurveyJS theme. According to the official documentation, SurveyJS come with two themes:
- Modern UI
- Default V2 UI
For our example, we will use the Default V2 UI theme.
Let's add the following dependency to the angular.json file:
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"job-application-form-app": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
...
"styles": [
"src/styles.css",
// Default V2 theme
"node_modules/survey-core/defaultV2.min.css"
],
...
},
....
},
"serve": {
...
},
"extract-i18n": {
...
},
"test": {
...
}
}
}
}
}
In order to apply the Default V2 UI theme, we should use the applyTheme(themeName) method and pass "defaultV2" as a parameter. So, in the app.component.js file let's call the following method:
app.component.js
import { Component } from "@angular/core";
import { StylesManager } from "survey-core";
StylesManager.applyTheme("defaultV2");
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
})
export class AppComponent {
title = "job-application-form-app";
}
Step 2: Create the Job Application Form model
The model describes the content of our form. The simplest way to create the form model is to use a JSON-based schema object. This schema contains the different sections and parts of our form.
So now let's create our model schema:
form.schema.ts
export const schema = {
title: 'Application For Software engineer position',
showProgressBar: 'top',
progressBarType: 'buttons',
pages: [
{
name: 'personalInformation',
navigationTitle: 'Personal information',
navigationDescription: 'Tell us about you',
startWithNewLine: false,
showQuestionNumbers: 'off',
elements: [
...
],
},
{
name: 'educationTraining',
navigationTitle: 'Education and training',
navigationDescription: 'Tell us about your education',
elements: [
...
],
},
{
name: 'experience',
navigationTitle: 'Experience',
navigationDescription: 'Tell us more about your experience',
elements: [
...
],
},
{
name: 'resumeCoverLetter',
navigationTitle: 'Resume and cover letter',
navigationDescription: 'Tell us why you are suitable for the position',
elements: [
...
],
},
],
};
In the code above, we've put "..." in the content of each page as a substitute for the actual code that we'll fill out shortly.
Our schema is a JSON object that contains the content of our form. Here's a brief explanation of every property:
title
: Displaying the form's title.showProgressBar
: Placement of the progress bar of the form. It can be ["off", "top", "bottom", "both"]progressBarType
: Type of information displayed in the progress bar. It can be ["pages", "questions", "requiredQuestions", "correctQuestions", "buttons"]pages
: Describes all the pages of the form. Here we have 4 different pages, each page is concerned with a specific part of our job application form.
In the following lines, we'll get into the code for each page.
- Page 1: Personal information
This page contains 5 simple text fields as shown below:
{
name: 'personalInformation',
navigationTitle: 'Personal information',
navigationDescription: 'Tell us about you',
startWithNewLine: false,
showQuestionNumbers: 'off',
elements: [
{
type: 'text',
name: 'fullname',
title: 'Full name',
},
{
type: 'text',
name: 'address',
title: 'Address',
},
{
type: 'text',
name: 'country',
title: 'Country',
},
{
type: 'text',
name: 'phone',
title: 'Telephone number',
},
{
type: 'text',
name: 'email',
title: 'Email',
},
],
},
The code itself is pretty simple and self-explanatory. Next, we'll see the 2nd page which is slightly more complex.
- Page 2: Education and training
This page is a bit complicated compared to the first page. Here we have a maximum of 3 fields and a minimum of 1 field for "Education". And for implementing this in an easy and elegant way, we'll use the SurveyJS component "paneldynamic". We'll see how in the code given below.
Here's the code:
{
name: 'educationTraining',
navigationTitle: 'Education and training',
navigationDescription: 'Tell us about your education',
elements: [
{
type: 'paneldynamic',
name: 'educations',
title: 'Educations',
keyName: 'diploma',
showQuestionNumbers: 'off',
templateTitle: 'Education #{panelIndex}',
minPanelCount: 1,
maxPanelCount: 3,
panelAddText: 'Add another education',
panelRemoveText: 'Remove education',
templateElements: [
{
type: 'dropdown',
name: 'diploma',
title: 'Diploma',
showNoneItem: true,
showOtherItem: true,
choices: [
'High school Degree',
'Bachelor Degree',
'Master Degree',
'Doctoral Degree',
],
},
{
type: 'text',
name: 'school1',
title: 'School or University',
},
{
type: 'text',
name: 'graduationYear',
title: 'Graduation year',
inputType: 'date',
},
{
type: 'comment',
name: 'description',
title: 'Description of what you have studied',
},
],
},
],
},
Let's move on to the 3rd page.
- Page 3: Experience
This page is very similar to the 2nd page. In fact, we'll use the "paneldynamic" component again to implement it.
Here's how:
{
name: 'experience',
navigationTitle: 'Experience',
navigationDescription: 'Tell us more about your experience',
elements: [
{
type: 'paneldynamic',
name: 'experiences',
title: 'Experiences',
keyName: 'position',
showQuestionNumbers: 'off',
templateTitle: 'Education #{panelIndex}',
minPanelCount: 1,
maxPanelCount: 3,
panelAddText: 'Add another experience',
panelRemoveText: 'Remove experience',
templateElements: [
{
type: 'text',
name: 'position',
title: 'Position',
isRequired: true,
},
{
type: 'text',
name: 'company',
title: 'Company',
isRequired: true,
},
{
type: 'text',
name: 'startDate',
title: 'Start date',
inputType: 'date',
isRequired: true,
},
{
type: 'text',
name: 'endDate',
title: 'End date',
inputType: 'date',
isRequired: true,
visibleIf: '{panel.untilNow}=false',
},
{
type: 'boolean',
name: 'untilNow',
title: 'Until now',
isRequired: true,
defaultValue: false,
},
{
type: 'comment',
name: 'description',
title: 'Description of what you have worked on',
},
],
},
],
},
What is interesting about this part is our use of Conditional Branching. We have used the Boolean "visibleIf" expression to hide or show the "end date" field, depending on the "until now" field. This is the simplest example of SurveyJS' conditional logic in action, but that's all you'll need in most cases. You can read more about Conditional Visibility here.
- Page 4: Resume and cover letter
The last page of our form contains simply 2 fields, one for uploading the resume and the other for writing a cover letter.
{
name: 'resumeCoverLetter',
navigationTitle: 'Resume and cover letter',
navigationDescription: 'Tell us why you are suitable for the position',
elements: [
{
type: 'file',
name: 'resume',
title: 'Please upload your resume',
showPreview: true,
maxSize: 102400,
},
{
type: 'comment',
name: 'coverLetter',
title: 'why you are suitable for the position ?',
},
],
},
SurveyJS comes with a special component for manipulating files which is QuestionFileModel. You can access the uploaded files by using the 'onUploadFiles' trigger of the form model, look at the code below:
import { Component, OnInit } from "@angular/core";
import { StylesManager, Model } from "survey-core";
import "survey-core/survey.i18n";
import { schema } from "./form.schema";
StylesManager.applyTheme('defaultV2');
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
title = 'job-application-form-app';
jobApplicationForm!: Model;
ngOnInit(): void {
const form = new Model(schema);
// access to the uploaded files
form.onUploadFiles.add((form, options) => {
console.log('File uploaded');
var formData = new FormData();
options.files.forEach(function (file: any) {
formData.append(file.name, file);
});
});
this.jobApplicationForm = form;
}
}
Step 3: Add Form Validation
To make our form more realistic, we should add form validation to mark required questions and to display errors when entering data in the different fields of our form. For that, we will use the following:
- isRequired: To mark every required field.
isRequired: true,
- inputType: To define the entry type of our field.
inputType: "tel";
// or
inputType: "email";
// or
inputType: "date";
- Validators with regular expressions (regex). For example, to validate the telephone number field, we will use the following property:
validators: [{
type: "regex",
regex: “\\+[0–9]{1} \\([0–9]{3}\\) [0–9]{3}-[0–9]{2}-[0–9]{2}”,
text: "Phone number must be in the following format: +0 (000) 000--00--00"
}]
- Validators with expressions which throws an error when the expression evaluates to false. For example, we can handle an error when the 'start date' field is after the 'end date' field.
{
type: 'text',
name: 'endDate',
title: {
default: 'End date',
fr: 'Date de fin',
},
inputType: 'date',
isRequired: true,
visibleIf: '{panel.untilNow}=false',
validators: [
{
type: 'expression',
expression: '{panel.startDate} < {panel.endDate}',
text: {
default: 'The end date should be after the start date',
fr: 'La date du fin doit être inférieur à la date du début',
},
},
],
},
Step 4: Add Form Localization Support
The SurveyJS library supports i18n out-of-the-box, with stock UI strings community-translated into over 50 languages, and an easy-to-use framework in place for including multiple translations of question strings without any external i18n library dependency (check the official documentation).
This will help us to add localization support to our form which will be available, say, in both French and English languages.
To enable the localization property, we should import this module into our app.component.ts file:
app.component.ts
import { Component, OnInit } from "@angular/core";
import { StylesManager, Model } from "survey-core";
import "survey-core/survey.i18n";
import { schema } from "./form.schema";
StylesManager.applyTheme("defaultV2");
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
})
export class AppComponent implements OnInit {
title = "job-application-form-app";
jobApplicationForm!: Model;
ngOnInit(): void {
const form = new Model(schema);
this.jobApplicationForm = form;
// To switch the language
form.locale = "fr";
// form.locale = "en"
}
}
Then, we should translate our form schema content as shown below:
export const schema = {
title: "Application For Software engineer position",
showProgressBar: "top",
progressBarType: "buttons",
pages: [
{
name: "personalInformation",
navigationTitle: {
default: "Personal information",
fr: "Informations personnelles",
},
navigationDescription: {
default: "Tell us about you",
fr: "Parlez nous de vous",
},
startWithNewLine: false,
showQuestionNumbers: "off",
elements: [
{
type: "text",
name: "fullname",
title: {
default: "Full name",
fr: "Nom et prénom",
},
isRequired: true,
},
{
type: "text",
name: "address",
title: {
default: "Address",
fr: "Adresse",
},
isRequired: true,
},
{
type: "text",
name: "country",
title: {
default: "Country",
fr: "Pays",
},
isRequired: true,
},
{
type: "text",
name: "phone",
title: {
default: "Telephone number",
fr: "Numéro de téléphone",
},
inputType: "tel",
isRequired: true,
validators: [
{
type: "regex",
regex: ‘\\+[0–9]{1} \\([0–9]{3}\\) [0–9]{3}-[0–9]{2}-[0–9]{2}’,
text: "Phone number must be in the following format: +0 (000) 000--00--00",
},
],
},
{
type: "text",
name: "email",
title: {
default: "Email",
fr: "Adresse email",
},
inputType: "email",
isRequired: true,
},
],
},
{
name: "educationTraining",
navigationTitle: {
default: "Education and training",
fr: "Éducation et formation",
},
navigationDescription: {
default: "Tell us about your education",
fr: "Parlez-nous de votre formation",
},
elements: [
{
type: "paneldynamic",
name: "educations",
title: {
default: "Educations",
fr: "Formations",
},
keyName: "diploma",
showQuestionNumbers: "off",
templateTitle: {
default: "Education #{panelIndex}",
fr: "Formation #{panelIndex}",
},
minPanelCount: 1,
maxPanelCount: 3,
panelAddText: {
default: "Add another education",
fr: "Ajouter une autre formation",
},
panelRemoveText: {
default: "Remove education",
fr: "Supprimer la formation",
},
templateElements: [
{
type: "dropdown",
name: "diploma",
title: {
default: "Diploma",
fr: "Diplôme",
},
isRequired: true,
showNoneItem: true,
showOtherItem: true,
choices: [
{
value: "bac",
text: {
default: "High school Degree",
fr: "Baccalauréat",
},
},
{
value: "bachelor",
text: {
default: "Bachelor Degree",
fr: "License",
},
},
{
value: "master",
text: {
default: "Master Degree",
fr: "Master",
},
},
{
value: "doctorate",
text: {
default: "Doctoral Degree",
fr: "Doctorat",
},
},
],
},
{
type: "text",
name: "school",
title: {
default: "School or University",
fr: "Ecole ou université",
},
isRequired: true,
},
{
type: "text",
name: "graduationYear",
title: {
default: "Graduation year",
fr: "Année d'obtention du diplôme",
},
inputType: "date",
isRequired: true,
},
{
type: "comment",
name: "description",
title: {
default: "Description of what you have studied",
fr: "Description de ce que vous avez étudié",
},
},
],
},
],
},
{
name: "experience",
navigationTitle: {
default: "Experience",
fr: "Experience",
},
navigationDescription: {
default: "Tell us more about your experience",
fr: "Dites-nous en plus sur votre expérience",
},
elements: [
{
type: "paneldynamic",
name: "experiences",
title: {
default: "Experiences",
fr: "Expériences",
},
keyName: "position",
showQuestionNumbers: "off",
templateTitle: {
default: "Experience #{panelIndex}",
fr: "Expérience #{panelIndex}",
},
minPanelCount: 1,
maxPanelCount: 3,
panelAddText: {
default: "Add another experience",
fr: "Ajouter une autre expérience",
},
panelRemoveText: {
default: "Remove experience",
fr: "Supprimer l'expérience",
},
templateElements: [
{
type: "text",
name: "position",
title: {
default: "Position",
fr: "Poste",
},
isRequired: true,
},
{
type: "text",
name: "company",
title: {
default: "Company",
fr: "Entreprise",
},
isRequired: true,
},
{
type: "text",
name: "startDate",
title: {
default: "Start date",
fr: "Date de début",
},
inputType: "date",
isRequired: true,
},
{
type: "text",
name: "endDate",
title: {
default: "End date",
fr: "Date de fin",
},
inputType: "date",
isRequired: true,
visibleIf: "{panel.untilNow}=false",
},
{
type: "boolean",
name: "untilNow",
title: {
default: "Until now",
fr: "Jusqu'à maintenant",
},
isRequired: true,
defaultValue: false,
},
{
type: "comment",
name: "description",
title: {
default: "Description of what you have worked on",
fr: "Description de ce sur quoi vous avez travaillé",
},
},
],
},
],
},
{
name: "resumeCoverLetter",
navigationTitle: {
default: "Resume and cover letter",
fr: "CV et lettre de motivation",
},
navigationDescription: {
default: "Tell us why you are suitable for the position",
fr: "Dites-nous pourquoi vous correspondez au poste",
},
elements: [
{
type: "file",
name: "resume",
title: {
default: "Please upload your resume",
fr: "Veuillez télécharger votre CV",
},
showPreview: true,
maxSize: 102400,
isRequired: true,
},
{
type: "comment",
name: "coverLetter",
title: {
default: "why you are suitable for the position ?",
fr: "pourquoi vous convenez pour le poste ?",
},
isRequired: true,
},
],
},
],
};
Step 5: Test and Serve the Application
Now, let's make a few more modifications to see the final result of our job application form.
First, we will add the SurveyJS module to the app.module.ts file:
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { SurveyModule } from "survey-angular-ui";
import { AppComponent } from "./app.component";
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, SurveyModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
Second, we will import our schema and create the form model in the app.component.ts file:
import { Component, OnInit } from '@angular/core';
import { StylesManager, Model } from 'survey-core';
import { schema } from './form.schema';
StylesManager.applyTheme('defaultV2');
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
title = 'job-application-form-app';
jobApplicationForm!: Model;
ngOnInit(): void {
const form = new Model(schema);
this.jobApplicationForm = form;
}
}
Third, we will update the content of the app.component.html file:
<div class="toolbar" role="banner">
<survey [model]="jobApplicationForm"></survey>
</div>
Finally, we're ready to compile and serve our application. Run the following:
$ npm run start
Now let's open our browser to see the final result of our job application form.
Output From Our Job Application Form
Job application form (Page 1):
Job application form (Page 2):
Job application form (Page 3):
Job application form (Page 4):
Conclusion
With that, we arrive at the end of this guide. Through this example, we have seen how we can use the SurveyJS Form Library to easily create multi-page forms. You can also create more complex forms (for example, an employee satisfaction survey) with this free and open-source library.
The complete code for this project can be found here:
GitHub - EmployeeSatisfactionSurvey/job-application-form-app
Moreover, if you want to explore beyond the free and open-source library, you can take a look at the pricing section to learn more about their paid plans.