How we built a Careers Website on WordPress with Vue.js and GreenHouse

Introduction

One of our clients, a fintech startup required a customized recruitment page with functionalities of filtering, quick loading, mobile responsiveness, and more. It can be achieved in a WordPress website using Vuejs, a front-end framework, and Greenhouse for creating and organizing job listings in the page. Read further to dive deep into the whole process.

The Client’s Use case

Our client is a mid-sized tech enterprise and decided to move their online recruiting platform to Greenhouse. Companies use the Greenhouse product to create job postings, organize them by departments and office locations and market these jobs on various social platforms. Candidates can also submit their job applications. 

Greenhouse offers several ways in which clients can use their product. The most basic and plain vanilla option is to simply embed an iframe on your website. With this option, you will have limited control over the look and feel. At the high-end, Greenhouse offers a set of APIs -- for jobs, departments, locations, and job descriptions -- that clients can consume and integrate into a completely custom branded experience. 

Our client decided to leverage the high-end option. Not only did they go with a completely branded experience, but the design was also extremely interactive and necessitated the use of a powerful reactive JS framework such as Vue. 

Since the client’s website runs on WordPress, we had to build a solution on it. It had to deliver a completely branded and interactive experience to the users while integrating deeply with Greenhouse APIs. 

The Technical Background

The careers website had three primary templates:

  • Home Page
  • Jobs Listing Page
  • Job Detail Page

The Home Page was designed as a classical component-driven template. This would be built in WordPress, and when the candidates click on the CTAs, they are taken to the job listing page. 

The Job Listing Page was designed as a very interactive experience with the following elements:

  • A robust search engine
  • A faceted filtering experience to shortlist jobs by departments or locations
  • The job listing would change dynamically based on the filters selected.

It is useful to note that this complete page would be API-driven. 

Finally, the job description page would show the details of a selected job, and the candidate would submit their job application as well. Again, this is an entirely API-driven page. 

Why did we need a reactive JS framework like Vue?

A website like this has a lot of interactive functionality that goes beyond traditional website interactions. For example, when the user clicks on a list of depts on the left, several things happen:

  • The list of filters on the left changes
  • The list of jobs on the right changes
  • The List count is updated
  • The pagination is changed

There are lots of such interactions on this site. We need reactive JS frameworks to build interactive applications such as this. Traditional programming languages like PHP would be insufficient and cumbersome. The most popular reactive frameworks are VueJS and ReactJS. We selected VueJS to build this. 

Complex user interactions involve the manipulation of a lot of HTML Document Object Model (DOM) elements.  When a user interacts with the page, the browser has to update these DOM elements and render them on the screen. Updating the DOM using traditional PHP and javascript is cumbersome.

VueJs operates utilizing the “Virtual DOM” concept.  Vuejs updates the virtual DOM instead of the real DOM when the stage changes. and offers the ability to control the timing at which the Virtual DOM is rendered. This makes the HTML page rendering pretty quick and improves application performance.

Integrating Vue with WordPress

We integrated Vue with WordPress by including the vue.js file in the wp_enqueue_scripts function. This function will load the vuejs files in the page header properly, reduce page load time and help avoid conflicts with other themes and plugins. We integrated the Greenhouse application in a similar fashion. You can see the code snippet below. 



function func_load_vuescripts() {	

	wp_register_script( 'wpvue_vuejs', 'https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js'); 	

	wp_register_script( 'wpvue_vue-routerjs', 'https://unpkg.com/vue-router/dist/vue-router.js');	

	wp_register_script('greenhouseapi', get_stylesheet_directory_uri() .'/wp-vue/greenhouse.js', array(), '1.0.0', true);

}
add_action('wp_enqueue_scripts', 'func_load_vuescripts');


We then initialize the vue app within the greenhouse.js file as follows:


var app = new Vue({
  el: '#wp-vue,
  data: {
    message: 'Wordpress Vue Integration!'
  }
})


Within the WordPress template file, we can render the vue elements by declaring a wrapper div with “id wp-vue” as follows.


<div id="wp-vue">
  {{ message }}
</div>

This straightforward template syntax initializes the vue app in WordPress. The data and the DOM are now linked, and everything is now reactive. We can test this by opening our browser’s JavaScript console and set app.message to a different value. When you now view the template, you will see the message like below:

 "Wordpress Vue Integration!"

The greenhouse jobs section is componentized into four separate vue components.

  • Search
  • Department List
  • Location List
  • Job List

The vue components we will create can be rendered by declaring inside the wrapper div as follows:


<div id="wp-vue">
   
  {{ message }}

  <search :searchValue='searchValue'>  </search>
  
  <job-list :jobs='jobs'>  </job-list>
  
  <department-list :departments='departments'>  </department-list> 

  <location-list :location='location'>  </location-list>

 </div>

All the four components are wrapped under the wp-vue div and can communicate to each other through the declared vue properties.

Consuming the Greenhouse JSON APIs with Vue

There are three main Greenhouse APIs that we connect with : Jobs, Departments and Locations

Jobs API

URL : https://boards-api.greenhouse.io/v1/boards/datsolutions/jobs

This API returns JSON data in the format shown below:


