Skip to content

Instantly share code, notes, and snippets.

@unwiredtech
Last active March 26, 2025 16:09
Show Gist options
  • Select an option

  • Save unwiredtech/06a312e84a4001c004e72070782e2861 to your computer and use it in GitHub Desktop.

Select an option

Save unwiredtech/06a312e84a4001c004e72070782e2861 to your computer and use it in GitHub Desktop.
<?php
/*
• A fully hierarchical, cascading search form
• AJAX populates States based on selected Workspace Type
• Then populates Cities based on selected State + Type
• Searches and returns locations filtered by all 3 levels
*/
function workspace_search_form_shortcode() {
ob_start();
?>
<form id="workspace-search-form">
<select name="workspace_type" id="workspace_type">
<option value="">Select Workspace Type</option>
<?php
$workspace_types = get_terms([
'taxonomy' => 'location-workspace-types',
'hide_empty' => false
]);
foreach ($workspace_types as $type) {
echo '<option value="' . esc_attr($type->term_id) . '">' . esc_html($type->name) . '</option>';
}
?>
</select>
<select name="state" id="state" disabled>
<option value="">Select State</option>
</select>
<select name="city" id="city" disabled>
<option value="">Select City</option>
</select>
<button type="submit">Search</button>
</form>
<div id="workspace-search-results"></div>
<script>
const ajaxUrl = "<?php echo admin_url('admin-ajax.php'); ?>";
document.addEventListener('DOMContentLoaded', () => {
const workspaceSelect = document.getElementById('workspace_type');
const stateSelect = document.getElementById('state');
const citySelect = document.getElementById('city');
workspaceSelect.addEventListener('change', function () {
const val = this.value;
stateSelect.innerHTML = '<option>Loading...</option>';
citySelect.innerHTML = '<option>Select City</option>';
citySelect.disabled = true;
if (!val) {
stateSelect.innerHTML = '<option>Select State</option>';
stateSelect.disabled = true;
return;
}
fetch(ajaxUrl + '?action=get_states_by_workspace_type&term_id=' + val)
.then(res => res.json())
.then(data => {
stateSelect.innerHTML = '<option value="">Select State</option>';
data.forEach(item => {
stateSelect.innerHTML += `<option value="${item.id}">${item.name}</option>`;
});
stateSelect.disabled = false;
});
});
stateSelect.addEventListener('change', function () {
const stateId = this.value;
const workspaceId = workspaceSelect.value;
if (!stateId || !workspaceId) return;
citySelect.innerHTML = '<option>Loading...</option>';
citySelect.disabled = true;
fetch(ajaxUrl + '?action=get_cities_by_state&workspace_type=' + workspaceId + '&state_id=' + stateId)
.then(res => res.json())
.then(data => {
citySelect.innerHTML = '<option value="">Select City</option>';
data.forEach(item => {
citySelect.innerHTML += `<option value="${item.id}">${item.name}</option>`;
});
citySelect.disabled = false;
});
});
const form = document.getElementById('workspace-search-form');
form.addEventListener('submit', function (e) {
e.preventDefault();
const data = new FormData(form);
data.append('action', 'workspace_search');
fetch(ajaxUrl, {
method: 'POST',
body: data
})
.then(res => res.text())
.then(html => {
document.getElementById('workspace-search-results').innerHTML = html;
});
});
});
</script>
<?php
return ob_get_clean();
}
add_shortcode('workspace_search_form', 'workspace_search_form_shortcode');
add_action('wp_ajax_get_states_by_workspace_type', 'get_states_by_workspace_type');
add_action('wp_ajax_nopriv_get_states_by_workspace_type', 'get_states_by_workspace_type');
function get_states_by_workspace_type() {
$term_id = intval($_GET['term_id']);
$location_ids = get_posts([
'post_type' => 'locations',
'posts_per_page' => -1,
'fields' => 'ids',
'tax_query' => [
[
'taxonomy' => 'location-workspace-types',
'field' => 'term_id',
'terms' => $term_id
]
]
]);
$terms = wp_get_object_terms($location_ids, 'cities-states', [
'parent' => 0,
'fields' => 'all'
]);
$unique_terms = [];
foreach ($terms as $term) {
$unique_terms[$term->term_id] = [
'id' => $term->term_id,
'name' => $term->name
];
}
wp_send_json(array_values($unique_terms));
}
add_action('wp_ajax_get_cities_by_state', 'get_cities_by_state');
add_action('wp_ajax_nopriv_get_cities_by_state', 'get_cities_by_state');
function get_cities_by_state() {
$workspace_type = intval($_GET['workspace_type']);
$state_id = intval($_GET['state_id']);
$location_ids = get_posts([
'post_type' => 'locations',
'posts_per_page' => -1,
'fields' => 'ids',
'tax_query' => [
[
'taxonomy' => 'location-workspace-types',
'field' => 'term_id',
'terms' => $workspace_type
],
[
'taxonomy' => 'cities-states',
'field' => 'term_id',
'terms' => get_term_children($state_id, 'cities-states')
]
]
]);
$terms = wp_get_object_terms($location_ids, 'cities-states', [
'parent' => $state_id,
'fields' => 'all'
]);
$unique_terms = [];
foreach ($terms as $term) {
$unique_terms[$term->term_id] = [
'id' => $term->term_id,
'name' => $term->name
];
}
wp_send_json(array_values($unique_terms));
}
add_action('wp_ajax_workspace_search', 'handle_workspace_search');
add_action('wp_ajax_nopriv_workspace_search', 'handle_workspace_search');
function handle_workspace_search() {
$workspace_type = intval($_POST['workspace_type']);
$state = intval($_POST['state']);
$city = intval($_POST['city']);
$args = [
'post_type' => 'locations',
'posts_per_page' => -1,
'tax_query' => [
'relation' => 'AND',
[
'taxonomy' => 'location-workspace-types',
'field' => 'term_id',
'terms' => [$workspace_type],
],
[
'taxonomy' => 'cities-states',
'field' => 'term_id',
'terms' => [$city],
]
]
];
$query = new WP_Query($args);
if ($query->have_posts()) {
echo '<ul>';
while ($query->have_posts()) {
$query->the_post();
echo '<li><a href="' . get_permalink() . '">' . get_the_title() . '</a></li>';
}
echo '</ul>';
} else {
echo '<p>No results found.</p>';
}
wp_reset_postdata();
wp_die();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment