Created
December 11, 2024 04:26
-
-
Save maietta/ba0bdf116b6be46e07c8ce9aeb795617 to your computer and use it in GitHub Desktop.
Insanely stupid Search Form
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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