"jobs": [
    {
      "absolute_url": "https://careers.dat.com/jobs/4182734004?gh_jid=4182734004",
      "data_compliance": [
        {
          "type": "gdpr",
          "requires_consent": false,
          "retention_period": null
        }
      ],
      "internal_job_id": 4126201004,
      "location": {
        "name": "Beaverton, OR, USA"
      },
      "metadata": [
        {
          "id": 4200041004,
          "name": "Employment Type",
          "value": "Regular",
          "value_type": "single_select"
        },
        {
          "id": 4209572004,
          "name": "Full-time/ Part-time",
          "value": "Full-time",
          "value_type": "single_select"
        }
      ],
      "id": 4182734004,
      "updated_at": "2021-11-16T16:25:31-05:00",
      "requisition_id": "272",
      "title": "Accountant I"
    },
  } ]


Departments API

URL  https://boards-api.greenhouse.io/v1/boards/datsolutions/departments

The JSON data format is shown below:


 "departments": [
    {
      "id": 4023682004,
      "name": "Billing Services (DAT730)",
      "parent_id": 4023681004,
      "child_ids": [        
      ],
      "jobs": [
        {
          "absolute_url": "https://careers.dat.com/jobs/4168749004?gh_jid=4168749004",
          "data_compliance": [
            {
              "type": "gdpr",
              "requires_consent": false,
              "retention_period": null
            }
          ],
          "internal_job_id": 4114273004,
          "location": {
            "name": "Beaverton, OR, USA"
          },
          "metadata": [
            {
              "id": 4200041004,
              "name": "Employment Type",
              "value": "Regular",
              "value_type": "single_select"
            },
            {
              "id": 4209572004,
              "name": "Full-time/ Part-time",
              "value": "Full-time",
              "value_type": "single_select"
            }
          ],
          "id": 4168749004,
          "updated_at": "2021-11-16T16:24:58-05:00",
          "requisition_id": "215",
          "title": " Collections Specialist "
        },]
    }
]


The data from the Greenhouse API is consumed by the javascript fetch( )  method and the response data can be structured based on our needs. In the code below, we show how to consume the Jobs JSON data with the fetch( ) method and assign the response data to the Jobs Component property. 


fetchJobList(){
	let url = 'https://boards-api.greenhouse.io/v1/boards/datsolutions/jobs?content=true';
	fetch(url).then((response) => {
		return response.json()
	}).then((data) => {
		datJobs = [];
		data.jobs.forEach(function (job) {
			var jobtitle = job.title;
			jobDepp = []; 
			job.departments.forEach(function(department) {
				jobDepp.push(department.name);
			}); 
			job['depp'] = jobDepp; 
			datJobs.push(job); 
		});
		this.jobs = datJobs;
		console.log("jobs:", this.jobs);
	});
},

These props will later be passed to the child component to display the Job list.  The same approach will be used to display the Department and Location listing.

Delivering an interactive experience with Vue

As discussed earlier, a website like this has a lot of interactive functionality that goes beyond traditional website interactions. Let us once again, consider the scenario when the user clicks on a list of depts on the left, several things happen:

  • The list of filters on the left changes
  • The list of jobs on the right changes
  • The List count is updated
  • The pagination is changed

Before the click the screen looks like this:

After the click the screen looks like this:

As you can see, several things have changed:

  • The checkbox is highlighted
  • We now see a “Clear All Filters” link
  • We see the selected checkbox value show up as a blue button
  • On the right, you can see that the list of jobs has been filtered by location.

If we now select another filter, this is what we see:

The list of jobs is now filtered by both location and job category. 

Let us now review the architecture behind delivering this highly interactive functionality. The overall structure is as follows:

As described earlier, the department, search, location and jobs components operate with the overall Greenhouse.js main component.  The filterbyDepartment( ) and filterbyCountry( ) methods filter the jobs array by the selected Department and Country values. 


methods: {
	filterByDepartment(jobs){
		if(!this.checkednames.includes('All')  ){
			return jobs.filter(f => this.checkednames.some(value => f.depId.includes(value))); 
			}else{
			return jobs;
		}
	},
	filterByCountry(jobs){
		if(!this.checkedcountry.includes('All')  ){
			return jobs.filter(f => this.checkedcountry.includes(f.location.name)); 
			}else{
			return jobs;
		}
	},	
},

computed: {
	fetchjobs: function() {			  
		return  this.filterByDepartment(this.filterByCountry(this.filterBySearch( this.jobs)));
	}, 
}

See below for the sequence of actions that is triggered when a user clicks on the checkbox, and the refreshed list of jobs is shown. 

A similar sequence runs and the element “Clear All Filters” is shown along with the selected filter values: 

Summary

Greenhouse has been a reliable recruiting platform for almost a decade now, and we wanted to deliver the best-customized and interactive recruitment experience to our client utilizing the best of Greenhouse's services and building upon it with Vue.JS’s flexibility. 

In this project, we’ve built a customized recruitment page with functionalities of filtering, branding, mobile responsiveness, and quick loading times coupled with a user interface that provides a seamless experience for applicants and recruiters alike. Combining the powerful capabilities of VueJS’s and Exemplifi’s expertise we were able to customize and integrate multiple APIs to give our clients the desired outcome and help empower their HR teams. 

If you liked this insight and want more updates, we’d love for you to join us on Linkedin at Exemplifi, Facebook at ExemplifiSites or Twitter at ExemplifiSites

No items found.

Related Insights

Subscribe to our newsletter