A client wanted to produce a list of job openings on a page in a grid like format. In addition, they wanted the user to be able to quickly filter the list based on location.
So the requirements were:
- Able to create a list of job openings on a page
- Each job listing to be associated with a location
- Able to let the user choose a location that filters the list
- Format each job listing using css
- job listed in a grid format
- centered job title on featured image
For item one I found a very simple plugin that only adds a shortcode to the website. It is called Display Posts by Bill Erickson. It will create a list of posts based on category or tag. It is well written and includes a number of filters one can hook into to alter the output even further. We could put each job opening as a post in a category called “job listing.”
Now that we have the ability to list all the job openings by adding a shortcode to a page, we needed a way to associate each job with one or more locations. We decided to use Tags for this. If a job opening was for Texas and New York, we would then tag it with those labels. Using a filter hook for the Display_posts shortcode, we could add those tags in a certain way to each listing. In this case I added a span with class “tags”. (In hindsight, I could have saved a step by putting the tags in a data attribute here instead of later using javascript) I added the following hook to the functions.php file.
add_filter( 'display_posts_shortcode_output', 'jh_display_posts', 10, 7 );
function jh_display_posts( $output, $atts, $image, $title, $date, $excerpt, $inner_wrapper ) {
$posttags = get_the_tags();
$tagsAsString = '';
if ($posttags) {
foreach($posttags as $tag) {
$tagsAsString .= $tag->name . ',';
}
}
// rebuild the output
$output = '<' . $inner_wrapper . ' class="listing-item">' . $image . $title . $date . $excerpt . '
' . '' . $inner_wrapper . '>';
// return the modified output
return $output;
}
For item 3, I need a dropdown form on the page that lists each possible location (sorted) and when a location is selected, only the jobs associated with that location are shown. I’ve done what I could with WordPress and PHP so now we work with the HTML output. This is a job for javascript. This can be broken down into the following
- Grab all the locations (‘tags’) from all the job items, find all unique values, and sort them.
- Place the sorted locations in the form on the page.
- Create an event on that form that list only jobs from the selected location
For the first item, I use this javascript. See the comments for each line
//find each job listing
var $allListingItems = $('.display-posts-listing .listing-item');
var $listContainer = $('.display-posts-listing');
//This will contain all tags
var allFilters = '';
// For each job listing -
$allListingItems.each(function (index) {
//get the element containing the tags
var $filters = $(this).find('.tags');
//from that element grab the tags as a string
var filters = $filters.text();
//add the string of tags to allFilters (we will remove duplicates and sort later)
allFilters += filters;
//add to the each listing its tags in an attribute called 'data-filters'
$(this).attr('data-filters', filters);
//we don't need the element containing the tags mucking up the view
$filters.remove();
}
);
Now all the tags are in the variable called allFilters. It is a comma delimited string. We need to get rid of the duplicates and sort them.
// First using a little regex we remove any trailing comma or whitespace
allFilters = allFilters.replace(/,s*$/, ''); //remove trailing comma or whitespace
//split string into array, then using chaining take that array and run the array prototype method, filter, on it that removes duplicates
var allFiltersArray = allFilters.split(',').filter(onlyUnique);
//Now perform the default sort method on the array
allFiltersArray.sort();
//a cool little function pass to the filter method. It removes duplicates
onlyUnique = function(value, index, self) {
return self.indexOf(value) === index;
}
Ok. That takes care of the first item of grabbing the tags, getting rid of duplicates and sorting them. Lets now take care of the next item of adding these tags to the form on the page.
// grab the form in the html to add the options too. In WordPress you will need to add a
// simple form element to your page with id "filterForm"
var $form = $('#filterForm');
//Add the first default option that will show all job listings
$form.append('');
//For each tag in the array, add a dropdown option to the form
for (i = 0, len = allFiltersArray.length; i < len; ++i) {
$form.find('select').append('');
}
By default all the jobs will be listed and now we have a form that lists all the locations. In order to filter or change what is listed, we need an event to trigger when the user changes the form to another location. Here’s the code with comments to do just that
//find each job listing - This is done in the beginning of the script so retain all job listings
var $allListingItems = $('.display-posts-listing .listing-item');
//Get the element that contains the job list
var $listContainer = $('.display-posts-listing');
//using the $form variable from previous code snippet
//The jQuery 'on' function can be used to trigger a function to run
//In this case we are running the function when the select element changes
$form.on('change', 'select', function (e) {
//prevents the default event for this form to run
e.preventDefault();
// 'this' is the form element so we can get its changed value
var selectedFilter = this.value;
// If 'All' is selected, then show all the jobs
if (selectedFilter == 'All') {
//remove all jobs from the page
$listContainer.find('.listing-item').detach();
// You don't know what this is for yet...keep reading this post
$allListingItems.each(addClasses);
// append ALL jobs to the page
$listContainer.append($allListingItems);
} else { // A specific location was selected
//The jQuery map function will go through each job listing. If the selected location is contained
// in the data-filters attribute (remember we added this earlier), then include this listing
// in the returned array
var filteredListingItems = $allListingItems.map(function (index, item) {
var filters = $(item).attr('data-filters');
// we also make sure filters is a string and not NULL so we don't get a runtime error.
if (typeof filters == "string" && filters.indexOf(selectedFilter) >= 0) {
return item;
}
});
//remove all jobs from the page - in hindsight I could have put this one line before the if statement
//to maintain the DRY principle
$listContainer.find('.listing-item').detach();
//Again, I'll explain this line soon
filteredListingItems.each(addClasses);
// append the filtered list to the page
$listContainer.append(filteredListingItems);
}
});
Lastly, we need to format our list to a specific format that the client wants. The first styling adventure to put the listing in a grid like format. Luckily, this client uses Genesis which makes the styling relatively easy. If we want a grid of three columns, then we only need to put the class ‘one-third’ for each of the job listings and the class ‘first’ for the first, fourth, seventh, etc job listing. Ok, the latter doesn’t sound that easy. That was done using javascript and the MOD operator, but could have been done in CSS with the nth-child selector. I’ve gotten bitten with browser compatibility with nth-child in the past so I went with javascript this time.
In the form change event above, you will see I ran each item through the function addClasses. This function will add the necessary classes to each job listing.
// function to add appropriate classes to items in order to be in grid
var addClasses = function(index, item) {
$item = $(item);
// I'm reusing this function every time, the user filters the list
// so we need to remove any existing grid formatting
$item.removeClass('one-third first');
$item.addClass('one-third');
//Using MOD we can find the item which will be on the left-most column
//and add the class 'first'
//In JavaScript index starts with 0. So the first item will be 0%3 which is true so
//it gets the class first.
if ( (index % 3) == 0) {
$item.addClass('first');
}
};
Most of the code above I encapsulated using a module pattern. I called the program jhgrid. I made the code above more specific to make it more clear. The jhgrid module is more flexible in that you can change how many columns you want, change the classes used to store the job listing etc. It can be found at
The rest of the styling adventure was to vertically and horizontally center the title over the image of each job listing. CSS is great for styling but vertical centering is not always very easy. I ended up using javascript again because of CSS limitations (or my knowledge limitations of CSS..:) )
/**
* $(window).load() was used after discovering Chrome and IE didn't work..parentHeight was 0.5 or -0.5
* So window.load makes sure all images (sub-elements) are loaded before running.
* Originally the code detached the list from the DOM before manipulating but discovered that the computed
* heights became 0.
*/
(function($) {
$(window).load(function () {
var settings = {
title: "a.title",
container: ".listing-item"
};
var $items = $(settings.container + " " + settings.title);
$items.each(function (index) {
var $title = $(this);
var $parent = $title.parent();
var parentHeight = $parent.height();
var titleHeight = $title.height();
var newTitleHeight = Math.floor(parentHeight / 2 - titleHeight / 2);
$title.css('top', newTitleHeight.toString() + "px");
});
});
})(jQuery);
See the gist above for the css I also added. Note: I have set widths in my css. If I had more time, I would also use javascript to set the widths and left properties as well so it would be even easier to change the grid from three columns to four, etc.
Future possibilities would be to use AJAX to query the list of jobs and add pagination…especially if we are talking about hundreds.