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

People Listing

There are no ACF fields assigned to this component.

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

.block-people-listing {
	padding-bottom: rem-calc(100);
	position: relative;

	.gx-0 {
		--bs-gutter-x: 0 !important;
	}

	.section-name {
		@include bp(767px, 'max') {
			display: none;
		}
	}

	&__search-bar {
		padding-top: rem-calc(16);
		padding-bottom: rem-calc(16);
		border-bottom: rem-calc(1) solid $border-grey;
	}

	&__search-bar-clear-icon {
		display: none;
		position: absolute;
		right: rem-calc(18);
		top: 50%;
		transform: translateY(-50%);
		cursor: pointer;
	}

	&__search-bar-title {
		font-size: rem-calc(32);
		font-weight: 500;
		text-transform: uppercase;
		letter-spacing: 1.6px;
		flex-shrink: 0;
	}

	&__search-bar-button {
		flex-shrink: 0;
		background-color: $primary-blue !important;
		border: none !important;
		height: rem-calc(56) !important;
		display: flex;
		align-items: center;
		justify-content: center;
		font-weight: 600;
		transition: background-color 0.3s ease-in-out;

		&:hover {
			background-color: $primary-purple !important;
		}

	}

	&__search-bar-form {
		display: flex;
		gap: rem-calc(16);
		flex-direction: column;

		@include bp($xl) {
			flex-direction: row;
			align-items: center;
		}

		input, select {
			margin-bottom: 0 !important;
			border: rem-calc(1) solid $border-grey !important;
		}

		select {
			padding-right: rem-calc(40);
			white-space: nowrap;
			overflow: hidden;
			text-overflow: ellipsis;
			max-width: 100%;
		}

		input[type="text"] {
			padding-right: rem-calc(35);
			white-space: nowrap;
			overflow: hidden;
			text-overflow: ellipsis;
			max-width: 100%;
		}

	}

	&__search-bar-inputs {
		display: flex;
		gap: rem-calc(16);
		flex-direction: column;

		@include bp($lg) {
			flex-direction: row;
			align-items: center;
		}
	}

	&__grid-section {
		position: relative;
		padding-top: rem-calc(50);
	}

	&__search-bar-form, &__results {
		@include bp($lg) {
			padding: 0 1rem;
		}

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

	&__tile {
		padding: rem-calc(25);
		border: rem-calc(1) solid $border-grey;
		height: 100%;
		margin-top: rem-calc(-1);
		position: relative;
		background-color: $white;
		overflow: hidden;
		display: flex;
		text-decoration: none;
		min-height: rem-calc(182);
		margin-left: rem-calc(-1);

		&:before {
			content: '';
			position: absolute;
			top: 0;
			left: 0;
			width: 100%;
			height: 100%;
			background: rgb(85,46,145);
			background: linear-gradient(0deg, rgba(85,46,145,1) 0%, rgba(42,172,226,1) 100%);
			transform: translateX(-110%);
			transition: transform 0.3s ease-in-out;
		}

		@media (hover: hover) {
			&:hover {
				.block-people-listing__tile-name {
					color: $white;
				}

				.block-people-listing__tile-job-title {
					color: $white;
				}

				&:before {
					transform: translateX(0);
				}
			}
		}

		&--has-thumbnail {
			padding: 8px;
			min-height: 146px;

			.block-people-listing__tile-inner {
				width: 100%;
				gap: 0;
			}

			.block-people-listing__tile-image {
				height: 130px;
				margin-right: rem-calc(27);
				flex: 1 0 auto;
				z-index: 1;
			}

			.block-people-listing__tile-name {
				font-size: $body-default;
				font-weight: 500;
			}
			.block-people-listing__tile-job-title {
				font-size: $body-default;
			}
		}
	}

	&__tile-inner {
		position: relative;
		z-index: 1;
		display: flex;
		flex-direction: column;
		justify-content: space-between;
		gap: rem-calc(20);
	}

	&__tile-name {
		font-size: rem-calc(20);
		font-weight: 500;
		margin-bottom: rem-calc(5);
		text-decoration: none;
		transition: color 0.3s ease-in-out;
	}

	&__tile-job-title {
		transition: color 0.3s ease-in-out;
	}

	&__tile-place-name {
		font-size: rem-calc(12);
		font-weight: 600;
		color: $light-grey;
		text-transform: uppercase;
		// margin-top: rem-calc(30);
	}

	&__tile-contact-details {
		display: flex;
		gap: rem-calc(20);
		align-items: center;
		margin-top: rem-calc(12);
	}

	&__grid-header {
		margin-bottom: rem-calc(16);
		font-size: rem-calc(24);
		font-weight: 500;
	}

	/* Loading spinner styles */
	.loading-spinner {
		display: flex;
		justify-content: center;
		align-items: center;
		min-height: 200px;
		width: 100%;
	}

	.spinner {
		text-align: center;
	}

	.spinner-circle {
		border: 4px solid rgba(0, 0, 0, 0.1);
		border-left-color: $primary-blue; /* Change to your theme color */
		border-radius: 50%;
		width: 50px;
		height: 50px;
		margin: 0 auto 15px;
		animation: spin 1s linear infinite;
	}

	@keyframes spin {
		0% { transform: rotate(0deg); }
		100% { transform: rotate(360deg); }
	}

	.spinner p {
		margin-top: 10px;
	}

	.pagination{
		transition-delay: .3s;
		margin-top: rem-calc(50);
		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;

			&.current, &:hover {
				background-color: $secondary-light-green;
			}
		}
	}

}
class PeopleListing {
	/**
	 * Create and initialise objects of this class
	 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor
	 * @param {object} block
	 */
	constructor() {
		this.blocks = document.querySelectorAll('.block-people-listing');
		this.minSearchLength = 2;
		this.searchTimeout = null;
		this.isSearching = false;
		this.currentPage = 1;
		this.comingFromPaginationClick = false;
		this.enableThumbnail = false;

		if (this.blocks.length) {
			this.init();
		}
	}

