Hi! A colleague and I are building a food donation app that connects food donations with local non-profits. It allows users to post either 1. a request for a certain kind of donation, or 2. leftover food they want to donate, and helps users find each other and make the donation happen as locally (and seamlessly) as possible.
We want users to be able to filter for donations or donation requests by location – namely, we want users to be able to filter a list-block that SIMULTANEOUSLY filters map data on the same page. That way, they can easily search by location. Is this possible?
We’ve tried finding out ways to do simultaneous list-block filtering with >1 list, but can’t find anything on it. However, our problem is specific to maps, as we basically want users to filter by a visible location. Any advice or feedback is super appreciated.
+1 We also have the same need. Ever figure this out?
1 Like
Not yet, but I’ll keep you in the loop on this thread.
1 Like
It can be done, via making custom filters as a global filter. Then lets say,
- On click on search, you can update the URL with the desired filter. List Filtering with URL – Softr Help Docs
- This will only work when you have same filter added on list and map.
- You can remove the filter block from the Map and List as well. There is some CSS files which you can mark as hidden.
Below is the code snippet.
I am getting dynamic dropdown values from the Airtable.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Filters with Airtable</title>
<!-- Link to the Inter font for a modern look -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
<style>
/* General body styling */
body {
font-family: 'Inter', sans-serif;
margin: 20px;
background-color: #f4f4f4;
color: #333;
}
/* Styling for the main heading */
h1 {
font-size: 24px;
color: #007bff;
text-align: left;
}
/* Container for filter elements */
#filters {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
align-items: center;
}
/* Label styling for dropdowns */
label {
font-size: 18px;
margin-bottom: 5px;
}
/* Dropdown styling */
select {
padding: 10px;
border: 1px solid #007bff;
border-radius: 5px;
font-size: 16px;
min-width: 200px;
}
/* Styling for the search button */
button {
background-color: #007bff;
color: #fff;
padding: 10px 20px;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
align-self: center;
}
/* Hover effect for the search button */
button:hover {
background-color: #0056b3;
}
/* Styling for results section */
#results {
margin-top: 20px;
white-space: pre-wrap;
}
</style>
</head>
<body>
<!-- Main title for filtering section -->
<h1>Filter Apartments</h1>
<!-- Filters container for dropdowns and search button -->
<div id="filters">
<!-- Label and dropdown for Bedrooms filter -->
<label for="bedrooms">Bedrooms:</label>
<select id="bedrooms">
<option value="">Select Bedrooms</option>
<!-- Dynamic options will be populated here -->
</select>
<!-- Label and dropdown for Pricing filter -->
<label for="pricing">Price Range:</label>
<select id="pricing">
<option value="">Select Price Range</option>
<!-- Dynamic options will be populated here -->
</select>
<!-- Search button to apply filters -->
<button id="searchButton">Search</button>
</div>
<!-- Section to display results or generated URL -->
<div id="results">
<!-- Filtered results will be displayed here -->
</div>
<!-- Include Axios for making API requests -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
// Airtable API details
const personalToken = 'KEY';
const BASE_ID = 'BaseID';
const TABLE_NAME = 'Tablename';
const API_URL = `https://api.airtable.com/v0/${BASE_ID}/${TABLE_NAME}`;
// Function to fetch data from Airtable
const fetchDataFromAirtable = async () => {
try {
const response = await fetch(API_URL, {
headers: {
'Authorization': `Bearer ${personalToken}`
}
});
if (!response.ok) {
throw new Error('Network response was not ok: ' + response.statusText);
}
const data = await response.json();
return data.records;
} catch (error) {
console.error('Error fetching data from Airtable:', error);
return [];
}
};
// Function to get URL parameters
function getUrlParam(name) {
const url = new URL(window.location.href);
return url.searchParams.get(name);
}
// Function to populate filter dropdowns
const populateFilters = async () => {
const data = await fetchDataFromAirtable();
if (!data || !data.length) {
console.error('No data returned or error in fetching data.');
return;
}
const bedroomsSet = new Set();
const pricingSet = new Set();
// Extract unique values for Bedrooms and Pricing from Airtable data
data.forEach(record => {
const bedrooms = record.fields['Bedrooms'];
const price = record.fields['Pricing']; // Ensure 'Pricing' matches your Airtable field name
if (bedrooms) bedroomsSet.add(bedrooms.trim().toUpperCase());
if (price) pricingSet.add(price.trim());
});
// Sort and populate the dropdowns
const bedroomsArray = Array.from(bedroomsSet).sort((a, b) => {
const numA = parseInt(a.match(/\d+/));
const numB = parseInt(b.match(/\d+/));
return numA - numB;
});
const pricingArray = Array.from(pricingSet).sort((a, b) => parseFloat(a.replace(/[^0-9.]+/g, "")) - parseFloat(b.replace(/[^0-9.]+/g, "")));
const bedroomsSelect = document.getElementById('bedrooms');
const pricingSelect = document.getElementById('pricing');
// Populate the Bedrooms dropdown
bedroomsArray.forEach(bedrooms => {
const optionElement = document.createElement('option');
optionElement.value = encodeURIComponent(bedrooms);
optionElement.textContent = bedrooms;
bedroomsSelect.appendChild(optionElement);
});
// Populate the Pricing dropdown
pricingArray.forEach(price => {
const optionElement = document.createElement('option');
optionElement.value = encodeURIComponent(price);
optionElement.textContent = price;
pricingSelect.appendChild(optionElement);
});
};
// Function to set filter values from URL after page reload
const setFilterValuesFromURL = () => {
// Wait for 15 seconds to ensure Airtable data is fully loaded
setTimeout(() => {
// Get selected filter values from the URL using getUrlParam function
const selectedBedrooms = getUrlParam('filter-by-Bedrooms');
const selectedPrice = getUrlParam('filter-by-Price');
// Get dropdown elements
const bedroomsSelect = document.getElementById('bedrooms');
const pricingSelect = document.getElementById('pricing');
// Log the URL and fetched parameters
console.log(`Current URL: ${window.location.href}`);
console.log(`Bedrooms: ${selectedBedrooms}`);
console.log(`Price: ${selectedPrice}`);
// Set the selected values in the dropdowns if they exist
if (selectedBedrooms) {
bedroomsSelect.value = selectedBedrooms;
}
if (selectedPrice) {
pricingSelect.value = selectedPrice;
}
}, 15000); // Wait for 15000ms to ensure the elements are populated
};
// Function to apply filters and update the URL
const applyFilters = () => {
// Get selected values from the dropdowns
const bedrooms = document.getElementById('bedrooms').value;
const price = document.getElementById('pricing').value;
// Construct the URL with the selected filters
let url = `<URL>`; //Change URL
if (bedrooms) {
url += `filter-by-Bedrooms=${bedrooms}`;
}
if (price) {
if (bedrooms) url += '&'; // Add '&' if Bedrooms filter is already in URL
url += `filter-by-Price=${price}`;
}
// Update the window location to apply filters and refresh the page
window.location.href = url;
};
// Initialize filters on page load
window.onload = () => {
// Populate filters from Airtable
populateFilters().then(() => {
// Add event listener to the search button
document.getElementById('searchButton').addEventListener('click', applyFilters);
// Set filter values from URL after a 15-second delay
setFilterValuesFromURL();
});
};
</script>
</body>
</html>
For removing List and map filter: Put this in the header.
<style>
.css-tzsjye {
display: none !important;
}
.css-bi377g {
display: none !important;
</style>