@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"]
}