Skip to content

Instantly share code, notes, and snippets.

@maietta
Created December 11, 2024 04:26
Show Gist options
  • Save maietta/ba0bdf116b6be46e07c8ce9aeb795617 to your computer and use it in GitHub Desktop.
Save maietta/ba0bdf116b6be46e07c8ce9aeb795617 to your computer and use it in GitHub Desktop.
Insanely stupid Search Form
<script>
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { onMount } from 'svelte';
import { queryParameters, ssp } from 'sveltekit-search-params';
import { propertySchema } from '$lib/helpers/propertySchema';
import CurrencyInput from '@canutin/svelte-currency-input';
let system = $state({ Class: '', Type: '' });
const form = queryParameters(
{
page: ssp.number(1),
Area: {
encode: (value) => (value && value.trim() ? value : undefined),
decode: (value) => (value && value.trim() ? value : undefined),
defaultValue: ''
},
AcresMin: ssp.number(0),
BedroomsMin: {
encode: (value) => (value && value.trim() ? value : undefined),
decode: (value) => (value && value.trim() ? value : undefined),
defaultValue: ''
},
BathroomsMin: {
encode: (value) => (value && value.trim() ? value : undefined),
decode: (value) => (value && value.trim() ? value : undefined),
defaultValue: ''
},
AskingPriceMin: ssp.number(0),
AskingPriceMax: ssp.number(9999999)
},
{
pushHistory: false, // Enables updating the browser history
showDefaults: false, // Don't include default values in the query string
debounceHistory: 500,
sort: false
}
);
// Initialize values on mount
onMount(() => {
// IF there is a `page` in URL params, then use it
if ($page.url.searchParams.get('page')) $form.page = Number($page.url.searchParams.get('page'));
const [classSegment, typeSegment] = $page.params.catchall.split('/').filter(Boolean);
system.Class = classSegment || '';
system.Type = typeSegment || '';
});
let previousUrl = '';
function formHasBeenChanged() {
// If class is not residential, clear bedrooms and bathrooms
if (system.Class !== 'residential') {
$form.BedroomsMin = '';
$form.BathroomsMin = '';
}
// If class is not land, clear acres
if (system.Class !== 'land') {
$form.AcresMin = 0;
}
$form.AskingPriceMin = 0; // Reset to the default. (0's are ignored by search engine)
$form.AskingPriceMax = 0; // Reset to the default. (0's are ignored by search engine)
// Loop over form to reset all fields.
for (const key in form) {
if (form.hasOwnProperty(key)) {
if (key == 'set' || key == 'subscribe' || key == 'update') continue;
$form[key] = undefined; // Reset value
}
}
}
$effect(() => {
if (system) {
const url = rebuildURL();
if (url !== previousUrl) {
previousUrl = url;
goto(url, { replaceState: true });
}
}
});
$effect(() => {
if (form) formHasBeenChanged();
});
function rebuildURL() {
let url = '/' + $page.params.searchType; // New way after addressing bug in route matching.
if (system.Class) {
url += `/${encodeURIComponent(system.Class)}`;
if (system.Type) url += `/${encodeURIComponent(system.Type)}`;
}
// Guard rail for invalid property class types
const validTypes = propertySchema[system.Class]?.types?.map((t) => t.slug) || [];
if (!validTypes.includes(system.Type)) {
system.Type = '';
}
const searchParams = new URLSearchParams($page.url.searchParams);
// If referrer does not start with /details, set page to 1.
if (!$page.url.searchParams.get('referrer')?.startsWith('/details')) {
searchParams.set('page', '1');
}
return `${url}?${searchParams.toString()}`;
}
/***
* Form specific logic
*/
const classes = Object.entries(propertySchema).map(([key, value]) => ({
value: key,
label: value.label
}));
</script>
<form class="flex flex-col">
<label for="Class">Property Class:</label>
<select name="Class" bind:value={system.Class}>
<option value="">— choose a property type —</option>
{#each classes as { value, label }}
<option {value}>{label}</option>
{/each}
</select>
{#if system.Class === 'residential'}
<label for="Type">Type of Residential:</label>
<select name="Type" bind:value={system.Type}>
<option value="">— any type of residential —</option>
{#each propertySchema[system.Class]?.types ?? [] as type}
<option value={type.slug}>{type.label}</option>
{/each}
</select>
<div class="w-full">
<div class="flex flex-wrap gap-0">
<div class="w-1/2 pr-1">
<label for="BedroomsMin">Bedrooms:</label>
<select name="BedroomsMin" id="BedroomsMin" class="w-full" bind:value={$form.BedroomsMin}>
<option value="">Any</option>
<option value="1">1 or more</option>
<option value="2">2 or more</option>
<option value="3">3 or more</option>
<option value="4">4 or more</option>
<option value="5">5+ or more</option>
</select>
</div>
<div class="w-1/2 pl-1">
<label for="BathroomsMin">Bathrooms:</label>
<select
name="BathroomsMin"
id="BathroomsMin"
class="w-full"
bind:value={$form.BathroomsMin}
>
<option value="">Any</option>
<option value="1">1 or more</option>
<option value="2">2 or more</option>
<option value="3">3 or more</option>
<option value="4">4 or more</option>
<option value="5">5+ or more</option>
</select>
</div>
</div>
</div>
{/if}
{#if system.Class === 'commercial'}
<label for="Type">Type of Commercial:</label>
<select name="Type" bind:value={system.Type} class="w-full">
<option value="">— choose commercial type —</option>
{#each propertySchema[system.Class]?.types ?? [] as type}
<option value={type.slug}>{type.label}</option>
{/each}
</select>
{/if}
{#if system.Class === 'land'}
<label for="Type">Type of Land:</label>
<select name="Type" id="Type" bind:value={system.Type} class="w-full">
<option value="">— choose land type —</option>
{#each propertySchema[system.Class]?.types ?? [] as type}
<option value={type.slug}>{type.label}</option>
{/each}
</select>
{/if}
{#if system.Class === 'multi-family'}
<label for="Type"> Type of Multi-Family: </label>
<select name="Type" id="Type" bind:value={system.Type} class="w-full">
<option value="">— choose multi-family property type —</option>
{#each propertySchema[system.Class]?.types ?? [] as type}
<option value={type.slug}>{type.label}</option>
{/each}
</select>
{/if}
<label for="Area">Location:</label>
<select name="Area" id="Area" class="w-full" bind:value={$form.Area}>
<option value="">Anywhere in Del Norte County</option>
<option value="Big Flat">Big Flat</option>
<option value="Crescent City">Crescent City</option>
<option value="Elk Valley">Elk Valley</option>
<option value="Fort Dick">Fort Dick</option>
<option value="Gasquet">Gasquet</option>
<option value="Hiouchi">Hiouchi</option>
<option value="Klamath">Klamath</option>
<option value="Klamath Glenn">Klamath Glenn</option>
<option value="Lake Earl">Lake Earl</option>
<option value="Orick">Orick</option>
<option value="Smith River">Smith River</option>
<option value="Other">Other</option>
</select>
<div class="border">
<legend class="text-base font-medium text-green-900">Asking Price (dollar amount): </legend>
<div class="flex w-full">
<div class="w-1/2 pr-1">
<label for="AskingPriceMin" class="font-medium text-green-900">Min:</label>
<div class="currency-input">
<CurrencyInput
name="AskingPriceMin"
id="AskingPriceMin"
isNegativeAllowed={false}
fractionDigits={0}
placeholder="$ 0"
locale="en-US"
currency="USD"
/>
</div>
</div>
<div class="w-1/2 pl-1">
<label for="AskingPriceMax" class="font-medium text-green-900">Max:</label>
<div class="currency-input">
<CurrencyInput
name="AskingPriceMax"
id="AskingPriceMax"
isNegativeAllowed={false}
bind:value={$form.AskingPriceMax}
fractionDigits={0}
placeholder="$ 5,000,000"
locale="en-US"
currency="USD"
/>
</div>
</div>
</div>
</div>
<a
href="/search"
role="button"
class="bg-black p-4 text-white hover:underline"
onclick={(e) => {
e.preventDefault();
Object.keys($form).forEach((key) => ($form[key] = ''));
}}
>
Reset Search
</a>
</form>
<style lang="postcss">
div.currency-input :global(input.currencyInput__formatted) {
@apply w-full border border-gray-500;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment