Components
53
Accordion Items Article Selection Author Info Basic Carousel Basic Hero Basic Map Blog Pull Out Carousel Carousel Contact Content Accordion Content Carousel Content Image Cta Cta Bar Cta Blocks Cta Collage Event Content Events Grid Example Find Firm Firm Search Firms By Town Gated Content Download Guides Carousel Hero History Homepage Hero Image Content Cta Image List Content Industries Job Content Job Listings Local Firm Carousel Our Firms Pages Carousel Partners Partners Slider People Listing Post Carousel Post Feed Pullquote Section Wrap Service List Split Content Stats Tax Guides Team Grid Title Logos Two Column Video Video Carousel Video Old

Job Listings

There are no ACF fields assigned to this component.

				
@import "../../resources/scss/util/variables";
@import "../../resources/scss/util/mixins";

.block-job-listings {
	padding: rem-calc(60px) 0;

	&__wrapper {
		@include bp($md) {
			padding: 0 1rem;
		}

		@include bp($lg) {
			padding: 0;
		}
	}

	&__heading {
		@include responsive-font($heading-7, $heading-6);
		font-weight: 400;
		margin-bottom: rem-calc(40px);
	}

	&__filters {
		margin-bottom: rem-calc(28px);

		@include bp($lg) {
			margin-bottom: rem-calc(48px);
		}

		select {
			@include filter-select;
			margin-bottom: 14px;

			@include bp($lg) {
				margin-bottom: 0;
			}
		}
	}

	&__no-results,
	&__error {
		text-align: center;
		padding: rem-calc(40px) 0;
		color: $primary;
		font-size: $heading-8;
	}

	.pagination-nav {
		&:hover {
			svg {
				path {
					stroke: $primary;
				}
			}
		}
	}

	.pagination {
		transition-delay: .3s;
		margin-top: rem-calc(48px);
		padding-bottom: 0;

		@include bp($md) {
			margin-top: 2rem;
		}
		@include bp($xl) {
			margin-top: 4rem;
		}

		.pagination-pages {
			display: flex;
			flex-wrap: wrap;
			justify-content: center;
			align-items: center;
		}

		svg {
			path {
				stroke: $primary;
			}
		}

		.page-numbers,
		.pagination-nav {
			padding: 8px 13px;
			color: $primary;
			text-decoration: none;
			transition: background-color 0.3s ease;
			margin: 0 5px;
			background-color: $white;

			&.current,
			&[aria-current="page"],
			&:hover {
				background-color: $secondary-light-green;
			}
		}
	}
}
class JobListings {
	block;
	currentPage = 1;

	constructor(block) {
		this.block = block;
		this.filters = this.block.querySelectorAll('.block-job-listings__filters select');
		this.init();
	}

	init() {
		this.events();
		this.loadFromURL();
	}

	events() {
		this.filters.forEach(filter => {
			filter.addEventListener('change', () => {
				this.currentPage = 1; // Reset to first page when filters change
				this.updateURL();
				this.filterJobs();
			});
		});

		// Set up pagination event delegation
		const paginationContainer = this.block.querySelector('.pagination');
		if (paginationContainer) {
			paginationContainer.addEventListener('click', (e) => {
				// Prevent default for all pagination links
				if (e.target.closest('a')) {
					e.preventDefault();

					// Get the clicked element or its closest anchor
					const pageLink = e.target.closest('a');

					// Don't do anything if it's the current page or disabled
					if (pageLink.classList.contains('current') || pageLink.classList.contains('disabled')) {
						return;
					}

					// Get the page number from data-page attribute or text content
					let pageNum;
					if (pageLink.dataset.page) {
						pageNum = parseInt(pageLink.dataset.page);
					} else if (pageLink.classList.contains('page-numbers')) {
						if (pageLink.classList.contains('next')) {
							pageNum = this.currentPage + 1;
						} else if (pageLink.classList.contains('prev')) {
							pageNum = this.currentPage - 1;
						} else {
							pageNum = parseInt(pageLink.textContent);
						}
					}

					if (!isNaN(pageNum)) {
						this.currentPage = pageNum;
						this.updateURL();
						this.filterJobs();
					}
				}
			});
		}
	}

	loadFromURL() {
		const pathParts = window.location.pathname.split('/');
		const pageIndex = pathParts.indexOf('page');

		// Load page number from URL path
		if (pageIndex !== -1 && pathParts[pageIndex + 1]) {
			this.currentPage = parseInt(pathParts[pageIndex + 1]);
		}

		// Load filter values from query parameters
		const urlParams = new URLSearchParams(window.location.search);
		this.filters.forEach(filter => {
			const value = urlParams.get(filter.name);
			if (value) {
				filter.value = value;
			}
		});

		// If we have any URL parameters or page number, perform the search
		if (this.currentPage > 1 || Array.from(this.filters).some(filter => filter.value)) {
			this.filterJobs();
		}
	}

	updateURL() {
		const urlParams = new URLSearchParams(window.location.search);

		// Update filter parameters
		this.filters.forEach(filter => {
			if (filter.value) {
				urlParams.set(filter.name, filter.value);
			} else {
				urlParams.delete(filter.name);
			}
		});

		// Build the new URL
		let newURL = window.location.pathname;

		// Remove existing page number if present
		newURL = newURL.replace(/\/page\/\d+\//, '/');

		// Add page number if not on first page
		if (this.currentPage > 1) {
			newURL = newURL.replace(/\/$/, '') + '/page/' + this.currentPage + '/';
		}

		// Add query parameters if any exist
		const queryString = urlParams.toString();
		if (queryString) {
			newURL += '?' + queryString;
		}

		// Update URL without reloading the page
		window.history.pushState({}, '', newURL);
	}

	filterJobs() {
		const params = {
			paged: this.currentPage
		};

		this.filters.forEach(filter => {
			const value = filter.value;
			if (!value) {
				return;
			}
			params[filter.name] = value;
		});

		fetch(`${window.location.protocol}//${window.location.hostname}/wp-json/wp/v2/jobs/search`, {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json',
			},
			body: JSON.stringify(params)
		})
		.then(response => response.json())
		.then(data => {
			const jobsInner = this.block.querySelector('.block-job-listings__jobs-inner');
			const paginationContainer = this.block.querySelector('.pagination');

			if (data.count === 0) {
				jobsInner.innerHTML = '
No jobs found matching your criteria.
'; if (paginationContainer) { paginationContainer.style.display = 'none'; } } else { jobsInner.innerHTML = data.html; if (paginationContainer) { if (data.max_pages > 1) { paginationContainer.innerHTML = data.pagination; paginationContainer.style.display = 'block'; // Update current page from response this.currentPage = data.current_page; } else { paginationContainer.style.display = 'none'; } } } }) .catch(error => { console.error('Error fetching jobs:', error); this.block.querySelector('.block-job-listings__jobs-inner').innerHTML = '
An error occurred while searching. Please try again.
'; }); } } document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('.block-job-listings').forEach((block) => { new JobListings(block); }) });
{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 2,
    "name": "strategiq/job-listings",
    "title": "Job Listings",
    "description": "Example block to be used as a template",
    "category": "strategiq",
    "icon": "strategiq",
    "acf": {
        "mode": "preview",
        "renderTemplate": "block-job-listings.php"
    },
    "supports": {
        "anchor": true,
        "align": false,
        "color": {
            "background": true,
            "text": false,
            "gradients": true
        },
        "spacing": {
            "padding": [
                "top",
                "bottom"
            ],
            "margin": [
                "top",
                "bottom"
            ]
        }
    },
    "example": {
        "attributes": {
            "mode": "preview",
            "data": {
                "heading_type": "h2",
                "heading_text": "Example - Job Listings",
                "content": "This is some example content to represent what the content will look like"
            }
        }
    },
    "style": ["file:../../assets/css/job-listings/block-job-listings.css"],
    "viewScript": ["job-listings"]
}
This component is not currently used on any pages.
There are is no readme file with this component.