	/**
	 * Initialize the people listing functionality
	 */
	init() {
		// Read URL parameters on page load
		this.readURLParameters();

		this.blocks.forEach((block) => {
			// Get search elements
			this.searchInput = block.querySelector('.block-people-listing__search-bar-input');
			this.locationSelect = block.querySelector('.block-people-listing__search-bar-select');
			this.serviceSelect = block.querySelector('.block-people-listing__search-bar-select[name="service"]');
			this.industrySelect = block.querySelector('.block-people-listing__search-bar-select[name="industry"]');
			this.searchButton = block.querySelector('.block-people-listing__search-bar-button');
			this.resultsContainer = block.querySelector('.block-people-listing__grid');
			this.paginationContainer = block.querySelector('.block-people-listing__pagination');

			// Check if thumbnails are enabled for this block
			this.enableThumbnail = block.querySelector('.block-people-listing__tile--has-thumbnail') !== null;

			// Set initial values from URL parameters
			if (this.searchInput && this.initialSearchQuery) {
				this.searchInput.value = this.initialSearchQuery;
			}

			if (this.locationSelect && this.initialLocation && this.initialLocation !== 'all') {
				this.locationSelect.value = this.initialLocation;
			}

			// Add event listeners
			if (this.searchInput) {
				this.searchInput.addEventListener('input', (e) => {
					// Clear any existing timeout
					if (this.searchTimeout) {
						clearTimeout(this.searchTimeout);
					}

					const searchValue = e.target.value.trim();

					// If search is empty, reset to initial state or perform search with just location filter
					if (searchValue === '') {
						this.currentPage = 1;
						this.performSearch();
						return;
					}

					// Only search if we have at least minSearchLength characters
					if (searchValue.length >= this.minSearchLength) {
						// Set a timeout to avoid searching on every keystroke
						this.searchTimeout = setTimeout(() => {
							this.currentPage = 1; // Reset to first page on new search
							this.performSearch();
						}, 300); // 300ms delay
					}
				});

				// Keep the Enter key functionality
				this.searchInput.addEventListener('keyup', (e) => {
					if (e.key === 'Enter') {
						if (this.searchTimeout) {
							clearTimeout(this.searchTimeout);
						}
						this.currentPage = 1;
						this.performSearch();
					}
				});
			}

			// Handle select element changes
			const handleSelectChange = () => {
				this.currentPage = 1; // Reset to first page on new search
				this.performSearch();
			};

			// Add change event listeners to all select elements
			[this.locationSelect, this.serviceSelect, this.industrySelect].forEach(select => {
				if (select) {
					select.addEventListener('change', handleSelectChange);
				}
			});

			if (this.searchButton) {
				this.searchButton.addEventListener('click', (e) => {
					e.preventDefault();
					if (this.searchTimeout) {
						clearTimeout(this.searchTimeout);
					}
					this.currentPage = 1; // Reset to first page on new search
					this.performSearch();
				});
			}

			// Set up pagination event delegation
			if (this.paginationContainer) {
				this.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;
						}

						// Set flag to indicate we're coming from pagination click
						this.comingFromPaginationClick = true;

						// Handle next/prev links - check for both class naming patterns
						if (pageLink.classList.contains('pagination-nav--next')) {
							this.currentPage++;
						} else if (pageLink.classList.contains('pagination-nav--previous')) {
							this.currentPage--;
						} else if (pageLink.classList.contains('page-numbers')) {
							// Handle numbered links - get the page number from the text content
							const pageNum = parseInt(pageLink.textContent);
							if (!isNaN(pageNum)) {
								this.currentPage = pageNum;
							}
						}

						// Update the browser URL
						this.updateURL();

						// Perform the search with the new page
						this.performSearch();
					}
				});
			}
		});

		// If we have initial parameters, perform search
		if (this.initialSearchQuery || this.initialLocation !== 'all' || this.currentPage > 1) {
			this.performSearch();
		}
	}

	/**
	 * Read URL parameters and set initial values
	 */
	readURLParameters() {
		// Get search and location from query params
		const urlParams = new URLSearchParams(window.location.search);
		this.initialSearchQuery = urlParams.get('search') || '';
		this.initialLocation = urlParams.get('location') || 'all';

		// Get page number from URL path
		const pageMatch = window.location.pathname.match(/\/page\/(\d+)\/?$/);
		this.currentPage = pageMatch ? parseInt(pageMatch[1]) : 1;
	}

	/**
	 * Update the URL with current search parameters
	 */
	updateURL() {
		const searchQuery = this.searchInput ? this.searchInput.value.trim() : '';
		const location = this.locationSelect ? this.locationSelect.value : 'all';

		// Create base URL (current path without pagination)
		let basePath = window.location.pathname;

		// Remove any existing /page/X/ from the path
		basePath = basePath.replace(/\/page\/\d+\/?$/, '/');

		// Ensure the path ends with a slash
		if (!basePath.endsWith('/')) {
			basePath += '/';
		}

		// Build the new path with pagination if needed
		let newPath = basePath;
		if (this.currentPage > 1) {
			newPath = `${basePath}page/${this.currentPage}/`;
		}

		// Handle query parameters for search and location
		const url = new URL(window.location.origin + newPath);
		const params = url.searchParams;

		// Clear existing parameters
		params.delete('search');
		params.delete('location');

		// Add parameters if they have values
		if (searchQuery) {
			params.set('search', searchQuery);
		}

		if (location !== 'all') {
			params.set('location', location);
		}

		// Update URL without reloading the page
		const newUrl = url.pathname + (params.toString() ? '?' + params.toString() : '');
		window.history.pushState({ path: newUrl }, '', newUrl);
	}

	/**
	 * Perform the AJAX search for people
	 */
	performSearch() {
		if (this.isSearching) return;
		this.isSearching = true;

		const searchQuery = this.searchInput ? this.searchInput.value : '';
		const location = this.locationSelect ? this.locationSelect.value : 'all';
		const service = this.serviceSelect ? this.serviceSelect.value : 'all';
		const industry = this.industrySelect ? this.industrySelect.value : 'all';

		// Only update URL if we're not coming from a pagination link click
		// (since that already updated the URL)
		if (!this.comingFromPaginationClick) {
			this.updateURL();
		}
		this.comingFromPaginationClick = false;

		// Show loading state
		if (this.resultsContainer) {
			this.resultsContainer.classList.add('is-loading');
			// Add loading spinner with more visible styling
			const loadingSpinner = document.createElement('div');
			loadingSpinner.className = 'loading-spinner';
			loadingSpinner.innerHTML = `
				

Loading results...

`; this.resultsContainer.innerHTML = ''; this.resultsContainer.appendChild(loadingSpinner); } // Update the grid header to show what we're searching for const gridHeader = document.querySelector('.block-people-listing__grid-header'); if (gridHeader) { let activeFilters = []; if (searchQuery) { activeFilters.push(`"${searchQuery}"`); } if (location !== 'all') { const selectedOption = this.locationSelect.options[this.locationSelect.selectedIndex]; activeFilters.push(selectedOption.textContent); } if (industry !== 'all') { const selectedOption = this.industrySelect.options[this.industrySelect.selectedIndex]; activeFilters.push(selectedOption.textContent); } if (service !== 'all') { const selectedOption = this.serviceSelect.options[this.serviceSelect.selectedIndex]; activeFilters.push(selectedOption.textContent); } let headerText = activeFilters.length > 0 ? `Results for: ${activeFilters.join(', ')}` : 'All People'; // Add page number if we're not on the first page if (this.currentPage > 1) { headerText += ` - Page ${this.currentPage}`; } gridHeader.textContent = headerText; } // Create base URL (current path without pagination) let basePath = window.location.pathname; // Remove any existing /page/X/ from the path basePath = basePath.replace(/\/page\/\d+\/?$/, '/'); // Ensure the path ends with a slash if (!basePath.endsWith('/')) { basePath += '/'; } // Perform AJAX request fetch(`${window.location.protocol}//${window.location.hostname}/wp-json/wp/v2/people/search`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ search: searchQuery, location: location, ...(this.serviceSelect && { service: this.serviceSelect.value }), ...(this.industrySelect && { industry: this.industrySelect.value }), page: this.currentPage, baseUrl: basePath, enable_thumbnail: this.enableThumbnail }) }) .then(response => response.json()) .then(data => { if (this.resultsContainer) { // Remove loading state this.resultsContainer.classList.remove('is-loading'); // Update results if (data.html) { this.resultsContainer.innerHTML = data.html; // Update pagination if (this.paginationContainer && data.pagination) { this.paginationContainer.innerHTML = data.pagination; this.paginationContainer.style.display = 'block'; } else if (this.paginationContainer) { this.paginationContainer.style.display = 'none'; } // Scroll to top of block with header offset const headerOffset = 100; // Adjust this value based on your fixed header height const blockTop = this.blocks[0].getBoundingClientRect().top + window.scrollY; window.scrollTo({ top: blockTop - headerOffset, behavior: 'smooth' }); } else { this.resultsContainer.innerHTML = '

No people found matching your search criteria.

'; if (this.paginationContainer) { this.paginationContainer.style.display = 'none'; } } } this.isSearching = false; }) .catch(error => { console.error('Error fetching people:', error); if (this.resultsContainer) { this.resultsContainer.classList.remove('is-loading'); this.resultsContainer.innerHTML = '

An error occurred while searching. Please try again.

'; } if (this.paginationContainer) { this.paginationContainer.style.display = 'none'; } this.isSearching = false; }); } } // Initialize the people listing functionality document.addEventListener('DOMContentLoaded', () => { new PeopleListing(); });
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 2,
	"name": "strategiq/people-listing",
	"title": "People Listing",
	"description": "Example block to be used as a template",
	"category": "strategiq",
	"icon": "strategiq",
	"acf": {
		"mode": "preview",
		"renderTemplate": "block-people-listing.php"
	},
	"supports": {
		"anchor": true,
		"align": false,
		"color": {
			"background": false,
			"text": false,
			"gradients": false
		},
		"spacing": {
			"padding": [
				"top",
				"bottom"
			],
			"margin": [
				"top",
				"bottom"
			]
		}
	},
	"style": ["file:../../assets/css/people-listing/block-people-listing.css"],
	"viewScript": ["people-listing"]
  }
This component is not currently used on any pages.
There are is no readme file with this component.