diff --git a/.eslintrc.js b/.eslintrc.cjs
similarity index 84%
rename from .eslintrc.js
rename to .eslintrc.cjs
index 76f220db..57ed377a 100644
--- a/.eslintrc.js
+++ b/.eslintrc.cjs
@@ -17,5 +17,7 @@ module.exports = {
'import/extensions': ['error', {
js: 'always',
}],
+ 'max-len': ['error', { "code": 200 }],
+ 'function-paren-newline': 'off',
},
};
diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml
index 27b3c3d7..9e966580 100644
--- a/.github/workflows/run-tests.yaml
+++ b/.github/workflows/run-tests.yaml
@@ -13,5 +13,6 @@ jobs:
node-version: '16' #required for npm 8 or later.
- run: npm install
- run: npm run lint
+ - run: npm test
env:
CI: true
diff --git a/.mocharc.json b/.mocharc.json
new file mode 100644
index 00000000..3b0fdf10
--- /dev/null
+++ b/.mocharc.json
@@ -0,0 +1,3 @@
+{
+ "recursive": true
+}
diff --git a/blocks/agent-search/builders/tags.js b/blocks/agent-search/builders/tags.js
index d5bcaf58..2838541b 100644
--- a/blocks/agent-search/builders/tags.js
+++ b/blocks/agent-search/builders/tags.js
@@ -28,6 +28,7 @@ export const addSelectionTag = (wrapper, filter, value) => {
`;
wrapper.querySelector('.selection-tags-list').append(li);
};
+
/**
* Builds the Container for the search bar selections.
*
diff --git a/blocks/header/header.css b/blocks/header/header.css
index 5af1aa86..2b27c00c 100644
--- a/blocks/header/header.css
+++ b/blocks/header/header.css
@@ -23,7 +23,7 @@ body.light-nav {
/ 1fr min-content;
align-items: start;
overflow-y: scroll;
- z-index: 50;
+ z-index: 1000;
}
.header.block nav[aria-expanded="true"] {
@@ -259,6 +259,7 @@ body.light-nav {
}
.header.block nav .nav-hamburger .open {
+ display: block;
position: absolute;
top: 0;
left: 0;
@@ -444,7 +445,7 @@ body.light-nav {
right: 0;
height: 5px;
background-color: var(--white);
- z-index: 10;
+ z-index: 1010;
}
.header.block nav[aria-expanded="true"] .nav-sections > ul > li > ul {
diff --git a/blocks/hero/search/agent.css b/blocks/hero/search/agent.css
index c35d9b6c..a8d71a0a 100644
--- a/blocks/hero/search/agent.css
+++ b/blocks/hero/search/agent.css
@@ -13,9 +13,6 @@
.hero.block .agent-search .selection-tags {
position: absolute;
-}
-
-.hero.block .agent-search .selection-tags {
padding: 0;
}
diff --git a/blocks/hero/search/home-delayed.js b/blocks/hero/search/home-delayed.js
deleted file mode 100644
index 6655b4bb..00000000
--- a/blocks/hero/search/home-delayed.js
+++ /dev/null
@@ -1,227 +0,0 @@
-import { getMetadata } from '../../../scripts/aem.js';
-import { BREAKPOINTS } from '../../../scripts/scripts.js';
-import {
- close as closeCountrySelect,
- getSelected as getSelectedCountry,
-} from '../../shared/search-countries/search-countries.js';
-import {
- abortSuggestions,
- getSuggestions,
- propertySearch,
- DOMAIN,
-} from '../../../scripts/apis/creg/creg.js';
-import { getSpinner } from '../../../scripts/util.js';
-import SearchType from '../../../scripts/apis/creg/SearchType.js';
-import SearchParameters from '../../../scripts/apis/creg/SearchParameters.js';
-
-const noOverlayAt = BREAKPOINTS.medium;
-
-const MORE_INPUT_NEEDED = 'Please enter at least 3 characters.';
-const NO_SUGGESTIONS = 'No suggestions found. Please modify your search.';
-const SEARCHING_SUGGESTIONS = 'Looking up suggestions...';
-
-const fixOverlay = () => {
- if (noOverlayAt.matches) {
- document.body.style.overflowY = 'hidden';
- } else {
- document.body.style.overflowY = null;
- }
-};
-
-const showFilters = (e) => {
- e.preventDefault();
- e.stopPropagation();
- e.currentTarget.closest('form').classList.add('show-filters');
- if (!noOverlayAt.matches) {
- document.body.style.overflowY = 'hidden';
- }
-};
-
-const closeFilters = (e) => {
- e.preventDefault();
- e.stopPropagation();
- const thisForm = e.currentTarget.closest('form');
- thisForm.classList.remove('show-filters');
- thisForm.querySelectorAll('.select-wrapper.open').forEach((select) => {
- select.classList.remove('open');
- });
-
- if (!noOverlayAt.matches) {
- document.body.style.overflowY = 'hidden';
- }
-};
-
-const selectClicked = (e) => {
- e.preventDefault();
- e.stopPropagation();
- const wrapper = e.currentTarget.closest('.select-wrapper');
- const wasOpen = wrapper.classList.contains('open');
- const thisForm = e.currentTarget.closest('form');
- thisForm.querySelectorAll('.select-wrapper.open').forEach((select) => {
- select.classList.remove('open');
- });
- closeCountrySelect(thisForm);
- if (!wasOpen) {
- wrapper.classList.add('open');
- }
-};
-
-const selectFilterClicked = (e) => {
- e.preventDefault();
- e.stopPropagation();
- const count = e.currentTarget.textContent;
- const wrapper = e.currentTarget.closest('.select-wrapper');
- wrapper.querySelector('.selected').textContent = count;
- wrapper.querySelector('ul li.selected')?.classList.toggle('selected');
- e.currentTarget.classList.add('selected');
- wrapper.querySelector('select option[selected="selected"]')?.removeAttribute('selected');
- wrapper.querySelector(`select option[value="${count.replace('+', '')}"]`).setAttribute('selected', 'selected');
- wrapper.classList.toggle('open');
-};
-
-const updateSuggestions = (suggestions, target) => {
- // Keep the first item - required character entry count.
- const first = target.querySelector(':scope li');
- target.replaceChildren(first, ...suggestions);
-};
-
-const buildSuggestions = (suggestions) => {
- const lists = [];
- suggestions.forEach((category) => {
- const list = document.createElement('li');
- list.classList.add('list-title');
- list.textContent = category.displayText;
- lists.push(list);
- const ul = document.createElement('ul');
- list.append(ul);
- category.results.forEach((result) => {
- const li = document.createElement('li');
- li.setAttribute('category', category.searchType);
- li.setAttribute('display', result.displayText);
- li.setAttribute('query', result.QueryString);
- li.textContent = result.SearchParameter;
- ul.append(li);
- });
- });
-
- return lists;
-};
-
-/**
- * Handles the input changed event for the text field. Will add suggestions based on user input.
- *
- * @param {Event} e the change event
- * @param {HTMLElement} target the container in which to add suggestions
- */
-const inputChanged = (e, target) => {
- const { value } = e.currentTarget;
- if (value.length > 0) {
- e.currentTarget.closest('.search-bar').classList.add('show-suggestions');
- } else {
- e.currentTarget.closest('.search-bar').classList.remove('show-suggestions');
- }
-
- if (value.length <= 2) {
- abortSuggestions();
- target.querySelector(':scope > li:first-of-type').textContent = MORE_INPUT_NEEDED;
- updateSuggestions([], target);
- } else {
- target.querySelector(':scope > li:first-of-type').textContent = SEARCHING_SUGGESTIONS;
- getSuggestions(value, getSelectedCountry(e.currentTarget.closest('form')))
- .then((suggestions) => {
- if (!suggestions) {
- // Undefined suggestions means it was aborted, more input coming.
- updateSuggestions([], target);
- return;
- }
- if (suggestions.length) {
- updateSuggestions(buildSuggestions(suggestions), target);
- } else {
- target.querySelector(':scope > li:first-of-type').textContent = NO_SUGGESTIONS;
- }
- });
- }
-};
-
-const suggestionSelected = (e, form) => {
- const query = e.target.getAttribute('query');
- const keyword = e.target.getAttribute('display');
- if (!query) {
- return;
- }
- form.querySelector('input[name="keyword"]').value = keyword;
- form.querySelector('input[name="query"]').value = query;
- form.querySelector('.search-bar').classList.remove('show-suggestions');
-};
-
-const formSubmitted = async (e) => {
- e.preventDefault();
- e.stopPropagation();
-
- const spinner = getSpinner();
- const form = e.currentTarget.closest('form');
- form.prepend(spinner);
-
- const franchisee = getMetadata('office-id');
- const type = SearchType[form.querySelector('input[name="type"]').value];
- const query = form.querySelector('input[name="query"]').value;
- const input = form.querySelector('input[name="keyword"]').value;
- const params = new SearchParameters(type);
- params.SearchInput = input;
- if (query) {
- params.populate(query);
- }
-
- if (franchisee) {
- params.franchisee = franchisee;
- }
- params.PageSize = 1;
- propertySearch(params).then((results) => {
- if (!results?.properties) {
- // What to do here?
- spinner.remove();
- return;
- }
-
- const domain = results.vanityDomain || `https://${DOMAIN}`;
- const searchPath = '/search';
- params.PageSize = SearchParameters.DEFAULT_PAGE_SIZE;
- params.ApplicationType = results.ApplicationType || params.ApplicationType;
- params.PropertyType = results.PropertyType || params.PropertyType;
- window.location = `${domain}${searchPath}?${params.asQueryString()}`;
- });
-};
-
-function addEventListeners() {
- const form = document.querySelector('.hero.block form.homes');
-
- noOverlayAt.addEventListener('change', fixOverlay);
-
- form.querySelectorAll('button[type="submit"]').forEach((button) => {
- button.addEventListener('click', formSubmitted);
- });
-
- form.querySelector('button.filter').addEventListener('click', showFilters);
-
- form.querySelectorAll('button.close').forEach((button) => {
- button.addEventListener('click', closeFilters);
- });
-
- form.querySelectorAll('.select-wrapper .selected').forEach((button) => {
- button.addEventListener('click', selectClicked);
- });
-
- form.querySelectorAll('.select-wrapper .select-items li').forEach((li) => {
- li.addEventListener('click', selectFilterClicked);
- });
-
- const suggestionsTarget = form.querySelector('.suggester-input .suggester-results');
- form.querySelector('.suggester-input input').addEventListener('input', (e) => {
- inputChanged(e, suggestionsTarget);
- });
- suggestionsTarget.addEventListener('click', (e) => {
- suggestionSelected(e, form);
- });
-}
-
-addEventListeners();
diff --git a/blocks/hero/search/home.css b/blocks/hero/search/home.css
index 974e2e30..962430b2 100644
--- a/blocks/hero/search/home.css
+++ b/blocks/hero/search/home.css
@@ -116,10 +116,16 @@
right: 0;
}
-.hero.block .content .homes .filters .select-wrapper.open .selected::after {
+.hero.block .content .homes .filters .select-wrapper.open > .selected::after {
content: '\f0d8';
}
+.hero.block .content .homes .filters .select-wrapper .selected span {
+ font-size: var(--body-font-size-xs);
+ color: var(--input-placeholder);
+}
+
+
.hero.block .content .homes .filters .select-wrapper .select-items {
display: none;
position: absolute;
diff --git a/blocks/hero/search/home.js b/blocks/hero/search/home.js
index 3d89f4e5..fb89fad2 100644
--- a/blocks/hero/search/home.js
+++ b/blocks/hero/search/home.js
@@ -1,61 +1,83 @@
import {
build as buildCountrySelect,
} from '../../shared/search-countries/search-countries.js';
+import { getMetadata, loadScript } from '../../../scripts/aem.js';
+import { getSpinner } from '../../../scripts/util.js';
+import { BED_BATHS, buildFilterSelect, getPlaceholder } from '../../shared/search/util.js';
+import Search, { SEARCH_URL } from '../../../scripts/apis/creg/search/Search.js';
+import { metadataSearch } from '../../../scripts/apis/creg/creg.js';
-function observeForm() {
- const script = document.createElement('script');
- script.type = 'module';
- script.src = `${window.hlx.codeBasePath}/blocks/hero/search/home-delayed.js`;
- document.head.append(script);
+async function observeForm(e) {
+ const form = e.target.closest('form');
+ try {
+ const mod = await import(`${window.hlx.codeBasePath}/blocks/shared/search/suggestion.js`);
+ mod.default(form);
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.log('failed to load suggestion library', error);
+ }
+ e.target.removeEventListener('focus', observeForm);
}
-/**
- * Creates a Select dropdown for filtering search.
- * @param {String} name
- * @param {String} placeholder
- * @param {number} number
- * @returns {HTMLDivElement}
- */
-function buildSelect(name, placeholder, number) {
- const wrapper = document.createElement('div');
- wrapper.classList.add('select-wrapper');
- wrapper.innerHTML = `
-
-
${placeholder}
-
- `;
+async function submitForm(e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const spinner = getSpinner();
+ const form = e.currentTarget.closest('form');
+ form.prepend(spinner);
+
+ const type = form.querySelector('input[name="type"]');
- const select = wrapper.querySelector('select');
- const ul = wrapper.querySelector('ul');
- for (let i = 1; i <= number; i += 1) {
- const option = document.createElement('option');
- const li = document.createElement('li');
- li.setAttribute('role', 'option');
-
- option.value = `${i}`;
- // eslint-disable-next-line no-multi-assign
- option.textContent = li.textContent = `${i}+`;
- select.append(option);
- ul.append(li);
+ let search = new Search();
+ if (type && type.value) {
+ try {
+ const mod = await import(`${window.hlx.codeBasePath}/scripts/apis/creg/search/types/${type.value}Search.js`);
+ if (mod.default) {
+ // eslint-disable-next-line new-cap
+ search = new mod.default();
+ }
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.log(`failed to load Search Type for ${type.value}`, error);
+ }
}
- return wrapper;
-}
+ search.populateFromSuggestion(new URLSearchParams(form.querySelector('input[name="query"]').value));
+ search.input = form.querySelector('input[name="keyword"]').value;
-function getPlaceholder(country) {
- if (country && country !== 'US') {
- return 'Enter City';
+ search.minPrice = form.querySelector('input[name="minPrice"]').value;
+ search.maxPrice = form.querySelector('input[name="maxPrice"]').value;
+ search.minBedrooms = form.querySelector('select[name="bedrooms"]').value;
+ search.minBathrooms = form.querySelector('select[name="bathrooms"]').value;
+
+ const franchisee = getMetadata('office-id');
+ if (franchisee) {
+ search.franchisee = franchisee;
}
- return 'Enter City, Address, Zip/Postal Code, Neighborhood, School or MLS#';
+ metadataSearch(search).then((results) => {
+ if (results) {
+ let url = '';
+ if (window.location.href.includes('localhost')) {
+ url += `/search?${search.asURLSearchParameters()}`;
+ } else if (results.vanityDomain) {
+ if (getMetadata('vanity-domain') === results.vanityDomain) {
+ url += `/search?${search.asURLSearchParameters()}`;
+ } else {
+ url += `${results.vanityDomain}/search?${search.asCregURLSearchParameters()}`;
+ }
+ } else {
+ url = `https://www.bhhs.com${results.searchPath}/search?${search.asCregURLSearchParameters()}`;
+ }
+ window.location = url;
+ }
+ spinner.remove();
+ });
}
async function buildForm() {
const form = document.createElement('form');
form.classList.add('homes');
- form.setAttribute('action', '/search');
+ form.setAttribute('action', SEARCH_URL);
form.innerHTML = `
`;
+ /* eslint-enable max-len */
+
observeForm();
initLogin();
}
diff --git a/blocks/property-listing/map-search.js b/blocks/property-listing/map-search.js
deleted file mode 100644
index 3501c1d1..00000000
--- a/blocks/property-listing/map-search.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import SearchType from '../../scripts/apis/creg/SearchType.js';
-import Search from './search.js';
-
-/**
- * Create and render property search based on a bounding box.
- */
-export default class MapSearch extends Search {
- more = false;
-
- #minLat;
-
- #minLon;
-
- #maxLat;
-
- #maxLon;
-
- constructor(minLat, minLon, maxLat, maxLon) {
- super(SearchType.Map, SearchType.Map.paramBuilder(minLat, maxLat, minLon, maxLon));
- this.#minLat = minLat;
- this.#minLon = minLon;
- this.#maxLat = maxLat;
- this.#maxLon = maxLon;
- }
-}
diff --git a/blocks/property-listing/property-listing.css b/blocks/property-listing/property-listing.css
index 91e4cca1..021d44fc 100644
--- a/blocks/property-listing/property-listing.css
+++ b/blocks/property-listing/property-listing.css
@@ -1,5 +1,5 @@
@import url('luxury-collection-template.css');
-@import url('./cards/cards.css');
+@import url('../shared/property/cards.css');
.property-listing.block {
overflow: hidden;
diff --git a/blocks/property-listing/property-listing.js b/blocks/property-listing/property-listing.js
index b5587445..7c714bb4 100644
--- a/blocks/property-listing/property-listing.js
+++ b/blocks/property-listing/property-listing.js
@@ -1,64 +1,7 @@
import { getMetadata, readBlockConfig } from '../../scripts/aem.js';
-import ApplicationType from '../../scripts/apis/creg/ApplicationType.js';
-import SearchType, { searchTypeFor } from '../../scripts/apis/creg/SearchType.js';
-import PropertyType from '../../scripts/apis/creg/PropertyType.js';
-import MapSearch from './map-search.js';
-import RadiusSearch from './radius-search.js';
-
-/* eslint-disable no-param-reassign */
-const buildListingTypes = (configEntry) => {
- const types = [];
- if (!configEntry) {
- types.push(ApplicationType.FOR_SALE);
- return types;
- }
-
- const [, configStr] = configEntry;
- if (configStr.match(/sale/i)) {
- types.push(ApplicationType.FOR_SALE);
- }
- if (configStr.match(/rent/gi)) {
- types.push(ApplicationType.FOR_RENT);
- }
- if (configStr.match(/pending/gi)) {
- types.push(ApplicationType.PENDING);
- }
- if (configStr.match(/sold/gi)) {
- types.push(ApplicationType.RECENTLY_SOLD);
- }
- return types;
-};
-/* eslint-enable no-param-reassign */
-
-const buildPropertyTypes = (configEntry) => {
- const types = [];
- if (!configEntry) {
- types.push(PropertyType.CONDO_TOWNHOUSE);
- types.push(PropertyType.SINGLE_FAMILY);
- return types;
- }
-
- const [, configStr] = configEntry;
- if (configStr.match(/(condo|townhouse)/i)) {
- types.push(PropertyType.CONDO_TOWNHOUSE);
- }
- if (configStr.match(/single\sfamily/gi)) {
- types.push(PropertyType.SINGLE_FAMILY);
- }
- if (configStr.match(/commercial/gi)) {
- types.push(PropertyType.COMMERCIAL);
- }
- if (configStr.match(/multi\s+family/gi)) {
- types.push(PropertyType.MULTI_FAMILY);
- }
- if (configStr.match(/(lot|land)/gi)) {
- types.push(PropertyType.LAND);
- }
- if (configStr.match(/(farm|ranch)/gi)) {
- types.push(PropertyType.FARM);
- }
- return types;
-};
+import { render as renderCards } from '../shared/property/cards.js';
+import Search from '../../scripts/apis/creg/search/Search.js';
+import { propertySearch } from '../../scripts/apis/creg/creg.js';
export default async function decorate(block) {
// Find and process list type configurations.
@@ -85,54 +28,12 @@ export default async function decorate(block) {
block.innerHTML = '';
}
- let search;
-
- const entries = Object.entries(config);
- const type = searchTypeFor(entries.find(([k]) => k.match(/search.*type/i))[1]);
-
- if (type === SearchType.Map) {
- const minLat = entries.find(([k]) => k.includes('min') && k.includes('lat'))[1];
- const maxLat = entries.find(([k]) => k.includes('max') && k.includes('lat'))[1];
- const minLon = entries.find(([k]) => k.includes('min') && k.includes('lon'))[1];
- const maxLon = entries.find(([k]) => k.includes('max') && k.includes('lon'))[1];
- search = new MapSearch(minLat, minLon, maxLat, maxLon);
- } else if (type === SearchType.Radius) {
- let lat = entries.find(([k]) => k.includes('lat'))[1];
- let lon = entries.find(([k]) => k.includes('lon'))[1];
- const radius = entries.find(([k]) => k.includes('distance'))[1];
-
- // Go looking for the search parameters.
- if (!lat) {
- const urlParams = new URLSearchParams(window.location.search);
- lat = urlParams.get('latitude');
- lon = urlParams.get('longitude');
- }
-
- search = new RadiusSearch(lat, lon, radius);
- } else if (type === SearchType.Community) {
- const { bbox } = window.liveby.geometry;
- const minLon = Math.min(...bbox.map((e) => e[0]));
- const maxLon = Math.max(...bbox.map((e) => e[0]));
- const minLat = Math.min(...bbox.map((e) => e[1]));
- const maxLat = Math.max(...bbox.map((e) => e[1]));
- search = new MapSearch(minLat, minLon, maxLat, maxLon);
- } else {
- search = new MapSearch(0, 0, 0, 0);
- }
-
- search.listingTypes = buildListingTypes(entries.find(([k]) => k.match(/listing.*type/i)));
- search.propertyTypes = buildPropertyTypes(entries.find(([k]) => k.match(/property.*type/i)));
-
- search.isNew = !!entries.find(([k]) => k.match(/new/i));
- search.isOpenHouse = !!entries.find(([k]) => k.match(/open.*house/i));
-
- [, search.minPrice] = entries.find(([k]) => k.match(/min.*price/i)) || [];
- [, search.maxPrice] = entries.find(([k]) => k.match(/max.*price/i)) || [];
-
- [, search.pageSize] = entries.find(([k]) => k.match(/page.*size/i)) || [];
- search.sortBy = config['sort-by'];
- search.sortDirection = config['sort-direction'];
- search.officeId = getMetadata('office-id');
-
- await search.render(block, false);
+ const search = await Search.fromBlockConfig(config);
+ search.franchiseeCode = getMetadata('office-id');
+ const list = document.createElement('div');
+ list.classList.add('property-list-cards', `rows-${Math.floor(search.pageSize / 8)}`);
+ block.append(list);
+ propertySearch(search).then((results) => {
+ renderCards(list, results.properties);
+ });
}
diff --git a/blocks/property-listing/radius-search.js b/blocks/property-listing/radius-search.js
deleted file mode 100644
index 5fc78824..00000000
--- a/blocks/property-listing/radius-search.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import SearchType from '../../scripts/apis/creg/SearchType.js';
-import Search from './search.js';
-
-/**
- * Create and render property search based on a point and radius.
- */
-export default class RadiusSearch extends Search {
- more = false;
-
- #lat;
-
- #lon;
-
- #radius;
-
- constructor(lat, lon, radius) {
- super(SearchType.Radius, SearchType.Radius.paramBuilder(lat, lon, radius));
- this.#lat = lat;
- this.#lon = lon;
- this.#radius = radius;
- }
-}
diff --git a/blocks/property-listing/search.js b/blocks/property-listing/search.js
deleted file mode 100644
index a9fdac62..00000000
--- a/blocks/property-listing/search.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import SearchParameters, { SortDirections, SortOptions } from '../../scripts/apis/creg/SearchParameters.js';
-import { render as renderCards } from './cards/cards.js';
-// eslint-disable-next-line no-unused-vars
-import SearchType from '../../scripts/apis/creg/SearchType.js';
-
-// function addMoreButton() {
-// TODO: add this logic if there's supposed to be more results;
-// }
-
-export default class Search {
- #searchParams;
-
- /**
- * Create a new Search object. (should not be called directly)
- *
- * @param {SearchType} type
- * @param {string} params the result of the paramBuilder for the specified type.
- */
- constructor(type, params) {
- this.#searchParams = new SearchParameters(type, params);
- }
-
- isNew;
-
- isOpenHouse;
-
- listingTypes;
-
- maxPrice;
-
- minPrice;
-
- officeId;
-
- pageSize;
-
- propertyTypes;
-
- sortBy;
-
- sortDirection;
-
- // eslint-disable-next-line no-unused-vars
- async render(parent, enableMore = false) {
- this.#searchParams.MinPrice = this.minPrice;
- this.#searchParams.MaxPrice = this.maxPrice;
- this.#searchParams.PageSize = this.pageSize || SearchParameters.DEFAULT_PAGE_SIZE;
- this.#searchParams.sortBy = this.sortBy || SortOptions.DATE;
- this.#searchParams.sortDirection = this.sortDirection || SortDirections.DESC;
- this.#searchParams.propertyTypes = this.propertyTypes;
- this.#searchParams.applicationTypes = this.listingTypes;
- this.#searchParams.NewListing = this.isNew || this.#searchParams.NewListing;
- this.#searchParams.OpenHouses = this.isOpenHouse ? '7' : undefined;
- this.#searchParams.franchisee = this.officeId;
-
- await renderCards(this.#searchParams, parent);
- // TODO: Enable the "Load More" for the Property Search page.
- }
-}
diff --git a/blocks/property-result-listing/property-result-listing.css b/blocks/property-result-listing/property-result-listing.css
deleted file mode 100644
index be4683ee..00000000
--- a/blocks/property-result-listing/property-result-listing.css
+++ /dev/null
@@ -1,350 +0,0 @@
-@import url('../property-listing/cards/cards.css');
-@import url('../property-search-bar/search-results-dropdown.css');
-@import url('../property-result-map/map.css');
-
-.property-result-listing.block {
- display: flex;
- flex-wrap: wrap;
-}
-
-.property-result-listing.block .search-results-loader {
- position: relative;
- overflow: hidden;
- z-index: 0;
-}
-
-.property-result-listing.block .search-results-loader-image.enter {
- opacity: 1;
-}
-
-.property-result-listing.block .search-results-loader-image.exit {
- opacity: 0;
- pointer-events: none;
- z-index: -1;
-}
-
-.property-result-listing.block .search-results-loader-image {
- text-align: center;
- margin: 0 !important;
- z-index: 1;
- height: 100%;
- width: 100%;
- display: flex;
- align-items: flex-start;
- justify-content: center;
- background-color: var(--white);
- transition: all 1s ease-in;
-}
-
-.property-search-template.search-map-active .property-result-listing.block > div {
- flex: 0 0 50%;
- max-width: 50%;
- padding: 0 15px;
-}
-
-.property-result-listing.block .button-container {
- display: flex;
- margin-bottom: 1.5rem;
-}
-
-.property-result-listing.block .hide {
- display: none;
-}
-
-.property-result-listing.block .property-list-cards {
- display: grid;
- grid-template: repeat(8, 1fr) / repeat(4, 1fr);
- height: 3340px;
- grid-gap: 20px;
-}
-
-.search-map-active .property-result-listing.block .property-list-cards{
- height: 6520px;
- grid-template: repeat(16, 1fr) / repeat(2, 1fr);
-}
-
-
-.property-result-listing.block .button-container a {
- cursor:pointer;
- font-family: var(--font-family-primary);
- letter-spacing: 1px;
- text-transform: uppercase;
- text-decoration: none;
- padding: 7px 25px;
- font-size: var(--body-font-size-xs);
- line-height: 1.5;
- border-radius: 0;
- background: var(--white);
- color: var(--black);
- display: inline-block;
- font-weight: 400;
- text-align: center;
- white-space: nowrap;
- vertical-align: middle;
- border: 1px solid var(--black);
-}
-
-.property-result-listing.block .button-container a:hover {
- border-color: var(--grey);
-}
-
-.property-result-listing.block .property-list-cards .property-labels .property-label.new-listing {
- text-transform: initial;
-}
-
-.property-result-listing.block [name="Page"].multiple-inputs .select-item{
- left: 0;
- border: 1px solid var(--platinum);
- max-height: 185px;
- overflow-y: scroll;
- overflow-x: hidden;
- width: 93px;
- display: block;
-}
-
-.property-result-listing.block [name="Page"].multiple-inputs .select-item.hide {
- display: none;
-}
-
-.property-result-listing.block [name="Page"] {
- display: flex;
- justify-content: flex-end;
-}
-
-.property-result-listing.block [name="Page"] .select-selected,
-.property-result-listing.block [name="Page"] .search-results-dropdown .select-item li {
- font-size: var(--body-font-size-xs);
- letter-spacing: var(--letter-spacing-reg);
- color: var(--body-color);
- cursor: pointer;
-}
-
-.property-result-listing.block [name="Page"] .select-wrapper {
- position: relative;
- width: 91px;
-}
-
-.property-result-listing.block [name="Page"] .select-selected {
- border: 1px solid var(--grey);
- height: 35px;
- line-height: 35px;
- padding: 0 15px;
- white-space: nowrap;
-}
-
-.property-result-listing.block [name="Page"] .select-selected::after {
- right: 5px;
-}
-
-.property-result-listing.block [name="Page"] .search-results-dropdown .select-item li:first-child {
- border-top: none;
-}
-
-.property-result-listing.block [name="Page"] .search-results-dropdown .select-item li:last-child {
- border-bottom: none;
-}
-
-.property-result-listing.block .pagination-arrows {
- display: flex;
- justify-content: flex-end;
- line-height: 32px;
-}
-
-.property-result-listing.block .pagination-arrows .arrow {
- border: 1px solid #ced4da;
- position: relative;
- height: 35px;
- width: 35px;
- padding: 4px;
- cursor: pointer;
- text-align: center
-}
-
-.property-result-listing.block .pagination-arrows .arrow.disabled {
- cursor: auto;
- border: 1px solid #adb5bd;
-}
-
-.property-result-listing.block .pagination-arrows .arrow.disabled::after {
- border: solid #ced4da;
- border-width: 0 2px 2px 0;
-}
-
-.property-result-listing.block .pagination-arrows .arrow.prev {
- margin-left: 0.5rem !important
-}
-
-.property-result-listing.block .pagination-arrows .arrow::after {
- content: "";
- border: solid black;
- border-width: 0 2px 2px 0;
- display: inline-block;
- padding: 7px;
-}
-
-.property-result-listing.block .pagination-arrows .arrow.prev::after {
- transform: rotate(135deg) translate(-3px,-3px);
-}
-
-.property-result-listing.block .pagination-arrows .arrow.next::after {
- transform: rotate(-45deg) translate(-3px,-3px);
-}
-
-.property-result-listing.block .disclaimer {
- font-family: var(--font-family-primary);
- font-size: var(--body-font-size-xs);
- font-weight: var(--font-weight-normal);
- letter-spacing: var(--letter-spacing-s);
- color: var(--dark-grey);
- line-height: var(--line-height-m);
-}
-
-.property-result-listing.block .disclaimer hr {
- height: 1px;
- background: var(--grey);
- width: 100%;
- margin: 30px 0 0;
- padding: 0;
- display: block;
- border: 0
-}
-
-.property-result-listing.block .disclaimer td[align="center"] {
-text-align: center;
-}
-
-.property-result-listing.block .disclaimer td[align="left"] {
- text-align: left;
-}
-
-.property-result-listing.block .disclaimer td[align="right"] {
- text-align: right;
-}
-
-.property-result-listing.block .disclaimer * {
- font-size: var(--body-font-size-xs);
-}
-
-.property-result-listing.block .disclaimer .text {
- color: var(--body-color);
- font-size: var(--body-font-size-xs);
- line-height: var(--line-height-s);
- margin: 30px 0;
- column-gap: 50px;
-}
-
-.property-result-listing.block .disclaimer .text-center {
- text-align: center;
-}
-
-.property-result-listing.block .disclaimer .text img {
- height: auto;
- margin: 0;
- padding: 0;
- width: 30%
-}
-
-.property-result-listing.block .property-search-results-buttons {
- display: flex;
- position: fixed;
- bottom: 0;
- left: 0;
- z-index: 5;
- background: var(--white);
- width: 100%;
- justify-content: center;
- padding: 10px 0;
- box-shadow: 0 0 6px 0 rgb(0 0 0 / 23%);
- cursor: pointer;
-}
-
-.property-result-listing.block .property-search-results-buttons .btn.btn-map {
- font-size: var(--body-font-size-xs);
- height: 35px;
- background: var(--white);
- border: 1px solid var(--grey);
- padding: 0 15px;
- line-height: var(--line-height-xxl);
- display: inline-block;
-}
-
-.property-result-listing.block .property-search-results-buttons section:first-of-type {
- margin-right: 16px;
-}
-
-.property-result-listing.block .property-search-results-buttons .btn.btn-map span {
- font-size: var(--body-font-size-xs);
- color: var(--body-color);
-}
-
-@media (min-width: 900px) {
- .property-result-listing.block .button-container {
- justify-content: flex-end;
- }
-
- .property-result-listing.block [class*="disclaimer"] * {
- font-family: var(--font-family-primary);
- font-size: var(--body-font-size-xs);
- color: var(--dark-grey);
- line-height: var(--line-height-s);
- letter-spacing: var(--letter-spacing-s);
- opacity: 1;
- }
-
- .property-result-listing.block [class*="disclaimer"] * p {
- font-family: var(--font-family-primary);
- font-size: var(--body-font-size-xs);
- color: var(--dark-grey);
- line-height: var(--line-height-s);
- letter-spacing: var(--letter-spacing-s);
- }
-
- .property-result-listing.block .property-search-results-buttons {
- display: none;
- }
-}
-
-@media (max-width: 899px) {
- .property-result-listing.block {
- width: 100%;
- flex-direction: column-reverse;
- }
-
- .property-result-listing.block .property-list-cards {
- grid-template: repeat(32, 1fr) / repeat(1, 1fr);
- height: 13440px;
- }
-
- .property-result-listing.block .property-list-cards .listing-tile {
- width: 100%;
- max-width: 100%;
- }
-
- .search-map-active .property-result-listing.block {
- width: 100%;
- min-height: 800px;
- }
-
- .search-map-active .property-result-listing.block .property-list-cards,
- .search-map-active .property-result-listing.block [name="Page"],
- .search-map-active .property-result-listing.block .property-list-cards .property-result-content .disclaimer {
- display: none;
- }
-
- .property-search-template.search-map-active .property-result-listing.block > div {
- flex: 0 0 100%;
- max-width: 100%;
- padding: 0 15px;
- }
-}
-
-
-@media (min-width: 1200px) {
- .property-result-listing.block .property-info-wrapper {
- font-size: var(--body-font-size-xl);
- }
-
- .property-result-listing.block .property-list-cards {
- grid-template: repeat(8, 1fr) / repeat(4, 1fr);
- }
-}
diff --git a/blocks/property-result-listing/property-result-listing.js b/blocks/property-result-listing/property-result-listing.js
deleted file mode 100644
index c72db6ab..00000000
--- a/blocks/property-result-listing/property-result-listing.js
+++ /dev/null
@@ -1,200 +0,0 @@
-import { createCard } from '../property-listing/cards/cards.js';
-import {
- getDisclaimer,
- getPropertiesCount,
- getPropertyDetails,
- getAllData,
-} from '../../scripts/search/results.js';
-import { getValueFromStorage, searchProperty, setFilterValue } from '../property-search-bar/filter-processor.js';
-import renderMap from '../property-result-map/map.js';
-import {
- showModal,
-} from '../../scripts/util.js';
-
-const event = new Event('onFilterChange');
-const ITEMS_PER_PAGE = 32;
-function buildLoader() {
- const wrapper = document.createElement('div');
- wrapper.classList.add('search-results-loader');
- wrapper.innerHTML = `
-
-
-
- `;
- return wrapper;
-}
-function updateStyles(count, div, items = 4) {
- if (count < ITEMS_PER_PAGE) {
- // adjust block height
- const lines = Math.ceil(count / items);
- const height = lines * 400 + 20 * (lines - 1);
- div.style.height = `${height}px`;
- div.style.gridTemplate = `repeat(${lines}, 400px) / repeat(${items}, 1fr)`;
- } else {
- div.style = '';
- }
-}
-
-function buildPropertySearchResultsButton() {
- const wrapper = document.createElement('div');
- wrapper.classList.add('property-search-results-buttons');
- wrapper.innerHTML = `
-
-
- `;
- return wrapper;
-}
-
-function buildDisclaimer(html) {
- const wrapper = document.createElement('div');
- wrapper.classList.add('disclaimer');
- wrapper.innerHTML = `
-
-
- ${html}
-
- `;
- return wrapper;
-}
-
-function buildPagination(currentPage, totalPages) {
- // set map view
- document.querySelector('body').classList.add('search-map-active');
- const wrapper = document.createElement('div');
- wrapper.setAttribute('name', 'Page');
- wrapper.classList.add('multiple-inputs');
- let options = '';
- let list = '';
- for (let i = 1; i <= totalPages; i += 1) {
- options += ``;
- }
- for (let i = 1; i <= totalPages; i += 1) {
- list += `${i}`;
- }
- wrapper.innerHTML = `
-
-
-
-
- ${`${currentPage} of ${totalPages}`}
-
-
-
-
- `;
- return wrapper;
-}
-
-export default async function decorate(block) {
- block.textContent = '';
- block.append(buildLoader());
- await renderMap(block);
- window.dispatchEvent(event);
- window.addEventListener('onResultUpdated', () => {
- if (getPropertiesCount() > 0) {
- document.querySelector('.property-result-map-container').style.display = 'block';
- const propertyResultContent = document.createElement('div');
- propertyResultContent.classList.add('property-result-content');
- const listings = getPropertyDetails();
- const div = document.createElement('div');
- div.classList.add('property-list-cards');
- const currentPage = parseInt(getValueFromStorage('Page'), 10);
- const totalPages = Math.ceil(getPropertiesCount() / ITEMS_PER_PAGE);
- const disclaimerHtml = getDisclaimer() === '' ? '' : getDisclaimer().Text;
-
- const disclaimerBlock = buildDisclaimer(disclaimerHtml);
- let nextPage;
- listings.forEach((listing) => {
- div.append(createCard(listing));
- });
- updateStyles(listings.length, div, 2);
- propertyResultContent.append(div);
- /** add pagination */
- propertyResultContent.append(buildPagination(currentPage, totalPages));
- /** add property search results button */
- propertyResultContent.append(buildPropertySearchResultsButton());
- /** build disclaimer */
- propertyResultContent.append(buildDisclaimer(disclaimerHtml));
- block.prepend(propertyResultContent);
-
- // update map
- window.updatePropertyMap(getAllData(), false);
-
- document.querySelector('.property-result-map-container').append(disclaimerBlock);
- /** update page on select change */
- block.querySelector('[name="Page"] .select-selected').addEventListener('click', () => {
- block.querySelector('[name="Page"] ul').classList.toggle('hide');
- });
- block.querySelector('[name="Page"] ul').addEventListener('click', (e) => {
- e.preventDefault();
- e.stopPropagation();
- nextPage = e.target.closest('li').getAttribute('data-value');
- setFilterValue('Page', nextPage);
- block.querySelector('[name="Page"] ul').classList.toggle('hide');
- searchProperty();
- });
- /** update page on arrow click */
- block.querySelector('.pagination-arrows .arrow.prev').addEventListener('click', (e) => {
- if (!e.target.closest('.arrow').classList.contains('disabled')) {
- e.preventDefault();
- e.stopPropagation();
- setFilterValue('Page', currentPage - 1);
- searchProperty();
- }
- });
- block.querySelector('.pagination-arrows .arrow.next').addEventListener('click', (e) => {
- if (!e.target.closest('.arrow').classList.contains('disabled')) {
- e.preventDefault();
- e.stopPropagation();
- setFilterValue('Page', currentPage + 1);
- searchProperty();
- }
- });
- document.querySelector('.map-toggle > a').addEventListener('click', (e) => {
- const span = e.target.closest('span');
- if (span.innerText === 'GRID VIEW') {
- span.innerText = 'map view';
- document.querySelector('body').classList.remove('search-map-active');
- updateStyles(listings.length, div, 4);
- } else {
- span.innerText = 'grid view';
- document.querySelector('body').classList.add('search-map-active');
- updateStyles(listings.length, div, 2);
- }
- });
- block.querySelector('.property-search-results-buttons .map').addEventListener('click', (e) => {
- e.preventDefault();
- e.stopPropagation();
- const span = e.target.closest('span');
- if (span.innerText === 'LIST VIEW') {
- span.innerText = 'map view';
- document.querySelector('body').classList.remove('search-map-active');
- updateStyles(listings.length, div, 1);
- } else {
- span.innerText = 'list view';
- document.querySelector('body').classList.add('search-map-active');
- }
- });
- } else {
- document.querySelector('.property-result-map-container').style.display = 'none';
- showModal('Your search returned 0 results.\n'
- + 'Please modify your search and try again.');
- }
- });
-}
diff --git a/blocks/property-result-map/Template.js b/blocks/property-result-map/Template.js
deleted file mode 100644
index 9c98da94..00000000
--- a/blocks/property-result-map/Template.js
+++ /dev/null
@@ -1,93 +0,0 @@
-export default class Template {
- // eslint-disable-next-line class-methods-use-this
- render = (data) => {
- const luxuryHTML = data.luxury && data.isCompanyListing
- ? `div class="position-absolute top w-100">
-
- ${data.luxuryLabel}
-
- `
- : '';
- const soldHTML = data.sellingOfficeName ? `${data.mlsStatus} ${data.ClosedDate}
` : '';
- const municipalityHTML = data.municipality ? `${data.municipality}
` : '';
- const CourtesyOfHr = data.CourtesyOf ? `
-
` : '';
- const addMlsFlagHTML = data.addMlsFlag ? `` : '';
- const courtersyHTML = data.CourtesyOf ? `
- ` : '';
- const sellingOfficeNameHTML = data.sellingOfficeName ? `
- ` : '';
- const addMLsFlagHTML = data.addMlsFlag ? `
-
- ${data.ddMlsFlag}` : '';
- return ``;
- };
-}
diff --git a/blocks/property-result-map/map-delayed.js b/blocks/property-result-map/map-delayed.js
deleted file mode 100644
index f9c4ab81..00000000
--- a/blocks/property-result-map/map-delayed.js
+++ /dev/null
@@ -1,1069 +0,0 @@
-/* global google */
-/* eslint-disable no-param-reassign, no-shadow, prefer-rest-params */
-
-import { fetchPlaceholders } from '../../scripts/aem.js';
-import { removeFilterValue, searchProperty, setFilterValue } from '../property-search-bar/filter-processor.js';
-import Template from './Template.js';
-import SearchParameters from '../../scripts/apis/creg/SearchParameters.js';
-import { propertySearch } from '../../scripts/apis/creg/creg.js';
-
-const Nr = [{
- featureType: 'administrative',
- elementType: 'labels.text.fill',
- stylers: [{
- color: '#444444',
- }],
-}, {
- featureType: 'administrative.locality',
- elementType: 'labels.text.fill',
- stylers: [{
- saturation: '-42',
- }, {
- lightness: '-53',
- }, {
- gamma: '2.98',
- }],
-}, {
- featureType: 'administrative.neighborhood',
- elementType: 'labels.text.fill',
- stylers: [{
- saturation: '1',
- }, {
- lightness: '31',
- }, {
- weight: '1',
- }],
-}, {
- featureType: 'administrative.neighborhood',
- elementType: 'labels.text.stroke',
- stylers: [{
- visibility: 'off',
- }],
-}, {
- featureType: 'administrative.land_parcel',
- elementType: 'labels.text.fill',
- stylers: [{
- lightness: '12',
- }],
-}, {
- featureType: 'landscape',
- elementType: 'all',
- stylers: [{
- saturation: '67',
- }],
-}, {
- featureType: 'landscape.man_made',
- elementType: 'geometry.fill',
- stylers: [{
- visibility: 'on',
- }, {
- color: '#ececec',
- }],
-}, {
- featureType: 'landscape.natural',
- elementType: 'geometry.fill',
- stylers: [{
- visibility: 'on',
- }],
-}, {
- featureType: 'landscape.natural.landcover',
- elementType: 'geometry.fill',
- stylers: [{
- visibility: 'on',
- }, {
- color: '#ffffff',
- }, {
- saturation: '-2',
- }, {
- gamma: '7.94',
- }],
-}, {
- featureType: 'landscape.natural.terrain',
- elementType: 'geometry',
- stylers: [{
- visibility: 'on',
- }, {
- saturation: '94',
- }, {
- lightness: '-30',
- }, {
- gamma: '8.59',
- }, {
- weight: '5.38',
- }],
-}, {
- featureType: 'poi',
- elementType: 'all',
- stylers: [{
- visibility: 'off',
- }],
-}, {
- featureType: 'poi.park',
- elementType: 'geometry',
- stylers: [{
- saturation: '-26',
- }, {
- lightness: '20',
- }, {
- weight: '1',
- }, {
- gamma: '1',
- }],
-}, {
- featureType: 'poi.park',
- elementType: 'geometry.fill',
- stylers: [{
- visibility: 'on',
- }],
-}, {
- featureType: 'road',
- elementType: 'all',
- stylers: [{
- saturation: -100,
- }, {
- lightness: 45,
- }],
-}, {
- featureType: 'road',
- elementType: 'geometry.fill',
- stylers: [{
- visibility: 'on',
- }, {
- color: '#fafafa',
- }],
-}, {
- featureType: 'road',
- elementType: 'geometry.stroke',
- stylers: [{
- visibility: 'off',
- }],
-}, {
- featureType: 'road',
- elementType: 'labels.text.fill',
- stylers: [{
- gamma: '0.95',
- }, {
- lightness: '3',
- }],
-}, {
- featureType: 'road',
- elementType: 'labels.text.stroke',
- stylers: [{
- visibility: 'off',
- }],
-}, {
- featureType: 'road.highway',
- elementType: 'all',
- stylers: [{
- visibility: 'simplified',
- }],
-}, {
- featureType: 'road.highway',
- elementType: 'geometry',
- stylers: [{
- lightness: '100',
- }, {
- gamma: '5.22',
- }],
-}, {
- featureType: 'road.highway',
- elementType: 'geometry.stroke',
- stylers: [{
- visibility: 'on',
- }],
-}, {
- featureType: 'road.arterial',
- elementType: 'labels.icon',
- stylers: [{
- visibility: 'off',
- }],
-}, {
- featureType: 'transit',
- elementType: 'all',
- stylers: [{
- visibility: 'off',
- }],
-}, {
- featureType: 'water',
- elementType: 'all',
- stylers: [{
- color: '#b3dced',
- }, {
- visibility: 'on',
- }],
-}, {
- featureType: 'water',
- elementType: 'labels.text.fill',
- stylers: [{
- visibility: 'on',
- }, {
- color: '#ffffff',
- }],
-}, {
- featureType: 'water',
- elementType: 'labels.text.stroke',
- stylers: [{
- visibility: 'off',
- }, {
- color: '#e6e6e6',
- }],
-}];
-
-let rd = [];
-let lg = [];
-let uf = [];
-let Zd = !1;
-const Bj = [];
-const Cj = [];
-let map;
-// eslint-disable-next-line no-unused-vars
-let hi = !1;
-const Hh = [];
-const rn = !0;
-
-function Yd(a) {
- this.baseMarker = a;
- const c = a.getPosition().lat();
- const f = a.getPosition().lng();
- this.lat = c;
- this.lng = f;
- this.pos = new google.maps.LatLng(c, f);
- this.icon = a.icon;
- this.propId = a.propId;
-}
-
-const bA = (d, h, k, m) => {
- d.forEach((d) => {
- h.push(d);
- d = new Yd(d);
- d.setMap(m);
- k.push(d);
- });
-};
-
-function ln(a) {
- google.maps.event.addListener(a, 'domready', () => {
- uf.push(a);
- });
-}
-// load Listing info by id
-async function yj(listingId) {
- // @todo generate dynamicly from result response MLSFlag, AddMlsFlag, OfficeCode
- const params = new SearchParameters('ListingId');
- params.ListingId = listingId;
- params.MLSFlag = 'false';
- params.AddMlsFlag = 'false';
- params.OfficeCode = 'MA312';
- params.SearchType = 'ListingId';
- return propertySearch(params);
-}
-
-function nn(a) {
- return a && a.smallPhotos && a.smallPhotos.length > 0 ? a.smallPhotos[0].mediaUrl : '';
-}
-function zj(a, c, f) {
- const path = f || '/property/detail';
- const d = nn(a);
- const h = a.ListPriceUS;
- const q = a.municipality;
- const m = a.addMlsFlag;
- const p = a.listAor;
- const k = a.mlsLogo;
- const n = a.mlsStatus;
- const B = a.ClosedDate;
- const t = a.ListingId;
- const y = a.CourtesyOf;
- const A = a.sellingOfficeName;
- const H = a.ApplicationType;
- const J = a.listPriceAlternateCurrency;
- const I = a.brImageUrl;
- const P = a.StreetName;
- const u = a.City;
- const v = a.PostalCode;
- const gb = a.PropId;
- const ea = a.StateOrProvince;
- let ka = a.PdpPath;
- const Uh = a.luxury;
- const E = a.isCompanyListing;
- a = a.originatingSystemName === '' || undefined === a.originatingSystemName || a.originatingSystemName === null ? ' ' : `Listing Provided by ${a.originatingSystemName}`;
- c = c || ka.split('/');
- ka = `${path}/${c[4]}/${c[5]}`;
- return {
- propertyImage: d,
- propertyPrice: h,
- municipality: q,
- addMlsFlag: m,
- listAor: p,
- mlsLogo: k,
- mlsStatus: n,
- ClosedDate: B,
- ListingId: t,
- CourtesyOf: y,
- sellingOfficeName: A,
- ApplicationType: H,
- propertyAltCurrencyPrice: J,
- brImageUrl: I,
- propertyAddress: P,
- propertyLinkUrl: ka,
- propertyProviders: a,
- propertyData: {
- city: u,
- zipCode: v,
- id: gb,
- stateOrProvince: ea,
- luxury: Uh,
- isCompanyListing: E,
- },
- };
-}
-function Yt(a) {
- a = a.listingPins;
- return (undefined === a ? [] : a).reduce((a, f) => {
- const c = `${f.lat}${f.lon}`;
- // eslint-disable-next-line no-unused-expressions
- Array.isArray(a[c]) || (a[c] = []);
- a[c].push(f);
- return a;
- }, {});
-}
-
-const U = {
- isXs() {
- return window.innerWidth <= 575;
- },
- isSm() {
- const a = window.innerWidth;
- return a >= 576 && a <= 599;
- },
- isMd() {
- const a = window.innerWidth;
- return a >= 600 && a <= 991;
- },
- isLg() {
- const a = window.innerWidth;
- return a >= 992 && a <= 1199;
- },
- isXl() {
- return window.innerWidth >= 1200;
- },
-};
-
-function on(a) {
- const c = [];
- a.getListingPins().forEach((a) => {
- c.push(yj(a.listingKey, a.officeCode));
- });
- return c;
-}
-function Xt(a) {
- on(a);
-}
-
-function $t() {
- return rn;
-}
-
-function vj(a, c) {
- let f = '/icons/maps/map-marker-standard.png';
- let d = 50;
- let h = 25;
- if (c === 'cluster') {
- f = '/icons/maps/map-clusterer-blue.png';
- h = 30;
- d = 30;
- }
- return (q) => {
- let m = q.labelText;
- let p = undefined === m ? '' : m;
- m = q.groupCount;
- const k = q.listingKey;
- q = new google.maps.LatLng(q.latitude, q.longitude);
- let n = 0;
- // eslint-disable-next-line no-unused-expressions
- c === 'cluster' && (n = (Math.round(2 * Math.log10(m)) / 2) * 6);
- n = {
- url: f,
- scaledSize: new google.maps.Size(d + n, h + n),
- };
- p = new google.maps.Marker({
- position: q,
- icon: n,
- map: a,
- anchor: new google.maps.Point(0, 0),
- label: {
- fontFamily: 'var(--font-family-primary)',
- fontWeight: 'var(--font-weight-semibold:)',
- fontSize: '12px',
- text: p || '',
- color: 'var(--white)',
- labelAnchor: new google.maps.Point(30, 0),
- },
- labelClass: 'no-class',
- width: 50,
- height: 50,
- });
- p.groupCount = m;
- p.listingKey = k;
- return p;
- };
-}
-
-function Jd() {
- uf.forEach((a) => {
- const c = a.anchor;
- // eslint-disable-next-line no-unused-expressions
- c && c.setOptions({
- zIndex: c.bhhsInitialZIndex,
- });
- a.close();
- });
- uf = [];
- Zd = !0;
- document.querySelector('.mobile-info-window').classList.remove('is-active');
- document.querySelector('.mobile-cluster-info-window').innerHTML = null;
-}
-
-function au(a) {
- let c = a.groupOfListingPins;
- const f = undefined === c ? [] : c;
- const d = a.propertiesMap;
- const h = a.isAgentPage;
- const q = a.agentHomePath;
- a = vj(d, 'cluster');
- c = [];
- const m = f[0];
- const p = f.length;
- const k = m.lat;
- const n = m.lon;
- const B = a({
- latitude: k,
- longitude: n,
- labelText: `${p}`,
- visible: !0,
- groupCount: p,
- });
- c.push(B);
- B.setZIndex(0);
- B.bhhsInitialZIndex = B.getZIndex();
- const t = {
- getListingPins() {
- return [].concat(f);
- },
- getCenter() {
- return new google.maps.LatLng(k, n);
- },
- };
- // eslint-disable-next-line no-unused-expressions
- U.isXs() ? B.addListener('click', () => {
- Jd();
- Xt(t, h, q);
- }) : (B.addListener('mouseover', () => {
- // eslint-disable-next-line no-unused-expressions
- $t && Zd && (Jd(),
- Zd = !1
- // Wt(t, d, B, h, q)
- );
- })
- );
- return c;
-}
-
-function Pt() {
- Yd.prototype = new google.maps.OverlayView();
- // eslint-disable-next-line func-names
- Yd.prototype.onRemove = function () {};
- // eslint-disable-next-line func-names
- Yd.prototype.onAdd = function () {
- const a = document.createElement('DIV');
- a.style.position = 'absolute';
- a.className = 'RevealMarker';
- const c = this.icon.scaledSize.height;
- const f = this.icon.scaledSize.width;
- let d = '50%';
- let h = '50%';
- // eslint-disable-next-line no-unused-expressions
- this.baseMarker.icon.labelOrigin && (h = `${this.baseMarker.icon.labelOrigin.x}px`,
- d = `${this.baseMarker.icon.labelOrigin.y}px`);
- a.innerHTML = `\x3cdiv class\x3d"reveal-marker"\x3e\x3cimg src\x3d"${this.icon.url.replace('map-marker', 'map-reveal-marker')}" style\x3d"width:${f}px;height:${c}px" alt\x3d"Marker image"\x3e\x3cspan class\x3d"reveal-marker__text" style\x3d"top:${d};left: ${h}"\x3e${this.baseMarker.label.text}\x3c/span\x3e\x3c/div\x3e`;
- a.style.visibility = 'hidden';
- this.getPanes().floatPane.appendChild(a);
- this.div = a;
- };
- // eslint-disable-next-line func-names
- Yd.prototype.draw = function () {
- this.getProjection();
- const a = this.icon.scaledSize.width;
- this.div.style.top = `${-1 * this.icon.scaledSize.height}px`;
- this.div.style.left = `${-1 * Math.round(a / 2)}px`;
- };
- // eslint-disable-next-line func-names
- Yd.prototype.hide = function () {
- // eslint-disable-next-line no-unused-expressions
- this.div && (this.div.style.visibility = 'hidden');
- };
- // eslint-disable-next-line func-names
- Yd.prototype.show = function () {
- // eslint-disable-next-line no-unused-expressions
- this.div && (this.div.style.visibility = 'visible');
- };
- // eslint-disable-next-line func-names
- Yd.prototype.getPosition = function () {
- return this.baseMarker.getPosition();
- };
-}
-
-function bu() {
- for (let a = 0; a < rd.length; a += 1) {
- rd[a].setMap(null);
- rd[a] = null;
- }
- rd = [];
- lg.forEach((a) => {
- a.clearMarkers();
- });
- lg = [];
-}
-
-function sn() {
- if (Bj.length > 0) for (let a = 0; a < Bj.length; a += 1) Bj[a].close();
- if (Cj.length > 0) for (let a = 0; a < Cj.length; a += 1) Cj[a].close();
- document.querySelector('.mobile-cluster-info-window').style.display = 'none';
- document.querySelector('.mobile-info-window').classList.remove('is-active');
-}
-
-function cu(a) {
- const c = new google.maps.LatLngBounds();
- a.forEach((a) => {
- c.extend(a.getPosition());
- });
- // eslint-disable-next-line no-unused-expressions
- a.length > 0 && (map.setCenter(c.getCenter()),
- map.fitBounds(c));
-}
-
-function Zt(a) {
- a.addListener('click', () => {
- Jd();
- });
-}
-
-function Qt() {
- const a = arguments.length > 0 && undefined !== arguments[0] ? arguments[0] : [];
- const c = vj(arguments[1], 'cluster');
- const f = [];
- a.forEach((a, h) => {
- const d = a.count;
- h = a.neLat;
- const m = a.neLon;
- const p = a.swLat;
- const k = a.swLon;
- a = c({
- latitude: a.centerLat,
- longitude: a.centerLon,
- labelText: `${d}`,
- visible: !0,
- groupCount: d,
- });
- a.neLat = h;
- a.neLon = m;
- a.swLat = p;
- a.swLon = k;
- f.push(a);
- });
- return f;
-}
-
-function Rt(a) {
- a.filter((a) => a.visible).forEach((a) => {
- a.addListener('click', () => {
- const event = new CustomEvent(
- 'search-by-cluster-click',
- {
- detail: {
- northEastLatitude: a.neLat,
- northEastLongitude: a.neLon,
- southWestLatitude: a.swLat,
- southWestLongitude: a.swLon,
- },
- },
- );
- window.dispatchEvent(event);
- const c = new google.maps.LatLngBounds();
- c.extend(new google.maps.LatLng(a.neLat, a.neLon));
- c.extend(new google.maps.LatLng(a.swLat, a.swLon));
- a.map.panTo(c.getCenter());
- });
- });
-}
-
-function commaSeparatedPriceToFloat(a) {
- a = (`${a}`).replace('$', '');
- a = a.replace(/,/g, '');
- a = parseFloat(a);
- return Number.isNaN(a) ? 0 : a;
-}
-
-function nFormatter(a, c) {
- const f = [{
- value: 1,
- symbol: '',
- }, {
- value: 1E3,
- symbol: 'k',
- }, {
- value: 1E6,
- symbol: 'M',
- }, {
- value: 1E9,
- symbol: 'G',
- }, {
- value: 1E12,
- symbol: 'T',
- }, {
- value: 1E15,
- symbol: 'P',
- }, {
- value: 1E18,
- symbol: 'E',
- }]; let
- d;
- for (d = f.length - 1; d > 0 && !(a >= f[d].value); d -= 1) ;
- return (a / f[d].value).toFixed(c).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + f[d].symbol;
-}
-
-function Tt(a) {
- a = commaSeparatedPriceToFloat(a);
- return `$${nFormatter(a, 1)}`;
-}
-
-function Aj(a) {
- let c = a.propertyData;
- let f = undefined === c ? {
- city: '',
- zipCode: '',
- id: '',
- stateOrProvince: '',
- luxury: !1,
- isCompanyListing: !1,
- } : c;
- c = a.propertyImage;
- const d = a.propertyPrice;
- const h = a.municipality;
- const q = a.addMlsFlag;
- const m = a.listAor;
- const p = a.mlsLogo;
- const k = a.mlsStatus;
- const n = a.ClosedDate;
- const B = a.ListingId;
- const t = a.CourtesyOf;
- const y = a.sellingOfficeName;
- const A = a.ApplicationType;
- const H = a.propertyAltCurrencyPrice;
- const J = a.brImageUrl;
- const I = a.propertyAddress;
- const P = a.propertyPdpPath;
- f = {
- city: f.city,
- state: f.stateOrProvince,
- postalCode: f.zipCode,
- listingKey: f.id,
- luxury: f.luxury,
- isCompanyListing: f.isCompanyListing,
- };
- f = undefined === f ? {
- city: '',
- state: '',
- postalCode: '',
- listingKey: '',
- luxury: !1,
- isCompanyListing: !1,
- } : f;
- a = a.propertyProviders;
- // @todo prepare content for info window
- const cont = new Template().render({
- image: c,
- price: d,
- municipality: h,
- addMlsFlag: q,
- listAor: m,
- mlsLogo: p,
- mlsStatus: k,
- ClosedDate: n,
- ListingId: B,
- CourtesyOf: t,
- sellingOfficeName: y,
- ApplicationType: A,
- altCurrencyPrice: H,
- brImageUrl: J,
- address: I,
- city: f.city,
- stateOrProvince: f.state,
- postalCode: f.postalCode,
- propertyId: f.listingKey,
- linkUrl: P,
- luxury: f.luxury,
- luxuryLabel: 'luxury collection',
- isCompanyListing: f.isCompanyListing,
- providers: a,
- });
- return new google.maps.InfoWindow({
- content: cont,
- });
-}
-
-function mn(a) {
- google.maps.event.addListener(a, 'domready', () => {
- uf.push(a);
- });
-}
-
-function jn(a) {
- return {
- leadParam: a.propertyLeadParam,
- StreetName: a.propertyAddress,
- PropId: a.propertyId,
- ApplicationType: a.propertyApplicationType,
- NotificationId: a.NotificationId,
- originatingSystemName: a.originatingSystemName,
- };
-}
-
-function St() {
- const a = arguments.length > 0 && undefined !== arguments[0] ? arguments[0] : [];
- const c = arguments[1];
- const f = arguments[2];
- const d = arguments[3];
- const h = vj(c);
- const q = [];
- a.forEach((a, p) => {
- const m = a.lat;
- const n = a.lon;
- const B = Tt(a.price);
- const t = a.listingKey;
- const y = a.officeCode;
- a = h({
- latitude: m,
- longitude: n,
- labelText: `${B}`,
- visible: !0,
- groupCount: 1,
- listingKey: t,
- });
- // eslint-disable-next-line no-unused-expressions
- U.isXs() ? (a.addListener('click', () => {
- Jd();
- }),
- a.bhhsInitialZIndex = a.getZIndex(),
- // eslint-disable-next-line func-names
- a.addListener('click', function () {
- this.setOptions({
- zIndex: 1000002,
- });
-
- // eslint-disable-next-line func-names
- })) : (a.addListener('mouseover', function () {
- if (Zd) {
- Jd();
- Zd = !1;
- const a = this;
- if (this.propertyPdpPath) {
- let h = a.propertyImage;
- const m = a.propertyPrice;
- const q = a.municipality;
- const p = a.addMlsFlag;
- const n = a.listAor;
- const k = a.mlsLogo;
- const x = a.mlsStatus;
- const B = a.ClosedDate;
- const u = a.ListingId;
- const v = a.CourtesyOf;
- const E = a.sellingOfficeName;
- const G = a.ApplicationType;
- const X = a.propertyAltCurrencyPrice;
- const Jb = a.brImageUrl;
- const Ab = a.propertyAddress;
- const Ba = a.propertyProviders || a.originatingSystemName;
- const da = a.propertyId;
- const Id = a.propertyCity;
- const Ma = a.propertyZipcode;
- const Wh = a.propertyState;
- const aa = a.propertyPdpPath;
- const qd = a.propertyLuxury;
- const L = a.propertyIsCompanyListing;
- const O = jn(a);
- h = Aj({
- propertyImage: h,
- propertyPrice: m,
- municipality: q,
- addMlsFlag: p,
- listAor: n,
- mlsLogo: k,
- mlsStatus: x,
- ClosedDate: B,
- ListingId: u,
- CourtesyOf: v,
- sellingOfficeName: E,
- ApplicationType: G,
- propertyAltCurrencyPrice: X,
- brImageUrl: Jb,
- propertyAddress: Ab,
- propertyPdpPath: aa,
- propertyProviders: Ba,
- propertyData: {
- city: Id,
- zipCode: Ma,
- id: da,
- stateOrProvince: Wh,
- luxury: qd,
- isCompanyListing: L,
- },
- });
- h.open(c, a);
- ln(h, O);
- Zd = !0;
- } else {
- const F = Aj({
- propertyImage: '',
- propertyPrice: 'loading...',
- propertyAltCurrencyPrice: '',
- brImageUrl: '',
- propertyAddress: '-',
- municipality: '',
- addMlsFlag: '',
- listAor: '',
- mlsLogo: '',
- mlsStatus: '',
- ClosedDate: '',
- ListingId: '',
- CourtesyOf: '',
- sellingOfficeName: '',
- ApplicationType: '',
- propertyPdpPath: '',
- propertyProviders: '-',
- propertyData: {},
- });
- mn(F);
- F.open(c, a);
- // @todo fix property info window with link to property detal page
-
- yj(t, y).then((h) => {
- F.close();
- Zd = !0;
- let m = zj(h, f, d);
- m = Aj({
- propertyImage: m.propertyImage,
- propertyPrice: m.propertyPrice,
- municipality: m.municipality,
- addMlsFlag: m.addMlsFlag,
- listAor: m.listAor,
- mlsLogo: m.mlsLogo,
- mlsStatus: m.mlsStatus,
- ClosedDate: m.ClosedDate,
- ListingId: m.ListingId,
- CourtesyOf: m.CourtesyOf,
- sellingOfficeName: m.sellingOfficeName,
- ApplicationType: m.ApplicationType,
- propertyAltCurrencyPrice: m.propertyAltCurrencyPrice,
- brImageUrl: m.brImageUrl,
- propertyAddress: m.propertyAddress,
- propertyProviders: m.propertyProviders,
- propertyPdpPath: m.propertyLinkUrl,
- propertyData: {
- city: m.propertyData.city,
- zipCode: m.propertyData.zipCode,
- id: m.propertyData.id,
- stateOrProvince: m.propertyData.stateOrProvince,
- luxury: m.propertyData.luxury,
- isCompanyListing: m.propertyData.isCompanyListing,
- },
- });
- m.open(c, a);
- ln(m, h);
- });
- }
- }
- }),
- // eslint-disable-next-line func-names
- a.addListener('mouseover', function () {
- this.setOptions({
- zIndex: 1000002,
- });
- }));
- a.setZIndex(200 + p);
- a.bhhsInitialZIndex = a.getZIndex();
- q.push(a);
- });
- return q;
-}
-
-function aA(d, h, k, m, p) {
- h = h || [];
- k = k || [];
- let q = [];
- Zt(d);
- if (h.length > 0) {
- k = Qt(h, d);
- Rt(k);
- q = q.concat(k);
- } else if (k.length > 0) {
- const n = Yt({
- listingPins: k,
- });
- const B = [];
- Object.keys(n).forEach((h) => {
- h = n[h];
- // eslint-disable-next-line no-unused-expressions
- h.length > 1 ? (h = au({
- groupOfListingPins: h,
- propertiesMap: d,
- isAgentPage: m,
- agentHomePath: p,
- }),
- q = q.concat(h)) : B.push(h[0]);
- });
- k = St(B, d, m, p);
- q = q.concat(k);
- }
- return {
- markers: q,
- markerClusters: [],
- };
-}
-
-// @todo move to search class
-function createMapSearchGeoJsonParameter(coordinates) {
- return {
- type: 'FeatureCollection',
- features: [{
- type: 'Feature',
- properties: {},
- geometry: {
- type: 'Polygon',
- coordinates: [coordinates],
- },
- }],
- };
-}
-
-async function initMap() {
- Pt();
- const mapDiv = document.querySelector('.property-result-map');
- const { Map } = await google.maps.importLibrary('maps');
- map = new Map(mapDiv, {
- zoom: 10,
- maxZoom: 18,
- center: new google.maps.LatLng(41.24216, -96.20799),
- mapTypeId: 'roadmap',
- clickableIcons: false,
- gestureHandling: 'greedy',
- styles: Nr,
- visualRefresh: true,
- disableDefaultUI: true,
- });
- window.mapInitialized = !0;
- window.renderPropertyMap = (d) => {
- const h = arguments.length > 1 && undefined !== arguments[1] ? arguments[1] : !0;
- // eslint-disable-next-line no-new
- new google.maps.InfoWindow({
- pixelOffset: new google.maps.Size(0, 0),
- });
- Jd();
- sn();
- // eslint-disable-next-line no-unused-expressions
- d && (Promise.resolve(1).then(() => {
- // update map center
- // eslint-disable-next-line no-unused-expressions,no-mixed-operators
- !h && window.boundsInitialized || cu(rd);
- }));
- document.querySelector('.map-style-hybrid').addEventListener('click', () => {
- document.querySelector('.map-style-hybrid').classList.toggle('activated');
- if (document.querySelector('.map-style-hybrid').classList.contains('activated')) {
- map.setMapTypeId(google.maps.MapTypeId.HYBRID);
- map.setOptions({
- styles: [],
- });
- } else {
- map.setOptions({ styles: Nr });
- map.setMapTypeId(google.maps.MapTypeId.ROADMAP);
- }
- });
- document.querySelector('.map-zoom-in').addEventListener('click', () => {
- hi = !0;
- map.setZoom(map.getZoom() + 1);
- });
- document.querySelector('.map-zoom-out').addEventListener('click', () => {
- hi = !0;
- map.setZoom(map.getZoom() - 1);
- });
- };
-
- window.updatePropertyMap = (d, h, k, m) => {
- // eslint-disable-next-line no-unused-expressions
- window.mapInitialized ? (clearTimeout(window.queuedRenderPropertyMap),
- bu(),
- window.renderPropertyMap(d, h),
- h = aA(map, d && d.listingClusters && d.listingClusters.length > 0
- ? d.listingClusters : [], d && d.listingPins && d.listingPins.length > 0
- ? d.listingPins
- : [], k, m),
- k = h.markerClusters,
- bA(h.markers, rd, Hh, map),
- lg = [].concat(k)) : window.queuedRenderPropertyMap = setTimeout(() => {
- window.updatePropertyMap(d);
- }, 200);
- };
- window.addEventListener('search-by-cluster-click', (e) => {
- // set Search params
- setFilterValue('NorthEastLatitude', e.detail.northEastLatitude);
- setFilterValue('NorthEastLongitude', e.detail.northEastLongitude);
- setFilterValue('SouthWestLatitude', e.detail.southWestLatitude);
- setFilterValue('SouthWestLongitude', e.detail.southWestLongitude);
- setFilterValue('SearchType', 'Map');
- let m = [];
- const p = [e.detail.southWestLongitude, e.detail.northEastLatitude];
- const q = [e.detail.northEastLongitude, e.detail.northEastLatitude];
- const x = [e.detail.northEastLongitude, e.detail.southWestLatitude];
- const T = [e.detail.southWestLongitude, e.detail.southWestLatitude];
- m.push(p);
- m.push(T);
- m.push(x);
- m.push(q);
- m.push(p);
- m = createMapSearchGeoJsonParameter(m);
- m = JSON.stringify(m);
- removeFilterValue('MapSearchType');
- setFilterValue('SearchParameter', m);
- searchProperty();
- });
-
- // add custom events handling
-}
-
-function loadJS(src) {
- const script = document.createElement('script');
- script.type = 'text/javascript';
- script.async = true;
- script.defer = true;
- script.innerHTML = `
- (()=>{
- let script = document.createElement('script');
- script.src = '${src}';
- document.head.append(script);
- })();
- `;
- document.head.append(script);
-}
-
-async function initGoogleMapsAPI() {
- const placeholders = await fetchPlaceholders();
- const CALLBACK_FN = 'initMap';
- window[CALLBACK_FN] = initMap;
- const { mapsApiKey } = placeholders;
-
- const clusterSrc = 'https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js';
- loadJS(clusterSrc);
- const src = `https://maps.googleapis.com/maps/api/js?key=${mapsApiKey}&libraries=places&callback=${CALLBACK_FN}`;
- loadJS(src);
-}
-
-initGoogleMapsAPI();
diff --git a/blocks/property-result-map/map.css b/blocks/property-result-map/map.css
deleted file mode 100644
index 215c9c17..00000000
--- a/blocks/property-result-map/map.css
+++ /dev/null
@@ -1,449 +0,0 @@
-.property-search-template.search-map-active .property-result-listing.block .d-none {
- display: none;
-}
-
-.property-search-template.search-map-active .info-window div {
- padding: 3px;
-}
-
-.property-search-template.search-map-active .property-result-listing.block > div:last-of-type {
- position: fixed;
- width: 50%;
- left: 50%;
- top: calc(var(--nav-height) + 50px + 120px);
-
- /* todo need to figureout on how to do this dynamicly */
- height: 600px;
- max-width: calc(1400px/2 - 30px);
-}
-
-.property-search-template.search-map-active .info-window {
- width: 178px;
- pointer-events: all;
-}
-
-.property-search-template.search-map-active .info-window a {
- color: inherit;
- text-decoration: none;
-}
-
-.property-search-template.search-map-active .info-window .info .price {
- font-size: var(--body-font-size-m);
- font-family: var(--font-family-primary);
- font-weight: var(--font-weight-bold);
-}
-
-.property-search-template.search-map-active svg:not(:root) {
- overflow: hidden;
-}
-
-.property-search-template.search-map-active .info-window .btn-contact-property svg,
-.property-search-template.search-map-active .info-window .btn-save-property svg {
- width: 100%;
- height: 100%;
-}
-
-.property-search-template.search-map-active .info-window .btn-contact-property .envelope,
-.property-search-template.search-map-active .btn-save-property .empty{
- position: absolute;
- opacity: 1;
-}
-
-.property-search-template.search-map-active .info-window .btn-contact-property .envelope-dark,
-.property-search-template.search-map-active .btn-save-property .empty-dark {
- position: absolute;
- opacity: 0;
-}
-
-.property-search-template.search-map-active .info-window .btn-save-property .full {
- position: absolute;
- display: none;
-}
-
-/* stylelint-disable selector-class-pattern */
-.property-search-template.search-map-active .info-window .info .altPrice {
- font-size: var(--body-font-size-s);
- font-family: var(--font-family-primary);
- font-weight: var(--font-weight-light);
-}
-
-.property-search-template.search-map-active .info-window .info .address,
-.property-search-template.search-map-active .info-window .info .providers {
- font-size: var(--body-font-size-xs);
-}
-
-.property-search-template.search-map-active .cmp-property-tile__extra-info {
- display: block;
- font-size: var(--body-font-size-xs);
- font-family: var(--font-family-primary);
- font-weight: var(--font-weight-light);
- padding-left: 10px;
- opacity: 1;
- color: var(--dark-grey);
- letter-spacing: 0;
- line-height: var(--line-height-xs);
-}
-
-.property-search-template.search-map-active .info-window .arrow {
- width: 0;
- border-top: 20px solid transparent;
- border-left: 20px solid transparent;
- border-right: 20px solid transparent;
- cursor: pointer;
- content: "";
- position: absolute;
- bottom: 0;
- left: 50%;
- transform: translateX(-50%);
- transition: .2s bottom opacity ease-out;
- opacity: 1;
-}
-
-.property-search-template.search-map-active .info-window .text-danger {
- color: #dc3545 !important;
-}
-
-.property-search-template.search-map-active .gm-ui-hover-effect {
- display: none !important;
-}
-
-.property-search-template.search-map-active .gm-ui-hover-effect, button[draggable] {
- opacity: 0;
-}
-
-.property-search-template hr {
- margin-top: 1rem;
- margin-bottom: 1rem;
- border: 0;
- border-top: 1px solid rgb(0 0 0 / 10%);
- box-sizing: content-box;
- height: 0;
- overflow: visible;
-}
-
-.property-search-template.search-map-active .info-window .property-image {
- width: 100%;
- height: 131px;
- background-size: cover;
- background-repeat: no-repeat;
- position: relative;
-}
-
-.property-search-template.search-map-active .btn-contact-property,
-.property-search-template.search-map-active .btn-save-property {
- display: block;
- position: relative;
- width: 18px;
- height: 18px;
- cursor: pointer;
- margin-left: auto;
-}
-
-.property-search-template .d-flex {
- display: flex !important;
-
-}
-
-.property-search-template .align-items-center {
- align-items: center !important;
-
-}
-
-.property-search-template .p-0 {
- padding: 0 !important;
-}
-
-.property-search-template .mr-2 {
- margin-right: 0.5rem !important;
-}
-
-.property-search-template.search-map-active .info-window .info {
- background: var(--white);
-}
-
-.property-search-template.search-map-active .info-window::before,
-.property-search-template.search-map-active .info-window::after {
- content: "";
- bottom: 0;
- opacity: 0;
- transition: .2s bottom opacity ease-in;
-}
-
-
-.property-search-template .property-result-map {
- height: 600px;
- width: 100%;
- display: none;
- position: absolute;
-}
-
-.property-search-template.search-map-active .property-result-map {
- display: block;
-
-}
-
-.property-search-template .map-controls-container {
- display: none;
-}
-
-
-.property-search-template.search-map-active .map-controls-container {
- height: 100%;
- width: 100% !important;
- padding-left: 0;
- padding-right: 0;
- max-width: 1350px;
- margin: 0 auto;
- display: block;
-}
-
-.property-search-template.search-map-active .custom-controls a {
- pointer-events: all;
- height: 45px;
- width: 45px;
- background: var(--white);
- display: block;
- position: relative;
- cursor: pointer;
-}
-
-.property-search-template.search-map-active .custom-controls a.map-style-hybrid {
- position: relative;
- box-shadow: 0 0 3px 2px rgb(0 0 0 / 30%);
-}
-
-.property-search-template.search-map-active .custom-controls a.map-style-hybrid::before {
- background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAACOhJREFUWAmlmXlsVFUUhzsz3RfLUgjQ0hYFVBqQRaAUgQCiskgQCIJ/iEpiZDUoi4CBKBFcQmKiUEwUjTFsScENxCgVUEsBC5VV9imUWgUrldKFTjt+v8e8YTrzXlvgJqfn3LPf7dz7po6wu2g9evSIi4qKSo+IiEivr6/vgKt4n7sKp9NZUltb666pqXEfPnz4+p2GcdyuYc+ePVvExsY+gd1Er9fbG5zscDgirfwgvwH/ElCATk5lZeWOwsLCq1a6djyXnSCYn5WV1TY5OXleZGTkp8gmA6VAEoFbM3sboZ8FUoDTwGT4LYCHoK+CNYMvM9PTUlJS4tq0aXO8tLS0El6TzdmkBgok9zyokECzwetI6EFmZxP9duBF+fn5U/bu3XuIvgd5rei8vLzJyBbDaw9vo8fj6SZbYE5MTMzvmZmZz0E32RpNsF+/fq1JbiOBPgZyqqurexD4dYIm4DkbXjbJvB0QBZHDv22QrURnrSA8PDxOtvJBfwtq60hyw4ABA1oF2IeQtgn27dv3XpfLlYvFIGA0wWYfOnSoBDoc52uAE9BLgKbaYhROAmuAcPnA1yzoURykIfjJVSz6ls0yQRkw4m0Ya+9k4XCHac2Ix0A/wjLPg19l8u2wdNB9FfkgZmyUqSef8LPoVymWXZIhCWrK2cxaggdYiq+Ba6ZTH54Pbzf7bmcQ37a7b9++HxH+jM8FgUr0/1MMxSJmjtVyhwcaQGv/rAXaYrge/Jqcsg9zGe1W+ko2E5lm0QsEN0ReSz6K7wBfM4tjwQn4HQ8MQ90BKNYwIBtQhfD78G9omGEYT2VffEYyI5ihH/v06ZNKWXkSRxNw0geVGOgI8Hb6vwGnoC+By7BT2chGTtc7Ax+x0DoAqpNdwX3BI5HVQldCH4TOuXHjxjcFBQUX+vfv/xh7/nt4U1n+z9Exmj9BkkniVjiOwmYUtIkbNKZfgfbALANK0OtGPwk6BlpbxUM/EhxGXwVah6keXEX/MrQOVTLQgv4QYqiAN2is1BpkE0g6g6SvSOhfYpJTUhGMfHkDK18HIxezmYJ8AXsqR7aUoUQCt8JpIjMYD71C6vQXo1dBvxy6bP/+/eWwPSQwEfwFNVEDCmlcjW9yYJ5hP85E+IYUjASZvUToGcBqgv8lQXAjOS2x1u+gT+Yh8D/QAqMxy7L1sj123eQ0/EsCBwnuIgn5uthQGhZ24MCBUgaxGv4McnqfWSw3RoKR7taWOFClt2zMxsMIiq9fvx6yNKYBOk6B2Q/G3MXFDFCgBC0bj4t1+FAleVwKhjMYkzD6lRGcs7S6ycwAnTx27Jj21x012RLrFCBflo1ZO0sueWyZSVJw6skEVsH8VgybpsOUhuPTNvJmswkuH+mA/4AGG6OzDRio3Jzx8fGdCJzEps4PVjT76enpURgkARdM3l3gIsXr3LmzceKt/DB7+dLRW9NJYp0IrNrktlIWLyEhQfUvHvJvO53m8oklHwlMTIydDWfhPDIPdfFenWLVpnJOb6mdAc60HC4GY9QmOz2C1yHz3wJWesyOfLjq6upsl5g4pfgqZ1I6OLg95kK8hdFOsAprcFNAjVbXUgH4T/RCTioy2WYCaloiO532yHSKcwE9NkISxZeqwaMkuthfqFFUIiGjR9mLMiI83bzGRIboianWHJ2bmjgJ8G3yfFgiI4YS1CjKuXrGg/UiDmndu3dvyT4sYlmWsxW2hyj4GBTqLXKMrwl2OhTi0cg2UBOftvs+oUhHcDFcZLCVWgYV3kQu63Z2Tn38OvaP7l7bhkOXwFYBAcsmH5YTYdoRpx1+EhlriZNZOUcngk4nUyEYX7t2TRd+Bfy2wbLb7RNLPipoWjnLxi2iXMKVm1PfrQS/wpHub6kN0+121+D4CpBqp3Mb/DTFO3PmTI2dDbOsw3YZfN7p+6jOgzHGzgC+NmwRjrs2otMsEYPsgqK7MWWWWLnkce0Ze1CnaTOGA+2+C3zOjoK7ZmRk2N4AjQWVTLbEuh84ZqfLAbkP2QBmb7N0jFpF5d6BURlr/4KdIXLVwJS4uDgVdsuGTr3AUggzOjq6IxOhh698WTZO7zTlQk7fS8Gog3p3UbDX0J/Laf7A6k3Ig7UAY9VE/dxxXraMtgV7txXvu0Q2tB6sqgRefAyFX8HDtBx+Gf71c4cHe9nWwbdMkBWU/UxglXIC33pRk/FqLueZBFnqU5Lc30iiHlkxgRdR76ZCd2Ok+ukjGsy2cap0GMsPrdcIJuH16FejfxnZCXQ7gIvF9zsOIOAvo1vNoPRoNVqDa8b8aELhMWbxB4pqGnthLI5VxDV646OJ4ErgAHw9nVRHy5jhSmZoLTwvlWE6dCz8VkAyul1Iuh/0KGg9TFRi9DLPQf8bfnEoavKjCWU1B6PdCB6Mo10YjwLqoPUNrFtCP6ttZQBjrW4UBvQlOrpJnpKzwOa7Qb5CPA7+PfjVoHW/q7BvA4bC202yU6CNaw58a4nVoUkwHcVcQD/+LKJIf3TkyJF/DSl/GEA+SS6E/A7wO/LJMWuwKD628SCQDbnv/dbHXI8vzfCL2KwEFxJrOriBz5AXBw50gsaT3B8oj+PU6oPK31jy93A4mO0w3M9sgmD5RqAyCNt3A1VZCfkeR6wTHJzxgRNh6oUkKIG+TTAYTSLRzFYeyYw0DVhaLYd+xljFDNg+Ok196eBjFUns4Wtvu8mXTw5QHn6iSHQ0X4jnTVkgtkxQCkoSp8MY9W6cbCPQh4BqoE7rDOABYAXQVFuJfRcGLJs6+SC51fJJ/ydiDLdLTo4bfXkUFxdXATn8KlrELMxCf3ZqamoiwfYx+qMEWYasHp1f5Kxjx47at176m9QnkSXYvcIgp8M+m5aWNh/2J/A605/DjC5VDOnaNcsdbaXcq1evNtwEOkAvIdfm3kOQLvTToTdpfxF4OTy1pfAXAvqcdYNVjgYDZfTX8iNmNr8TqjY22ZqdoOnJ/BGdQBMI3BucAra8n5HpO1gf6gXgLXfyI/ptJ2gmKmz+G4KZ0zdzByBefBKqAEog3VVVVUV382+I/wHjBJq6X/x9ywAAAABJRU5ErkJggg==");
- content: " ";
- height: 22px;
- width: 22px;
- top: 5px;
- left: 50%;
- transform: translateX(-50%);
- position: absolute;
- background-repeat: no-repeat;
- background-size: contain;
-}
-
-.property-search-template.search-map-active .custom-controls a.map-style-hybrid::after {
- content: attr(data-text);
- position: absolute;
- text-align: center;
- width: 100%;
- bottom: 8px;
- line-height: 0;
- font-size: var(--body-font-size-xs);
- color: var(--body-color);
-}
-
-.property-search-template.search-map-active a.map-draw-complete {
- display: flex;
- margin-top: 15px;
- box-shadow: 0 0 3px 2px rgb(0 0 0 / 30%);
-}
-
-.property-search-template.search-map-active .custom-controls a.map-draw {
- margin-top: 15px;
- box-shadow: 0 0 3px 2px rgb(0 0 0 / 30%);
-
- /* @todo change visibility for draw functionality https://github.com/hlxsites/hsf-commonmoves/issues/122 */
- visibility: hidden;
-}
-
-.property-search-template.search-map-active .custom-controls a.map-draw::before {
- background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIxN3B4IiBoZWlnaHQ9IjE3cHgiIHZpZXdCb3g9IjAgMCAxNyAxNyIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4gICAgICAgIDx0aXRsZT5Db21iaW5lZCBTaGFwZTwvdGl0bGU+ICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPiAgICA8ZyBpZD0iRG9jdW1lbnQtU3ltYm9scyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+ICAgICAgICA8ZyBpZD0iVUkvUGVuY2lsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTMuMDAwMDAwLCAtOC4wMDAwMDApIiBmaWxsPSIjM0EzQTNBIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNzY0MTY1LDggTDE3LjYwNzQ2MTksOS44NDI4MDc5IEwxNC44NDIyMzQzLDEyLjYwNzk5OTMgTDEzLDEwLjc2NDE2MiBMMTUuNzY0MTY1LDggWiBNMzAsMjUgTDI2LjkwODAwNDIsMjQuNjcyMTczNSBMMjkuNjcyNjY3MywyMS45MDc5NzgzIEwzMCwyNSBaIE0yOC43NTA3MywyMC45ODU1OTE1IEwyNS45ODYwNjY5LDIzLjc1MDc0OTYgTDE1Ljc2NDE3NSwxMy41MjkzODk5IEwxOC41MjkzMDI5LDEwLjc2NDE2NTQgTDI4Ljc1MDczLDIwLjk4NTU5MTUgWiIgaWQ9IkNvbWJpbmVkLVNoYXBlIj48L3BhdGg+ICAgICAgICA8L2c+ICAgIDwvZz48L3N2Zz4=");
- content: " ";
- height: 17px;
- width: 17px;
- top: 8px;
- left: 13px;
- position: absolute;
- background-repeat: no-repeat;
- background-size: contain;
-}
-
-.property-search-template.search-map-active .custom-controls a.map-draw::after {
- content: attr(data-text);
- position: absolute;
- text-align: center;
- width: 100%;
- bottom: 8px;
- line-height: 0;
- font-size: var(--body-font-size-xs);
- color: var(--body-color);
-}
-
-.property-search-template.search-map-active .custom-controls {
- position: absolute;
- left: auto;
- right: 15px;
- pointer-events: none;
- bottom: 15px;
-}
-
-.property-search-template.search-map-active .custom-controls .zoom-controls {
- box-shadow: 0 0 3px 2px rgb(0 0 0 / 30%);
- bottom: 0;
- right: 0;
- z-index: 0;
- pointer-events: all;
-}
-
-.property-search-template.search-map-active .custom-controls a.map-zoom-in::before {
- content: " ";
- position: absolute;
- display: block;
- background-color: var(--body-color);
- width: 1px;
- margin-left: 0;
- left: 50%;
- top: calc(50% - 8px);
- z-index: 9;
- height: 16px;
-}
-
-.property-search-template.search-map-active .custom-controls a.map-zoom-in::after {
- content: " ";
- position: absolute;
- display: block;
- background-color: var(--body-color);
- height: 1px;
- top: calc(50% - 1px);
- left: calc(50% - 8px);
- width: 16px;
- z-index: 9;
-}
-
-.property-search-template.search-map-active .custom-controls a.map-zoom-out::after {
- content: " ";
- position: absolute;
- display: block;
- background-color: var(--body-color);
- height: 1px;
- top: calc(50% - 1px);
- left: calc(50% - 8px);
- width: 16px;
- z-index: 9;
-}
-
-.property-search-template.search-map-active .map-draw-tooltip {
- position: absolute;
- width: 100%;
- height: 30px;
- line-height: var(--line-height-xl);
- font-size: var(--body-font-size-xs);
- background: var(--light-grey);
- text-align: center;
- color: var(--body-color);
- font-family: var(--font-family-primary);
- z-index: 1;
-}
-
-.property-search-template.search-map-active .map-search-wrapper {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- overflow: hidden;
-}
-
-.property-search-template.search-map-active .map-search-wrapper a.map-search-toggle {
- box-shadow: 0 0 3px 2px rgb(0 0 0 / 30%);
- width: 100%;
- text-align: center;
- height: 30px;
- font-size: 12px;
- line-height: 30px;
- background: #fff;
- margin-bottom: 10px;
- cursor: pointer;
- display: none;
-}
-
-/* hide google map keyboard shortcuts text and google logo */
-.property-search-template.search-map-active .gm-style-cc {
- display: none;
-}
-
-.property-search-template.search-map-active a[href^="http://maps.google.com/maps"] {
- display: none !important
-}
-
-.property-search-template.search-map-active a[href^="https://maps.google.com/maps"] {
- display: none !important
-}
-
-/* end */
-
-@media (min-width: 600px) {
- .property-search-template.search-map-active .custom-controls {
- bottom: 16vw;
- }
-
- .property-search-template.search-map-active .property-result-listing.block > div:last-of-type {
- width: 100%;
- left: 0;
- top: calc(var(--nav-height) + 50px + 120px);
- height: 600px;
-
- }
-}
-
-.property-result-listing.block .property-result-map-container .disclaimer {
- display: none;
-}
-
-@media (max-width: 899px) {
- .property-search-template.search-map-active .property-result-listing.block .property-result-map-container .disclaimer {
- display: block;
- }
-}
-
-@media (min-width: 900px) {
- .property-search-template.search-map-active .property-result-listing.block > div:last-of-type {
- position: fixed;
- width: 100%;
- left: 50%;
- top: calc(var(--nav-height) + 50px + 120px);
-
- /* todo need to figureout on how to do this dynamicly */
- height: 600px;
- }
-
- .property-search-template.search-map-active .custom-controls {
- right: 20px;
- bottom: 20px;
- position: absolute;
- left: auto;
- pointer-events: none;
- }
-
- .property-search-template.search-map-active .custom-controls .zoom-controls {
- margin-top: 15px;
- }
-
- .property-search-template.search-map-active .map-draw-tooltip {
- font-size: 16px;
- height: 65px;
- line-height: 65px;
- }
-
- .property-search-template.search-map-active .map-search-wrapper {
- left: 20px;
- right: auto;
- top: auto;
- bottom: 25vw;
- width: 165px;
- overflow: visible;
- }
-
- .property-search-template.search-map-active .map-search-wrapper a.map-search-toggle {
- margin: 0;
- }
-}
-
-@media (min-width: 1200px) {
- .property-search-template.search-map-active .property-result-listing.block > div:last-of-type {
- max-width: calc(1400px/2 - 30px);
- }
-}
diff --git a/blocks/property-result-map/map.js b/blocks/property-result-map/map.js
deleted file mode 100644
index 2334af35..00000000
--- a/blocks/property-result-map/map.js
+++ /dev/null
@@ -1,54 +0,0 @@
-let alreadyDeferred = false;
-
-function buildCustomControls() {
- const container = document.createElement('div');
- container.classList.add('map-controls-container');
- container.innerHTML = `
-
- Click points on the map to draw your search
-
-
- `;
- return container;
-}
-
-function initGoogleMapsAPI() {
- if (alreadyDeferred) {
- return;
- }
- alreadyDeferred = true;
- const script = document.createElement('script');
- script.type = 'text/partytown';
- script.id = crypto.randomUUID();
- script.innerHTML = `
- const script = document.createElement('script');
- script.type = 'module';
- script.src = '${window.hlx.codeBasePath}/blocks/property-result-map/map-delayed.js';
- document.head.append(script);
- `;
- document.head.append(script);
-}
-
-export default async function renderMap(block) {
- const container = document.createElement('div');
- const mobileClusterInfo = document.createElement('div');
- mobileClusterInfo.classList.add('mobile-cluster-info-window');
- const mobileInfo = document.createElement('div');
- mobileInfo.classList.add('mobile-info-window');
- container.classList.add('property-result-map-container');
- const map = document.createElement('div');
- map.classList.add('property-result-map');
- container.append(map, buildCustomControls(), mobileClusterInfo, mobileInfo);
- block.append(container);
- initGoogleMapsAPI();
-}
diff --git a/blocks/property-search-bar/README.md b/blocks/property-search-bar/README.md
new file mode 100644
index 00000000..9879bf9b
--- /dev/null
+++ b/blocks/property-search-bar/README.md
@@ -0,0 +1,15 @@
+### Property Search Bar
+
+#### State Changes
+
+Search bar contains all filters, details on what will be searched.
+
+* Opening advanced search, or changing viewport to < 900px, synchronizes the Property attributes from the bar to the advanced filter list.
+
+* Closing the advanced search, changing the viewport to > 900px, or applying the parameters, synchronizes the Property attributes from the advanced list, to the bar.
+
+#### Searching
+
+Clicking Search on bar, or Clicking 'Apply' in the advanced filter view, will either:
+ 1. Change the URL to the search results page, if not there
+ 1. Emit the Search Event with the parameters as a payload. Search Results block handles everything else.
diff --git a/blocks/property-search-bar/common-function.js b/blocks/property-search-bar/common-function.js
deleted file mode 100644
index 50e212e5..00000000
--- a/blocks/property-search-bar/common-function.js
+++ /dev/null
@@ -1,367 +0,0 @@
-/* eslint-disable no-param-reassign, no-plusplus, no-mixed-operators, no-unused-expressions, no-nested-ternary, eqeqeq, max-len */
-
-import ApplicationType from '../../scripts/apis/creg/ApplicationType.js';
-
-export const TOP_LEVEL_FILTERS = {
- Price: { label: 'price', type: 'range' },
- MinBedroomsTotal: { label: 'beds', type: 'select' },
- MinBathroomsTotal: { label: 'baths', type: 'select' },
- LivingArea: { label: 'square feet', type: 'range' },
-};
-export const EXTRA_FILTERS = {
- PropertyType: { label: 'property type', type: 'property' },
- Features: { label: 'keyword search', type: 'keywords-search' },
- MatchAnyFeatures: { label: 'Match', type: 'child' },
- YearBuilt: { label: 'year built', type: 'range' },
- NewListing: { label: 'new listings', type: 'toggle' },
- RecentPriceChange: { label: 'recent price change', type: 'toggle' },
- OpenHouses: { label: 'open houses', type: 'open-houses' },
- Luxury: { label: 'luxury', type: 'toggle' },
- FeaturedCompany: { label: 'berkshire hathaway homeServices listings only', type: 'toggle' },
-};
-
-export const BOTTOM_LEVEL_FILTERS = {
- ApplicationType: { label: 'Search Types', type: 'search-types' },
- Sort: { label: 'Sort By', type: 'select' },
- Page: { label: '', type: 'child' },
-};
-
-const SQUARE_FEET = [
- { value: '500', label: '500 Sq Ft' },
- { value: '750', label: '750 Sq Ft' },
- { value: '1000', label: '1,000 Sq Ft' },
- { value: '1250', label: '1,250 Sq Ft' },
- { value: '1500', label: '1,500 Sq Ft' },
- { value: '1750', label: '1,750 Sq Ft' },
- { value: '2000', label: '2,000 Sq Ft' },
- { value: '2250', label: '2,250 Sq Ft' },
- { value: '2500', label: '2,500 Sq Ft' },
- { value: '2750', label: '2,750 Sq Ft' },
- { value: '3000', label: '3,000 Sq Ft' },
- { value: '3500', label: '3,500 Sq Ft' },
- { value: '4000', label: '4,000 Sq Ft' },
- { value: '5000', label: '5,000 Sq Ft' },
- { value: '7500', label: '7,500 Sq Ft' },
-];
-
-const YEAR_BUILT = [
- { value: '1900', label: '1900' },
- { value: '1920', label: '1920' },
- { value: '1940', label: '1940' },
- { value: '1950', label: '1950' },
- { value: '1960', label: '1960' },
- { value: '1970', label: '1970' },
- { value: '1980', label: '1980' },
- { value: '1990', label: '1990' },
- { value: '1995', label: '1995' },
- { value: '2000', label: '2000' },
- { value: '2005', label: '2005' },
- { value: '2014', label: '2014' },
- { value: '2015', label: '2015' },
- { value: '2016', label: '2016' },
- { value: '2017', label: '2017' },
- { value: '2018', label: '2018' },
- { value: '2019', label: '2019' },
-];
-
-const SORT_BY = [
- { value: 'DISTANCE_ASCENDING', label: 'Distance' },
- { value: 'PRICE_DESCENDING', label: 'Price (Hi-Lo)' },
- { value: 'PRICE_ASCENDING', label: 'Price (Lo-Hi)' },
- { value: 'DATE_DESCENDING', label: 'DATE (NEW-OLD)' },
- { value: 'DATE_ASCENDING', label: 'DATE (OLD-NEW)' },
-];
-
-export function getFilterLabel(filterName) {
- let config;
- switch (filterName) {
- case 'Price':
- case 'MinBedroomsTotal':
- case 'MinBathroomsTotal':
- case 'LivingArea':
- config = TOP_LEVEL_FILTERS;
- break;
- case 'ApplicationType':
- case 'Sort':
- config = BOTTOM_LEVEL_FILTERS;
- break;
- default:
- config = EXTRA_FILTERS;
- break;
- }
- return config[filterName].label;
-}
-export function getConfig(filterName) {
- let output = '';
- switch (filterName) {
- case 'MinBedroomsTotal':
- case 'MinBathroomsTotal':
- output = 5;
- break;
- case 'LivingArea':
- output = SQUARE_FEET;
- break;
- case 'YearBuilt':
- output = YEAR_BUILT;
- break;
- case 'ApplicationType':
- output = [ApplicationType.FOR_SALE.label, ApplicationType.FOR_RENT.label, ApplicationType.PENDING.label, ApplicationType.RECENTLY_SOLD.label];
- break;
- case 'Sort':
- output = SORT_BY;
- break;
- default:
- break;
- }
- return output;
-}
-
-export function toggleOverlay() {
- const hideClass = 'hide';
- const overlay = document.querySelector('.property-search-bar.block .overlay');
- overlay.classList.toggle(hideClass);
- if (overlay.classList.contains(hideClass)) {
- document.getElementsByTagName('body')[0].classList.remove('no-scroll');
- } else {
- document.getElementsByTagName('body')[0].classList.add('no-scroll');
- }
-}
-
-export function hideOverlay() {
-
-}
-/**
- *
- * @param {string} filterName
- * @param {string} defaultValue
- * @returns {string}
- */
-function buildSelectOptions(filterName, defaultValue) {
- const conf = getConfig(filterName);
- let output = ``;
- if (Array.isArray(conf)) {
- conf.forEach((el) => {
- output += ``;
- });
- } else {
- const labelSuf = `+ ${defaultValue.split(' ')[1]}`;
- for (let i = 1; i <= conf; i += 1) {
- const label = `${i} ${labelSuf}`;
- output += ``;
- }
- }
- return output;
-}
-
-/**
- * @param {string} filterName
- * @param {string} defaultValue
- * @returns {string}
- */
-function buildListBoxOptions(filterName, defaultValue) {
- const config = getConfig(filterName);
- let output = `${defaultValue}`;
- if (Array.isArray(config)) {
- config.forEach((conf) => {
- output += `${conf.label}`;
- });
- } else {
- const labelSuf = `+ ${defaultValue.split(' ')[1]}`;
- for (let i = 1; i <= config; i += 1) {
- const label = `${i} ${labelSuf}`;
- output += `${label}`;
- }
- }
-
- return output;
-}
-
-export function addOptions(filterName, defaultValue = '', mode = '', name = '') {
- let output = `
-
-
`;
- if (mode === 'multi') {
- output += `
${defaultValue}
`;
- }
- output += `
${buildListBoxOptions(filterName, defaultValue, mode)}
-
- `;
- return output;
-}
-
-export function formatInput(string) {
- return string.replace(/[/\s]/g, '-').toLowerCase();
-}
-
-export function getPlaceholder(country) {
- return country === 'US' ? 'Enter City, Address, Zip/Postal Code, Neighborhood, School or MLS#' : 'Enter City';
-}
-
-export function addRangeOption(filterName) {
- const config = { ...TOP_LEVEL_FILTERS, ...EXTRA_FILTERS };
- const { label } = config[filterName];
- const filterLabel = label.charAt(0).toLocaleUpperCase() + label.slice(1).toLowerCase();
- const fromLabel = 'No Min';
- const toLabel = 'No Max';
- const maxLength = 14;
- let output = '';
- if (filterName === 'Price') {
- output = ``;
- }
- if (filterName === 'LivingArea' || filterName === 'YearBuilt') {
- const fromName = filterName === 'LivingArea' ? 'MinLivingArea' : '';
- const toName = filterName === 'LivingArea' ? 'MaxLivingArea' : '';
- output = `
- ${addOptions(filterName, fromLabel, 'multi', fromName)}
- to
- ${addOptions(filterName, toLabel, 'multi', toName)}
-
-
- `;
- }
- return output;
-}
-
-function abbrNum(d, h) {
- h = 10 ** h;
- const k = ['k', 'm', 'b', 't']; let
- m;
- for (m = k.length - 1; m >= 0; m--) {
- const n = 10 ** (3 * (m + 1));
- if (n <= d) {
- d = Math.round(d * h / n) / h;
- d === 1E3 && m < k.length - 1 && (d = 1, m++);
- d += k[m];
- break;
- }
- }
- return d;
-}
-
-export function formatPriceLabel(minPrice, maxPrice) {
- const d = minPrice.replace(/[^0-9]/g, '');
- const h = maxPrice.replace(/[^0-9]/g, '');
- return d !== '' && h !== ''
- ? `$${abbrNum(d, 2)} - $${abbrNum(h, 2)}`
- : d !== '' ? `$${abbrNum(d, 2)}`
- : d == '' && h !== '' ? `$0 - $${abbrNum(h, 2)}`
- : 'Price';
-}
-
-export function processSearchType(value, defaultInput = ApplicationType.FOR_SALE.type) {
- const name = value.replace(' ', '_').toUpperCase();
- const wrapper = document.createElement('div');
- wrapper.classList.add('filter-toggle', formatInput(value), 'flex-row', 'mb-1');
- wrapper.setAttribute('name', name);
- wrapper.innerHTML = `
-
-
-
- `;
- return wrapper;
-}
-
-export function buildKeywordEl(keyword, removeItemCallback) {
- const item = document.createElement('span');
- const keywordInput = document.querySelector('[name="Features"] input[type="text"]');
- const keywordContainer = document.querySelector('#container-tags');
- item.classList.add('tag');
- item.textContent = `${keyword} `;
- const closeBtn = document.createElement('span');
- closeBtn.classList.add('close');
- item.appendChild(closeBtn);
- keywordContainer.append(item);
- closeBtn.addEventListener(
- 'click',
- (e) => {
- e.preventDefault();
- e.stopPropagation();
- const itemEl = e.target.closest('.tag');
- removeItemCallback('Features', itemEl.textContent.trim());
- itemEl.remove();
- },
- );
- keywordInput.value = '';
-}
-
-export function buildFilterSearchTypesElement() {
- const config = getConfig('ApplicationType');
- const columns = [[config[0], config[1]], [config[2], config[3]]];
- let el;
- let output = '';
-
- columns.forEach((column) => {
- output += '
';
- column.forEach((value) => {
- el = processSearchType(value);
- el.querySelector('label').classList.add('text-up');
- output += el.outerHTML;
- });
- output += '
';
- });
- output += '
';
- return output;
-}
-
-export function hideFilter(element) {
- element.classList.remove('open');
- element.querySelector('.search-results-dropdown').classList.add('hide');
-}
-
-export function togglePropertyForm() {
- const hideClass = 'hide';
- document.querySelector('.filter-block').classList.toggle(hideClass);
- toggleOverlay();
- document.querySelector('.filter-buttons').classList.toggle(hideClass);
- document.querySelectorAll('.filter-container svg').forEach(
- (el) => el.classList.toggle(hideClass),
- );
-}
-
-export function closeTopLevelFilters(all = true) {
- if (all && document.querySelector('[name="AdditionalFilters"] a >svg:first-of-type').classList.contains('hide')) {
- togglePropertyForm();
- }
- document.querySelectorAll('.container-item .header').forEach((elem) => {
- if (elem.parentElement.classList.contains('open')) {
- hideFilter(elem.parentElement);
- }
- if (elem.parentElement.querySelectorAll('.select-item').length > 0) {
- elem.parentElement.querySelectorAll('.select-item').forEach((el) => {
- el.classList.remove('show');
- });
- }
- });
- if (document.querySelector('.search-bar').classList.contains('show-suggestions')) {
- document.querySelector('.search-bar').classList.remove('show-suggestions');
- }
- if (document.querySelector('[name="Sort"] .select-item').classList.contains('show')) {
- document.querySelector('[name="Sort"] .select-item').classList.remove('show');
- }
-}
-
-export function updateFilters(el) {
- const filter = el.closest('.filter');
- const forRentEl = filter.querySelector('.for-rent');
- const pendingEl = filter.querySelector('.pending');
- const isForRentChecked = filter.querySelector('.for-rent .checkbox').classList.contains('checked');
- const isPendingChecked = filter.querySelector('.pending .checkbox').classList.contains('checked');
-
- forRentEl.classList.toggle('disabled', isPendingChecked);
- pendingEl.classList.toggle('disabled', isForRentChecked);
-}
diff --git a/blocks/property-search-bar/delayed.js b/blocks/property-search-bar/delayed.js
new file mode 100644
index 00000000..256fbc4c
--- /dev/null
+++ b/blocks/property-search-bar/delayed.js
@@ -0,0 +1,918 @@
+import { BREAKPOINTS } from '../../scripts/scripts.js';
+import { closeOnBodyClick, filterItemClicked } from '../shared/search/util.js';
+import { SQUARE_FEET } from './property-search-bar.js';
+import ListingType from '../../scripts/apis/creg/search/types/ListingType.js';
+import Search, { UPDATE_SEARCH_EVENT, SEARCH_URL } from '../../scripts/apis/creg/search/Search.js';
+
+function toggleAdvancedFilters(e) {
+ const open = e.currentTarget.classList.toggle('open');
+ const wrapper = e.currentTarget.closest('.search-form-wrapper');
+ const filters = wrapper.querySelector('.advanced-filters');
+ filters.classList.toggle('open');
+ filters.scrollTo({ top: 0, behavior: 'smooth' });
+ wrapper.querySelector('.search-overlay').classList.toggle('visible');
+
+ if (open) {
+ document.body.style.overflowY = 'hidden';
+ } else {
+ document.body.style.overflowY = '';
+ }
+}
+
+const updateExpanded = (wrapper) => {
+ const wasOpen = wrapper.classList.contains('open');
+ const thisForm = wrapper.closest('form');
+ thisForm.querySelectorAll('[class*="-wrapper"][class*="open"]').forEach((item) => {
+ if (item !== wrapper && item.contains(wrapper)) {
+ return;
+ }
+ item.classList.remove('open');
+ item.querySelector('[aria-expanded="true"]')?.setAttribute('aria-expanded', 'false');
+ });
+ if (!wasOpen) {
+ wrapper.classList.add('open');
+ wrapper.querySelector('[aria-expanded="false"]')?.setAttribute('aria-expanded', 'true');
+ closeOnBodyClick(wrapper);
+ }
+};
+
+async function observeSearchInput(e) {
+ e.preventDefault();
+ const form = e.target.closest('form');
+ try {
+ const mod = await import(`${window.hlx.codeBasePath}/blocks/shared/search/suggestion.js`);
+ mod.default(form);
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.log('failed to load suggestion library', error);
+ }
+ e.target.removeEventListener('focus', observeSearchInput);
+}
+
+const createPriceList = (d) => {
+ let options = '';
+ const k = [10, 100, 1E3, 1E4, 1E5, 1E6];
+ // eslint-disable-next-line no-plusplus
+ if (d) for (let m = 1; m <= 6; m++) options += ``;
+ return options;
+};
+
+function abbrNum(value, exp) {
+ let abbr = value;
+ const factor = 10 ** exp;
+ const k = ['k', 'm', 'b', 't'];
+ let m;
+ for (m = k.length - 1; m >= 0; m -= 1) {
+ const n = 10 ** (3 * (m + 1));
+ if (n <= abbr) {
+ abbr = Math.round((value * factor) / n) / factor;
+ if (abbr === 1E3 && m < k.length - 1) {
+ abbr = 1;
+ m += 1;
+ }
+ abbr += k[m];
+ break;
+ }
+ }
+ return abbr;
+}
+
+function updatePriceLabel(wrapper) {
+ const min = wrapper.querySelector('input[name="min-price"]').value;
+ const max = wrapper.querySelector('input[name="max-price"]').value;
+
+ let content;
+ const low = min.replace(/[^0-9]/g, '');
+ const high = max.replace(/[^0-9]/g, '');
+ if (low !== '' && high !== '') {
+ content = `$${abbrNum(low, 2)} -
$${abbrNum(high, 2)}`;
+ } else if (low !== '') {
+ content = `$${abbrNum(low, 2)}`;
+ } else if (high !== '') {
+ content = `$0 -
$${abbrNum(high, 2)}`;
+ } else {
+ content = 'Price';
+ }
+ wrapper.querySelector('.selected span').innerHTML = content;
+}
+
+function observePriceInput(e) {
+ e.preventDefault();
+ const { target } = e;
+ const { value } = target;
+ const datalist = target.closest('.input-dropdown').querySelector('datalist');
+ datalist.innerHTML = createPriceList(value);
+}
+
+function filterSelectClicked(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ updateExpanded(e.currentTarget.closest('.select-wrapper'));
+}
+
+function rangeSelectClicked(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ updateExpanded(e.currentTarget.closest('.range-wrapper'));
+}
+
+function sqftSelectClicked(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ updateExpanded(e.currentTarget.closest('.select-wrapper'));
+}
+
+function updateSqftLabel(wrapper) {
+ const min = wrapper.querySelector('#list-min-sqft li.selected');
+ const max = wrapper.querySelector('#list-max-sqft li.selected');
+
+ let label = wrapper.querySelector('div.selected').getAttribute('aria-label');
+
+ if (min.getAttribute('data-value') || max.getAttribute('data-value')) {
+ label = `${min.textContent} -
${max.textContent}`;
+ }
+ wrapper.querySelector('span').innerHTML = label;
+}
+
+function addKeyword(wrapper, value) {
+ const keyword = document.createElement('div');
+ keyword.classList.add('keyword');
+ keyword.innerHTML = `
+ ${value}
+ X
+ `;
+ keyword.querySelector('.close').addEventListener('click', (localE) => {
+ localE.stopPropagation();
+ localE.preventDefault();
+ localE.currentTarget.closest('.keyword').remove();
+ });
+
+ wrapper.querySelector('.keywords-list').append(keyword);
+}
+
+async function updateParameters() {
+ const form = document.querySelector('.property-search-bar.block form');
+ // Build Search Obj and store it.
+ let search;
+ if (window.location.pathname !== SEARCH_URL) {
+ let type = form.querySelector('input[name="type"]').value;
+ if (type) {
+ type = type.replaceAll(/\s/g, '');
+ if (type === 'ZipCode') {
+ type = 'PostalCode';
+ } else if (type === 'MLS #') {
+ type = 'MLSListingKey';
+ }
+ }
+ search = await Search.load(type);
+ } else {
+ search = await Search.fromQueryString(window.location.search);
+ }
+ let input = form.querySelector('.suggester-input input[type="text"]');
+ if (input.value) search.input = input.value;
+ input = form.querySelector('.result-filters input[name="min-price"]');
+ if (input.value) search.minPrice = input.value;
+ input = form.querySelector('.result-filters input[name="max-price"]');
+ if (input.value) search.maxPrice = input.value;
+ input = form.querySelector('.result-filters .bedrooms select');
+ if (input.value) search.minBedrooms = input.value;
+ input = form.querySelector('.result-filters .bathrooms select');
+ if (input.value) search.minBathrooms = input.value;
+ input = form.querySelector('.result-filters #min-sqft select');
+ if (input.value) search.minSqft = input.value;
+ input = form.querySelector('.result-filters #max-sqft select');
+ if (input.value) search.maxSqft = input.value;
+ search.listingTypes = [];
+ input = form.querySelector('.listing-types input[name="FOR_SALE"]');
+ if (input.checked) search.addListingType(ListingType.FOR_SALE);
+ input = form.querySelector('.listing-types input[name="FOR_RENT"]');
+ if (input.checked) search.addListingType(ListingType.FOR_RENT);
+ input = form.querySelector('.listing-types input[name="PENDING"]');
+ if (input.checked) search.addListingType(ListingType.PENDING);
+ input = form.querySelector('.listing-types input[name="RECENTLY_SOLD"]');
+ if (input.checked) search.addListingType(ListingType.RECENTLY_SOLD);
+ search.propertyTypes = [];
+ form.querySelectorAll('.property-types button.selected').forEach((btn) => search.addPropertyType(btn.name));
+ search.keyword = [];
+ form.querySelectorAll('.keywords .keywords-list .keyword span:first-of-type').forEach((kw) => search.keywords.push(kw.textContent));
+ if (form.querySelector('.keywords input[name="matchType"]').value === 'any') {
+ search.matchAnyKeyword = true;
+ }
+ input = form.querySelector('.year-range #min-year select');
+ if (input.value) search.minYear = input.value;
+ input = form.querySelector('.year-range #max-year select');
+ if (input.value) search.maxYear = input.value;
+ search.isNew = form.querySelector('.is-new input').checked;
+ search.priceChange = form.querySelector('.price-change input').checked;
+ input = form.querySelector('.open-houses input');
+ if (input.checked) search.openHouses = form.querySelector('.open-houses input[name="open-houses-timeframe"]:checked').value;
+ input = form.querySelector('.lux input');
+ if (input.checked) search.luxury = true;
+ input = form.querySelector('.bhhs-only input');
+ if (input.checked) search.bhhsOnly = true;
+
+ form.querySelector('a.filter.open')?.dispatchEvent(new MouseEvent('click'));
+ if (window.location.pathname !== SEARCH_URL) {
+ window.location = `${SEARCH_URL}?${search.asURLSearchParameters().toString()}`;
+ } else {
+ window.dispatchEvent(new CustomEvent(UPDATE_SEARCH_EVENT, { detail: { search } }));
+ }
+}
+
+function syncToBar() {
+ const bar = document.querySelector('.property-search-bar.block form .result-filters');
+ const attrib = document.querySelector('.property-search-bar.block form .advanced-filters .attributes');
+ bar.querySelector('input[name="min-price"]').value = attrib.querySelector('input[name="adv-min-price"]').value;
+ bar.querySelector('input[name="max-price"]').value = attrib.querySelector('input[name="adv-max-price"]').value;
+ updatePriceLabel(bar.querySelector('.range-wrapper.price'));
+
+ const bedrooms = attrib.querySelector('.bedrooms li input:checked').value;
+ bar.querySelector(`.select-wrapper.bedrooms li[data-value="${bedrooms}"]`).dispatchEvent(new MouseEvent('click'));
+ const bathrooms = attrib.querySelector('.bathrooms li input:checked').value;
+ bar.querySelector(`.select-wrapper.bathrooms li[data-value="${bathrooms}"]`).dispatchEvent(new MouseEvent('click'));
+
+ const minSqft = attrib.querySelector('#adv-min-sqft select').value;
+ bar.querySelector(`.range-wrapper.sqft #min-sqft ul li[data-value="${minSqft}"]`).dispatchEvent(new MouseEvent('click'));
+ const maxSqft = attrib.querySelector('#adv-max-sqft select').value;
+ bar.querySelector(`.range-wrapper.sqft #max-sqft ul li[data-value="${maxSqft}"]`).dispatchEvent(new MouseEvent('click'));
+}
+
+function syncToAdvanced() {
+ const bar = document.querySelector('.property-search-bar.block form .result-filters');
+ const attrib = document.querySelector('.property-search-bar.block form .advanced-filters .attributes');
+ attrib.querySelector('input[name="adv-min-price"]').value = bar.querySelector('input[name="min-price"]').value;
+ attrib.querySelector('input[name="adv-max-price"]').value = bar.querySelector('input[name="max-price"]').value;
+
+ const bedrooms = bar.querySelector('.bedrooms select').value;
+ attrib.querySelector(`.bedrooms li[data-value="${bedrooms}"] input`).checked = true;
+ const bathrooms = bar.querySelector('.bathrooms select').value;
+ attrib.querySelector(`.bathrooms li[data-value="${bathrooms}"] input`).checked = true;
+
+ const minSqft = bar.querySelector('#min-sqft select').value;
+ attrib.querySelector(`.sqft #adv-min-sqft ul li[data-value="${minSqft}"]`).dispatchEvent(new MouseEvent('click'));
+ const maxSqft = bar.querySelector('#max-sqft select').value;
+ attrib.querySelector(`.sqft #adv-max-sqft ul li[data-value="${maxSqft}"]`).dispatchEvent(new MouseEvent('click'));
+}
+
+function resetForm(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ const form = e.currentTarget.closest('form');
+ form.querySelectorAll('.advanced-filters .listing-types input[checked="checked"]').forEach((input) => input.closest('.filter-toggle').dispatchEvent(new MouseEvent('click')));
+ form.querySelector('.advanced-filters input[name="FOR_SALE"]').closest('.filter-toggle').dispatchEvent(new MouseEvent('click'));
+ form.querySelectorAll('.advanced-filters .range-wrapper.price input[type="text"]').forEach((input) => {
+ input.value = '';
+ });
+ form.querySelectorAll('.advanced-filters .bedrooms li[data-value=""] input').forEach((li) => li.dispatchEvent(new MouseEvent('click')));
+ form.querySelectorAll('.advanced-filters .bathrooms li[data-value=""] input').forEach((li) => li.dispatchEvent(new MouseEvent('click')));
+ form.querySelectorAll('.advanced-filters .sqft li[data-value=""]').forEach((li) => li.dispatchEvent(new MouseEvent('click')));
+ form.querySelectorAll('.advanced-filters .property-types button').forEach((button) => button.classList.add('selected'));
+ form.querySelectorAll('.advanced-filters .property-types button[name="COMMERCIAL"]').forEach((button) => button.classList.remove('selected'));
+ form.querySelector('.advanced-filters .property-types .all input[type="checkbox"]').checked = false;
+ form.querySelectorAll('.advanced-filters .misc .year-range li[data-value=""]').forEach((li) => li.dispatchEvent(new MouseEvent('click')));
+ form.querySelectorAll('.advanced-filters .misc .filter-toggle input[checked="checked"]').forEach((input) => input.closest('.filter-toggle').dispatchEvent(new MouseEvent('click')));
+}
+
+/**
+ * Updates the bar and advanced form parameters based on the provided Search object.
+ * @param {Search} search
+ */
+// eslint-disable-next-line import/prefer-default-export
+export function updateForm(search) {
+ const bar = document.querySelector('.property-search-bar.block .search-form-wrapper');
+ const advanced = document.querySelector('.property-search-bar.block .advanced-filters');
+
+ bar.querySelector('input[name="type"]').value = search.type;
+ bar.querySelector('input[name="keyword"]').value = search.input || '';
+ bar.querySelector('input[name="min-price"]').value = search.minPrice || '';
+ advanced.querySelector('input[name="adv-min-price"]').value = search.minPrice || '';
+ bar.querySelector('input[name="max-price"]').value = search.maxPrice || '';
+ advanced.querySelector('input[name="adv-max-price"]').value = search.maxPrice || '';
+ updatePriceLabel(bar.querySelector('.range-wrapper.price'));
+
+ const beds = search.minBedrooms;
+ let display;
+ bar.querySelector('.select-wrapper.bedrooms ul li.selected')?.classList.toggle('selected');
+ if (beds) {
+ bar.querySelector(`.select-wrapper.bedrooms select option[value="${beds}"]`).selected = true;
+ advanced.querySelector(`div.bedrooms input[value="${beds}"]`).checked = true;
+ const selected = bar.querySelector(`.select-wrapper.bedrooms ul li[data-value="${beds}"]`);
+ selected.classList.add('selected');
+ display = selected.textContent;
+ } else {
+ bar.querySelector('.select-wrapper.bedrooms select option[value=""]').selected = true;
+ advanced.querySelector('div.bedrooms input[value=""]').checked = true;
+ const selected = bar.querySelector('.select-wrapper.bedrooms ul li[data-value=""]');
+ selected.classList.add('selected');
+ display = selected.textContent;
+ }
+
+ bar.querySelector('.select-wrapper.bedrooms div.selected span').textContent = display;
+ const baths = search.minBathrooms;
+ bar.querySelector('.select-wrapper.bathrooms ul li.selected')?.classList.toggle('selected');
+ if (baths) {
+ bar.querySelector(`.select-wrapper.bathrooms select option[value="${baths}"]`).selected = true;
+ advanced.querySelector(`div.bathrooms input[value="${baths}"]`).checked = true;
+ const selected = bar.querySelector(`.select-wrapper.bathrooms ul li[data-value="${baths}"]`);
+ selected.classList.add('selected');
+ display = selected.textContent;
+ } else {
+ bar.querySelector('.select-wrapper.bathrooms select option[value=""]').selected = true;
+ advanced.querySelector('div.bathrooms input[value=""]').checked = true;
+ const selected = bar.querySelector('.select-wrapper.bathrooms ul li[data-value=""]');
+ selected.classList.add('selected');
+ display = selected.textContent;
+ }
+ bar.querySelector('.select-wrapper.bathrooms div.selected span').textContent = display;
+
+ const { minSqft, maxSqft } = search;
+ bar.querySelector('.range-wrapper.sqft #min-sqft ul li.selected').classList.remove('selected');
+ advanced.querySelector('div.sqft #adv-min-sqft ul li.selected').classList.remove('selected');
+ if (minSqft) {
+ bar.querySelector(`.range-wrapper.sqft #min-sqft select option[value="${minSqft}"]`).selected = true;
+ bar.querySelector(`.range-wrapper.sqft #min-sqft ul li[data-value="${minSqft}"]`).classList.add('selected');
+ advanced.querySelector(`div.sqft #adv-min-sqft select option[value="${minSqft}"]`).selected = true;
+ advanced.querySelector(`div.sqft #adv-min-sqft ul li[data-value="${minSqft}"]`).classList.add('selected');
+ } else {
+ bar.querySelector('.range-wrapper.sqft #min-sqft select option[value=""]').selected = true;
+ bar.querySelector('.range-wrapper.sqft #min-sqft ul li[data-value=""]').classList.add('selected');
+ advanced.querySelector('div.sqft #adv-min-sqft select option[value=""]').selected = true;
+ advanced.querySelector('div.sqft #adv-min-sqft ul li[data-value=""]').classList.add('selected');
+ }
+ bar.querySelector('.range-wrapper.sqft #min-sqft div.selected span').textContent = bar.querySelector('.range-wrapper.sqft #min-sqft ul li.selected').textContent;
+ advanced.querySelector('div.sqft #adv-min-sqft div.selected span').textContent = advanced.querySelector('div.sqft #adv-min-sqft ul li.selected').textContent;
+
+ bar.querySelector('.range-wrapper.sqft #max-sqft ul li.selected').classList.remove('selected');
+ advanced.querySelector('div.sqft #adv-max-sqft ul li.selected').classList.remove('selected');
+ if (maxSqft) {
+ bar.querySelector(`.range-wrapper.sqft #max-sqft select option[value="${maxSqft}"]`).selected = true;
+ bar.querySelector(`.range-wrapper.sqft #max-sqft ul li[data-value="${maxSqft}"]`).classList.add('selected');
+ advanced.querySelector(`div.sqft #adv-max-sqft select option[value="${maxSqft}"]`).selected = true;
+ advanced.querySelector(`div.sqft #adv-max-sqft ul li[data-value="${maxSqft}"]`).classList.add('selected');
+ } else {
+ bar.querySelector('.range-wrapper.sqft #max-sqft select option[value=""]').selected = true;
+ bar.querySelector('.range-wrapper.sqft #max-sqft ul li[data-value=""]').classList.add('selected');
+ advanced.querySelector('div.sqft #adv-max-sqft select option[value=""]').selected = true;
+ advanced.querySelector('div.sqft #adv-max-sqft ul li[data-value=""]').classList.add('selected');
+ }
+ bar.querySelector('.range-wrapper.sqft #max-sqft div.selected span').textContent = bar.querySelector('.range-wrapper.sqft #max-sqft ul li.selected').textContent;
+ advanced.querySelector('div.sqft #adv-max-sqft div.selected span').textContent = advanced.querySelector('div.sqft #adv-max-sqft ul li.selected').textContent;
+ updateSqftLabel(bar.querySelector('.range-wrapper.sqft'));
+
+ advanced.querySelectorAll('.listing-types .filter-toggle.disabled').forEach((t) => t.classList.remove('disabled'));
+ advanced.querySelectorAll('.listing-types .filter-toggle input[type="checkbox"]').forEach((c) => {
+ c.removeAttribute('checked');
+ c.nextElementSibling.classList.remove('checked');
+ });
+ search.listingTypes.forEach((t) => {
+ const chkbx = advanced.querySelector(`.listing-types .filter-toggle input[name="${t.type}"]`);
+ chkbx.checked = true;
+ chkbx.nextElementSibling.classList.add('checked');
+ if (t.type === ListingType.FOR_RENT.type) {
+ advanced.querySelector(`.listing-types .filter-toggle input[name="${ListingType.PENDING.type}"]`).closest('.filter-toggle').classList.add('disabled');
+ } else if (t.type === ListingType.PENDING.type) {
+ advanced.querySelector(`.listing-types .filter-toggle input[name="${ListingType.FOR_RENT.type}"]`).closest('.filter-toggle').classList.add('disabled');
+ }
+ });
+
+ advanced.querySelectorAll('.property-types button.selected').forEach((b) => b.classList.remove('selected'));
+ search.propertyTypes.forEach((t) => {
+ advanced.querySelector(`.property-types button[name="${t.name}"]`).classList.add('selected');
+ });
+ const unselected = advanced.querySelector('.property-types button:not(.selected)');
+ if (unselected) {
+ advanced.querySelector('.property-types .all label input').checked = false;
+ } else {
+ advanced.querySelector('.property-types .all label input').checked = true;
+ }
+
+ const kwWrapper = advanced.querySelector('.keywords');
+ kwWrapper.querySelector('.keywords-list').replaceChildren();
+ search.keywords.forEach((kw) => addKeyword(kwWrapper, kw));
+ if (search.matchAnyKeyword) {
+ advanced.querySelector('.keywords input[value="any"]').checked = true;
+ } else {
+ advanced.querySelector('.keywords input[value="all"]').checked = true;
+ }
+
+ const { minYear, maxYear } = search;
+ const minYearWrapper = advanced.querySelector('div.year-range #min-year');
+ minYearWrapper.querySelector('ul li.selected').classList.remove('selected');
+ if (minYear) {
+ minYearWrapper.querySelector(`select option[value="${minYear}"]`).selected = true;
+ minYearWrapper.querySelector(`ul li[data-value="${minYear}"]`).classList.add('selected');
+ } else {
+ minYearWrapper.querySelector('select option[value=""]').selected = true;
+ minYearWrapper.querySelector('ul li[data-value=""]').classList.add('selected');
+ }
+ minYearWrapper.querySelector('div.selected span').textContent = minYearWrapper.querySelector('ul li.selected').textContent;
+
+ const maxYearWrapper = advanced.querySelector('div.year-range #max-year');
+ maxYearWrapper.querySelector('ul li.selected').classList.remove('selected');
+ if (maxYear) {
+ maxYearWrapper.querySelector(`select option[value="${maxYear}"]`).selected = true;
+ maxYearWrapper.querySelector(`ul li[data-value="${maxYear}"]`).classList.add('selected');
+ } else {
+ maxYearWrapper.querySelector('select option[value=""]').selected = true;
+ maxYearWrapper.querySelector('ul li[data-value=""]').classList.add('selected');
+ }
+ maxYearWrapper.querySelector('div.selected span').textContent = maxYearWrapper.querySelector('ul li.selected').textContent;
+
+ if (search.isNew) {
+ advanced.querySelector('.is-new .filter-toggle input').checked = true;
+ advanced.querySelector('.is-new .filter-toggle .checkbox').classList.add('checked');
+ } else {
+ advanced.querySelector('.is-new .filter-toggle input').checked = false;
+ advanced.querySelector('.is-new .filter-toggle .checkbox').classList.remove('checked');
+ }
+
+ if (search.priceChange) {
+ advanced.querySelector('.price-change .filter-toggle input').checked = true;
+ advanced.querySelector('.price-change .filter-toggle .checkbox').classList.add('checked');
+ } else {
+ advanced.querySelector('.price-change .filter-toggle input').checked = false;
+ advanced.querySelector('.price-change .filter-toggle .checkbox').classList.remove('checked');
+ }
+
+ if (search.openHouses) {
+ advanced.querySelector('.open-houses .filter-toggle input').checked = true;
+ advanced.querySelector('.open-houses .filter-toggle .checkbox').classList.add('checked');
+ advanced.querySelector('.open-houses .open-houses-timeframe').classList.add('visible');
+ advanced.querySelector(`.open-houses input[value=${search.openHouses.name}]`).checked = true;
+ } else {
+ advanced.querySelector('.open-houses .filter-toggle input').checked = false;
+ advanced.querySelector('.open-houses .filter-toggle .checkbox').classList.remove('checked');
+ advanced.querySelector('.open-houses .open-houses-timeframe').classList.remove('visible');
+ }
+
+ if (search.luxury) {
+ advanced.querySelector('.lux .filter-toggle input').checked = true;
+ advanced.querySelector('.lux .filter-toggle .checkbox').classList.add('checked');
+ } else {
+ advanced.querySelector('.lux .filter-toggle input').checked = false;
+ advanced.querySelector('.lux .filter-toggle .checkbox').classList.remove('checked');
+ }
+
+ if (search.luxury) {
+ advanced.querySelector('.bhhs-only .filter-toggle input').checked = true;
+ advanced.querySelector('.bhhs-only .filter-toggle .checkbox').classList.add('checked');
+ } else {
+ advanced.querySelector('.bhhs-only .filter-toggle input').checked = false;
+ advanced.querySelector('.bhhs-only .filter-toggle .checkbox').classList.remove('checked');
+ }
+}
+
+function buildAdvancedFilters() {
+ const wrapper = document.querySelector('.property-search-bar.block .advanced-filters');
+ wrapper.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
No Min
+
+
+
to
+
+
+
No Max
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
No Min
+
+
+
to
+
+
+
No Max
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ wrapper.querySelectorAll('#adv-min-sqft, #adv-max-sqft').forEach((item) => {
+ SQUARE_FEET.forEach((b) => {
+ const opt = document.createElement('option');
+ opt.value = b.value;
+ opt.textContent = b.label;
+ item.querySelector('select').append(opt);
+ const li = document.createElement('li');
+ li.setAttribute('data-value', b.value);
+ li.setAttribute('role', 'option');
+ li.textContent = b.label;
+ item.querySelector('ul').append(li);
+ });
+ });
+
+ wrapper.querySelectorAll('#min-year, #max-year').forEach((item) => {
+ let year = new Date().getFullYear();
+ const select = item.querySelector('select');
+ const ul = item.querySelector('ul');
+
+ const addOption = (value) => {
+ const opt = document.createElement('option');
+ opt.value = value;
+ opt.textContent = value;
+ select.append(opt);
+ const li = document.createElement('li');
+ li.setAttribute('data-value', value);
+ li.setAttribute('role', 'option');
+ li.textContent = value;
+ ul.append(li);
+ };
+
+ // Last 6 years individually
+ for (let i = 0; i < 6; i += 1) {
+ addOption(year);
+ year -= 1;
+ }
+
+ // 6 blocks of 5 year increments
+ year -= year % 5;
+ for (let i = 1; i < 6; i += 1) {
+ addOption(year);
+ year -= 5;
+ }
+
+ // 6 blocks of 10 year increments
+ year -= year % 10;
+ for (let i = 1; i < 6; i += 1) {
+ addOption(year);
+ year -= 10;
+ }
+
+ // remaining at 20 year increments
+ while (year >= 1900) {
+ addOption(year);
+ year -= 20;
+ }
+ });
+}
+
+function observeForm(form) {
+ const searchInput = form.querySelector('.suggester-input input');
+ searchInput.addEventListener('focus', observeSearchInput);
+
+ form.querySelector('a.search-submit').addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (!BREAKPOINTS.medium.matches) {
+ syncToBar();
+ }
+ updateParameters();
+ });
+
+ form.querySelectorAll('.result-filters > .select-wrapper div.selected, .advanced-filters .misc .select-wrapper div.selected').forEach((button) => {
+ button.addEventListener('click', filterSelectClicked);
+ });
+
+ form.querySelectorAll('.select-wrapper .select-items li').forEach((li) => {
+ li.addEventListener('click', filterItemClicked);
+ });
+
+ form.querySelectorAll('.range-wrapper > div.selected').forEach((button) => {
+ button.addEventListener('click', rangeSelectClicked);
+ });
+
+ form.querySelectorAll('.range-wrapper .range-items div[id="min-price"], .range-wrapper .range-items div[id="max-price"]').forEach((price) => {
+ price.addEventListener('keyup', (e) => {
+ observePriceInput(e);
+ updatePriceLabel(price.closest('.range-wrapper'));
+ });
+ });
+
+ form.querySelectorAll('.filter-toggle').forEach((t) => {
+ t.addEventListener('click', (e) => {
+ e.preventDefault();
+ const { currentTarget } = e;
+ const ipt = currentTarget.querySelector('input');
+ ipt.checked = currentTarget.querySelector('div.checkbox').classList.toggle('checked');
+ });
+ });
+
+ form.querySelectorAll('.range-wrapper .range-items div[id="adv-min-price"], .range-wrapper .range-items div[id="adv-max-price"]').forEach((price) => {
+ price.addEventListener('keyup', observePriceInput);
+ });
+
+ form.querySelector('.listing-types').addEventListener('click', (e) => {
+ e.preventDefault();
+ const input = e.target.closest('.filter-toggle')?.querySelector('input');
+ if (input && input.value === ListingType.FOR_RENT.type) {
+ e.currentTarget.querySelector(`input[value="${ListingType.PENDING.type}"]`).closest('.filter-toggle').classList.toggle('disabled');
+ } else if (input && input.value === ListingType.PENDING.type) {
+ e.currentTarget.querySelector(`input[value="${ListingType.FOR_RENT.type}"]`).closest('.filter-toggle').classList.toggle('disabled');
+ }
+ });
+
+ form.querySelectorAll('.range-items div[id$="-sqft"] > .selected').forEach((sqft) => {
+ sqft.addEventListener('click', sqftSelectClicked);
+ });
+
+ form.querySelectorAll('.range-wrapper .range-items div[id$="-sqft"] .select-items li').forEach((li) => {
+ li.addEventListener('click', (e) => {
+ e.preventDefault();
+ updateSqftLabel(e.currentTarget.closest('.range-wrapper'));
+ });
+ });
+
+ const allTypes = form.querySelector('.property-types .all');
+ form.querySelectorAll('.property-types .options button').forEach((b) => {
+ b.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const exists = e.currentTarget.classList.toggle('selected');
+ if (!exists) {
+ allTypes.querySelector('input[type="checkbox"]').checked = false;
+ }
+ });
+ });
+
+ form.querySelector('.property-types .all label').addEventListener('click', (e) => {
+ if (e.currentTarget.querySelector('input').checked) {
+ e.currentTarget.closest('.property-types').querySelectorAll('.options button').forEach((b) => b.classList.add('selected'));
+ } else {
+ e.currentTarget.closest('.property-types').querySelectorAll('.options button').forEach((b) => b.classList.remove('selected'));
+ }
+ });
+
+ form.querySelector('.advanced-filters .keywords button').addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const wrapper = e.currentTarget.closest('.keywords');
+ const input = wrapper.querySelector('.keywords-input input[type="text"]');
+ const { value } = input;
+ if (!value) {
+ return;
+ }
+ addKeyword(wrapper, value);
+ input.value = '';
+ });
+
+ form.querySelectorAll('.year-range .select-wrapper div.selected').forEach((button) => {
+ button.addEventListener('click', filterSelectClicked);
+ });
+
+ form.querySelector('.advanced-filters .open-houses .filter-toggle').addEventListener('click', (e) => {
+ const input = e.currentTarget.querySelector('input');
+ const timeframe = e.currentTarget.closest('.open-houses').querySelector('.open-houses-timeframe');
+ if (input.checked) {
+ timeframe.classList.add('visible');
+ } else {
+ timeframe.classList.remove('visible');
+ }
+ });
+
+ const filterBtn = form.querySelector('a.filter');
+ filterBtn.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ toggleAdvancedFilters(e);
+ });
+
+ form.querySelector('a#search-apply').addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (!BREAKPOINTS.medium.matches) {
+ syncToBar();
+ }
+ updateParameters();
+ });
+
+ form.querySelector('a#search-cancel').addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ filterBtn.dispatchEvent(new MouseEvent('click'));
+ });
+
+ form.querySelector('a#search-reset').addEventListener('click', resetForm);
+
+ BREAKPOINTS.medium.addEventListener('change', (e) => {
+ if (e.matches) {
+ syncToBar();
+ } else {
+ syncToAdvanced();
+ }
+ });
+}
+
+buildAdvancedFilters();
+observeForm(document.querySelector('.property-search-bar.block form'));
+// Reset bar if user moves through history state.
+window.addEventListener('popstate', async () => {
+ document.querySelectorAll('.property-search-bar .open').forEach((el) => el.classList.remove('open'));
+ document.querySelectorAll('.property-search-bar .search-overlay.visible').forEach((el) => el.classList.remove('visible'));
+ document.querySelectorAll('.property-search-bar [aria-expanded="true"]').forEach((el) => el.setAttribute('aria-expanded', 'false'));
+});
diff --git a/blocks/property-search-bar/filter-processor.js b/blocks/property-search-bar/filter-processor.js
deleted file mode 100644
index caa77140..00000000
--- a/blocks/property-search-bar/filter-processor.js
+++ /dev/null
@@ -1,367 +0,0 @@
-import {
- setParam, getParam, removeParam, getSearchObject, buildUrl,
-} from '../../scripts/search.js';
-import {
- buildKeywordEl, formatPriceLabel,
- TOP_LEVEL_FILTERS, EXTRA_FILTERS, BOTTOM_LEVEL_FILTERS, getConfig,
-} from './common-function.js';
-
-import {
- propertySearch,
-} from '../../scripts/apis/creg/creg.js';
-
-import { setPropertyDetails as setResults } from '../../scripts/search/results.js';
-import SearchParameters from '../../scripts/apis/creg/SearchParameters.js';
-
-import SearchType from '../../scripts/apis/creg/SearchType.js';
-import ApplicationType from '../../scripts/apis/creg/ApplicationType.js';
-
-export function searchProperty() {
- if (document.querySelector('.property-result-content')) {
- document.querySelector('.property-result-content').remove();
- }
- if (document.querySelector('.property-result-map-container .disclaimer')) {
- document.querySelector('.property-result-map-container .disclaimer').remove();
- }
- document.querySelector('.search-results-loader').style.display = 'block';
- const overlay = document.querySelector('.search-results-loader-image');
- overlay.classList.remove('exit');
- overlay.classList.add('enter');
- const type = getParam('SearchType');
- const searchParams = getSearchObject();
- const params = new SearchParameters(SearchType[type]);
- const result = {
- properties: [],
- cont: 0,
- disclaimer: {},
- listingClusters: [],
- result: {},
- };
- // set params from session storage
- Object.keys(searchParams).forEach((key) => {
- if (key === 'ApplicationType') {
- params.applicationTypes = searchParams[key].split(',').map((propertyType) => ApplicationType[propertyType]);
- } else if (key === 'PropertyType') {
- params.propertyTypes = searchParams[key].split(',');
- } else if (key === 'franchiseeCode') {
- params.franchisee = searchParams[key];
- } else if (key === 'Sort') {
- params.sortBy = searchParams[key];
- } else if (key === 'isFranchisePage') {
- // do nothing
- } else {
- params[key] = searchParams[key];
- }
- });
-
- propertySearch(params).then((results) => {
- result.properties = results.properties;
- result.count = results['@odata.count'];
- result.disclaimer = results.disclaimer;
- result.listingClusters = results.listingClusters;
- result.result = results;
- setResults(result);
- }).catch(() => {
- setResults(result);
- }).finally(() => {
- document.querySelector('.search-results-loader').style.display = 'none';
- overlay.classList.remove('enter');
- overlay.classList.add('exit');
- });
-
- // update url
- const nextUrl = buildUrl();
- const nextTitle = 'Property Search Results Commonwealth Real Estate | Berkshire Hathaway HomeServices';
- const nextState = { additionalInformation: 'Updated the URL with JS' };
- window.history.replaceState(nextState, nextTitle, nextUrl);
-}
-/**
- *
- * @param filterName
- * @param value
- * @returns {string|string|*}
- */
-export function formatValue(filterName, value) {
- let conf; let
- formattedValue = '';
- switch (filterName) {
- case 'Price':
- formattedValue = formatPriceLabel(value.min, value.max);
- break;
- case 'MinBedroomsTotal':
- formattedValue = value ? `${value}+ Beds` : 'Any Beds';
- break;
- case 'MinBathroomsTotal':
- formattedValue = value ? `${value}+ Baths` : 'Any Baths';
- break;
- case 'LivingArea':
- formattedValue = `${value.min}Sq Ft-${value.max} Sq Ft`;
- if (value.min === '') {
- formattedValue = `no min- ${value.max} Sq Ft`;
- }
- if (value.max === '') {
- formattedValue = `${value.min} Sq Ft - no max`;
- }
- if (value.min === '' && value.max === '') {
- formattedValue = 'square feet';
- }
- break;
- case 'Sort':
- conf = getConfig(filterName);
- for (let i = 0; i < conf.length; i += 1) {
- if (conf[i].value === value) {
- formattedValue = conf[i].label;
- }
- }
- break;
- default:
- formattedValue = value;
- }
- return formattedValue;
-}
-
-/**
- * Get filter value
- * @param {string} filterName
- * @returns {string}
- *
- */
-export function getValueFromStorage(filterName) {
- let minValue = '';
- let maxValue = '';
- let value = '';
- switch (filterName) {
- case 'Price':
- case 'LivingArea':
- value = { min: getParam(`Min${filterName}`) ?? '', max: getParam(`Max${filterName}`) ?? '' };
- break;
- case 'Features':
- case 'ApplicationType':
- case 'PropertyType':
- value = getParam(filterName) ? getParam(filterName).split(',') : [];
- break;
- case 'YearBuilt':
- [minValue, maxValue] = (getParam('YearBuilt') || '-').split('-').map((val) => {
- if (val === '1899') return 'No Min';
- if (val === '2100') return 'No Max';
- return val;
- });
- value = { min: minValue, max: maxValue };
- break;
- case 'FeaturedCompany':
- value = getParam('FeaturedCompany') === 'BHHS';
- break;
- default:
- value = getParam(filterName) ?? false;
- }
- return value;
-}
-
-/**
- *
- * @param {string} name
- * @param {string|obj} value
- */
-export function setFilterValue(name, value) {
- let params;
- let values;
- switch (name) {
- case 'PropertyType':
- params = getValueFromStorage('PropertyType');
- if (value.length === 1) {
- params.push(value);
- params = params.join(',');
- setParam('PropertyType', params);
- } else if (value.length > 1) {
- values = value.split(',');
- values = [...params, ...values];
- values = [...new Set(values)];
- values = values.join(',');
- setParam('PropertyType', values);
- }
- break;
- case 'FeaturedCompany':
- // eslint-disable-next-line no-unused-expressions
- value ? setParam('FeaturedCompany', 'BHHS') : removeParam('FeaturedCompany');
- break;
- case 'Luxury':
- case 'RecentPriceChange':
- // eslint-disable-next-line no-unused-expressions
- value ? setParam(name, true) : removeParam(name);
- break;
- case 'Features':
- params = getParam(name) ?? '';
- params = params.length > 0 ? params.concat(',', value) : value;
- values = params.split(',');
- values = [...new Set(values)];
- setParam(name, values.join(','));
- break;
- case 'ApplicationType':
- if (value.length > 0) {
- setParam(name, value);
- values = value.split(',');
- params = values.map((val) => {
- let param;
- if (val === ApplicationType.FOR_SALE.type
- || val === ApplicationType.FOR_RENT.type) param = 1;
- if (val === ApplicationType.PENDING.type) param = 2;
- if (val === ApplicationType.RECENTLY_SOLD.type) param = 3;
- return param;
- });
- const unique = [...new Set(params)];
- setParam('ListingStatus', unique.join(','));
- } else {
- removeParam(name);
- removeParam('ListingStatus');
- }
- break;
- case 'MinPrice':
- case 'MaxPrice':
- case 'MinBedroomsTotal':
- case 'MinBathroomsTotal':
- // eslint-disable-next-line no-unused-expressions
- value.length > 0 ? setParam(name, value) : removeParam(name);
- break;
- default:
- setParam(name, value);
- }
-}
-export function removeFilterValue(name, value = '') {
- let params;
- let paramsToArray;
- switch (name) {
- case 'Features':
- params = getParam('Features') ?? '';
- paramsToArray = params.split(',');
- paramsToArray = paramsToArray.filter((i) => i !== value);
- params = paramsToArray.join(',');
- setParam('Features', params);
- break;
- case 'PropertyType':
- if (value.length > 0) {
- params = getValueFromStorage('PropertyType');
- params = params.filter((i) => i !== value);
- setParam('PropertyType', params.join(','));
- } else {
- removeParam('PropertyType');
- }
- break;
- default:
- removeParam(name);
- }
-}
-
-export function setInitialValuesFromUrl() {
- const url = window.location.href;
- const queryString = url.split('?')[1];
- if (queryString) {
- queryString.split('&').forEach((query) => {
- // eslint-disable-next-line prefer-const
- let [key, value] = query.split('=');
- value = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : '';
- setFilterValue(key, value);
- });
- }
-}
-
-export function populatePreSelectedFilters(topMenu = true) {
- let filters; let
- el;
- let value = '';
- if (topMenu) {
- filters = { ...TOP_LEVEL_FILTERS, ...BOTTOM_LEVEL_FILTERS };
- Object.keys(filters).forEach((name) => {
- const selector = `[name="${name}"] .title span`;
- value = getValueFromStorage(name);
- if (Object.keys(BOTTOM_LEVEL_FILTERS).includes(name)) {
- if (name === 'ApplicationType') {
- value.forEach((key) => {
- document.querySelector(`[name="${name}"] > [name="${key}"] .checkbox`).classList.add('checked');
- });
- }
- if (name === 'Sort' && value) {
- el = document.querySelector('[name="Sort"]');
- el.querySelector('.select-selected').innerText = formatValue(name, value);
- el.querySelector('.highlighted').classList.toggle('highlighted');
- el.querySelector(`[data-value="${value}"]`).classList.toggle('highlighted');
- }
- } else {
- document.querySelector(selector).innerText = formatValue(name, value);
- }
- });
- } else {
- const storageKeyToName = { ...TOP_LEVEL_FILTERS, ...EXTRA_FILTERS, ...BOTTOM_LEVEL_FILTERS };
- Object.keys(storageKeyToName).forEach((name) => {
- value = getValueFromStorage(name);
- let min;
- let max;
- let filter;
- switch (name) {
- case 'Price':
- document.querySelector('.filter [name="MinPrice"]').value = value.min;
- document.querySelector('.filter [name="MaxPrice"]').value = value.max;
- break;
- case 'MinBedroomsTotal':
- case 'MinBathroomsTotal':
- value = value || 'Any';
- document.querySelectorAll(`[name="${name}"] input`).forEach(
- (input) => { input.checked = (input.value === value); },
- );
- break;
- case 'LivingArea':
- document.querySelector('.filter [name="MinLivingArea"]').innerText = value.min.length > 0 ? `${value.min} Sq Ft` : 'No Min';
- document.querySelector('.filter [name="MaxLivingArea"]').innerText = value.max.length > 0 ? `${value.max} Sq Ft` : 'No Max';
- document.querySelectorAll('.filter [name="MinLivingArea"] ~ul li').forEach((li) => {
- li.classList.toggle('highlighted', li.getAttribute('data-value') === value.min);
- });
- document.querySelectorAll('.filter [name="MaxLivingArea"] ~ul li').forEach((li) => {
- li.classList.toggle('highlighted', li.getAttribute('data-value') === value.max);
- });
-
- break;
- case 'PropertyType':
- document.querySelectorAll('.filter[name="PropertyType"] button').forEach((button) => {
- button.classList.toggle('selected', value.includes(button.value));
- });
- break;
- case 'Features':
- if (document.querySelector('#container-tags').childElementCount === 0) {
- value.forEach((key) => {
- buildKeywordEl(key, removeFilterValue);
- });
- }
- break;
- case 'YearBuilt':
- [min, max] = [value.min !== '' ? value.min : 'No Min', value.max !== '' ? value.max : 'No Max'];
- document.querySelectorAll('[name="YearBuilt"] .select-selected').forEach((elem, i) => {
- elem.innerText = i === 0 ? min : max;
- });
- break;
- case 'OpenHouses':
- filter = document.querySelector('[name="OpenHouses"]');
- filter.classList.toggle('selected', !!value);
- filter.querySelector('input[type="checkbox"]').checked = !!value;
- if (value) {
- filter.querySelector(`[name="OpenHouses"] input[value="${value}"]`).checked = true;
- }
- break;
- case 'MatchAnyFeatures':
- document.querySelector('[name="matchTagsAll"]').checked = !value;
- document.querySelector('[name="matchTagsAny"]').checked = value;
- break;
- case 'ApplicationType':
- value.forEach((key) => {
- document.querySelector(`[name="${name}"] .column [name="${key}"] .checkbox`).classList.add('checked');
- });
- break;
- case 'Sort':
- case 'Page':
- // do nothing
- break;
- default:
- document.querySelector(`.filter[name="${name}"] .checkbox`).classList.toggle('checked', value);
- document.querySelector(`.filter[name="${name}"] input`).value = value;
- }
- });
- }
-}
diff --git a/blocks/property-search-bar/filters/additional-filter-buttons-delayed.js b/blocks/property-search-bar/filters/additional-filter-buttons-delayed.js
deleted file mode 100644
index 0e7e34e6..00000000
--- a/blocks/property-search-bar/filters/additional-filter-buttons-delayed.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import { togglePropertyForm } from '../common-function.js';
-import { populatePreSelectedFilters, setFilterValue, setInitialValuesFromUrl } from '../filter-processor.js';
-
-const event = new Event('onFilterChange');
-
-function addEventListeners() {
- const block = document.querySelector('.property-search-bar.block');
- // close form on click cancel button
- block.querySelector('.filter-buttons a[title="cancel"]').addEventListener('click', () => {
- togglePropertyForm();
- });
- // reset form on click reset button
- block.querySelector('.filter-buttons a[title="reset"]').addEventListener('click', () => {
- // @todo set up initial values
- setInitialValuesFromUrl();
- // layout fields
- populatePreSelectedFilters(false);
- // top menu
- populatePreSelectedFilters();
- });
- // apply filters on click apply button
- block.querySelector('.filter-buttons a[title="apply"]').addEventListener('click', (e) => {
- e.preventDefault();
- e.stopPropagation();
- togglePropertyForm();
- setFilterValue('MinPrice', document.querySelector('.filter [name="MinPrice"]').value);
- setFilterValue('MaxPrice', document.querySelector('.filter [name="MaxPrice"]').value);
- populatePreSelectedFilters();
- window.dispatchEvent(event);
- });
-}
-addEventListeners();
diff --git a/blocks/property-search-bar/filters/additional-filter-buttons.js b/blocks/property-search-bar/filters/additional-filter-buttons.js
deleted file mode 100644
index cf9eb666..00000000
--- a/blocks/property-search-bar/filters/additional-filter-buttons.js
+++ /dev/null
@@ -1,37 +0,0 @@
-function observeButtons() {
- const script = document.createElement('script');
- script.id = crypto.randomUUID();
- script.type = 'text/partytown';
- script.innerHTML = `
- const script = document.createElement('script');
- script.type = 'module';
- script.src = '${window.hlx.codeBasePath}/blocks/property-search-bar/filters/additional-filter-buttons-delayed.js';
- document.head.append(script);
- `;
- document.head.append(script);
-}
-
-function build() {
- const buttons = ['cancel', 'reset'];
- const wrapper = document.createElement('div');
- wrapper.classList.add('filter-buttons', 'button-container', 'flex-row', 'vertical-center', 'hide');
- let output = `
-
- apply
- `;
- buttons.forEach((button) => {
- output += `
-
- ${button}
- `;
- });
- wrapper.innerHTML = output;
- observeButtons();
- return wrapper;
-}
-
-const layoutButtons = {
- build,
-};
-
-export default layoutButtons;
diff --git a/blocks/property-search-bar/filters/additional-filters.js b/blocks/property-search-bar/filters/additional-filters.js
deleted file mode 100644
index 3e657747..00000000
--- a/blocks/property-search-bar/filters/additional-filters.js
+++ /dev/null
@@ -1,215 +0,0 @@
-import {
- addRangeOption, EXTRA_FILTERS, formatInput, TOP_LEVEL_FILTERS,
- getConfig, buildFilterSearchTypesElement, getFilterLabel,
-} from '../common-function.js';
-import PropertyType from '../../../scripts/apis/creg/PropertyType.js';
-import OpenHouses from '../../../scripts/apis/creg/OpenHouses.js';
-
-const SEARCH_TYPES = { ApplicationType: { label: 'Search Types', type: 'search-types' } };
-const FILTERS = { ...SEARCH_TYPES, ...TOP_LEVEL_FILTERS, ...EXTRA_FILTERS };
-
-function observeFilters() {
- const script = document.createElement('script');
- script.id = crypto.randomUUID();
- script.type = 'text/partytown';
- script.innerHTML = `
- const script = document.createElement('script');
- script.type = 'module';
- script.src = '${window.hlx.codeBasePath}/blocks/property-search-bar/filters/additional-params-delayed.js';
- document.head.append(script);
- `;
- document.head.append(script);
-}
-
-function buildPropertyColumn(properties = []) {
- let output = '';
- [...properties].forEach((property) => {
- output += ``;
- });
- return output;
-}
-
-function buildCheckBox(ariaLabel, label = '') {
- return `
-
-
`;
-}
-
-function buildPropertyFilterHtml(label) {
- const firstColumnValues = [
- PropertyType.CONDO_TOWNHOUSE,
- PropertyType.COMMERCIAL,
- PropertyType.LAND,
- ];
- const secondColumnValues = [
- PropertyType.SINGLE_FAMILY,
- PropertyType.MULTI_FAMILY,
- PropertyType.FARM,
- ];
- return `
-
-
${buildPropertyColumn(firstColumnValues)}
-
${buildPropertyColumn(secondColumnValues)}
-
- ${buildCheckBox(label, 'Select All')}
-`;
-}
-
-function buildFilterOpenHouses() {
- return `
-
- ${buildCheckBox('Open Houses Only')}
-
-
-
-
-
-
-
-`;
-}
-function buildKeywordSearch() {
- return `
-
-
-
-
-
-
-
-`;
-}
-
-function buildFilterToggle() {
- return `
- `;
-}
-
-function buildSectionFilter(filterName) {
- const number = getConfig(filterName);
- const defaultValue = 'Any';
- const name = filterName.toLowerCase();
- let output = `
- ';
- return output;
-}
-
-function getOptions(name) {
- let options = '';
- const { type } = FILTERS[name];
- switch (type) {
- case 'select':
- options = buildSectionFilter(name);
- break;
- case 'range':
- options = addRangeOption(name);
- break;
- case 'toggle':
- options = buildFilterToggle();
- break;
- case 'keywords-search':
- options = buildKeywordSearch();
- break;
- case 'open-houses':
- options = buildFilterOpenHouses();
- break;
- case 'property':
- options = buildPropertyFilterHtml();
- break;
- case 'search-types':
- options = buildFilterSearchTypesElement();
- break;
- default:
- break;
- }
- return options;
-}
-
-function buildPlaceholder(filterName) {
- const { type } = FILTERS[filterName];
- if (type === 'child') {
- return '';
- }
- const placeholder = document.createElement('div');
- const label = getFilterLabel(filterName);
- const options = getOptions(filterName);
- placeholder.setAttribute('name', filterName);
- placeholder.classList.add('filter');
- placeholder.innerHTML = `
- ${options}`;
- return placeholder.outerHTML;
-}
-
-async function build() {
- const wrapper = document.createElement('div');
- let output = '';
- Object.keys(FILTERS).forEach((filter) => { output += buildPlaceholder(filter); });
- wrapper.classList.add('filter-block', 'hide', 'input');
- wrapper.innerHTML = `
- ${output}`;
- observeFilters();
- return wrapper;
-}
-
-const additionalFilters = {
- build,
-};
-
-export default additionalFilters;
diff --git a/blocks/property-search-bar/filters/additional-params-delayed.js b/blocks/property-search-bar/filters/additional-params-delayed.js
deleted file mode 100644
index 5b8c8c22..00000000
--- a/blocks/property-search-bar/filters/additional-params-delayed.js
+++ /dev/null
@@ -1,162 +0,0 @@
-import {
- removeFilterValue,
- setFilterValue,
-} from '../filter-processor.js';
-import { buildKeywordEl, updateFilters } from '../common-function.js';
-import OpenHouses from '../../../scripts/apis/creg/OpenHouses.js';
-
-const event = new Event('onFilterChange');
-
-function toggleFilter(el) {
- const div = el.querySelector('.checkbox');
- const name = el.closest('.filter').getAttribute('name');
- div.classList.toggle('checked');
- let value = div.classList.contains('checked');
- el.querySelector('input').value = value;
- if (name === 'ApplicationType') {
- value = [];
- el.closest('[name="ApplicationType"]').querySelectorAll('.filter-toggle .checked').forEach((elem) => {
- value.push(elem.parentElement.getAttribute('name'));
- });
- value = value.join(',');
- }
- setFilterValue(name, value);
-}
-
-function addEventListeners() {
- const block = document.querySelector('.property-search-bar.block');
- const propertyButtons = block.querySelectorAll('[name="PropertyType"] button');
- const openHousesFilter = block.querySelector('[name="OpenHouses"]');
- const openHousesCheckbox = openHousesFilter.querySelector('input[type="checkbox"]');
- const keyWordSearchAny = block.querySelector('[name="Features"] .filter-radiobutton input[name="matchTagsAny"]');
- const keyWordSearchAll = block.querySelector('[name="Features"] .filter-radiobutton input[name="matchTagsAll"]');
- // events for filters with type toggle
- block.querySelectorAll('.filter-toggle').forEach((el) => {
- el.addEventListener('click', (e) => {
- toggleFilter(el);
- if (el.classList.contains('for-rent') || el.classList.contains('pending')) {
- updateFilters(el);
- }
- if (el.parentNode.classList.contains('top-menu')) {
- // search property if we click top level filter
- e.preventDefault();
- e.stopPropagation();
- window.dispatchEvent(event);
- }
- });
- });
- // select all property filters on select all
- block.querySelector('[name="PropertyType"] input[type="checkbox"]').addEventListener('change', () => {
- const isChecked = block.querySelector('[name="PropertyType"] input[type="checkbox"]').checked;
- propertyButtons.forEach((el) => {
- el.classList.toggle('selected', isChecked);
- });
- if (isChecked) {
- setFilterValue('PropertyType', '1,2,3,5,4,6');
- } else {
- removeFilterValue('PropertyType');
- }
- });
-
- // add logic to select property type on click
- propertyButtons.forEach((el) => {
- let value;
- el.addEventListener('click', () => {
- el.classList.toggle('selected');
- value = el.getAttribute('value');
- // eslint-disable-next-line no-unused-expressions
- el.classList.contains('selected') ? setFilterValue('PropertyType', value) : removeFilterValue('PropertyType', value);
- });
- });
-
- openHousesCheckbox.addEventListener('change', () => {
- openHousesFilter.classList.toggle('selected');
- if (!openHousesCheckbox.checked) {
- removeFilterValue('OpenHouses');
- } else if
- (openHousesFilter.querySelector('input[type="radio"]:checked')) {
- setFilterValue('OpenHouses', openHousesFilter.querySelector('input[type="radio"]:checked').getAttribute('value'));
- }
- });
- block.querySelectorAll('[name="OpenHouses"] input[type="radio"]').forEach((el) => {
- el.addEventListener('change', () => {
- if (el.checked) {
- setFilterValue('OpenHouses', el.getAttribute('value'));
- }
- if (el.getAttribute('value') === '7') {
- block.querySelector(`[name="OpenHouses"] input[value="${OpenHouses.ANYTIME.value}"]`).checked = false;
- } else {
- block.querySelector(`[name="OpenHouses"] input[value="${OpenHouses.ONLY_WEEKEND.value}"]`).checked = false;
- }
- });
- });
- block.querySelectorAll('#container-tags .close').forEach((el) => {
- el.addEventListener('click', (e) => {
- e.target.parentNode.remove();
- });
- });
- keyWordSearchAny.addEventListener('change', () => {
- if (keyWordSearchAny.checked) {
- keyWordSearchAll.checked = false;
- setFilterValue('MatchAnyFeatures', true);
- }
- });
-
- keyWordSearchAll.addEventListener('change', () => {
- if (keyWordSearchAll.checked) {
- keyWordSearchAny.checked = false;
- removeFilterValue('MatchAnyFeatures');
- }
- });
- // add key words to search
- block.querySelector('[name="Features"] .btn').addEventListener('click', () => {
- const keyword = block.querySelector('[name="Features"] input[type="text"]').value;
- if (keyword) {
- buildKeywordEl(keyword, removeFilterValue);
- setFilterValue('Features', keyword.trim());
- }
- });
- // year, square feet, sort input logic on additional filters
- block.querySelectorAll('.filter .select-item .tooltip-container').forEach((element) => {
- element.addEventListener('click', (e) => {
- const selectedElValue = element.innerText;
- const container = element.closest('section');
- const filter = element.closest('.filter');
- let name = filter.getAttribute('name');
- let value = element.getAttribute('data-value');
- container.querySelector('.highlighted').classList.remove('highlighted');
- element.classList.toggle('highlighted');
- const headerTitle = container.querySelector('.select-selected');
- if (name === 'Sort') {
- headerTitle.innerText = selectedElValue;
- } else {
- headerTitle.innerHTML = `${selectedElValue}`;
- }
- if (filter.querySelector('.multiple-inputs')) {
- if (name !== 'YearBuilt') {
- name = element.closest('section > div').querySelector('.select-selected').getAttribute('name');
- }
- if (name === 'YearBuilt') {
- const values = element.closest('.multiple-inputs').querySelectorAll('.select-selected');
- if (values[0].innerText === 'No Min' && values[1].innerText === 'No Max') {
- removeFilterValue('YearBuilt');
- return;
- }
- const minYear = values[0].innerText === 'No Min' ? 1899 : values[0].innerText;
- const maxYear = values[1].innerText === 'No Max' ? 2100 : values[1].innerText;
- value = `${minYear}-${maxYear}`;
- }
- element.closest('.select-item').classList.remove('show');
- }
- setFilterValue(name, value);
- if (name === 'Sort') {
- e.stopPropagation();
- e.preventDefault();
- window.dispatchEvent(event);
- }
- element.closest('.select-item').classList.remove('show');
- });
- });
-}
-
-addEventListeners();
diff --git a/blocks/property-search-bar/filters/top-delayed.js b/blocks/property-search-bar/filters/top-delayed.js
deleted file mode 100644
index 8973e943..00000000
--- a/blocks/property-search-bar/filters/top-delayed.js
+++ /dev/null
@@ -1,232 +0,0 @@
-import { abortSuggestions, getSuggestions } from '../../../scripts/apis/creg/creg.js';
-import { getAttributes, setSearchParams } from '../search/suggestion.js';
-import {
- populatePreSelectedFilters,
- setFilterValue,
-} from '../filter-processor.js';
-import {
- formatPriceLabel, closeTopLevelFilters, togglePropertyForm, hideFilter,
-} from '../common-function.js';
-import { getPropertiesCount } from '../../../scripts/search/results.js';
-
-const event = new Event('onFilterChange');
-
-const MORE_INPUT_NEEDED = 'Please enter at least 3 characters.';
-const NO_SUGGESTIONS = 'No suggestions found. Please modify your search.';
-const SEARCHING_SUGGESTIONS = 'Looking up suggestions...';
-
-function showFilter(element) {
- element.classList.add('open');
- element.querySelector('.search-results-dropdown').classList.remove('hide');
-}
-
-const updateSuggestions = (suggestions, target) => {
- // Keep the first item - required character entry count.
- const first = target.querySelector(':scope li');
- target.replaceChildren(first, ...suggestions);
-};
-
-const createPriceList = (d) => {
- let optionlist = '';
- const k = [10, 100, 1E3, 1E4, 1E5, 1E6];
- // eslint-disable-next-line no-plusplus
- if (d) for (let m = 1; m <= 6; m++) optionlist += ``;
- return optionlist;
-};
-
-function addChangeHandler(filter) {
- let value;
- filter.forEach((el) => {
- el.addEventListener('change', () => {
- if (el.checked) {
- filter.forEach((input) => {
- if (input.id !== el.id) input.checked = false;
- });
- value = el.value === 'Any' ? '' : el.value;
- setFilterValue(el.closest('.filter').getAttribute('name'), value);
- }
- });
- });
-}
-
-const buildSuggestions = (suggestions) => {
- const lists = [];
- let attr;
- suggestions.forEach((category) => {
- const list = document.createElement('li');
- list.classList.add('list-title');
- list.textContent = category.displayText;
- lists.push(list);
- const ul = document.createElement('ul');
- list.append(ul);
- category.results.forEach((result) => {
- const li = document.createElement('li');
- attr = getAttributes(result);
- Object.keys(attr).forEach((key) => {
- li.setAttribute(key, attr[key]);
- });
- li.textContent = result.SearchParameter;
- ul.append(li);
- });
- });
-
- return lists;
-};
-
-/**
- * Handles the input changed event for the text field. Will add suggestions based on user input.
- *
- * @param {Event} e the change event
- * @param {HTMLElement} target the container in which to add suggestions
- */
-const inputChanged = (e, target) => {
- const { value } = e.currentTarget;
- if (value.length > 0) {
- e.currentTarget.closest('.search-bar').classList.add('show-suggestions');
- } else {
- e.currentTarget.closest('.search-bar').classList.remove('show-suggestions');
- }
-
- if (value.length <= 2) {
- abortSuggestions();
- target.querySelector(':scope > li:first-of-type').textContent = MORE_INPUT_NEEDED;
- updateSuggestions([], target);
- } else {
- target.querySelector(':scope > li:first-of-type').textContent = SEARCHING_SUGGESTIONS;
- getSuggestions(value)
- .then((suggestions) => {
- if (!suggestions) {
- // Undefined suggestions means it was aborted, more input coming.
- updateSuggestions([], target);
- return;
- }
- if (suggestions.length) {
- updateSuggestions(buildSuggestions(suggestions), target);
- } else {
- target.querySelector(':scope > li:first-of-type').textContent = NO_SUGGESTIONS;
- }
- });
- }
-};
-
-const suggestionSelected = (e, block) => {
- e.stopPropagation();
- e.preventDefault();
- const searchParameter = e.target.getAttribute('search-parameter');
- const keyword = e.target.getAttribute('search-input');
- if (!searchParameter) {
- return;
- }
- setSearchParams(e.target);
- block.querySelector('input[name="keyword"]').value = keyword;
- block.querySelector('.search-bar').classList.remove('show-suggestions');
-
- window.dispatchEvent(event);
-};
-
-function addEventListeners() {
- const block = document.querySelector('.property-search-bar.block');
- const priceRangeInputs = block.querySelector('.container-item[name="Price"] .multiple-inputs');
-
- // update top menu input placeholder on click
- block.querySelectorAll('.container-item .select-item .tooltip-container').forEach((element) => {
- element.addEventListener('click', () => {
- let selectedElValue = element.innerText;
- const value = element.getAttribute('data-value');
- const container = element.closest('.container-item');
- let name = container.getAttribute('name');
- container.querySelector('.highlighted').classList.remove('highlighted');
- element.classList.toggle('highlighted');
- const headerTitle = container.querySelector('.header .title');
- if (container.querySelector('.multiple-inputs')) {
- // logic
- element.closest('section > div').querySelector('.select-selected').innerHTML = selectedElValue;
- name = element.closest('section > div').querySelector('.select-selected').getAttribute('name');
- const headerItems = container.querySelectorAll('.multiple-inputs .select-selected');
- const fromSelectedValue = headerItems[0].innerText;
- const toSelectedValue = headerItems[1].innerText;
- if (fromSelectedValue === 'No Min' && toSelectedValue === 'No Max') {
- selectedElValue = 'square feet';
- } else {
- selectedElValue = `${fromSelectedValue}-${toSelectedValue}`;
- }
- element.closest('.select-item').classList.remove('show');
- } else {
- hideFilter(container);
- }
- setFilterValue(name, value);
- headerTitle.innerHTML = `${selectedElValue}`;
- window.dispatchEvent(event);
- });
- });
-
- // add logic on price range change
- priceRangeInputs.addEventListener('keyup', (e) => {
- const minPrice = priceRangeInputs.querySelector('[name="MinPrice"]').value;
- const maxPrice = priceRangeInputs.querySelector('[name="MaxPrice"]').value;
- // display datalist
- const activeElement = e.target.closest('.price-range-input');
- const name = activeElement.getAttribute('name');
- const { value } = activeElement;
- activeElement.list.innerHTML = createPriceList(activeElement.value);
-
- // update label
- block.querySelector('[name="Price"] .title > span').innerText = formatPriceLabel(minPrice, maxPrice);
- setFilterValue(name, value);
- window.dispatchEvent(event);
- });
-
- block.querySelectorAll('.container-item .header').forEach((selectedFilter) => {
- selectedFilter.addEventListener('click', () => {
- const isOpened = selectedFilter.parentElement.classList.contains('open');
- closeTopLevelFilters();
- if (!isOpened) {
- showFilter(selectedFilter.parentElement);
- }
- });
- });
- // baths and beds
- addChangeHandler(block.querySelectorAll('[name="MinBathroomsTotal"] input'));
- addChangeHandler(block.querySelectorAll('[name="MinBedroomsTotal"] input'));
-
- // open additional filters
- block.querySelector('.filter-container').addEventListener('click', () => {
- togglePropertyForm();
- const overlay = document.querySelector('.property-search-bar.block .overlay');
- const toggledOnClose = overlay.classList.contains('hide');
- closeTopLevelFilters(false);
- if (toggledOnClose) {
- setFilterValue('MinPrice', document.querySelector('.filter [name="MinPrice"]').value);
- setFilterValue('MaxPrice', document.querySelector('.filter [name="MaxPrice"]').value);
- }
- populatePreSelectedFilters(toggledOnClose);
- });
-
- block.querySelectorAll('.select-selected').forEach((el) => {
- let isOpened;
- el.addEventListener('click', () => {
- if (el.closest('.multiple-inputs').getAttribute('name') === 'Sort') {
- isOpened = document.querySelector('[name="Sort"] .select-item').classList.contains('show');
- closeTopLevelFilters();
- if (isOpened) {
- document.querySelector('[name="Sort"] .select-item').classList.add('show');
- }
- }
- el.closest('section > div').querySelector('.select-item').classList.toggle('show');
- });
- });
- // suggestions
- const suggestionsTarget = block.querySelector('.suggester-input .suggester-results');
- block.querySelector('.search-listing-block [name="keyword"]').addEventListener('input', (e) => {
- inputChanged(e, suggestionsTarget);
- });
- suggestionsTarget.addEventListener('click', (e) => {
- suggestionSelected(e, block);
- });
- window.addEventListener('onResultUpdated', () => {
- const count = getPropertiesCount();
- block.querySelector('.total-results > div').textContent = `Showing ${count} of ${count} Properties`;
- });
-}
-
-addEventListeners();
diff --git a/blocks/property-search-bar/filters/top-menu.js b/blocks/property-search-bar/filters/top-menu.js
deleted file mode 100644
index 6ee162a1..00000000
--- a/blocks/property-search-bar/filters/top-menu.js
+++ /dev/null
@@ -1,168 +0,0 @@
-import {
- getPlaceholder,
- addRangeOption,
- addOptions,
- TOP_LEVEL_FILTERS,
- getConfig,
- processSearchType,
- getFilterLabel,
-} from '../common-function.js';
-
-function observeFilters() {
- const script = document.createElement('script');
- script.id = crypto.randomUUID();
- script.type = 'text/partytown';
- script.innerHTML = `
- const script = document.createElement('script');
- script.type = 'module';
- script.src = '${window.hlx.codeBasePath}/blocks/property-search-bar/filters/top-delayed.js';
- document.head.append(script);
- `;
- document.head.append(script);
-}
-
-function buildTotalResults() {
- const wrapper = document.createElement('div');
- wrapper.classList.add('total-results');
- wrapper.innerHTML = '';
- return wrapper;
-}
-
-function buildMapToggle() {
- const wrapper = document.createElement('div');
- wrapper.classList.add('map-toggle', 'flex-row', 'center');
- wrapper.innerHTML = `
-
-
- grid view
- `;
- return wrapper;
-}
-
-function buildButton(label, primary = false) {
- const button = document.createElement('div');
- button.classList.add('button-container');
- button.innerHTML = `
-
- ${label}
- `;
- return button;
-}
-
-function buildFilterToggle() {
- const wrapper = document.createElement('div');
- wrapper.setAttribute('name', 'AdditionalFilters');
- wrapper.classList.add('filter-container', 'flex-row', 'center', 'bl');
- wrapper.innerHTML = `
-
-
-
- `;
- return wrapper;
-}
-
-function buildSortByEl() {
- const filterName = 'Sort';
- const label = getFilterLabel(filterName);
- const defaultValue = 'Price (Hi-Lo)';
- const options = addOptions(filterName, defaultValue, 'multi', filterName);
- const dropdownContainer = document.createElement('div');
- dropdownContainer.classList.add('flex-row', 'multiple-inputs', 'filter');
- dropdownContainer.setAttribute('name', filterName);
- dropdownContainer.innerHTML = `
- ${options}
`;
- dropdownContainer.querySelector('.select-selected').classList.add('text-up');
- dropdownContainer.querySelectorAll('.select-item li').forEach((el) => {
- el.classList.add('text-up');
- if (el.getAttribute('data-value') === '') {
- el.remove();
- }
- if (el.innerText === defaultValue) {
- el.classList.add('highlighted');
- }
- });
- return dropdownContainer;
-}
-
-function buildTopFilterPlaceholder(filterName) {
- const dropdownContainer = document.createElement('div');
- const { type } = TOP_LEVEL_FILTERS[filterName];
- let label = getFilterLabel(filterName);
- let options = addRangeOption(filterName);
- if (type === 'select') {
- options = addOptions(filterName, `Any ${label}`);
- label = `Any ${label}`;
- }
- dropdownContainer.classList.add('bl', 'container-item');
- dropdownContainer.setAttribute('name', filterName);
- dropdownContainer.innerHTML = `
- ${options}
`;
-
- return dropdownContainer;
-}
-function buildFilterSearchTypesElement() {
- const wrapper = document.createElement('div');
- let el;
- wrapper.classList.add('filter', 'flex-row', 'center', 'top-menu');
- wrapper.setAttribute('name', 'ApplicationType');
- getConfig('ApplicationType').forEach((value) => {
- el = processSearchType(value);
- el.classList.add('center', 'ml-1');
- el.querySelector('label').classList.add('fs-xs');
- wrapper.append(el);
- });
- return wrapper;
-}
-
-async function build() {
- const wrapper = document.createElement('div');
- const container = document.createElement('div');
- const div = document.createElement('div');
- const bfContainer = document.createElement('div');
- const bfRightSection = document.createElement('div');
- const filterContainer = document.createElement('div');
- filterContainer.classList.add('result-filters', 'flex-row');
- bfRightSection.classList.add('flex-row', 'space-between');
- bfContainer.classList.add('bf-container');
- container.classList.add('search-listing-container', 'flex-row');
- wrapper.classList.add('search-listing-block');
-
- const primaryFilters = document.createElement('div');
- primaryFilters.classList.add('primary-search', 'flex-row');
- primaryFilters.innerHTML = ` `;
- wrapper.prepend(primaryFilters, buildButton('Search', true));
- Object.keys(TOP_LEVEL_FILTERS).forEach((filter) => {
- const filterElement = buildTopFilterPlaceholder(filter);
- wrapper.append(filterElement);
- });
- wrapper.append(buildFilterToggle(), buildButton('save search', true));
- div.append(wrapper);
- bfRightSection.append(buildSortByEl(), buildMapToggle());
- bfContainer.append(buildFilterSearchTypesElement(), bfRightSection);
- filterContainer.append(buildTotalResults(), bfContainer);
- container.append(div, filterContainer);
- observeFilters();
- return container;
-}
-
-const topMenu = {
- build,
-};
-
-export default topMenu;
diff --git a/blocks/property-search-bar/property-search-bar.css b/blocks/property-search-bar/property-search-bar.css
index 30f82d71..d00c5d1b 100644
--- a/blocks/property-search-bar/property-search-bar.css
+++ b/blocks/property-search-bar/property-search-bar.css
@@ -1,1017 +1,958 @@
-@import url('./search-results-dropdown.css');
+/* stylelint-disable no-descending-specificity */
+/** Override global settings **/
main .section.property-search-bar-container {
- max-width: 100vw;
- padding: 0;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.property-search-template .section.property-search-bar-container {
- margin-bottom: 0;
- position: sticky;
- top: 65px;
- z-index: 10;
- background: white;
+ max-width: 100vw;
+ padding: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 0;
}
-.property-search-template header {
- position: sticky;
- top: 0;
- background: white;
- z-index: 11;
+main .section.property-search-bar-container .property-search-bar-wrapper {
+ padding: 0;
}
-.property-search-template footer {
- background-color: var(--light-grey);
- border-top: 1px solid var(--platinum);
- position: relative;
- z-index: 4;
-}
+/** End overrides **/
.property-search-bar.block {
- width: 100vw;
+ width: 100vw;
+ background-color: var(--primary-color);
}
-main .section .property-search-bar .mb-1 {
- margin-bottom: 1em;
+.property-search-bar.block .search-form-wrapper form {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ height: 50px;
+ padding: 0 15px;
+ margin: 0 auto;
+ max-width: 1500px;
}
-main .section .property-search-bar .mt-1 {
- margin-top: 1em;
+.property-search-bar.block .search-form-wrapper span.icon {
+ height: 20px;
+ width: 20px;
}
-main .section .property-search-bar .ml-1 {
- margin-left: 1em;
+.property-search-bar.block .search-form-wrapper span.icon > img {
+ height: 100%;
+ width: 100%;
+ filter: brightness(0) invert(1);
}
-main .section .property-search-bar .mr-1 {
- margin-right: 1em;
+.property-search-bar.block form .search-bar {
+ position: relative;
+ flex-grow: 1;
+ max-width: 550px;
}
-main .section .property-search-bar .fs-1 {
- font-size: var(--body-font-size-s)
+.property-search-bar.block .search-bar input[name="keyword"] {
+ padding: 10px;
+ border: 1px solid var(--grey);
+ height: 36px;
+ width: 100%;
+ background-color: rgba(194 153 175 / 30%);
+ color: var(--primary-light);
+ letter-spacing: normal;
+ font-size: var(--body-font-size-s);
}
-main .section .property-search-bar .c-w {
- color:var(--white);
+.property-search-bar.block .search-bar input[name="keyword"]::placeholder {
+ color: var(--primary-light);
}
-main .section .property-search-bar .center {
- text-align: center;
+.property-search-bar.block .search-bar input[name="keyword"]:focus {
+ color: var(--body-color);
+ background-color: var(--white);
}
-main .section .property-search-bar .bl {
- border-left: 1px solid var(--black);
+.property-search-bar.block form .result-filters {
+ display: flex;
+ align-items: center;
}
-main .section .property-search-bar .fs-xs {
- font-size: var(--body-font-size-xs);
+.property-search-bar.block form .result-filters .selected span {
+ color: var(--white);
}
-main .section .property-search-bar .flex-column {
- display: flex;
- flex-direction: column;
-}
-main .section .property-search-bar .flex-row {
- display: flex;
- flex-direction: row;
+.property-search-bar.block .result-filters .range-wrapper,
+.property-search-bar.block .result-filters .select-wrapper {
+ display: none;
+ position: relative;
+ align-items: center;
+ margin: 0;
+ padding: 0 15px;
+ height: 50px;
+ text-overflow: ellipsis;
+ border-left: 1px solid var(--black);
}
-main .section .property-search-bar .flex-row.center {
- justify-content: center;
- align-items: center
+.property-search-bar.block .result-filters .range-wrapper:last-of-type {
+ border-right: 1px solid var(--black);
}
-main .section .property-search-bar .flex-row.vertical-center {
- align-items: center;
-}
-main .section .property-search-bar .flex-row.space-between {
- justify-content: space-between;
+.property-search-bar.block .result-filters .range-wrapper .select-wrapper {
+ border: none;
+ padding: 0;
+ height: 45px;
}
-.property-search-bar.block .search-listing-container {
- justify-content: flex-start;
- flex-wrap: wrap;
-
+.property-search-bar.block .result-filters .select-wrapper select,
+.property-search-bar.block .advanced-filters .misc .select-wrapper select {
+ display: none;
}
-.property-search-bar.block .total-results {
- flex-basis: 100%;
- max-width: var(--normal-page-width);
- padding: 20px 0 0 30px;
- color: var(--body-color);
- font-size: var(--body-font-size-s);
+.property-search-bar.block .result-filters .range-wrapper > .selected,
+.property-search-bar.block .result-filters .select-wrapper > .selected {
+ display: flex;
+ position: relative;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ height: 100%;
+ min-width: 100px;
+ line-height: 40px;
+ color: var(--white);
}
-main .section .property-search-bar .overlay {
- position: absolute;
- overflow-y: auto;
- left: 0;
- top: 50px;
- width: 100vw;
- height: 100vh;
- background-color: #212529;
- opacity: .5;
- z-index: 998;
+.property-search-bar.block .result-filters .range-wrapper .select-wrapper > .selected {
+ padding: 0 15px;
+ width: 140px;
+ height: 45px;
+ color: var(--body-color);
+ border: 1px solid var(--platinum);
}
-main .section .property-search-bar .hide {
- display: none;
+.property-search-bar.block .result-filters .range-wrapper > .selected span,
+.property-search-bar.block .result-filters .select-wrapper > .selected span {
+ display: block;
+ font-size: var(--body-font-size-xs);
+ font-weight: var(--font-weight-semibold);
+ letter-spacing: unset;
+ line-height: normal;
+ text-transform: uppercase;
}
-main .section .property-search-bar .button-container {
- margin: 0 15px 0 8px;
+.property-search-bar.block .result-filters .range-wrapper .select-wrapper > .selected span {
+ text-transform: none;
}
-.property-search-bar.block .container-item *,
-.property-search-bar.block .filter-block * ,
-.property-search-bar.block .filter-buttons * {
- font-size: var(--body-font-size-s);
+.property-search-bar.block .result-filters .range-wrapper > .selected::after,
+.property-search-bar.block .result-filters .select-wrapper > .selected::after {
+ display: block;
+ content: '\f0d7';
+ font-family: var(--font-family-fontawesome);
+ text-align: right;
+ width: 25px;
}
-.property-search-bar.block .search-listing-block .button-container * {
- font-size: var(--body-font-size-xs);
+.property-search-bar.block .result-filters .range.open > .selected::after,
+.property-search-bar.block .result-filters .select-wrapper.open > .selected::after {
+ content: '\f0d8';
}
-.property-search-bar.block .filter-container li,
-.property-search-bar.block .filter-container input,
-.property-search-bar.block .filter-container label,
-.property-search-bar.block .filter-container a,
-.property-search-bar.block .filter-container svg{
- cursor: pointer;
+.property-search-bar.block .result-filters .range-wrapper .range-items,
+.property-search-bar.block .result-filters .select-wrapper .select-items {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ padding: 10px;
+ background-color: var(--white);
+ z-index: 501;
+ box-shadow: 0 .5rem 1rem rgba(0 0 0 / 15%);
}
-.property-search-bar.block .result-filters {
- flex-wrap: wrap;
- width: 100%;
+.property-search-bar.block .result-filters .range-wrapper .select-wrapper .select-items {
+ padding: 0;
+ max-height: 185px;
+ overflow-y: scroll;
+ width: 100%;
+ border: 1px solid var(--platinum);
}
-.property-search-bar.block .bf-container {
- display: flex;
- flex-basis: 100%;
- max-width: var(--normal-page-width);
- justify-content: flex-end;
+.property-search-bar.block .result-filters .range-wrapper:first-of-type .range-items {
+ left: 0;
+ right: unset;
}
-.property-search-bar.block .result-filters >div:last-of-type {
- width: 100%;
+.property-search-bar.block .result-filters .range-wrapper .range-items {
+ left: unset;
+ right: 0;
+ color: var(--body-color);
}
-.property-search-bar.block .filter-container a {
- display: block;
- width: 35px;
- height: 35px;
- position: relative;
+.property-search-bar.block .result-filters .select-wrapper.open .select-items {
+ display: block;
}
-main .section .property-search-bar .button-container a {
- text-transform: uppercase;
+.property-search-bar.block .result-filters .select-wrapper .select-items li {
+ display: flex;
+ font-size: var(--body-font-size-s);
+ padding: 4px 15px;
+ line-height: 30px;
+ cursor: pointer;
+ border-left: 1px solid var(--platinum);
+ border-right: 1px solid var(--platinum);
}
-.property-search-bar.block .property-search-bar-wrapper {
- width: 100%;
+.property-search-bar.block .result-filters .range-wrapper .select-wrapper .select-items li {
+ border: none;
+ padding: 4px 8px;
}
-.property-search-bar.block .search-listing-block {
- padding: 0 15px;
- text-align: center;
- display: flex;
- height: 50px;
- width: 100vw;
- background-color: var(--primary-color);
- color: var(--white);
- align-items: center;
- cursor: pointer;
- justify-content: flex-start;
+.property-search-bar.block .result-filters > .select-wrapper .select-items li:first-of-type {
+ border-top: 1px solid var(--platinum);
}
-.property-search-bar.block .search-listing-block span{
- color: var(--white);
+.property-search-bar.block .result-filters > .select-wrapper .select-items li:last-of-type {
+ border-bottom: 1px solid var(--platinum);
}
-.property-search-bar.block .search-listing-container > div:first-of-type {
- width: 100%;
- justify-content: flex-start;
+.property-search-bar.block .result-filters .select-wrapper .select-items li:hover {
+ color: var(--body-color);
+ background-color: var(--light-grey);
}
-.property-search-bar.block [name="ApplicationType"] {
- padding: 0 20px;
+.property-search-bar.block .result-filters .select-wrapper .select-items li.selected {
+ color: var(--body-color);
+ background-color: var(--light-grey);
}
-.property-search-bar.block .result-filters [name="ApplicationType"] {
- display: none
+.property-search-bar.block .result-filters .range-wrapper.open .range-items {
+ display: flex;
+ align-items: center;
}
-.property-search-bar.block .result-filters .map-toggle {
- display: none
+.property-search-bar.block .result-filters .range-wrapper .range-items .input-dropdown select {
+ display: none;
+ visibility: hidden;
}
-.property-search-bar.block .filter {
- padding: 20px;
+.property-search-bar.block .result-filters .range-wrapper .range-items .input-dropdown input {
+ padding: 10px;
+ height: 45px;
+ width: 115px;
+ font-size: var(--body-font-size-s);
+ letter-spacing: normal;
+ border: 1px solid var(--platinum);
}
-.property-search-bar.block .container-item .header {
- position: relative;
- display: flex;
- justify-content: center;
-
+.property-search-bar.block .result-filters .range-wrapper .range-items > span {
+ text-align: center;
+ margin: 0 16px;
+ text-transform: uppercase;
+ font-weight: var(--font-weight-light);
}
-main .section .property-search-bar .title {
- font-size: var(--body-font-size-s);
- white-space: pre;
- line-height: 50px;
- position: relative;
-}
-
-/* arrow near dropdown start */
-main .section .property-search-bar .title::after {
- display: inline;
- position: absolute;
- right: -20px;
- width: 10px;
- content: '\f0d7';
- font-family: var(--font-family-fontawesome);
- height: unset;
- color: var(--white);
+.property-search-bar.block .result-filters a.filter {
+ display: flex;
+ margin: 0 8px;
+ height: 35px;
+ width: 35px;
+ align-items: center;
+ justify-content: center;
}
-main .section .property-search-bar .open .title::after {
- content: '\f0d8';
+.property-search-bar.block .result-filters a.filter span.icon-close-x-white,
+.property-search-bar.block .result-filters a.filter.open span.icon-filter-white {
+ display: none;
}
-/* end */
-
-.property-search-bar.block .filter-buttons {
- padding: 15px;
- justify-content: center;
- box-shadow: 0 0 4px 0 rgb(0 0 0 / 50%);
- width: 100vw;
- position: fixed;
- bottom: 0;
- left: 0;
- background-color: var(--white);
- height: 64px;
- margin: 0;
- z-index: 1000;
+.property-search-bar.block .result-filters a.filter span.icon-filter-white,
+.property-search-bar.block .result-filters a.filter.open span.icon-close-x-white {
+ display: block;
}
-.property-search-bar.block .btn-map-toggle > span {
- display: block;
- font-size: var(--body-font-size-xs);
- height: 35px;
- background: var(--white);
- color: var(--body-color);
- border: 1px solid var(--grey);
- padding: 0 10px;
- line-height: 35px;
+.property-search-bar.block form a.save-search {
+ display: none;
}
-main .section .property-search-bar .btn.btn-secondary {
- background: #fff;
- color: var(--black);
- border: 1px solid var(--black);
-}
-main .section .property-search-bar .btn.btn-primary {
- background: var(--primary-color);
- color: var(--white);
- border: 1px solid var(--grey);
- min-width: 70px;
- padding: 10px;
- font-size: var(--body-font-size-xs);
- white-space: nowrap;
+.property-search-bar.block form a.search-submit {
+ height: 36px;
+ width: 36px;
+ padding: 7px;
+ color: var(--body-color);
+ border: 1px solid var(--grey);
+ background-color: transparent;
}
-main .section .property-search-bar .filter-buttons .btn,
-.property-search-bar.block [name="Features"] button{
- padding: 7px 25px;
- margin-right: 10px;
- vertical-align: middle;
+.property-search-bar.block .advanced-filters {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ height: calc(100vh - var(--nav-height) - 50px);
+ overflow-y: scroll;
+ z-index: 500;
+ background-color: var(--white);
}
-main .section .property-search-bar .title span {
- position: relative;
- letter-spacing: var(--letter-spacing-s);
+.property-search-bar.block .advanced-filters.open {
+ display: block;
}
-main .section .property-search-bar .btn.btn-secondary:hover {
- color: var(--black);
- border: 1px solid var(--grey);
- box-shadow: none;
+.property-search-bar.block .advanced-filters > div {
+ padding: 20px;
+ border-bottom: 1px solid var(--grey);
}
-.property-search-bar.block [name="Features"] button {
- background: var(--white);
- border: 1px solid var(--grey);
+.property-search-bar.block .advanced-filters .listing-types {
+ display: flex;
+ flex-wrap: wrap;
}
-.property-search-bar.block [name="Sort"] .title span {
- font-size: var(--body-font-size-s);
- letter-spacing: normal;
+.property-search-bar.block .advanced-filters .listing-types > label {
+ flex-basis: 100%;
}
-.property-search-bar.block .filter-buttons .btn-primary:hover,
-.property-search-bar.block .filter-buttons .btn-primary span:hover {
- background-color: var(--white);
- color: var(--body-color);
+.property-search-bar.block .advanced-filters .listing-types > div {
+ flex-basis: 50%;
}
-.property-search-bar.block .input-container input {
- padding: 10px;
- border: 1px solid var(--grey);
- letter-spacing: normal;
- height: 36px;
- background-color: rgb(241 241 241 / 10%);
- width: 100%;
-}
-.property-search-bar.block .input-dropdown input {
- width: 100%;
- height: 55px;
- padding: 10px;
- border: 1px solid var(--grey);
- color: var(--black);
+.property-search-bar.block .advanced-filters .attributes > div:not(:last-of-type) {
+ margin-bottom: 1.5rem;
}
-.property-search-bar.block .filter-block input,
-.property-search-bar.block .filter-block input::placeholder,
-.property-search-bar.block .filter-block .select-selected {
- font-weight: 300;
- font-size: var(--body-font-size-m);
- color: var(--input-placeholder);
- letter-spacing: normal;
+.property-search-bar.block .advanced-filters .select-wrapper {
+ position: relative;
}
-.property-search-bar.block input:focus {
- background: var(--white);
- color: var(--body-color);
- outline: none;
+.property-search-bar.block .advanced-filters .select-wrapper > .selected {
+ display: flex;
+ position: relative;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ height: 100%;
+ min-width: 100px;
+ line-height: 40px;
+ color: var(--white);
}
-.property-search-bar.block .search-bar input:first-of-type {
- padding: 10px;
- border: 1px solid var(--grey);
- letter-spacing: normal;
- height: 36px;
- background-color: rgb(241 241 241 / 10%);
- width: 100%;
- color: var(--white);
+.property-search-bar.block .advanced-filters .attributes .select-wrapper > .selected,
+.property-search-bar.block .advanced-filters .misc .select-wrapper > .selected {
+ padding: 0 15px;
+ height: 45px;
+ color: var(--body-color);
+ border: 1px solid var(--platinum);
}
-.property-search-bar.block .filter .multiple-inputs .input-dropdown,
-.property-search-bar.block .filter .multiple-inputs section{
- flex: 0 0 41.6667%;
- max-width: 41.6667%;
+.property-search-bar.block .advanced-filters .attributes .select-wrapper > .selected span,
+.property-search-bar.block .advanced-filters .misc .select-wrapper > .selected span {
+ overflow: hidden;
+ font-size: var(--body-font-size-s);
+ font-weight: var(--font-weight-light);
+ line-height: var(--line-height-xs);
}
-.property-search-bar.block .multiple-inputs .range-label {
- margin: 0 1rem;
- color: var(--body-color);
- font-weight: 300;
- font-size: 15px;
- letter-spacing: var(--letter-spacing-xxs);
+.property-search-bar.block .advanced-filters .select-wrapper > .selected::after {
+ display: block;
+ content: '\f0d7';
+ font-family: var(--font-family-fontawesome);
+ text-align: right;
+ width: 15px;
}
-.property-search-bar.block .filter .multiple-inputs .range-label {
- flex: 0 0 16.6667%;
- max-width: 16.6667%;
- text-align: center;
+.property-search-bar.block .advanced-filters .select-wrapper.open > .selected::after {
+ content: '\f0d8';
}
-.property-search-bar.block .filter .multiple-inputs .input-dropdown input {
- width: 100%
+.property-search-bar.block .advanced-filters .select-wrapper .select-items {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ padding: 10px;
+ background-color: var(--white);
+ z-index: 501;
+ box-shadow: 0 .5rem 1rem rgba(0 0 0 / 15%);
}
-.property-search-bar.block .filter-checkbox .checkbox {
- cursor: pointer;
- height: 24px;
- width: 24px;
- border: 1px solid var(--grey);
- margin-right: 0.5rem;
- position: relative;
+.property-search-bar.block .advanced-filters .attributes .select-wrapper .select-items,
+.property-search-bar.block .advanced-filters .misc .select-wrapper .select-items {
+ padding: 0;
+ max-height: 185px;
+ overflow-y: scroll;
+ width: 100%;
+ border: 1px solid var(--platinum);
}
-.property-search-bar.block .filter .filter-toggle .checkbox {
- width: 24px;
- height: 16px;
- border-radius: 100px;
- position: relative;
- border: 1px solid #b4b4b4;
- background: var(--white);
+.property-search-bar.block .advanced-filters .select-wrapper.open .select-items {
+ display: block;
}
-.property-search-bar.block .filter .filter-toggle .checkbox::before {
- content: '';
- position: absolute;
- right: 2px;
- top: 2px;
- height: 10px;
- width: 10px;
- border-radius: 10px;
- z-index: 1;
- background: #b4b4b4;
+.property-search-bar.block .advanced-filters .select-wrapper .select-items li {
+ display: flex;
+ font-size: var(--body-font-size-s);
+ padding: 4px 15px;
+ line-height: 30px;
+ cursor: pointer;
+ border-left: 1px solid var(--platinum);
+ border-right: 1px solid var(--platinum);
}
-.property-search-bar.block .filter .filter-toggle .checkbox.checked {
- background: var(--body-color);
- border: 1px solid transparent
+.property-search-bar.block .advanced-filters .attributes .select-wrapper .select-items li,
+.property-search-bar.block .advanced-filters .misc .select-wrapper .select-items li {
+ border: none;
+ padding: 4px 8px;
}
-.property-search-bar.block .filter .filter-toggle .checkbox.checked::before{
- transform: translateX(-8px);
- background: var(--white);
+.property-search-bar.block .advanced-filters .select-wrapper .select-items li:hover {
+ color: var(--body-color);
+ background-color: var(--light-grey);
}
-.property-search-bar.block .filter .filter-toggle.disabled {
- pointer-events: none;
- opacity: .3;
+.property-search-bar.block .advanced-filters .select-wrapper .select-items li.selected{
+ color: var(--body-color);
+ background-color: var(--light-grey);
}
-.property-search-bar.block .container {
- margin-right: 0.5rem;
- margin-left: 0.5rem;
- padding: 0 15px;
+.property-search-bar.block .advanced-filters .attributes .price .range-items,
+.property-search-bar.block .advanced-filters .attributes .sqft .range-items,
+.property-search-bar.block .advanced-filters .misc .year-range .range-items {
+ display: flex;
+ align-items: center;
+ color: var(--body-color);
}
-.property-search-bar.block .container-item {
- margin: 0 0.5rem;
- order: 3;
- padding: 0 15px;
+.property-search-bar.block .advanced-filters .attributes .sqft .range-items > *,
+.property-search-bar.block .advanced-filters .misc .year-range .range-items > * {
+ flex: 1 1 auto;
}
-.property-search-bar.block .primary-search {
- width: 550px;
- order: 1;
+.property-search-bar.block .advanced-filters .attributes .range-items .select-wrapper select,
+.property-search-bar.block .advanced-filters .year-range .range-items .select-wrapper select {
+ display: none;
+ visibility: hidden;
}
-.property-search-bar.block .square-feet {
- border-right: 1px solid var(--black);
- padding-right: 30px;
+.property-search-bar.block .advanced-filters .attributes .price .range-items > span,
+.property-search-bar.block .advanced-filters .attributes .sqft .range-items > span,
+.property-search-bar.block .advanced-filters .misc .year-range .range-items > span {
+ text-align: center;
+ margin: 0 16px;
+ text-transform: uppercase;
+ font-weight: var(--font-weight-light);
}
-.property-search-bar.block .filter-container {
- width: 35px;
- order: 2;
+
+.property-search-bar.block .advanced-filters .attributes .price .range-items .input-dropdown {
+ flex: 1 1 auto;
}
-.property-search-bar.block .filter-container a svg {
- width: 21px;
- height: 20px;
- position: absolute;
- top: calc(50% - 10px);
- left: calc(50% - 10px);
+.property-search-bar.block .advanced-filters .attributes .price .range-items .input-dropdown input {
+ padding: 10px;
+ height: 50px;
+ width: 100%;
+ font-size: var(--body-font-size-s);
+ letter-spacing: normal;
+ border: 1px solid var(--platinum);
}
-.property-search-bar.block .label-image {
- height: auto;
- width: 25px;
- margin-right: 5px;
- padding: 5px;
+.property-search-bar.block .advanced-filters .attributes .bedrooms ul,
+.property-search-bar.block .advanced-filters .attributes .bathrooms ul {
+ display: flex;
+ align-items: center;
}
-.property-search-bar.block .search-bar {
- flex-grow: 1;
- position: relative;
+.property-search-bar.block .advanced-filters .attributes .bedrooms ul li,
+.property-search-bar.block .advanced-filters .attributes .bathrooms ul li {
+ flex: 1;
+ border: 1px solid var(--platinum);
+ border-right: none;
}
-.property-search-bar.block .input-container {
- flex-grow: 1;
+.property-search-bar.block .advanced-filters .attributes .bedrooms ul li:last-of-type,
+.property-search-bar.block .advanced-filters .attributes .bathrooms ul li:last-of-type {
+ border-right: 1px solid var(--platinum);
}
-.property-search-bar.block .select-item {
- padding: 10px;
- box-shadow: 0 0.5rem 1rem rgb(0 0 0 / 15%);
- width: 120px;
- color: var(--body-color);
- position: absolute;
- background: var(--white);
- z-index: 99;
- white-space: nowrap;
- left: -15px;
- display: none;
+ .property-search-bar.block .advanced-filters .attributes .bedrooms ul li input[type="radio"],
+.property-search-bar.block .advanced-filters .attributes .bathrooms ul li input[type="radio"] {
+ display: none;
}
-.property-search-bar.block .open .select-item {
- display: block;
+.property-search-bar.block .advanced-filters .attributes .bedrooms ul li label,
+.property-search-bar.block .advanced-filters .attributes .bathrooms ul li label {
+ display: flex;
+ height: 50px;
+ margin: 0;
+ align-items: center;
+ justify-content: center;
+ font-size: var(--body-font-size-m);
+ font-weight: var(--font-weight-light);
+ color: var(--body-color);
+ text-transform: capitalize;
}
-.property-search-bar.block .open .multiple-inputs .select-item {
- display: none;
+.property-search-bar.block .advanced-filters .attributes .bedrooms ul li input[type="radio"]:checked+label,
+.property-search-bar.block .advanced-filters .attributes .bathrooms ul li input[type="radio"]:checked+label {
+ background-color: var(--light-grey);
}
-.property-search-bar.block .multiple-inputs .select-item.show {
- display: block;
+.property-search-bar.block .advanced-filters .section-label {
+ display: block;
+ margin-bottom: 16px;
+ font-weight: var(--font-weight-bold);
+ font-size: var(--body-font-size-xs);
+ letter-spacing: var(--letter-spacing-xs);
+ text-transform: uppercase;
}
-.property-search-bar.block [name="Sort"].multiple-inputs .select-item,
-.property-search-bar.block .search-results-dropdown .multiple-inputs .select-item{
- width: 125px;
- left: 0;
- border: 1px solid var(--platinum);
- max-height: 185px;
- overflow-y: scroll;
+.property-search-bar.block .advanced-filters label {
+ display: block;
+ margin: 8px 0;
+ font-size: var(--body-font-size-s);
+ letter-spacing: var(--letter-spacing-xs);
+ text-transform: uppercase;
}
-.property-search-bar.block .filter .tile li {
- flex: 1;
- border: 1px solid #dee2e6;
- border-left: none;
- text-align: center;
+
+.property-search-bar.block .advanced-filters input[type="radio"] {
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ opacity: 0;
}
-.property-search-bar.block .filter li:first-of-type {
- border-left: 1px solid #dee2e6;
+.property-search-bar.block .advanced-filters .radio-button {
+ position: relative;
+ width: 20px;
+ height: 20px;
+ border: 1px solid var(--grey);
+ border-radius: 100%;
}
-.property-search-bar.block .filter-checkbox label {
- display: flex;
- justify-content: flex-start;
- align-items: center;
+.property-search-bar.block .advanced-filters input[type="radio"]:checked+.radio-button {
+ background-color: var(--black);
+ border-color: var(--black);
}
-.property-search-bar.block .filter input[type="radio"] {
- position: absolute;
- width: 20px;
- height: 20px;
- min-width: 20px;
- opacity: 0
+.property-search-bar.block .advanced-filters input[type="radio"]:checked+.radio-button::after {
+ position: absolute;
+ content: '';
+ display: block;
+ width: 10px;
+ height: 10px;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 100%;
}
-.property-search-bar.block .filter li label{
- height: 50px;
- display: flex;
- justify-content: center;
- align-content: center;
- flex-wrap: wrap;
- font-weight: 300;
- color: #495057;
+.property-search-bar.block .advanced-filters .filter-toggle {
+ display: flex;
+ gap: 1em;
+ align-items: center;
+ font-size: var(--body-font-size-s);
+ text-transform: uppercase;
}
-.property-search-bar.block .filter li input[type="radio"]:checked+label {
- background-color: var(--light-grey);
- text-align: center;
+.property-search-bar.block .advanced-filters .filter-toggle .checkbox {
+ min-width: 24px;
+ height: 16px;
+ border-radius: 100px;
+ position: relative;
+ border: 1px solid #b4b4b4;
+ background: var(--white);
}
-.property-search-bar.block .filter .multiple-inputs .select-item{
- border-bottom: 1px solid var(--platinum);
- max-height: 185px;
- overflow-y: scroll;
- width: 100%;
- left: 0;
+.property-search-bar.block .advanced-filters .filter-toggle .checkbox::before {
+ content: '';
+ position: absolute;
+ right: 2px;
+ top: 2px;
+ height: 10px;
+ width: 10px;
+ border-radius: 10px;
+ z-index: 1;
+ background: #b4b4b4;
+}
+.property-search-bar.block .advanced-filters .filter-toggle .checkbox.checked {
+ background: var(--body-color);
+ border: 1px solid transparent
}
-/* suggestions */
-.property-search-bar.block .search-bar .suggester-results {
- display: none;
- position: absolute;
- left: 0;
- width: 100%;
- max-height: 300px;
- overflow-y: scroll;
- background-color: var(--white);
- border: 1px solid var(--grey);
- box-shadow: 0 3px 9px 2px rgba(0 0 0 / 23%);
- z-index: 10;
- line-height: 1.5;
+.property-search-bar.block .advanced-filters .filter-toggle .checkbox.checked::before {
+ transform: translateX(-8px);
+ background: var(--white);
}
-.property-search-bar.block .search-bar.show-suggestions .suggester-results {
- display: block;
+.property-search-bar.block .advanced-filters .filter-toggle.disabled {
+ pointer-events: none;
+ opacity: .3;
}
-.property-search-bar.block .search-bar .suggester-results > li > ul {
- padding: 0 15px;
+.property-search-bar.block .advanced-filters .property-types div.options {
+ display: flex;
+ flex-wrap: wrap;
+ margin-bottom: 16px;
}
-.property-search-bar.block .search-bar .suggester-results > li > ul > li {
- padding: 8px 0;
- font-size: var(--body-font-size-m);
- font-weight: 400;
- text-transform: none;
- letter-spacing: normal;
+.property-search-bar.block .advanced-filters .property-types div.options button {
+ flex-basis: 50%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 5px;
+ height: 80px;
+ width: 100%;
+ border: 1px solid var(--platinum);
+ background-color: var(--white);
}
-.property-search-bar.block .search-bar .suggester-results .list-title {
- padding: 15px 15px 5px;
- font-family: var(--font-family-primary);
- font-size: var(--body-font-size-s);
- font-weight: 700;
- text-transform: uppercase;
- letter-spacing: .5px;
- color: var(--black);
+.property-search-bar.block .advanced-filters .property-types div.options button.selected {
+ background-color: var(--platinum);
+ border: 1px solid var(--grey);
}
-.property-search-bar.block [name="Sort"].multiple-inputs .select-item li,
-.property-search-bar.block .filter .multiple-inputs .select-item li {
- border: none;
+.property-search-bar.block .advanced-filters .property-types div.options button > svg {
+ height: 21px;
+ width: 21px;
}
+.property-search-bar.block .advanced-filters .property-types div.options button > span {
+ text-align: center;
+ white-space: break-spaces;
+ font-size: var(--body-font-size-s);
+ line-height: var(--line-height-xs);
+}
-.property-search-bar.block .search-results-dropdown .select-item li:first-child {
- border-top: 1px solid var(--platinum);
+.property-search-bar.block .advanced-filters .property-types .all label {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 16px;
}
-.property-search-bar.block .search-results-dropdown .select-item li:last-child {
- border-bottom: 1px solid var(--platinum);
+.property-search-bar.block .property-types .all input[type="checkbox"] {
+ position: absolute;
+ height: 24px;
+ width: 24px;
+ margin: 0;
+ opacity: 0;
}
+.property-search-bar.block .advanced-filters .property-types .all .checkbox {
+ position: relative;
+ height: 24px;
+ width: 24px;
+ border: 1px solid var(--grey);
+ z-index: 100;
+}
-.property-search-bar.block .search-results-dropdown .multiple-inputs .select-item li {
- border: none;
+.property-search-bar.block .advanced-filters .property-types .all .checkbox svg {
+ display: none;
+ position: absolute;
+ top: calc(50% - 5px);
+ left: 5px;
+ height: 10px;
+ width: 12px;
+ filter: brightness(0) saturate(100%) invert(12%) sepia(4%) saturate(1639%) hue-rotate(303deg) brightness(97%) contrast(94%);
}
-.property-search-bar.block .search-results-dropdown .select-item .custom-tooltip {
- background: var(--white);
- position: absolute;
- left: 100%;
- top: 0;
+.property-search-bar.block .advanced-filters .property-types .all input[type="checkbox"]:checked+.checkbox svg {
+ display: block;
}
-.property-search-bar.block .search-results-dropdown .select-item li a {
- display: flex;
- align-items: center;
+.property-search-bar.block .advanced-filters .property-types .all span.label {
+ font-size: var(--body-font-size-s);
+ letter-spacing: var(--letter-spacing-m);
+ text-transform: initial;
+ line-height: unset;
}
-.property-search-bar.block .multiple-inputs {
- display: flex;
- align-items: center;
- color: var(--body-color);
- background-color: var(--white);
+.property-search-bar.block .advanced-filters .keywords {
+ background-color: var(--light-grey);
}
-.property-search-bar.block .input-dropdown input::placeholder {
- color: var(--input-placeholder);
- opacity: 1; /* Firefox */
+.property-search-bar.block .advanced-filters .keywords .keywords-input {
+ display: flex;
+ padding: 1px 7px;
+ height: 50px;
+ align-items: center;
+ gap: 7px;
+ background-color: var(--white);
+ border: 1px solid var(--grey);
}
-.property-search-bar.block [name="Price"] .search-results-dropdown,
-.property-search-bar.block [name="LivingArea"] .search-results-dropdown
-{
- position: absolute;
- padding: 10px;
- background-color: var(--white);
- z-index: 1000;
+.property-search-bar.block .advanced-filters .keywords .keywords-input input {
+ flex: 1 1 auto;
+ height: 35px;
+ border: none;
+ font-weight: var(--font-weight-light);
+ line-height: var(--line-height-m);
+ letter-spacing: normal;
}
-.property-search-bar.block .shadow {
- box-shadow: 0 0.5rem 1rem rgb(0 0 0 / 15%) !important;
+.property-search-bar.block .advanced-filters .keywords .keywords-input input::placeholder {
+ font-weight: var(--font-weight-light);
+ line-height: var(--line-height-m);
+ letter-spacing: normal;
}
-.property-search-bar.block .multiple-inputs section div:first-child {
- width: 125px;
+.property-search-bar.block .advanced-filters .keywords .keywords-input button {
+ padding: 7px 25px;
+ background-color: var(--white);
+ border: 1px solid var(--grey);
+ vertical-align: middle;
+ text-align: center;
}
-.property-search-bar.block .multiple-inputs .select-selected {
- color: var(--black);
- text-align: left;
- font-size: var(--body-font-size-s);
- height: 45px;
- font-family: var(--font-family-primary);
- line-height: 45px;
- border: 1px solid var(--platinum);
- padding: 0 15px;
- position: relative;
- letter-spacing: var(--letter-spacing-xxs);
+.property-search-bar.block .advanced-filters .keywords .keywords-input button span {
+ font-size: var(--body-font-size-s);
+ letter-spacing: var(--letter-spacing-m);
+ text-transform: uppercase;
}
-.property-search-bar.block .search-listing-block > .button-container {
- order: 3;
+.property-search-bar.block .advanced-filters .keywords .keywords-list {
+ margin: 8px 0;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
}
-.property-search-bar.block .container-item,
-.property-search-bar.block .search-listing-block .button-container:last-child {
- display: none;
+.property-search-bar.block .advanced-filters .keywords .keywords-list .keyword {
+ padding: 10px;
+ background-color: var(--white);
}
-.property-search-template.no-scroll {
- height: 100%;
- overflow: hidden;
+.property-search-bar.block .advanced-filters .keywords .keywords-list .keyword span {
+ font-size: var(--body-font-size-m);
+ line-height: var(--line-height-xs);
+ color: var(--body-color);
}
-.property-search-bar.block .filter-block {
- top: 115px;
- letter-spacing: 0.5px;
- position: fixed;
- overflow-y: auto;
- left: 0;
- width: 100vw;
- height: calc(100% - var(--nav-height) - 115px);
- background-color: var(--white);
- z-index: 999;
+.property-search-bar.block .advanced-filters .keywords .keywords-list .keyword span.close {
+ color: var(--black);
+ margin-left: 4px;
}
-.property-search-bar.block .filter-block .multiple-inputs {
- margin-right: 2rem;
+.property-search-bar.block .advanced-filters .keywords .keywords-match {
+ display: flex;
+ gap: 14px;
}
-.property-search-bar.block [name="OpenHouses"] > div > div:not(.filter-checkbox) {
- display: none;
+.property-search-bar.block .advanced-filters .keywords .keywords-match label,
+.property-search-bar.block .advanced-filters .open-houses .open-houses-timeframe label {
+ display: flex;
+ margin: 0;
+ gap: 5px;
+ align-items: center;
}
-.property-search-bar.block [name="Sort"].select-selected,
-.property-search-bar.block [name="Sort"] .search-results-dropdown .select-item li {
- font-size: var(--body-font-size-xs);
- letter-spacing: var(--letter-spacing-reg);
- color: var(--body-color);
- cursor: pointer;
+.property-search-bar.block .advanced-filters .keywords .keywords-match label span,
+.property-search-bar.block .advanced-filters .open-houses .open-houses-timeframe label span {
+ font-size: var(--body-font-size-s);
+ text-transform: capitalize;
}
-.property-search-bar.block .search-bar .suggester-results > li:first-child:not(:only-child){
- display: none;
+.property-search-bar.block .advanced-filters .misc hr {
+ margin: 16px 0;
+ border: none;
+ border-top: 1px solid var(--platinum);
}
-.property-search-bar.block [name="Sort"].select-selected {
- border: 1px solid var(--grey);
- height: 35px;
- line-height: 35px;
+.property-search-bar.block .advanced-filters .misc .filter-toggle {
+ justify-content: space-between;
}
-.property-search-bar.block [name="Sort"].multiple-inputs .select-item {
- left: 0;
- width: 100%;
+.property-search-bar.block .advanced-filters .misc .filter-toggle label {
+ font-size: var(--body-font-size-s);
+ font-weight: var(--font-weight-bold);
}
-.property-search-bar.block [name="Sort"].multiple-inputs section div:first-child {
- width: 145px;
+.property-search-bar.block .advanced-filters .misc .open-houses .open-houses-timeframe {
+ display: none;
}
-.property-search-bar.block [name="Sort"] .search-results-dropdown .select-item li:first-child {
- border-top: none;
+.property-search-bar.block .advanced-filters .misc .open-houses .open-houses-timeframe.visible {
+ display: flex;
+ gap: 16px;
}
-.property-search-bar.block [name="Sort"] .search-results-dropdown .select-item li:last-child {
- border-bottom: none;
+.property-search-bar.block .advanced-filters .buttons {
+ position: sticky;
+ bottom: 0;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ z-index: 502;
+ background-color: var(--white);
+ box-shadow: 0 0 4px 0 rgb(0 0 0 / 50%);
}
-.property-search-bar.block .filter-block .multiple-inputs section div:first-child {
- width: 100%;
- position: relative;
+.property-search-bar.block .advanced-filters .buttons p.button-container {
+ margin-bottom: 0;
+}
+.property-search-bar.block .advanced-filters .buttons p.button-container a {
+ padding: 7px 25px;
}
-.property-search-bar.block .filter-block button {
- font-family: var(--font-family-primary);
+.property-search-bar.block .advanced-filters .buttons p.button-container a,
+.property-search-bar.block .advanced-filters .buttons p.button-container.secondary a:hover {
+ background-color: var(--primary-color);
+ border-color: var(--white);
+ color: var(--white);
}
-.property-search-bar.block .filter-block [name="Features"] {
- background: var(--light-grey);
- padding: 27px 20px;
+.property-search-bar.block .advanced-filters .buttons p.button-container a:hover,
+.property-search-bar.block .advanced-filters .buttons p.button-container.secondary a {
+ background-color: var(--white);
+ color: var(--primary-color);
+ border-color: var(--primary-color);
}
-.property-search-bar.block [name="Features"] input[type="text"]{
- height: 35px;
- border: none;
- flex: 0 0 83.3333%;
- padding: 1px 15px 1px 7px;
+.property-search-bar.block .search-overlay {
+ display: none;
+ position: absolute;
+ width: 100vw;
+ height: calc(100vh - var(--nav-height) - 50px);
+ top: 50px;
+ left: 0;
+ background-color: var(--dark-grey);
+ opacity: .5;
+ z-index: 2;
}
-.property-search-bar.block .container-input {
- height: 50px;
- border: 1px solid var(--grey);
- padding-right: 7px;
- background: var(--white);
+.property-search-bar.block .search-overlay.visible {
+ display: block;
}
-.property-search-bar.block .container-input .button.secondary {
- flex: 0 0 16.6667%;
- border: 1px solid var(--grey);
+
+/* !* suggestions *! */
+.property-search-bar.block .search-bar .suggester-results {
+ display: none;
+ position: absolute;
+ left: 0;
+ width: 100%;
+ max-height: 300px;
+ overflow-y: scroll;
+ background-color: var(--white);
+ border: 1px solid var(--grey);
+ box-shadow: 0 3px 9px 2px rgba(0 0 0 / 23%);
+ z-index: 510;
+ line-height: 1.5;
}
-.property-search-bar.block .filter li input[type="radio"] {
- display: none;
+.property-search-bar.block .search-bar.show-suggestions .suggester-results {
+ display: block;
}
-.property-search-bar.block .filter .radio-btn {
- width: 20px;
- height: 20px;
- border: 1.4px solid #cecece;
- margin-right: 8px;
- position: relative;
- border-radius: 100%;
- overflow: hidden;
+.property-search-bar.block .search-bar .suggester-results .list-title {
+ padding: 15px 15px 5px;
+ font-family: var(--font-family-primary);
+ font-size: var(--body-font-size-s);
+ font-weight: var(--font-weight-bold);
+ text-transform: uppercase;
+ letter-spacing: .5px;
+ color: var(--black);
}
-.property-search-bar.block .filter input[type="radio"]:checked+.radio-btn {
- background: black;
- border-color: black;
+.property-search-bar.block .search-bar .suggester-results > li > ul {
+ padding: 0 15px;
}
-.property-search-bar.block .filter input[type="radio"]:checked+.radio-btn::after {
- content: "";
- display: block;
- width: 10px;
- height: 10px;
- background: white;
- border-radius: 100%;
- position: absolute;
- left: 50%;
- top: 50%;
- transform: translate(-50%, -50%);
+.property-search-bar.block .search-bar .suggester-results > li > ul > li {
+ padding: 8px 0;
+ font-size: var(--body-font-size-m);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
}
-.property-search-bar.block .column-2 .column {
- flex: 0 0 50%;
- width: 100%;
+.property-search-bar.block .search-bar .suggester-results > li:first-child:not(:only-child) {
+ display: none;
}
-.property-search-bar.block .column-2 .column button {
- border: 1px solid #e7e7e7;
- flex-direction: column;
- height: 80px;
+@media screen and (min-width: 600px) {
+ .property-search-bar.block .advanced-filters .property-types div.options {
+ gap: 8px;
+ }
+
+ .property-search-bar.block .advanced-filters .property-types div.options button {
+ flex-basis: calc(50% - 4px);
+ display: flex;
+ flex-direction: row;
align-items: center;
+ justify-content: flex-start;
+ gap: 16px;
+ height: 40px;
width: 100%;
- justify-content: center;
- overflow: visible;
- background: var(--white);
+ }
}
-.property-search-bar.block .filter .column svg {
- height: 21px;
- width: 20px;
- pointer-events: none;
-}
+@media screen and (min-width: 900px) {
+ .property-search-bar.block .search-bar {
+ flex: 1 1 auto;
+ }
-.property-search-bar.block .column-2 .column button.selected {
- background: var(--platinum);
- border: 1px solid grey;
-}
+ .property-search-bar.block form .result-filters {
+ order: 3;
+ }
+
+ .property-search-bar.block .result-filters .range-wrapper,
+ .property-search-bar.block .result-filters .select-wrapper {
+ display: flex;
+ }
+
+ .property-search-bar.block form a.search-submit {
+ order: 2;
+ }
-.property-search-bar.block .section-label {
- font-family: var(--font-family-primary);
- font-weight: 700;
+ .property-search-bar.block form a.save-search {
+ display: initial;
+ padding: 7px 10px;
+ border: 1px solid var(--white);
font-size: var(--body-font-size-s);
- color: var(--body-color);
- margin: 0 0 16px;
- line-height: 1;
- display: block;
-}
+ font-weight: var(--font-weight-bold);
+ letter-spacing: var(--letter-spacing-m);
+ line-height: var(--line-height-s);
+ white-space: nowrap;
+ }
-.property-search-bar.block .filter-checkbox input[type="checkbox"] {
- height: 24px;
- min-width: 24px;
- width: 24px;
- border-radius: 50%;
- border: 1px solid var(--body-color);
- opacity: 0;
- position: absolute;
- z-index: 1;
-}
+ .property-search-bar.block form a.save-search span {
+ font-size: inherit;
+ font-weight: inherit;
+ letter-spacing: inherit;
+ line-height: inherit;
+ color: var(--white);
+ text-transform: uppercase;
+ }
-.property-search-bar.block .filter-checkbox .checkbox svg {
+ .property-search-bar.block .advanced-filters {
+ width: 600px;
+ right: 0;
+ left: unset;
+ }
+
+ .property-search-bar.block .advanced-filters .attributes {
display: none;
- height: 10px;
- width: 12px;
- top: calc(50% - 5px);
- position: absolute;
- left: 5px;
-}
+ }
-.property-search-bar.block .filter-checkbox input[type="checkbox"]:checked+.checkbox svg {
- display: block;
+ .property-search-bar.block .search-overlay {
+ position: fixed;
+ height: unset;
+ top: calc(var(--nav-height) + 50px);
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: var(--dark-grey);
+ opacity: .5;
+ }
}
-
-.property-search-bar.block [name="FeaturedCompany"],
-.property-search-bar.block [name="Luxury"], [name="RecentPriceChange"], [name="NewListing"] {
- display: flex;
- justify-content: space-between;
-}
-
-.property-search-bar.block .tag {
- background-color: var(--white);
- padding: 10px;
- margin-top: 10px;
- margin-right: 10px;
- font-size: var(--body-font-size-m);
- letter-spacing: initial;
- line-height: 33px;
-}
-
-.tag .close::after {
- content: "x";
-}
-
-.property-search-bar.block .filter-buttons.button-container.flex-row.vertical-center {
- margin: 0;
-}
-
-@media (min-width: 600px) {
- .property-search-bar.block .column-2 .column button {
- flex-direction: row;
- height: 40px;
- align-items: center;
- width: 100%;
- justify-content: start;
- overflow: visible;
- margin-bottom: 0.5rem;
- }
-
- .property-search-bar.block .filter .column svg {
- height: 21px;
- width: 20px;
- pointer-events: none;
- }
-
- .property-search-bar.block .column-2 .column:last-child {
- padding-left: 0.5rem;
- }
-}
-
-.property-search-bar.block [name="OpenHouses"].selected > div > div:not(.filter-checkbox) {
- display: block;
-}
-
-@media (min-width: 900px) {
- .property-search-template .section.property-search-bar-container {
- margin-bottom: 0;
- position: sticky;
- top: 140px;
- z-index: 10;
- background: white;
- }
-
- .property-search-bar.block .overlay {
- top: calc(var(--nav-height) + 50px);
- }
-
- .property-search-bar.block .search-listing-container {
- justify-content: center;
- }
-
- .property-search-bar.block .search-listing-block {
- max-width: var(--normal-page-width);
- justify-content: space-between;
- }
-
- .property-search-bar.block .container-item,
- .property-search-bar.block .search-listing-block .button-container:last-child {
- display: block;
- }
-
- .property-search-bar.block .filter-block {
- width: 600px;
- height: calc(100% - 110px - var(--nav-height));
- top: calc(var(--nav-height) + 50px);
- left: calc(100% - 600px);
- }
-
- .property-search-bar.block .filter-buttons {
- justify-content: flex-start;
- padding: 15px 15px 15px 20px;
- width: 600px;
- left: calc(100% - 600px);
- box-shadow: none;
- border-top: 1px solid var(--body-color);
- }
-
- .property-search-bar.block .filter-block [name="Price"],
- .property-search-bar.block .filter-block [name="LivingArea"],
- .property-search-bar.block .filter-block [name="MinBedroomsTotal"],
- .property-search-bar.block .filter-block [name="MinBathroomsTotal"],
- .property-search-bar.block .filter-block [name="ApplicationType"] {
- display: none;
- }
-
- .property-search-bar.block .search-listing-block > div {
- order: 3;
- }
-
- .property-search-bar.block .search-listing-container > div:first-of-type {
- display: flex;
- width: 100%;
- justify-content: space-evenly;
- background: var(--primary-color);
- }
-
- .property-search-bar.block .search-listing-container > div:last-of-type {
- display: flex;
- flex-basis: 100%;
- max-width: var(--normal-page-width);
- }
-
- .property-search-bar.block .result-filters [name="ApplicationType"],
- .property-search-bar.block .result-filters .map-toggle {
- display: flex
- }
-
- .property-search-bar.block .bf-container {
- justify-content: space-between;
- }
-
- .property-search-bar.block .primary-search {
- max-width: 40vw;
- }
-
- .property-search-bar.block .search-listing-block .multiple-inputs input {
- border: 1px solid #dee2e6;
- margin: 0;
- color: var(--black);
- width: 165px;
- }
-}
\ No newline at end of file
diff --git a/blocks/property-search-bar/property-search-bar.js b/blocks/property-search-bar/property-search-bar.js
index 74406e02..5a432e3b 100644
--- a/blocks/property-search-bar/property-search-bar.js
+++ b/blocks/property-search-bar/property-search-bar.js
@@ -1,40 +1,71 @@
import {
- populatePreSelectedFilters, setInitialValuesFromUrl, searchProperty,
-} from './filter-processor.js';
+ BED_BATHS,
+ buildDataListRange,
+ buildFilterSelect,
+ buildSelectRange,
+ getPlaceholder,
+} from '../shared/search/util.js';
+import { decorateIcons, loadScript } from '../../scripts/aem.js';
-import {
- buildFilterSearchTypesElement, closeTopLevelFilters,
-} from './common-function.js';
-import topMenu from './filters/top-menu.js';
-import additionalFilters from './filters/additional-filters.js';
-import layoutButtons from './filters/additional-filter-buttons.js';
+export const SQUARE_FEET = [
+ { value: '500', label: '500 Sq Ft' },
+ { value: '750', label: '750 Sq Ft' },
+ { value: '1000', label: '1,000 Sq Ft' },
+ { value: '1250', label: '1,250 Sq Ft' },
+ { value: '1500', label: '1,500 Sq Ft' },
+ { value: '1750', label: '1,750 Sq Ft' },
+ { value: '2000', label: '2,000 Sq Ft' },
+ { value: '2250', label: '2,250 Sq Ft' },
+ { value: '2500', label: '2,500 Sq Ft' },
+ { value: '2750', label: '2,750 Sq Ft' },
+ { value: '3000', label: '3,000 Sq Ft' },
+ { value: '3500', label: '3,500 Sq Ft' },
+ { value: '4000', label: '4,000 Sq Ft' },
+ { value: '5000', label: '5,000 Sq Ft' },
+ { value: '7500', label: '7,500 Sq Ft' },
+];
-// const event = new Event('onFilterChange');
+function buildBar() {
+ const div = document.createElement('div');
+ div.classList.add('search-form-wrapper');
+ div.innerHTML = `
+
+ `;
+ return div;
+}
export default async function decorate(block) {
- setInitialValuesFromUrl();
- /** build top menu html */
- const overlay = document.createElement('div');
- overlay.classList.add('overlay', 'hide');
- const additionalConfig = document.createElement('div');
- additionalConfig.append(buildFilterSearchTypesElement());
- const topMenuBlock = await topMenu.build();
- const additionalFiltersBlock = await additionalFilters.build();
- const buttons = await layoutButtons.build();
- block.append(topMenuBlock, additionalFiltersBlock, overlay, buttons);
-
- populatePreSelectedFilters();
-
- // close filters on click outside
- document.addEventListener('click', (e) => {
- if (!block.contains(e.target)) {
- closeTopLevelFilters();
- }
- });
-
- window.addEventListener('onFilterChange', (e) => {
- e.preventDefault();
- e.stopPropagation();
- searchProperty();
- });
+ block.replaceChildren(buildBar());
+ decorateIcons(block);
+ window.setTimeout(() => {
+ loadScript(`${window.hlx.codeBasePath}/blocks/property-search-bar/delayed.js`, { type: 'module' });
+ }, 3000);
}
diff --git a/blocks/property-search-bar/search-results-dropdown.css b/blocks/property-search-bar/search-results-dropdown.css
deleted file mode 100644
index 36ad67c5..00000000
--- a/blocks/property-search-bar/search-results-dropdown.css
+++ /dev/null
@@ -1,66 +0,0 @@
-.property-search-template .highlighted {
- background: var(--light-grey);
-}
-
-.property-search-template .select-item::-webkit-scrollbar {
- width: 14px;
-}
-
-.property-search-template .select-item::-webkit-scrollbar-thumb{
- background: #c1c1c1;
- border-radius: 14px;
- border: 3px solid var(--platinum)
-}
-
-.property-search-template .select-item::-webkit-scrollbar-track{
- border-radius: 0;
- background: var(--platinum);
-}
-
-.property-search-template .multiple-inputs .select-selected::after {
- position: absolute;
- content: "";
- height: 7px;
- width: 12px;
- background-size: cover;
- background-position: 50% 50%;
- background-repeat: no-repeat;
- top: calc(50% - 3.5px);
- right: 15px;
- background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIxMnB4IiBoZWlnaHQ9IjdweCIgdmlld0JveD0iMCAwIDEyIDciIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+ICAgICAgICA8dGl0bGU+VUkvQ2Fycm90L0NvbGxhcHNlL0RhcmtHcmF5IENvcHkgMzwvdGl0bGU+ICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPiAgICA8ZGVmcz48L2RlZnM+ICAgIDxnIGlkPSItLS0t4pe877iOLVVJLUVsZW1lbnRzIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4gICAgICAgIDxnIGlkPSJVSS1FbGVtZW50cy9JY29ucyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTM5NC4wMDAwMDAsIC0yODguMDAwMDAwKSIgZmlsbD0iI0FBQUFBQSI+ICAgICAgICAgICAgPGcgaWQ9IlVJL0NhcnJvdC9FeHBhbmQvR3JheSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzk0LjAwMDAwMCwgMjg4LjAwMDAwMCkiPiAgICAgICAgICAgICAgICA8cG9seWdvbiBpZD0iUGF0aC0yIiBwb2ludHM9IjAuNzM0NTU4NTc2IDAuNjcxMzk5NzczIDExLjI5MzM0NTkgMC42NzEzOTk3NzMgNi4wMTM5NTIyNSA2LjY2MDUzMjIiPjwvcG9seWdvbj4gICAgICAgICAgICA8L2c+ICAgICAgICA8L2c+ICAgIDwvZz48L3N2Zz4=");
-}
-
-.property-search-template .select-item {
- padding: 10px;
- box-shadow: 0 0.5rem 1rem rgb(0 0 0 / 15%);
- width: 120px;
- color: var(--body-color);
- position: absolute;
- background: var(--white);
- z-index: 99;
- white-space: nowrap;
- left: -15px;
- display: none;
-}
-
-.property-search-template .search-results-dropdown div:first-child{
- position: relative;
-}
-
-.property-search-template .select-item li,
-.property-search-template .search-results-dropdown .select-item li{
- display: flex;
- border-left: 1px solid var(--platinum);
- border-right: 1px solid var(--platinum);
- padding: 4px 10px;
- line-height: 30px;
- letter-spacing: var(--letter-spacing-xxs);
- font-size: var(--body-font-size-s);
- align-items: center;
- justify-content: flex-start;
- position: relative
-}
-
-.property-search-template .select-item li:hover {
- background: var(--light-grey);
-}
\ No newline at end of file
diff --git a/blocks/property-search-bar/search/suggestion.js b/blocks/property-search-bar/search/suggestion.js
deleted file mode 100644
index 36ca8cd1..00000000
--- a/blocks/property-search-bar/search/suggestion.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import { setFilterValue } from '../filter-processor.js';
-
-const CONFIG = {
- Address: 'CoverageZipcode',
- PostalCode: 'CoverageZipcode',
- Neighborhood: 'BID',
- School: 'BID',
- 'School District': 'BID',
- MLSListingKey: 'ListingId',
-};
-
-function parseQueryString(queryString) {
- return queryString.split('&').reduce((resultObject, pair) => {
- const [key, value] = pair.split('=');
- resultObject[key] = decodeURIComponent(value.replace(/\+/g, '%20'));
- return resultObject;
- }, {});
-}
-
-export function getAttributes(result) {
- const query = parseQueryString(result.QueryString);
- query.BID = result.BID ?? '';
- const type = query.SearchType;
- const key = CONFIG[type];
- const output = {
- 'search-input': result.displayText,
- 'search-type': type,
- 'search-parameter': query.SearchParameter,
- };
- if (Object.keys(CONFIG).includes(type)) {
- output[key] = query[key];
- }
- return output;
-}
-
-export function setSearchParams(target) {
- const searchParameter = target.getAttribute('search-parameter');
- const keyword = target.getAttribute('search-input');
- const type = target.getAttribute('search-type');
- let attr;
- setFilterValue('SearchType', type);
- setFilterValue('SearchInput', keyword);
- setFilterValue('SearchParameterOriginal', searchParameter);
- Object.keys(CONFIG).forEach((key) => {
- attr = CONFIG[key].toLowerCase();
- if (target.hasAttribute(attr)) {
- setFilterValue(CONFIG[key], target.getAttribute(attr));
- }
- });
-}
diff --git a/blocks/property-search-results/README.md b/blocks/property-search-results/README.md
new file mode 100644
index 00000000..a8bf35a1
--- /dev/null
+++ b/blocks/property-search-results/README.md
@@ -0,0 +1,50 @@
+### Property Search Results
+
+This block renders the map and list of properties returned from a search.
+
+#### Default Search
+
+If no parameters were used (i.e. someone typed in the URL of the page), a default Search parameter set is used.
+
+#### Keeping in Sync
+
+This and the Property Search Bar block have shared parameters. When this block is used to set parameters, the search is performed immediately and the Search Bar state is updated (see `updateForm` in the Property Search Bar `delayed.js`)
+
+If the Property Search Bar is updated and a search submitted or applied, it emits an event, which is processed by this block. The `updateFilters` block will keep the few items in sync when the new search is executed.
+
+#### Results, Map and Browser History
+
+This page rendering these results should not refresh each time a new search is performed. So we're using `pushState` and `popstate` history manipulation.
+
+When new search parameters are provided, push that state to the history, then perform the search (via `doSearch`);
+
+The trick here is making sure that cyclic searching doesn't occur.
+
+When the page is first rendered:
+ 1. Generate and center the map
+ 1. Perform the search.
+ 1. Render results
+ 1. Decorate the map.
+
+Any time a Search Event occurs:
+ 1. Perform the search.
+ 1. Render results
+ 1. Decorate the map.
+
+If the Map bounds change:
+ 1. Create a new Search object with the bounds
+ 1. Emit the Search Event with the payload
+
+If the Search Filters change:
+ 1. Create a new Search object with the bounds
+ 1. Emit the Search Event with the payload
+
+If a history pop-state occurs:
+ 1. Sync the Filters to the history state
+ 1. Tell Search Bar to sync its state.
+ 1. Create search from history URL, then perform the search.
+ 1. Render results
+ 1. Decorate the map.
+
+
+Anytime the Map is decorating, which causes the bounds to change, flag that it's rendering. This prevents a render event from causing a second, new search.
diff --git a/blocks/property-search-results/loader.js b/blocks/property-search-results/loader.js
new file mode 100644
index 00000000..152bbf10
--- /dev/null
+++ b/blocks/property-search-results/loader.js
@@ -0,0 +1,10 @@
+import { div, img, domEl } from '../../scripts/dom-helpers.js';
+
+const loader = div({ class: 'search-results-loader' },
+ div({ class: 'animation' },
+ domEl('picture', img({ src: '/styles/images/loading.png' })),
+ div({ class: 'pulse' }),
+ ),
+);
+
+export default loader;
diff --git a/blocks/property-search-results/map.css b/blocks/property-search-results/map.css
new file mode 100644
index 00000000..f4eabc63
--- /dev/null
+++ b/blocks/property-search-results/map.css
@@ -0,0 +1,562 @@
+.property-search-results.block .search-map-container {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ background-color: var(--white);
+ min-height: 250px;
+}
+
+.property-search-results.block .search-map-container.drawing {
+ cursor: pointer;
+}
+
+.property-search-results.block .search-map-container .search-results-map {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+}
+
+.property-search-results.block .search-map-container .search-results-map #gmap-canvas {
+ height: 100%;
+ width: 100%;
+}
+
+.property-search-results.block .search-map-container .map-controls-wrapper {
+ position: absolute;
+ display: flex;
+ flex-direction: column;
+ right: 15px;
+ bottom: 15px;
+ pointer-events: none;
+ z-index: 1;
+}
+
+.property-search-results.block .search-map-container .map-controls-wrapper .custom-controls {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+}
+
+.property-search-results.block .search-map-container .custom-controls > a {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-evenly;
+ height: 45px;
+ width: 45px;
+ box-shadow: 0 0 3px 2px rgba(0 0 0 / 30%);
+ pointer-events: all;
+ background-color: var(--white);
+ cursor: pointer;
+}
+
+.property-search-results.block .search-map-container .custom-controls a span {
+ font-family: var(--font-family-proxima);
+ font-size: var(--body-font-size-xxs);
+ line-height: 1em;
+ letter-spacing: normal;
+ color: var(--body-color);
+}
+
+.property-search-results.block .search-map-container.drawing .custom-controls .map-style {
+ pointer-events: none;
+}
+
+.property-search-results.block .search-map-container.satellite .custom-controls .map-style span.map {
+ display: none;
+}
+
+.property-search-results.block .search-map-container.map .custom-controls .map-style span.satellite {
+ display: none;
+}
+
+.property-search-results.block .search-map-container .custom-controls .map-style img {
+ height: 24px;
+ width: 24px;
+}
+
+.property-search-results.block .search-map-container.drawing .custom-controls .map-style * {
+ opacity: .3;
+}
+
+.property-search-results.block .search-map-container .custom-controls .map-draw img.draw {
+ height: 17px;
+ width: 17px;
+}
+
+.property-search-results.block .search-map-container .custom-controls .map-draw img.close {
+ height: 22px;
+ width: 22px;
+}
+
+.property-search-results.block .search-map-container .custom-controls .map-draw img.close,
+.property-search-results.block .search-map-container .custom-controls .map-draw span.close {
+ display: none;
+}
+
+.property-search-results.block .search-map-container.drawing .custom-controls .map-draw img.draw,
+.property-search-results.block .search-map-container.drawing .custom-controls .map-draw span.draw {
+ display: none;
+}
+
+.property-search-results.block .search-map-container.drawing .custom-controls .map-draw img.close,
+.property-search-results.block .search-map-container.drawing .custom-controls .map-draw span.close {
+ display: initial;
+}
+
+.property-search-results.block .search-map-container .custom-controls .map-draw-complete {
+ display: none;
+}
+
+.property-search-results.block .search-map-container .custom-controls .map-draw-complete.disabled {
+ pointer-events: none;
+}
+
+.property-search-results.block .search-map-container .custom-controls .map-draw-complete.disabled * {
+ opacity: .3;
+}
+
+.property-search-results.block .search-map-container.drawing .custom-controls .map-draw-complete {
+ display: flex;
+}
+
+.property-search-results.block .search-map-container.drawing .custom-controls .map-draw-complete img {
+ height: 33px;
+ width: 33px;
+}
+
+.property-search-results.block .search-map-container .custom-controls .zoom-controls {
+ display: none;
+}
+
+.property-search-results.block .search-map-container.drawing .custom-controls .zoom-controls a {
+ pointer-events: none;
+}
+
+.property-search-results.block .search-map-container.drawing .custom-controls .zoom-controls a::before,
+.property-search-results.block .search-map-container.drawing .custom-controls .zoom-controls a::after {
+ opacity: .3;
+}
+
+
+.property-search-results.block .search-map-container .map-draw-info {
+ display: none;
+}
+
+.property-search-results.block .search-map-container.drawing .map-draw-info {
+ display: block;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ background-color: var(--light-grey);
+}
+
+.property-search-results.block .search-map-container.drawing .map-draw-info p {
+ padding: 0;
+ margin: 0;
+ font-family: var(--font-family-proxima);
+ font-size: var(--body-font-size-xs);
+ line-height: var(--line-height-l);
+ text-align: center;
+ letter-spacing: normal;
+}
+
+.property-search-results.block .search-map-container .map-draw-info .map-draw-boundary-link {
+ display: none;
+}
+
+.property-search-results.block .search-map-container.bound .map-draw-info .map-draw-boundary-link {
+ display: initial;
+}
+
+.property-search-results.block .search-map-wrapper .info-window {
+ position: relative;
+ background-color: var(--white);
+ z-index: 1;
+ border-bottom: 1px solid var(--platinum);
+}
+
+.property-search-results.block .search-map-wrapper .info-window.cluster {
+ max-height: calc(var(--nav-height) + 50px); /* Search bar */
+ overflow-y: scroll;
+}
+
+.property-search-results.block .search-map-wrapper .info-window .loading {
+ padding: 10px;
+ margin: 20px;
+}
+
+.property-search-results.block .search-map-wrapper .info-window .loading p {
+ font-size: var(--body-font-size-xl);
+ font-weight: var(--font-weight-semibold);
+ text-align: center;
+ margin: 0;
+}
+
+.property-search-results.block .search-map-wrapper .info-window .loading p::after {
+ content: '';
+ display: inline-block;
+ position: absolute;
+ right: 30px;
+ top: 0;
+ height: 100%;
+ width: 25px;
+ vertical-align: bottom;
+ animation: ellipsis 3s infinite;
+ background-color: white;
+}
+
+@keyframes ellipsis {
+ to {
+ width: 0;
+ }
+}
+
+.property-search-results.block .search-map-wrapper .info-window a.info-wrapper,
+.property-search-results.block .search-map-wrapper .info-window.cluster a.info-wrapper {
+ display: grid;
+ margin: 10px;
+ align-items: center;
+ padding-bottom: 10px;
+ gap: 10px;
+ border-bottom: 1px solid var(--grey);
+ text-decoration: none;
+ grid-template:
+ "image info"
+ / 50px 1fr;
+}
+
+.property-search-results.block .search-map-wrapper .info-window a.info-wrapper:last-of-type {
+ border-bottom: none;
+ padding-bottom: 0;
+}
+
+.property-search-results.block .search-map-wrapper .info-window .info-wrapper .image-wrapper {
+ grid-area: image;
+ position: relative;
+ height: 105px;
+ width: 150px;
+}
+
+.property-search-results.block .search-map-wrapper .mobile-info-window .info-wrapper .image-wrapper,
+.property-search-results.block .search-map-wrapper .info-window.cluster .info-wrapper .image-wrapper {
+ position: relative;
+ height: 35px;
+ width: 50px;
+}
+
+.property-search-results.block .search-map-wrapper .mobile-info-window .info-wrapper .image-wrapper .luxury,
+.property-search-results.block .search-map-wrapper .info-window.cluster .info-wrapper .image-wrapper .luxury {
+ display: none;
+}
+
+.property-search-results.block .search-map-wrapper .info-window .info-wrapper .image-wrapper img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ object-fit: contain;
+ object-position: center;
+}
+
+.property-search-results.block .search-map-wrapper .info-window .info-wrapper .info {
+ grid-area: info;
+ display: flex;
+ flex-direction: column;
+}
+
+.property-search-results.block .search-map-wrapper .mobile-info-window .info-wrapper .info hr,
+.property-search-results.block .search-map-wrapper .info-window.cluster .info-wrapper .info hr {
+ display: none;
+
+}
+
+.property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .price span {
+ display: block;
+ font-size: var(--body-font-size-l);
+ font-weight: var(--font-weight-semibold);
+ line-height: var(--line-height-s);
+ letter-spacing: var(--letter-spacing-xxs);
+}
+
+.property-search-results.block .search-map-wrapper .mobile-info-window .info-wrapper .info .price .alt,
+.property-search-results.block .search-map-wrapper .info-window.cluster .info-wrapper .info .price .alt {
+ display: none;
+}
+
+.property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .details span {
+ display: block;
+ font-size: var(--body-font-size-xs);
+ letter-spacing: normal;
+ line-height: var(--line-height-xs);
+}
+
+.property-search-results.block .search-map-wrapper .mobile-info-window .info-wrapper .info .details .address .danger,
+.property-search-results.block .search-map-wrapper .info-window.cluster .info-wrapper .info .details .address .danger,
+.property-search-results.block .search-map-wrapper .mobile-info-window .info-wrapper .info .details .address .locality,
+.property-search-results.block .search-map-wrapper .info-window.cluster .info-wrapper .info .details .address .locality {
+ display: none;
+}
+
+.property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .providers {
+ padding: 3px 0;
+ font-size: var(--body-font-size-xxs);
+ line-height: var(--line-height-m);
+ letter-spacing: normal;
+}
+
+.property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .property-buttons,
+.property-search-results.block .search-map-wrapper .info-window.cluster .info-wrapper .info .property-buttons {
+ display: none;
+}
+
+.property-search-results.block .search-map-wrapper .mobile-info-window .info-wrapper .info .listing-info,
+.property-search-results.block .search-map-wrapper .info-window.cluster .info-wrapper .info .listing-info {
+ display: none;
+}
+
+#gmap-canvas a[href^="https://maps.google.com/maps"] {
+ display: none !important; /* Don't want to use this, but theres an inline style we have to override. */
+}
+
+#gmap-canvas .gm-style-cc {
+ display: none;
+}
+
+/* overriding some google styles */
+.gm-style .gm-style-iw-d {
+ overflow: initial !important;
+}
+
+.gm-style .gm-style-iw-d::-webkit-scrollbar-track,
+.gm-style .gm-style-iw-d::-webkit-scrollbar-track-piece,
+.gm-style .gm-style-iw-t::after,
+.gm-style .gm-style-iw-c {
+ box-shadow: none !important;
+ border-radius: 0 !important;
+ padding: 0 !important;
+ overflow: initial !important;
+}
+
+.cmp-properties-map .gm-ui-hover-effect {
+ display: none !important;
+}
+
+.button.gm-ui-hover-effect, button[draggable] {
+ opacity: 0 !important;
+}
+
+@media screen and (min-width: 900px) {
+ .property-search-results.block .search-map-container .map-controls-wrapper {
+ bottom: 50%;
+ transform: translateY(50%);
+ }
+
+ .property-search-results.block .search-map-wrapper .mobile-info-window {
+ display: none;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window {
+ max-width: 250px;
+ max-height: 250px;
+ overflow-y: scroll;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window:not(.cluster) {
+ max-width: 200px;
+ padding: 0;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window a.info-wrapper {
+ grid-template:
+ "image"
+ "info";
+ margin: 0;
+ max-height: 250px;
+ overflow-y: scroll;
+ gap: 0;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .image-wrapper {
+ grid-area: image;
+ position: relative;
+ width: 100%;
+ padding-top: 73.5%;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .image-wrapper img {
+ object-fit: cover;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .image-wrapper .luxury {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 30px;
+ text-transform: uppercase;
+ text-align: center;
+ background-color: var(--black);
+ z-index: 1;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .image-wrapper .luxury span {
+ font-size: var(--body-font-size-xs);
+ font-weight: var(--font-weight-bold);
+ letter-spacing: var(--letter-spacing-m);
+ color: var(--white);
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info {
+ padding: 5px;
+ flex-flow: row wrap;
+ justify-content: space-between;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .price .alt {
+ display: block;
+ font-size: var(--body-font-size-s);
+ font-weight: var(--font-weight-normal);
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .property-buttons {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ margin: 0 5px;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .property-buttons a {
+ position: relative;
+ width: 18px;
+ height: 18px;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .property-buttons span {
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .property-buttons img {
+ width: 18px;
+ height: 18px;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .property-buttons a span.icon-envelopedark,
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .property-buttons a span.icon-heartemptydark,
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .property-buttons a:hover span.icon-envelope,
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .property-buttons a:hover span.icon-heartempty {
+ display: none;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .property-buttons a:hover span.icon-envelopedark,
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .property-buttons a:hover span.icon-heartemptydark {
+ display: block;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .property-buttons a span.icon-heartfull {
+ display: none;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .details > * {
+ padding: 3px;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .details .address .danger {
+ display: block;
+ color: #832b39;
+ font-size: var(--body-font-size-xxs);
+ font-weight: var(--font-weight-bold);
+ line-height: var(--line-height-m);
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info hr {
+ width: 100%;
+ border: none;
+ border-top: 1px solid var(--platinum);
+ margin: 0;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .listing-info {
+ display: block;
+ padding: 3px;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .listing-info span {
+ display: block;
+ font-size: 8px;
+ line-height: var(--line-height-s);
+ letter-spacing: normal;
+ color: var(--dark-grey);
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .listing-info .aor-img {
+ height: 30px;
+ width: 60px;
+ position: relative;
+ }
+
+ .property-search-results.block .search-map-wrapper .info-window .info-wrapper .info .listing-info .aor-img img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ object-fit: contain;
+ object-position: center;
+ }
+
+ .property-search-results.block .search-map-container.drawing .map-draw-info p {
+ font-size: var(--body-font-size-m);
+ line-height: 65px;
+ }
+
+
+ .property-search-results.block .search-map-container .custom-controls .zoom-controls {
+ display: block;
+ box-shadow: 0 0 3px 2px rgba(0 0 0 / 30%);
+ }
+
+ /* stylelint-disable-next-line no-descending-specificity */
+ .property-search-results.block .search-map-container .custom-controls .zoom-controls a {
+ display: block;
+ position: relative;
+ height: 45px;
+ width: 45px;
+ background-color: var(--white);
+ cursor: pointer;
+ pointer-events: all;
+
+ }
+
+ .property-search-results.block .search-map-container .custom-controls .zoom-controls a::before {
+ content: ' ';
+ position: absolute;
+ display: block;
+ background-color: var(--body-color);
+ height: 1px;
+ left: calc(50% - 8px);
+ top: 50%;
+ width: 16px;
+ }
+
+
+ .property-search-results.block .search-map-container .custom-controls .zoom-controls a.zoom-in::after {
+ content: ' ';
+ position: absolute;
+ display: block;
+ background-color: var(--body-color);
+ width: 1px;
+ top: calc(50% - 8px);
+ left: 50%;
+ height: 16px;
+ }
+
+}
diff --git a/blocks/property-search-results/map.js b/blocks/property-search-results/map.js
new file mode 100644
index 00000000..4fe764ea
--- /dev/null
+++ b/blocks/property-search-results/map.js
@@ -0,0 +1,434 @@
+/* global google */
+
+import { loadScript } from '../../scripts/aem.js';
+import loadMaps from '../../scripts/google-maps/index.js';
+import {
+ a, div, img, p, span,
+} from '../../scripts/dom-helpers.js';
+import displayClusters from './map/clusters.js';
+import { UPDATE_SEARCH_EVENT } from '../../scripts/apis/creg/search/Search.js';
+import BoxSearch from '../../scripts/apis/creg/search/types/BoxSearch.js';
+import {
+ clearInfos,
+ hideInfos,
+ getMarkerClusterer,
+ displayPins,
+} from './map/pins.js';
+import {
+ startPolygon,
+ addPolygonPoint,
+ closePolygon,
+ clearPolygon,
+} from './map/drawing.js';
+import PolygonSearch from '../../scripts/apis/creg/search/types/PolygonSearch.js';
+
+const zoom = 10;
+const maxZoom = 18;
+
+let gmap;
+let renderInProgress = false;
+
+const mapMarkers = [];
+let clusterer;
+let boundsTimeout;
+
+let drawingClickListener;
+let polygon;
+
+const MAP_STYLE = [{
+ featureType: 'administrative',
+ elementType: 'labels.text.fill',
+ stylers: [{ color: '#444444' }],
+}, {
+ featureType: 'administrative.locality',
+ elementType: 'labels.text.fill',
+ stylers: [{ saturation: '-42' }, { lightness: '-53' }, { gamma: '2.98' }],
+}, {
+ featureType: 'administrative.neighborhood',
+ elementType: 'labels.text.fill',
+ stylers: [{ saturation: '1' }, { lightness: '31' }, { weight: '1' }],
+}, {
+ featureType: 'administrative.neighborhood',
+ elementType: 'labels.text.stroke',
+ stylers: [{ visibility: 'off' }],
+}, {
+ featureType: 'administrative.land_parcel',
+ elementType: 'labels.text.fill',
+ stylers: [{ lightness: '12' }],
+}, {
+ featureType: 'landscape',
+ elementType: 'all',
+ stylers: [{ saturation: '67' }],
+}, {
+ featureType: 'landscape.man_made',
+ elementType: 'geometry.fill',
+ stylers: [{ visibility: 'on' }, { color: '#ececec' }],
+}, {
+ featureType: 'landscape.natural',
+ elementType: 'geometry.fill',
+ stylers: [{ visibility: 'on' }],
+}, {
+ featureType: 'landscape.natural.landcover',
+ elementType: 'geometry.fill',
+ stylers: [{ visibility: 'on' }, { color: '#ffffff' }, { saturation: '-2' }, { gamma: '7.94' }],
+}, {
+ featureType: 'landscape.natural.terrain',
+ elementType: 'geometry',
+ stylers: [{ visibility: 'on' }, { saturation: '94' }, { lightness: '-30' }, { gamma: '8.59' }, { weight: '5.38' }],
+}, {
+ featureType: 'poi',
+ elementType: 'all',
+ stylers: [{ visibility: 'off' }],
+}, {
+ featureType: 'poi.park',
+ elementType: 'geometry',
+ stylers: [{ saturation: '-26' }, { lightness: '20' }, { weight: '1' }, { gamma: '1' }],
+}, {
+ featureType: 'poi.park',
+ elementType: 'geometry.fill',
+ stylers: [{ visibility: 'on' }],
+}, {
+ featureType: 'road',
+ elementType: 'all',
+ stylers: [{ saturation: -100 }, { lightness: 45 }],
+}, {
+ featureType: 'road',
+ elementType: 'geometry.fill',
+ stylers: [{ visibility: 'on' }, { color: '#fafafa' }],
+}, {
+ featureType: 'road',
+ elementType: 'geometry.stroke',
+ stylers: [{ visibility: 'off' }],
+}, {
+ featureType: 'road',
+ elementType: 'labels.text.fill',
+ stylers: [{ gamma: '0.95' }, { lightness: '3' }],
+}, {
+ featureType: 'road',
+ elementType: 'labels.text.stroke',
+ stylers: [{ visibility: 'off' }],
+}, {
+ featureType: 'road.highway',
+ elementType: 'all',
+ stylers: [{ visibility: 'simplified' }],
+}, {
+ featureType: 'road.highway',
+ elementType: 'geometry',
+ stylers: [{ lightness: '100' }, { gamma: '5.22' }],
+}, {
+ featureType: 'road.highway',
+ elementType: 'geometry.stroke',
+ stylers: [{ visibility: 'on' }],
+}, {
+ featureType: 'road.arterial',
+ elementType: 'labels.icon',
+ stylers: [{ visibility: 'off' }],
+}, {
+ featureType: 'transit',
+ elementType: 'all',
+ stylers: [{ visibility: 'off' }],
+}, {
+ featureType: 'water',
+ elementType: 'all',
+ stylers: [{ color: '#b3dced' }, { visibility: 'on' }],
+}, {
+ featureType: 'water',
+ elementType: 'labels.text.fill',
+ stylers: [{ visibility: 'on' }, { color: '#ffffff' }],
+}, {
+ featureType: 'water',
+ elementType: 'labels.text.stroke',
+ stylers: [{ visibility: 'off' }, { color: '#e6e6e6' }],
+}];
+
+function decorateMap(parent) {
+ /* @formatter:off */
+ const controls = div({ class: 'map-controls-wrapper' },
+ div({ class: 'custom-controls' },
+ a({
+ class: 'map-style',
+ role: 'button',
+ 'aria-label': 'Satellite View',
+ },
+ img({ src: '/icons/globe.png' }),
+ span({ class: 'satellite' }, 'Satellite'),
+ span({ class: 'map' }, 'Map'),
+ ),
+ a({
+ class: 'map-draw-complete disabled',
+ role: 'button',
+ 'aria-label': 'Complete Draw',
+ },
+ img({ src: '/icons/checkmark.svg' }),
+ span('Done')),
+ a({
+ class: 'map-draw',
+ role: 'button',
+ 'aria-label': 'Draw',
+ },
+ img({ class: 'draw', src: '/icons/pencil.svg' }),
+ img({ class: 'close', src: '/icons/close-x.svg' }),
+ span({ class: 'draw' }, 'Draw'),
+ span({ class: 'close' }, 'Close'),
+ ),
+ div({ class: 'zoom-controls' },
+ a({ class: 'zoom-in', role: 'button', 'aria-label': 'Zoom In' }),
+ a({ class: 'zoom-out', role: 'button', 'aria-label': 'Zoom Out' }),
+ ),
+ ),
+ );
+ const info = div({ class: 'map-draw-info' },
+ p({ class: 'map-draw-tooltip' }, 'Click points on the map to draw your search.'),
+ p({ class: 'map-draw-boundary-link' },
+ a({ role: 'button', 'aria-label': 'Remove Map Boundary' }, 'Add Map Boundary'),
+ ),
+ );
+
+ /* @formatter:on */
+ parent.append(controls, info);
+}
+
+async function boundsChanged() {
+ if (polygon) return;
+ window.clearTimeout(boundsTimeout);
+ boundsTimeout = window.setTimeout(() => {
+ const bounds = gmap.getBounds();
+ const search = new BoxSearch();
+ search.populateFromURLSearchParameters(new URLSearchParams(window.location.search));
+ search.maxLat = bounds.getNorthEast().lat();
+ search.maxLon = bounds.getNorthEast().lng();
+ search.minLat = bounds.getSouthWest().lat();
+ search.minLon = bounds.getSouthWest().lng();
+ window.dispatchEvent(new CustomEvent(UPDATE_SEARCH_EVENT, { detail: { search } }));
+ }, 1000);
+}
+
+function doneDrawing() {
+ polygon = closePolygon();
+ drawingClickListener.remove();
+ gmap.setOptions({ gestureHandling: 'cooperative' });
+ document.querySelector('.property-search-results.block .search-map-container').classList.remove('drawing');
+ const search = new PolygonSearch();
+ search.populateFromURLSearchParameters(new URLSearchParams(window.location.search));
+ polygon.getPath().forEach((latLng) => {
+ search.addPoint({ lat: latLng.lat(), lon: latLng.lng() });
+ });
+ window.dispatchEvent(new CustomEvent(UPDATE_SEARCH_EVENT, { detail: { search } }));
+}
+
+function endDrawing() {
+ clearPolygon();
+ polygon = undefined;
+ drawingClickListener.remove();
+ gmap.setOptions({ gestureHandling: 'cooperative' });
+ document.querySelector('.property-search-results.block .search-map-container').classList.remove('drawing');
+ boundsChanged();
+}
+
+function drawingClickHandler(e) {
+ if (addPolygonPoint(e.latLng)) {
+ doneDrawing();
+ }
+}
+
+function startDrawing() {
+ gmap.setOptions({ gestureHandling: 'none' });
+ drawingClickListener = gmap.addListener('click', drawingClickHandler);
+ startPolygon(gmap);
+}
+
+function observeControls(block) {
+ block.querySelector('.search-map-container a.map-draw').addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const start = e.currentTarget.closest('.search-map-container').classList.toggle('drawing');
+ if (start) {
+ startDrawing(gmap);
+ } else {
+ endDrawing();
+ }
+ });
+
+ block.querySelector('.search-map-container a.map-draw-complete').addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ doneDrawing();
+ });
+
+ block.querySelector('.search-map-container a.map-style').addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ e.currentTarget.closest('.search-map-container').classList.toggle('map');
+ e.currentTarget.closest('.search-map-container').classList.toggle('satellite');
+ const type = gmap.getMapTypeId();
+ if (type === google.maps.MapTypeId.ROADMAP) {
+ gmap.setMapTypeId(google.maps.MapTypeId.SATELLITE);
+ } else {
+ gmap.setMapTypeId(google.maps.MapTypeId.ROADMAP);
+ }
+ });
+
+ block.querySelector('.search-map-container a.zoom-in').addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ gmap.setZoom(gmap.getZoom() + 1);
+ });
+
+ block.querySelector('.search-map-container a.zoom-out').addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ gmap.setZoom(gmap.getZoom() - 1);
+ });
+}
+
+function clearMarkers() {
+ clearInfos();
+ clusterer.clearMarkers();
+ if (mapMarkers.length) {
+ mapMarkers.forEach((marker) => {
+ marker.setVisible(false);
+ marker.setMap(null);
+ });
+ }
+ mapMarkers.length = 0;
+}
+
+function getMarkerBounds(markers) {
+ const bounds = new google.maps.LatLngBounds();
+ markers.forEach((m) => {
+ bounds.extend(m.getPosition());
+ });
+ return bounds;
+}
+
+/**
+ * Updates the map view with the new results.
+ * @param results
+ */
+async function displayResults(results) {
+ renderInProgress = true;
+ // Map isn't loaded yet.
+ if (!gmap) {
+ window.setTimeout(() => {
+ displayResults(results);
+ }, 1000);
+ return;
+ }
+
+ // Map needs to be initialized.
+ if (gmap.getRenderingType() === google.maps.RenderingType.UNINITIALIZED) {
+ gmap.addListener('renderingtype_changed', () => {
+ displayResults(results);
+ });
+ return;
+ }
+ // Clear any info windows
+ // Clear any existing Markers.
+ if (!results.properties || results.properties.length === 0) return;
+
+ clearMarkers();
+
+ if (results.clusters.length) {
+ mapMarkers.push(...(await displayClusters(gmap, results.clusters)));
+ } else if (results.pins.length) {
+ mapMarkers.push(...(await displayPins(gmap, results.pins)));
+ clusterer.addMarkers(mapMarkers);
+ }
+
+ const bounds = getMarkerBounds(mapMarkers);
+ if (!gmap.getBounds().contains(bounds.getCenter())) {
+ gmap.fitBounds(bounds);
+ }
+ renderInProgress = false;
+}
+
+/**
+ * Reinitialize the Map based on history navigation.
+ *
+ * @param search the search that will be performed.
+ */
+function reinitMap(search) {
+ clearMarkers();
+ clearPolygon();
+ if (search instanceof PolygonSearch) {
+ startPolygon(gmap);
+ search.points.forEach((point) => {
+ const { lat, lon } = point;
+ addPolygonPoint(new google.maps.LatLng({ lat: parseFloat(lat), lng: parseFloat(lon) }));
+ });
+ polygon = closePolygon();
+ }
+}
+
+/**
+ * Loads Google Maps and draws the initial view.
+ * @param block
+ * @return {Promise}
+ */
+async function initMap(block, search) {
+ const ele = block.querySelector('#gmap-canvas');
+
+ gmap = new google.maps.Map(ele, {
+ zoom,
+ maxZoom,
+ center: { lat: 41.24216, lng: -96.207990 },
+ mapTypeId: google.maps.MapTypeId.ROADMAP,
+ clickableIcons: false,
+ gestureHandling: 'cooperative',
+ styles: MAP_STYLE,
+ visualRefresh: true,
+ disableDefaultUI: true,
+ });
+
+ decorateMap(block.querySelector('.search-results-map'));
+ block.querySelector('.search-map-wrapper').append(div({ class: 'mobile-info-window info-window' }));
+ observeControls(block);
+
+ gmap.addListener('click', () => {
+ hideInfos();
+ });
+
+ // Don't use 'bounds_changed' event for updating results - fires too frequently.
+ gmap.addListener('dragend', () => {
+ hideInfos();
+ boundsChanged();
+ });
+ gmap.addListener('dblclick', () => {
+ hideInfos();
+ boundsChanged();
+ });
+
+ gmap.addListener('zoom_changed', () => {
+ if (renderInProgress) {
+ return;
+ }
+ hideInfos();
+ boundsChanged();
+ });
+
+ clusterer = getMarkerClusterer(gmap);
+ if (search instanceof PolygonSearch) {
+ startPolygon(gmap);
+ search.points.forEach((point) => {
+ const { lat, lon } = point;
+ addPolygonPoint(new google.maps.LatLng({ lat: parseFloat(lat), lng: parseFloat(lon) }));
+ });
+ polygon = closePolygon();
+ }
+}
+
+// Anytime a search is performed, hide any existing markers.
+// window.addEventListener(EVENT_NAME, hideMarkers);
+
+/* Load all the map libraries here */
+loadMaps();
+await google.maps.importLibrary('core');
+await google.maps.importLibrary('maps');
+await google.maps.importLibrary('marker');
+await loadScript('https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js', { type: 'application/javascript' });
+await loadScript('https://unpkg.com/jsts/dist/jsts.min.js', { type: 'application/javascript' });
+export {
+ displayResults,
+ initMap,
+ reinitMap,
+};
diff --git a/blocks/property-search-results/map/clusters.js b/blocks/property-search-results/map/clusters.js
new file mode 100644
index 00000000..79c0b65d
--- /dev/null
+++ b/blocks/property-search-results/map/clusters.js
@@ -0,0 +1,73 @@
+/* global google */
+
+import Search, { UPDATE_SEARCH_EVENT } from '../../../scripts/apis/creg/search/Search.js';
+import { DRAWING_ENDED, DRAWING_STARTED } from './drawing.js';
+
+let drawing = false;
+
+const clusterClickHandler = async (map, cluster) => {
+ if (drawing) return;
+ const center = new google.maps.LatLng(cluster.centerLat, cluster.centerLon);
+ map.panTo(center);
+ const search = await Search.load('Box');
+ search.populateFromURLSearchParameters(new URLSearchParams(window.location.search));
+ search.minLat = cluster.swLat;
+ search.minLon = cluster.swLon;
+ search.maxLat = cluster.neLat;
+ search.maxLon = cluster.neLon;
+ window.dispatchEvent(new CustomEvent(UPDATE_SEARCH_EVENT, { detail: { search } }));
+};
+
+const compensateForCluster = (count) => {
+ let increase = 0;
+ const log = Math.log10(count);
+ increase = 6 * (Math.round(log * 2) / 2);
+ return increase;
+};
+
+/**
+ * Create a cluster marker from a search result cluster.
+ * @param {Object} cluster
+ * @param {Number} cluster.centerLat Latitude center of the cluster
+ * @param {Number} cluster.centerLon Longitude center of the cluster
+ * @param {Number} cluster.count Number of properties in cluster.
+ */
+export function createClusterMaker(cluster) {
+ // const dimensions = { width: 30, height: 30 };
+ const sizeCompensation = compensateForCluster(cluster.count);
+ const size = 30 + sizeCompensation;
+ const icon = {
+ url: '/icons/maps/map-clusterer-blue.png',
+ scaledSize: new google.maps.Size(size, size),
+ anchor: new google.maps.Point(size / 2, size / 2),
+ };
+
+ return new google.maps.Marker({
+ position: new google.maps.LatLng(cluster.centerLat, cluster.centerLon),
+ zIndex: cluster.count,
+ icon,
+ label: {
+ fontFamily: 'sans-serif',
+ fontSize: '12px',
+ text: `${cluster.count}`,
+ color: 'white',
+ className: 'no-class',
+ },
+ });
+}
+
+export default async function displayClusters(map, clusters) {
+ map.getDiv().addEventListener(DRAWING_STARTED, () => { drawing = true; });
+ map.getDiv().addEventListener(DRAWING_ENDED, () => { drawing = false; });
+
+ const markers = [];
+ clusters.forEach((cluster) => {
+ const marker = createClusterMaker(cluster);
+ marker.setMap(map);
+ marker.addListener('click', () => {
+ clusterClickHandler(marker.map, cluster);
+ });
+ markers.push(marker);
+ });
+ return markers;
+}
diff --git a/blocks/property-search-results/map/drawing.js b/blocks/property-search-results/map/drawing.js
new file mode 100644
index 00000000..81d870f2
--- /dev/null
+++ b/blocks/property-search-results/map/drawing.js
@@ -0,0 +1,150 @@
+/* global google */
+/* global jsts */
+
+const DRAWING_STARTED = 'MapDrawingStarted';
+const DRAWING_ENDED = 'MapDrawingEnded';
+
+let gmap;
+
+let mouseListener;
+const lines = [];
+let activeLine;
+let polygon;
+
+function mouseHandler(e) {
+ if (activeLine.getPath().length) {
+ activeLine.getPath().setAt(1, e.latLng);
+ }
+}
+
+/**
+ * Check if the line intersects any of the existing ones.
+ * @param {Array} existing list of existing lines
+ * @param {google.map.Polyline} potential potential line that may intersect
+ * @return {boolean} whether or not the line intersects
+ */
+function intersects(existing, potential) {
+ const coords = [];
+ if (existing.length) {
+ coords.push(new jsts.geom.Coordinate(existing[0].getPath().getAt(0).lat(), existing[0].getPath().getAt(0).lng()));
+ existing.forEach((line) => {
+ coords.push(new jsts.geom.Coordinate(line.getPath().getAt(1).lat(), line.getPath().getAt(1).lng()));
+ });
+ coords.push(new jsts.geom.Coordinate(potential.getPath().getAt(1).lat(), potential.getPath().getAt(1).lng()));
+ const simple = jsts.operation.IsSimpleOp.isSimple(new jsts.geom.GeometryFactory().createLineString(coords));
+ return !simple; // Simple LineStrings do not intersect.
+ }
+ return false;
+}
+
+/**
+ * Clears the map of any lines or polygon
+ */
+function clearPolygon() {
+ lines.forEach((l) => { l.setMap(null); });
+ lines.length = 0;
+ if (activeLine) {
+ activeLine.getPath().clear();
+ activeLine.setMap(null);
+ }
+ if (polygon) {
+ polygon.setMap(null);
+ polygon = undefined;
+ }
+}
+
+/**
+ * Closes the active polygon, creating the instance and returning it.
+ * @return {google.maps.Polygon} the completed polygon
+ */
+function closePolygon() {
+ // eslint-disable-next-line no-unused-expressions
+ mouseListener && mouseListener.remove();
+
+ activeLine.getPath().clear();
+ activeLine.setMap(null);
+ if (lines.length === 1) {
+ lines[0].setMap(null);
+ lines.length = 0;
+ }
+
+ const points = [];
+ points.push(lines[0].getPath().getAt(0));
+ lines.forEach((line) => {
+ points.push(line.getPath().getAt(1));
+ });
+ polygon = new google.maps.Polygon({
+ map: gmap,
+ paths: points,
+ strokeColor: '#BA9BB2',
+ strokeWeight: 2,
+ clickable: false,
+ fillOpacity: 0,
+ });
+ return polygon;
+}
+
+/**
+ * Adds a point to the polygon.
+ * If the next line intersects an existing line, it closes the polygon and returns it.
+ * @param {google.maps.LatLng} point
+ * @return {boolean} if adding this point closed the polygon
+ */
+function addPolygonPoint(point) {
+ const prev = activeLine.getPath().getAt(0);
+ if (prev) {
+ const line = new google.maps.Polyline({
+ clickable: false,
+ strokeColor: '#BA9BB2',
+ strokeWeight: 2,
+ path: [prev, point],
+ });
+ if (intersects(lines, line)) {
+ return true;
+ }
+ line.setMap(gmap);
+ lines.push(line);
+ }
+ if (lines.length > 1) {
+ document.querySelector('.property-search-results.block .search-map-container .custom-controls .map-draw-complete.disabled')?.classList.remove('disabled');
+ }
+ activeLine.getPath().setAt(0, point);
+ return false;
+}
+
+/**
+ * Starts drawing the polygon by adding a line to the map.
+ * Also initiates the tracking line for potential next line in polygon definition.
+ *
+ * @param {google.maps.Map} map
+ */
+function startPolygon(map) {
+ clearPolygon();
+ gmap = map;
+ activeLine = new google.maps.Polyline({
+ map: gmap,
+ clickable: false,
+ strokeOpacity: 0,
+ icons: [{
+ icon: {
+ path: 'M 0,-1 0,1',
+ strokeOpacity: 1,
+ scale: 2,
+ strokeColor: '#BA9BB2',
+ },
+ offset: 0,
+ repeat: '20px',
+ }],
+ });
+ mouseListener = gmap.addListener('mousemove', mouseHandler);
+ gmap.getDiv().dispatchEvent(new CustomEvent(DRAWING_STARTED));
+}
+
+export {
+ DRAWING_STARTED,
+ DRAWING_ENDED,
+ startPolygon,
+ addPolygonPoint,
+ closePolygon,
+ clearPolygon,
+};
diff --git a/blocks/property-search-results/map/pins.js b/blocks/property-search-results/map/pins.js
new file mode 100644
index 00000000..22988f30
--- /dev/null
+++ b/blocks/property-search-results/map/pins.js
@@ -0,0 +1,342 @@
+/* global google */
+/* global markerClusterer */
+
+import { formatPrice } from '../../../scripts/util.js';
+import { createClusterMaker } from './clusters.js';
+import { BREAKPOINTS } from '../../../scripts/scripts.js';
+import { getDetails } from '../../../scripts/apis/creg/creg.js';
+import {
+ a, p, div, img, span, domEl,
+} from '../../../scripts/dom-helpers.js';
+import { DRAWING_ENDED, DRAWING_STARTED } from './drawing.js';
+
+let drawing;
+let moTimeout;
+let moController;
+
+const infoWindows = [];
+
+function createInfo(property) {
+ const href = property.PdpPath.includes('www.commonmoves.com') ? `/property/detail/pid-${property.ListingId}` : property.PdpPath;
+ const providers = [];
+ if (property.propertyProviders || property.originatingSystemName) {
+ providers.push('Listing Provided by: ');
+ providers.push(property.propertyProviders || property.originatingSystemName);
+ }
+
+ const details = div({ class: 'details' },
+ div({ class: 'address' },
+ span({ class: 'street' }, property.StreetName || ''),
+ span({ class: 'locality' }, `${`${property.City}, ` || ' '} ${`${property.StateOrProvince} ` || ' '} ${property.PostalCode || ''}`),
+ ),
+ );
+
+ if (property.sellingOfficeName) {
+ const address = details.querySelector('.address');
+ address.prepend(
+ span({ class: 'danger' }, `${property.mlsStatus || ''} ${property.ClosedDate || ''}`),
+ );
+ }
+
+ if (property.municipality) {
+ details.append(span({ class: 'municipality' }, property.municipality));
+ }
+
+ details.append(span({ class: 'providers' }, ...providers));
+
+ const listing = div({ class: 'listing-info' });
+ if (property.addMlsFlag === 'true') {
+ listing.append(span({ class: 'mls' }, `MLS ID: ${property.ListingId}`));
+ }
+ if (property.CourtesyOf) {
+ listing.append(span({ class: 'courtesy' }, `Listing courtesy of: ${property.CourtesyOf}`));
+ }
+ if (property.sellingOfficeName) {
+ listing.append(span({ class: 'courtesy' }, `Listing sold by: ${property.sellingOfficeName}`));
+ }
+ if (property.addMlsFlag && property.listAor) {
+ const aor = div({ class: 'aor' }, span(`Listing provided by: ${property.listAor}`));
+ if (property.brImageUrl) {
+ aor.append(span({ class: 'aor-img' }, img({ src: property.brImageUrl })));
+ }
+ listing.append(aor);
+ }
+
+ const image = div({ class: 'image-wrapper' },
+ div({ class: 'luxury' }, span('Luxury Collection')),
+ img({ src: property.smallPhotos[0]?.mediaUrl }),
+ );
+ const info = div({ class: 'info' },
+ div({ class: 'price' },
+ span({ class: 'us' }, property.ListPriceUS || ''),
+ span({ class: 'alt' }, property.listPriceAlternateCurrency || ''),
+ ),
+ div({ class: 'property-buttons' },
+ a({ class: 'contact-us', 'aria-label': `Contact us about ${property.StreetName}` },
+ span({ class: 'icon icon-envelope' }, img({
+ 'data-icon-name': 'envelope',
+ src: '/icons/envelope.svg',
+ alt: 'envelope',
+ })),
+ span({ class: 'icon icon-envelopedark' }, img({
+ 'data-icon-name': 'envelopedark',
+ src: '/icons/envelopedark.svg',
+ alt: 'envelope',
+ })),
+ ),
+ a({ class: 'save', 'aria-label': `Save ${property.StreetName}` },
+ span({ class: 'icon icon-heartempty' }, img({
+ 'data-icon-name': 'heartempty',
+ src: '/icons/heartempty.svg',
+ alt: 'heart',
+ })),
+ span({ class: 'icon icon-heartemptydark' }, img({
+ 'data-icon-name': 'heartemptydark',
+ src: '/icons/heartemptydark.svg',
+ alt: 'heart',
+ })),
+ span({ class: 'icon icon-heartfull' }, img({
+ 'data-icon-name': 'heartfull',
+ src: '/icons/heartfull.svg',
+ alt: 'heart',
+ })),
+ ),
+ ),
+ details,
+ domEl('hr'),
+ listing,
+ );
+
+ return a({ class: 'info-wrapper', rel: 'noopener', href }, image, info);
+}
+
+/**
+ * Removes all Info Windows that may be on the map or attached to markers.
+ */
+function clearInfos() {
+ document.querySelector('.property-search-results.block .mobile-info-window').replaceChildren();
+ infoWindows.forEach((iw) => {
+ iw.close();
+ iw.visible = false;
+ });
+ infoWindows.length = 0;
+}
+
+/**
+ * Hides any visible Info Windows on the map.
+ */
+function hideInfos() {
+ document.querySelector('.property-search-results.block .mobile-info-window').replaceChildren();
+ infoWindows.forEach((iw) => {
+ if (iw.visible) {
+ iw.close();
+ iw.visible = false;
+ }
+ });
+}
+
+async function clusterMouseHandler(marker, cluster) {
+ moController?.abort();
+ moController = new AbortController();
+ const controller = moController;
+ if (marker.infoWindow?.visible) {
+ return;
+ }
+ hideInfos();
+ if (!marker.infoWindow && !controller.signal.aborted) {
+ const content = div({ class: 'info-window cluster' }, div({ class: 'loading' }, p('Loading...')));
+ const tmp = new google.maps.InfoWindow({ content });
+ tmp.open({ anchor: marker, shouldFocus: false });
+ const center = marker.getMap().getCenter();
+ // But if this fetch was canceled, don't show the info window.
+ const ids = [];
+ cluster.markers.forEach((m) => {
+ ids.push(m.listingKey);
+ });
+ await getDetails(...ids).then((listings) => {
+ // If we got this far, may as well add the content to info window.
+ const infos = [];
+ listings.forEach((property) => {
+ infos.push(createInfo(property));
+ });
+ content.replaceChildren(...infos);
+ const iw = new google.maps.InfoWindow({ content });
+ iw.setContent(content);
+ iw.addListener('close', () => marker.getMap().panTo(center));
+ infoWindows.push(iw);
+ marker.infoWindow = iw;
+ tmp.close();
+ });
+ }
+ if (controller.signal.aborted) {
+ return;
+ }
+ marker.infoWindow.open({ anchor: marker, shouldFocus: false });
+ marker.infoWindow.visible = true;
+}
+
+/**
+ * Display the Mobile Info Window with the desired content.
+ * @param {Promise>} promise a promise that resolves to the content to display
+ * @param cluster flag to indicate if this is a list of properties
+ */
+async function showMobileInfoWindow(promise, cluster = false) {
+ window.scrollTo({ top: 115, behavior: 'smooth' });
+ const iw = document.querySelector('.property-search-results.block .search-map-wrapper .mobile-info-window');
+ if (cluster) iw.classList.add('cluster');
+ iw.replaceChildren(div({ class: 'loading' }, p('Loading...')));
+ promise.then((content) => {
+ // eslint-disable-next-line no-param-reassign
+ content = content.length ? content : [content];
+ iw.replaceChildren(...content);
+ });
+}
+
+/*
+ See https://googlemaps.github.io/js-markerclusterer/interfaces/MarkerClustererOptions.html#renderer
+ */
+const ClusterRenderer = {
+ render: (cluster) => {
+ const marker = createClusterMaker({
+ centerLat: cluster.position.lat(),
+ centerLon: cluster.position.lng(),
+ count: cluster.count,
+ });
+
+ // Do not fire the fetch immediately, give the user a beat to move their mouse to desired target.
+ marker.addListener('mouseout', () => window.clearTimeout(moTimeout));
+ marker.addListener('mouseover', () => {
+ if (drawing) return;
+ if (BREAKPOINTS.medium.matches) {
+ moTimeout = window.setTimeout(() => clusterMouseHandler(marker, cluster), 500);
+ }
+ });
+ return marker;
+ },
+};
+
+function pinGroupClickHandler(e, cluster) {
+ if (drawing) return;
+ if (BREAKPOINTS.medium.matches && e.domEvent instanceof TouchEvent) {
+ clusterMouseHandler(cluster.marker, cluster);
+ } else {
+ const listings = cluster.markers.map((m) => m.listingKey);
+ const promise = getDetails(...listings).then((details) => {
+ const links = [];
+ details.forEach((property) => {
+ links.push(createInfo(property));
+ });
+ return links;
+ });
+ showMobileInfoWindow(promise, true);
+ }
+}
+
+/**
+ * Generate a new Marker Clusterer from the map.
+ * @param map
+ * @return {markerClusterer.MarkerClusterer}
+ */
+function getMarkerClusterer(map) {
+ return new markerClusterer.MarkerClusterer({ map, renderer: ClusterRenderer, onClusterClick: pinGroupClickHandler });
+}
+
+async function pinMouseHandler(marker, pin) {
+ moController?.abort();
+ moController = new AbortController();
+ const controller = moController;
+ if (marker.infoWindow?.visible) {
+ return;
+ }
+ hideInfos();
+ if (!marker.infoWindow && !controller.signal.aborted) {
+ const content = div({ class: 'info-window' }, div({ class: 'loading' }, p('Loading...')));
+ const tmp = new google.maps.InfoWindow({ content });
+ tmp.open({ anchor: marker, shouldFocus: false });
+ const center = marker.getMap().getCenter();
+ // But if this fetch was canceled, don't show the info window.
+ await getDetails(pin.listingKey).then((listings) => {
+ content.replaceChildren(createInfo(listings[0]));
+ const iw = new google.maps.InfoWindow({ content });
+ iw.setContent(content);
+ iw.addListener('close', () => marker.getMap().panTo(center));
+ infoWindows.push(iw);
+ marker.infoWindow = iw;
+ tmp.close();
+ });
+ }
+ if (controller.signal.aborted) {
+ return;
+ }
+ marker.infoWindow.open({ anchor: marker, shouldFocus: false });
+ marker.infoWindow.visible = true;
+}
+
+/**
+ * Create a cluster marker from a search result cluster.
+ * @param {Object} pin
+ * @param {Number} pin.lat Latitude of the pin
+ * @param {Number} pin.lon Longitude of the pin
+ * @param {Number} pin.price Price of the pin listing
+ * @param {Number} pin.listingKey Listing id of the pin
+ * @param {Number} pin.officeCode Office code of the listing.
+ */
+function createPinMarker(pin) {
+ const icon = {
+ url: '/icons/maps/map-marker-standard.png',
+ scaledSize: new google.maps.Size(50, 25),
+ anchor: new google.maps.Point(25, 0),
+ };
+
+ const marker = new google.maps.Marker({
+ position: new google.maps.LatLng(pin.lat, pin.lon),
+ zIndex: 1,
+ icon,
+ label: {
+ fontFamily: 'sans-serif',
+ fontSize: '12px',
+ text: `$${formatPrice(pin.price)}`,
+ color: 'white',
+ className: 'no-class',
+ },
+ });
+ marker.addListener('click', (e) => {
+ if (drawing) return;
+ if (BREAKPOINTS.medium.matches && e.domEvent instanceof TouchEvent) {
+ pinMouseHandler(marker, pin);
+ } else {
+ showMobileInfoWindow(getDetails(pin.listingKey).then((details) => createInfo(details[0])));
+ }
+ });
+ // Do not fire the fetch immediately, give the user a beat to move their mouse to desired target.
+ marker.addListener('mouseout', () => window.clearTimeout(moTimeout));
+ marker.addListener('mouseover', () => {
+ if (drawing) return;
+ if (BREAKPOINTS.medium.matches) {
+ moTimeout = window.setTimeout(() => pinMouseHandler(marker, pin), 500);
+ }
+ });
+
+ marker.listingKey = pin.listingKey;
+ return marker;
+}
+
+async function displayPins(map, pins) {
+ map.getDiv().addEventListener(DRAWING_STARTED, () => { drawing = true; });
+ map.getDiv().addEventListener(DRAWING_ENDED, () => { drawing = false; });
+
+ const markers = [];
+ pins.forEach((pin) => {
+ const marker = createPinMarker(pin);
+ marker.setMap(map);
+ markers.push(marker);
+ });
+ return markers;
+}
+
+export {
+ clearInfos,
+ hideInfos,
+ getMarkerClusterer,
+ displayPins,
+};
diff --git a/blocks/property-search-results/observers.js b/blocks/property-search-results/observers.js
new file mode 100644
index 00000000..5b513a7f
--- /dev/null
+++ b/blocks/property-search-results/observers.js
@@ -0,0 +1,87 @@
+import Search, { UPDATE_SEARCH_EVENT } from '../../scripts/apis/creg/search/Search.js';
+import { input } from '../../scripts/dom-helpers.js';
+import ListingType from '../../scripts/apis/creg/search/types/ListingType.js';
+import { closeOnBodyClick } from '../shared/search/util.js';
+
+export default function observe(block) {
+ block.querySelectorAll('a.map-view').forEach((btn) => {
+ btn.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const blk = e.currentTarget.closest('.block');
+ blk.classList.remove('list-view');
+ blk.classList.add('map-view');
+ });
+ });
+
+ block.querySelectorAll('a.list-view').forEach((btn) => {
+ btn.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const blk = e.currentTarget.closest('.block');
+ blk.classList.remove('map-view');
+ blk.classList.add('list-view');
+ });
+ });
+
+ block.querySelectorAll('.listing-types .filter-toggle').forEach((t) => {
+ t.addEventListener('click', async (e) => {
+ e.preventDefault();
+ const { currentTarget } = e;
+ const search = await Search.fromQueryString(window.location.search);
+ const checked = currentTarget.querySelector('div.checkbox').classList.toggle('checked');
+ const ipt = currentTarget.querySelector('input');
+ input.checked = checked;
+ if (checked) {
+ search.addListingType(ipt.value);
+ } else {
+ search.removeListingType(ipt.value);
+ }
+ window.dispatchEvent(new CustomEvent(UPDATE_SEARCH_EVENT, { detail: { search } }));
+ });
+ });
+
+ block.querySelector('.listing-types').addEventListener('click', (e) => {
+ e.preventDefault();
+ const ipt = e.target.closest('.filter-toggle')?.querySelector('input');
+ if (ipt && ipt.value === ListingType.FOR_RENT.type) {
+ e.currentTarget.querySelector(`input[value="${ListingType.PENDING.type}"]`).closest('.filter-toggle').classList.toggle('disabled');
+ } else if (ipt && ipt.value === ListingType.PENDING.type) {
+ e.currentTarget.querySelector(`input[value="${ListingType.FOR_RENT.type}"]`).closest('.filter-toggle').classList.toggle('disabled');
+ }
+ });
+
+ const sortSelect = block.querySelector('.sort-options .select-wrapper div.selected');
+ sortSelect.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const button = e.currentTarget;
+ const wrapper = button.closest('.select-wrapper');
+ const open = wrapper.classList.toggle('open');
+ button.setAttribute('aria-expanded', open);
+ if (open) {
+ closeOnBodyClick(wrapper);
+ }
+ });
+
+ sortSelect.querySelectorAll('.select-items li').forEach((opt) => {
+ opt.addEventListener('click', async (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const target = e.currentTarget;
+ const value = target.getAttribute('data-value');
+ const wrapper = target.closest('.select-wrapper');
+ wrapper.querySelector('.selected span').textContent = target.textContent;
+ wrapper.querySelector('ul li.selected')?.classList.toggle('selected');
+ const selected = wrapper.querySelector(`select option[value="${value}"]`);
+ selected.selected = true;
+ target.classList.add('selected');
+ wrapper.classList.remove('open');
+ wrapper.querySelector('[aria-expanded="true"]')?.setAttribute('aria-expanded', 'false');
+ const search = await Search.fromQueryString(window.location.search);
+ search.sortBy = selected.getAttribute('data-sort-by');
+ search.sortDirection = selected.getAttribute('data-sort-direction');
+ window.dispatchEvent(new CustomEvent(UPDATE_SEARCH_EVENT, { detail: { search } }));
+ });
+ });
+}
diff --git a/blocks/property-search-results/property-search-results.css b/blocks/property-search-results/property-search-results.css
new file mode 100644
index 00000000..425edb6a
--- /dev/null
+++ b/blocks/property-search-results/property-search-results.css
@@ -0,0 +1,502 @@
+@import url('../shared/property/cards.css');
+@import url('map.css');
+
+
+/* Override global settings */
+main .section.property-search-results-container {
+ padding: 0;
+ margin: 0 auto;
+}
+
+.property-search-results-wrapper {
+ padding: 0;
+ margin: 0;
+}
+
+/* End global overrides */
+
+.property-search-results.block {
+ position: relative;
+ min-height: calc(100vh - var(--nav-height) - 50px - 40px);
+
+ --map-height: calc(100vh - var(--nav-height) - 50px - 75px - 55px); /* Nav, search bar, sort options, mobile links */
+}
+
+.property-search-results.block.map-view .search-map-wrapper {
+ height: var(--map-height);
+}
+
+.property-search-results.block .search-results-loader {
+ position: absolute;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ top: 0;
+ left: 50%;
+ height: 100%;
+ width: 100%;
+ max-width: 400px;
+ transform: translateX(-50%);
+ margin: 0 auto;
+ opacity: 0;
+ visibility: hidden;
+ transition: all 2s linear;
+ z-index: 2
+}
+
+.property-search-results.block .loading .search-results-loader {
+ opacity: 1;
+ visibility: visible;
+ transition: all 2s linear;
+}
+
+.property-search-results.block .search-results-loader .animation {
+ position: relative;
+ width: 100%;
+}
+
+@keyframes pulse {
+ from {
+ border: 0 solid white;
+ }
+
+ to {
+ border: 75px solid white
+ }
+}
+
+.property-search-results.block .search-results-loader .pulse {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ overflow: hidden;
+ animation: linear 2s infinite alternate pulse;
+ z-index: 2;
+}
+
+.property-search-results.block .search-results-loader picture {
+ position: relative;
+ display: block;
+ width: 100%;
+ padding-bottom: 100%;
+ border-radius: 50%;
+ overflow: hidden;
+}
+
+.property-search-results.block .search-results-loader picture img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ object-position: center;
+ object-fit: cover;
+}
+
+.property-search-results.block .search-results-content {
+ position: relative;
+}
+
+.property-search-results.block .search-results-wrapper {
+ min-height: 400px;
+ padding: 10px;
+ opacity: 1;
+ visibility: visible;
+ transition: all 2s linear;
+}
+
+.property-search-results.block .loading .search-results-wrapper {
+ opacity: 0;
+ visibility: hidden;
+ transition: all 2s linear;
+}
+
+.property-search-results.block.list-view .search-map-wrapper {
+ display: none;
+}
+
+.property-search-results.block.map-view .search-results-wrapper {
+ display: none;
+}
+
+.property-search-results.block .view-options a {
+ padding: 0 15px;
+ height: 35px;
+ font-size: var(--body-font-size-xs);
+ letter-spacing: var(--letter-spacing-m);
+ line-height: 35px;
+ border: 1px solid var(--grey);
+ white-space: nowrap;
+}
+
+.property-search-results.block.list-view .view-options a.list-view {
+ display: none;
+}
+
+.property-search-results.block.map-view .view-options a.map-view {
+ display: none;
+}
+
+.property-search-results.block .select-wrapper select {
+ display: none;
+}
+
+.property-search-results.block .select-wrapper > .selected {
+ display: flex;
+ position: relative;
+ padding: 0 15px;
+ align-items: center;
+ justify-content: space-between;
+ gap: 3px;
+ width: 100%;
+ height: 35px;
+ min-width: 100px;
+ line-height: 35px;
+ color: var(--body-color);
+ border: 1px solid var(--grey);
+}
+
+.property-search-results.block .select-wrapper > .selected::after {
+ display: block;
+ content: '\f0d7';
+ font-family: var(--font-family-fontawesome);
+ color: var(--dark-grey);
+ text-align: right;
+ width: 15px;
+}
+
+.property-search-results.block .select-wrapper.open > .selected::after {
+ content: '\f0d8';
+}
+
+.property-search-results.block .select-wrapper .select-items {
+ display: none;
+ position: absolute;
+ padding: 0;
+ max-height: 185px;
+ top: 100%;
+ left: -1px;
+ width: 145px;
+ overflow-y: scroll;
+ background-color: var(--white);
+ border: 1px solid var(--grey);
+ box-shadow: 0 .5rem 1rem rgba(0 0 0 / 15%);
+ white-space: nowrap;
+ z-index: 1;
+}
+
+.property-search-results.block .select-wrapper.open .select-items {
+ display: block;
+}
+
+.property-search-results.block .select-wrapper > .selected span {
+ overflow: hidden;
+ font-size: var(--body-font-size-xs);
+ font-weight: var(--font-weight-light);
+ line-height: var(--line-height-xl);
+ letter-spacing: var(--letter-spacing-m);
+ text-transform: uppercase;
+ white-space: nowrap;
+}
+
+.property-search-results.block .select-wrapper .select-items li {
+ display: flex;
+ padding: 4px 15px;
+ cursor: pointer;
+ font-size: var(--body-font-size-xs);
+ color: var(--body-color);
+ text-transform: uppercase;
+ line-height: var(--line-height-xl);
+ letter-spacing: var(--letter-spacing-xs);
+}
+
+.property-search-results.block .select-wrapper .select-items li:hover {
+ color: var(--body-color);
+ background-color: var(--light-grey);
+}
+
+.property-search-results.block .select-wrapper .select-items li.selected {
+ color: var(--body-color);
+ background-color: var(--light-grey);
+}
+
+
+.property-search-results.block .property-search-filters {
+ display: grid;
+ grid-template-columns: 1fr;
+ padding: 0 15px;
+ gap: 30px;
+ margin: 20px 0;
+}
+
+.property-search-results.block .listing-types {
+ display: none;
+ padding: 0 15px;
+ gap: 15px;
+}
+
+.property-search-results.block .listing-types .filter-toggle {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ font-size: var(--body-font-size-s);
+}
+
+.property-search-results.block .listing-types .filter-toggle label {
+ font-size: var(--body-font-size-xs);
+ letter-spacing: var(--letter-spacing-xs);
+ line-height: normal;
+ color: var(--body-color);
+}
+
+.property-search-results.block .listing-types .filter-toggle .checkbox {
+ min-width: 24px;
+ height: 16px;
+ border-radius: 100px;
+ position: relative;
+ border: 1px solid #b4b4b4;
+ background: var(--white);
+}
+
+.property-search-results.block .listing-types .filter-toggle .checkbox::before {
+ content: '';
+ position: absolute;
+ right: 2px;
+ top: 2px;
+ height: 10px;
+ width: 10px;
+ border-radius: 10px;
+ z-index: 1;
+ background: #b4b4b4;
+}
+
+.property-search-results.block .listing-types .filter-toggle .checkbox.checked {
+ background: var(--body-color);
+ border: 1px solid transparent
+}
+
+.property-search-results.block .listing-types .filter-toggle .checkbox.checked::before {
+ transform: translateX(-8px);
+ background: var(--white);
+}
+
+.property-search-results.block .listing-types .filter-toggle.disabled {
+ pointer-events: none;
+ opacity: .3;
+}
+
+.property-search-results.block .sort-options {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ justify-self: flex-end;
+}
+
+/* stylelint-disable-next-line no-descending-specificity */
+.property-search-results.block .sort-options label {
+ font-size: var(--body-font-size-s);
+ line-height: var(--line-height-m);
+ color: var(--body-color);
+ white-space: nowrap;
+}
+
+.property-search-results.block .sort-options .select-wrapper {
+ position: relative;
+ width: 145px;
+}
+
+.property-search-results.block .search-results-content .search-results-disclaimer-wrapper {
+ margin: 30px 0;
+}
+
+.property-search-results.block .search-results-disclaimer-wrapper .search-results-disclaimer {
+ margin: 15px 0;
+ padding: 0 16px;
+}
+
+.property-search-results.block .search-results-disclaimer > div {
+ margin: 16px 0;
+}
+
+.property-search-results.block .search-results-disclaimer p {
+ margin: 5px 0;
+ font-size: var(--body-font-size-xs);
+ line-height: var(--line-height-xs);
+ letter-spacing: var(--letter-spacing-s);
+ color: var(--dark-grey);
+}
+
+.property-search-results.block .search-results-disclaimer p.image:not(.img-first) {
+ line-height: 0;
+ margin: 5px auto;
+ text-align: center;
+}
+
+.property-search-results.block .search-results-disclaimer p.image img {
+ height: auto;
+ width: auto;
+ max-width: 140px;
+ max-height: 30px;
+}
+
+.property-search-results.block .pagination-wrapper .select-wrapper {
+ position: relative;
+ width: 145px;
+}
+
+.property-search-results.block .search-results-pagination .pagination-wrapper {
+ display: flex;
+ margin-top: 30px;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 15px;
+}
+
+.property-search-results.block .search-results-wrapper .search-results-pagination .link-wrapper {
+ display: flex;
+ gap: 15px;
+}
+
+.property-search-results.block .search-results-wrapper .search-results-pagination .link-wrapper a {
+ height: 35px;
+ width: 35px;
+ border: 1px solid var(--grey);
+}
+
+.property-search-results.block .search-results-wrapper .search-results-pagination .link-wrapper a.disabled {
+ pointer-events: none;
+ border: 1px solid var(--platinum);
+}
+
+.property-search-results.block .search-results-wrapper .search-results-pagination .link-wrapper a svg {
+ padding: 5px;
+ height: 100%;
+ width: 100%;
+}
+
+.property-search-results.block .search-results-wrapper .search-results-pagination .link-wrapper a.disabled svg {
+ filter: invert(99%) sepia(0%) saturate(1103%) hue-rotate(210deg) brightness(113%) contrast(81%);
+}
+
+
+.property-search-results.block .search-results-wrapper .search-results-pagination .link-wrapper a.prev svg {
+ transform: rotate(-180deg);
+}
+
+
+.property-search-results.block .view-options p {
+ margin: 0;
+}
+
+.property-search-results.block .mobile-view-options {
+ position: sticky;
+ bottom: 0;
+ padding: 10px 0;
+ box-shadow: 0 0 6px 0 rgba(0 0 0 / 23%);
+ background-color: var(--white);
+ z-index: 100;
+}
+
+.property-search-results.block .mobile-view-options p {
+ display: flex;
+ justify-content: center;
+ gap: 16px;
+}
+
+.property-search-results.block .desktop-view-options {
+ display: none;
+}
+
+/** Override the default card display */
+
+.property-search-results.block .search-results-wrapper .property-list-cards {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 30px;
+}
+
+.property-search-results.block .search-results-wrapper .property-list-cards .listing-tile {
+ width: 100%;
+ max-width: unset;
+}
+
+@media screen and (min-width: 600px) {
+ .property-search-results.block .search-results-wrapper .property-list-cards {
+ grid-template-columns: repeat(3, 1fr);
+ gap: 15px;
+ }
+}
+
+@media screen and (min-width: 900px) {
+ .property-search-results.block.map-view {
+ display: grid;
+ grid-template:
+ "filters filters"
+ "map results"
+ / 58% 1fr;
+ }
+
+ .property-search-results.block .property-search-filters {
+ grid-area: filters;
+ grid-template-columns: 1fr min-content min-content;
+ }
+
+ .property-search-results.block .search-map-wrapper {
+ grid-area: map;
+ overflow: scroll;
+ }
+
+ .property-search-results.block .search-results-content {
+ padding: 0;
+ grid-area: results;
+ max-height: var(--map-height);
+ overflow: scroll;
+ }
+
+ /* Override the Property Bar listing type section when this block's are visible */
+ .property-search-bar.block .listing-types {
+ display: none;
+ }
+
+ .property-search-results.block .desktop-view-options {
+ display: flex;
+ align-items: center;
+ }
+
+ .property-search-results.block .listing-types {
+ display: flex;
+ }
+
+ .property-search-results.block .mobile-view-options {
+ display: none;
+ }
+
+ .property-search-results.block .search-results-wrapper {
+ padding: 0 10px;
+ }
+
+ .property-search-results.block.map-view .search-results-wrapper {
+ display: block;
+ }
+
+ .property-search-results.block .search-results-wrapper .property-list-cards {
+ grid-template-columns: repeat(3, 1fr);
+ }
+
+ .property-search-results.block.map-view .search-results-wrapper .property-list-cards {
+ grid-template-columns: 1fr
+ }
+}
+
+@media screen and (min-width: 1200px) {
+ .property-search-results.block .search-results-wrapper .property-list-cards {
+ grid-template-columns: repeat(4, 1fr);
+ }
+
+ .property-search-results.block.map-view .search-results-wrapper .property-list-cards {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
diff --git a/blocks/property-search-results/property-search-results.js b/blocks/property-search-results/property-search-results.js
new file mode 100644
index 00000000..7a12daad
--- /dev/null
+++ b/blocks/property-search-results/property-search-results.js
@@ -0,0 +1,264 @@
+import {
+ a, div, input, label, li, option, p, select, span, ul, img, domEl,
+} from '../../scripts/dom-helpers.js';
+import Search, {
+ UPDATE_SEARCH_EVENT,
+ SEARCH_URL,
+ STORAGE_KEY,
+} from '../../scripts/apis/creg/search/Search.js';
+import ListingType from '../../scripts/apis/creg/search/types/ListingType.js';
+import { propertySearch } from '../../scripts/apis/creg/creg.js';
+import { getMetadata, readBlockConfig } from '../../scripts/aem.js';
+import { updateForm } from '../property-search-bar/delayed.js';
+import { BREAKPOINTS } from '../../scripts/scripts.js';
+import observe from './observers.js';
+import loader from './loader.js';
+import { displayResults as displayList } from './results.js';
+import { displayResults as displayMap, reinitMap } from './map.js';
+
+let searchController;
+
+let initMap;
+
+/**
+ * Converts the Disclaimer returned from search results and extracts images and text.
+ * @param {String} disclaimer
+ */
+function sanitizeDisclaimer(disclaimer) {
+ const tmp = document.createElement('div');
+ tmp.innerHTML = disclaimer;
+ const content = [];
+ tmp.querySelectorAll(':scope > div').forEach((d) => {
+ const text = [];
+ let image;
+ let imgFirst = false;
+ // eslint-disable-next-line no-bitwise
+ const walker = document.createTreeWalker(d, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
+ let next = walker.firstChild();
+ while (next) {
+ if (next.nodeType === Node.TEXT_NODE && next.textContent.trim() !== '') {
+ text.push(p(next.textContent));
+ } else if (next.nodeName === 'IMG') {
+ if (text.length === 0) imgFirst = true;
+ const config = { src: next.src };
+ if (next.height) config.height = next.height;
+ if (next.width) config.width = next.width;
+ image = p({ class: `image ${imgFirst ? 'img-first' : ''}` }, img(config));
+ }
+ next = walker.nextNode();
+ }
+ if (image) {
+ if (imgFirst) {
+ text.unshift(image);
+ } else {
+ text.push(image);
+ }
+ }
+ content.push(div(...text));
+ });
+ return content;
+}
+
+function updateFilters(search) {
+ const filters = document.querySelector('.property-search-results.block .property-search-filters');
+ filters.querySelectorAll('.listing-types .filter-toggle.disabled').forEach((t) => t.classList.remove('disabled'));
+ filters.querySelectorAll('.listing-types .filter-toggle input[type="checkbox"]').forEach((c) => {
+ c.removeAttribute('checked');
+ c.nextElementSibling.classList.remove('checked');
+ });
+ search.listingTypes.forEach((t) => {
+ const chkbx = filters.querySelector(`.listing-types .filter-toggle input[name="${t.type}"]`);
+ chkbx.setAttribute('checked', 'checked');
+ chkbx.nextElementSibling.classList.add('checked');
+ if (t.type === ListingType.FOR_RENT.type) {
+ filters.querySelector(`.listing-types .filter-toggle input[name="${ListingType.PENDING.type}"]`).closest('.filter-toggle').classList.add('disabled');
+ } else if (t.type === ListingType.PENDING.type) {
+ filters.querySelector(`.listing-types .filter-toggle input[name="${ListingType.FOR_RENT.type}"]`).closest('.filter-toggle').classList.add('disabled');
+ }
+ });
+
+ const sort = `${search.sortBy}_${search.sortDirection}`;
+ filters.querySelector('.sort-options ul li.selected').classList.remove('selected');
+ filters.querySelector(`.sort-options select option[value="${sort}"]`).selected = true;
+ filters.querySelector(`.sort-options ul li[data-value="${sort}"]`).classList.add('selected');
+ filters.querySelector('.selected span').textContent = filters.querySelector('.sort-options ul li.selected').textContent;
+}
+
+/**
+ * Perform the search
+ * @param {Search} search the search to perform
+ * @param {boolean} redraw if the map should be updated
+ * @return {Promise}
+ */
+async function doSearch(search, redraw = true) {
+ searchController?.abort();
+ searchController = new AbortController();
+ window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(search));
+ search.franchiseeCode = getMetadata('office-id');
+ const contentWrapper = document.querySelector('.property-search-results.block .search-results-content');
+ contentWrapper.classList.add('loading');
+ contentWrapper.scrollTo({ top: 0, behavior: 'smooth' });
+ const parent = document.querySelector('.property-search-results.block .search-results-wrapper');
+ return new Promise(() => {
+ const controller = searchController;
+ propertySearch(search).then((results) => {
+ if (!controller.signal.aborted) {
+ displayList(parent, results);
+ contentWrapper.querySelector('.search-results-disclaimer-wrapper').replaceChildren(
+ domEl('hr', { role: 'presentation', 'aria-hidden': true, tabindex: -1 }),
+ div({ class: 'search-results-disclaimer' }, ...sanitizeDisclaimer(results.disclaimer)),
+ );
+ contentWrapper.classList.remove('loading');
+ if (redraw) displayMap(results);
+ }
+ });
+ });
+}
+
+export default async function decorate(block) {
+ const config = readBlockConfig(block);
+
+ const view = BREAKPOINTS.medium.matches ? 'map-view' : 'list-view';
+ block.classList.add(view);
+ /* @formatter:off */
+ const filters = div({ class: 'property-search-filters' },
+ div({ class: 'listing-types' },
+ div({ class: 'filter-toggle' },
+ input({
+ name: 'FOR_SALE',
+ hidden: 'hidden',
+ type: 'checkbox',
+ 'aria-label': 'Hidden Checkbox',
+ checked: 'checked',
+ value: `${ListingType.FOR_SALE.type}`,
+ }),
+ div({ class: 'checkbox checked' }),
+ label({ role: 'presentation' }, ListingType.FOR_SALE.label),
+ ),
+ div({ class: 'filter-toggle' },
+ input({
+ name: 'FOR_RENT',
+ hidden: 'hidden',
+ type: 'checkbox',
+ 'aria-label': 'Hidden Checkbox',
+ value: `${ListingType.FOR_RENT.type}`,
+ }),
+ div({ class: 'checkbox' }),
+ label({ role: 'presentation' }, ListingType.FOR_RENT.label),
+ ),
+ div({ class: 'filter-toggle' },
+ input({
+ name: 'PENDING',
+ hidden: 'hidden',
+ type: 'checkbox',
+ 'aria-label': 'Hidden Checkbox',
+ value: `${ListingType.PENDING.type}`,
+ }),
+ div({ class: 'checkbox' }),
+ label({ role: 'presentation' }, ListingType.PENDING.label),
+ ),
+ div({ class: 'filter-toggle' },
+ input({
+ name: 'RECENTLY_SOLD',
+ hidden: 'hidden',
+ type: 'checkbox',
+ 'aria-label': 'Hidden Checkbox',
+ value: `${ListingType.RECENTLY_SOLD.type}`,
+ }),
+ div({ class: 'checkbox' }),
+ label({ role: 'presentation' }, 'Sold'),
+ ),
+ ),
+ div({ class: 'sort-options' },
+ label({ role: 'presentation' }, 'Sort by'),
+ div({ class: 'select-wrapper' },
+ select({ name: 'sort', 'aria-label': 'Distance' },
+ // eslint-disable-next-line object-curly-newline
+ option({ value: 'DISTANCE_ASC', 'data-sort-by': 'DISTANCE', 'data-sort-direction': 'ASC', selected: 'selected' }, 'Distance'),
+ option({ value: 'PRICE_DESC', 'data-sort-by': 'PRICE', 'data-sort-direction': 'DESC' }, 'Price (Hi-Lo)'),
+ option({ value: 'PRICE_ASC', 'data-sort-by': 'PRICE', 'data-sort-direction': 'ASC' }, 'Price (Lo-Hi)'),
+ option({ value: 'DATE_DESC', 'data-sort-by': 'DATE', 'data-sort-direction': 'DESC' }, 'Date (New-Old)'),
+ option({ value: 'DATE_ASC', 'data-sort-by': 'DATE', 'data-sort-direction': 'ASC' }, 'Date (Old-New)'),
+ ),
+ // eslint-disable-next-line object-curly-newline
+ div({ class: 'selected', role: 'combobox', 'aria-haspopup': 'listbox', 'aria-label': 'Distance', 'aria-expanded': false, 'aria-controls': 'search-results-sort', tabindex: 0 },
+ span('Distance'),
+ ),
+ ul({ id: 'search-results-sort', class: 'select-items', role: 'listbox' },
+ li({ 'data-value': 'DISTANCE_ASC', role: 'option', class: 'selected' }, 'Distance'),
+ li({ 'data-value': 'PRICE_DESC', role: 'option' }, 'Price (Hi-Lo)'),
+ li({ 'data-value': 'PRICE_ASC', role: 'option' }, 'Price (Lo-Hi)'),
+ li({ 'data-value': 'DATE_DESC', role: 'option' }, 'Date (New-Old)'),
+ li({ 'data-value': 'DATE_ASC', role: 'option' }, 'Date (Old-New)'),
+ ),
+ ),
+ ),
+ div({ class: 'desktop-view-options view-options' },
+ p({ class: 'button-container' },
+ a({ class: 'map-view', role: 'button', rel: 'noopener noreferrer' }, 'Map View'),
+ a({ class: 'list-view', role: 'button', rel: 'noopener noreferrer' }, 'List View'),
+ ),
+ ),
+ );
+
+ const map = div({ class: 'search-map-wrapper' },
+ div({ class: 'search-map-container satellite' },
+ div({ class: 'search-results-map' }, div({ id: 'gmap-canvas' })),
+ ),
+ );
+ /* @formatter:on */
+
+ const list = div({ class: 'search-results-wrapper' });
+ const disclaimer = div({ class: 'search-results-disclaimer-wrapper' });
+
+ const content = div({ class: 'search-results-content loading' }, loader, list, disclaimer);
+
+ const buttons = div({ class: 'mobile-view-options view-options' },
+ p({ class: 'button-container' },
+ a({ target: '_blank', role: 'button', rel: 'noopener noreferrer' }, 'Save'),
+ a({ class: 'map-view', role: 'button', rel: 'noopener noreferrer' }, 'Map View'),
+ a({ class: 'list-view', role: 'button', rel: 'noopener noreferrer' }, 'List View'),
+ ),
+ );
+
+ block.replaceChildren(filters, map, content, buttons);
+
+ // Default the search results.
+ let search;
+ if (window.location.search === '') {
+ const data = window.sessionStorage.getItem(STORAGE_KEY);
+ if (data) {
+ search = await Search.fromJSON(JSON.parse(data));
+ } else {
+ search = await Search.fromBlockConfig(config);
+ }
+ window.history.replaceState(null, '', new URL(`/search?${search.asURLSearchParameters()}`, window.location));
+ } else {
+ search = await Search.fromQueryString(window.location.search);
+ }
+ updateFilters(search);
+ updateForm(search);
+ observe(block);
+
+ window.addEventListener('popstate', async () => {
+ const newSearch = await Search.fromQueryString(window.location.search);
+ updateFilters(newSearch);
+ updateForm(newSearch);
+ reinitMap(newSearch);
+ doSearch(newSearch);
+ });
+
+ window.addEventListener(UPDATE_SEARCH_EVENT, async (e) => {
+ const { search: newSearch, redraw } = e.detail;
+ updateFilters(newSearch);
+ window.history.pushState(null, '', new URL(`${SEARCH_URL}?${newSearch.asURLSearchParameters().toString()}`, window.location));
+ doSearch(newSearch, redraw);
+ });
+
+ window.setTimeout(async () => {
+ const mod = await import(`${window.hlx.codeBasePath}/blocks/property-search-results/map.js`);
+ initMap = mod.initMap;
+ initMap(block, search);
+ doSearch(search);
+ }, 3000);
+}
diff --git a/blocks/property-search-results/results.js b/blocks/property-search-results/results.js
new file mode 100644
index 00000000..980aa2b0
--- /dev/null
+++ b/blocks/property-search-results/results.js
@@ -0,0 +1,102 @@
+import {
+ a, div, li, option, select, span, ul,
+} from '../../scripts/dom-helpers.js';
+import { closeOnBodyClick } from '../shared/search/util.js';
+import Search, { UPDATE_SEARCH_EVENT } from '../../scripts/apis/creg/search/Search.js';
+import { render as renderCards } from '../shared/property/cards.js';
+
+/**
+ * Builds the pagination of results
+ * @param {Number} total total number of pages
+ * @param {Number} current current page number
+ */
+function buildPagination(total, current) {
+ if (Number.isNaN(total) || Number.isNaN(current)) return div();
+ const options = [];
+ const lis = [];
+ for (let i = 1; i <= total; i += 1) {
+ options.push(option({ value: i }, i));
+ const config = { 'data-value': i };
+ if (i === current) config.class = 'selected';
+ lis.push(li(config, i));
+ }
+ const displayLabel = `${current} of ${total}`;
+ const wrapper = div({ class: 'pagination-wrapper' },
+ div({ class: 'select-wrapper' },
+ select({ name: 'page', 'aria-label': displayLabel }, ...options),
+ div({
+ class: 'selected',
+ role: 'button',
+ 'aria-haspopup': 'listbox',
+ 'aria-label': displayLabel,
+ 'aria-expanded': false,
+ tabindex: 0,
+ }, span(displayLabel)),
+ ul({ class: 'select-items', role: 'listbox' }, ...lis),
+ ),
+ div({ class: 'link-wrapper' }),
+ );
+
+ const prev = a({
+ class: 'prev',
+ 'aria-label': 'Previous Page',
+ role: 'button',
+ 'data-value': `${current - 1}`,
+ });
+ prev.innerHTML = '';
+ const next = a({
+ class: 'next',
+ 'aria-label': 'Next Page',
+ role: 'button',
+ 'data-value': `${current + 1}`,
+ });
+ next.innerHTML = '';
+
+ if (current === 1) {
+ prev.classList.add('disabled');
+ } else if (current === total) {
+ next.classList.add('disabled');
+ }
+
+ wrapper.querySelector('.link-wrapper').append(prev, next);
+
+ closeOnBodyClick(wrapper);
+ const selectWrapper = wrapper.querySelector('.select-wrapper');
+ selectWrapper.querySelector('.selected').addEventListener('click', (e) => {
+ e.stopPropagation();
+ e.preventDefault();
+ const open = selectWrapper.classList.toggle('open');
+ e.currentTarget.setAttribute('aria-expanded', open);
+ });
+
+ const changePage = async (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const page = e.currentTarget.getAttribute('data-value');
+ const search = await Search.fromQueryString(window.location.search);
+ search.page = page;
+ window.dispatchEvent(new CustomEvent(UPDATE_SEARCH_EVENT, { detail: { search, redraw: false } }));
+ };
+
+ selectWrapper.querySelectorAll('.select-items li').forEach((opt) => {
+ opt.addEventListener('click', changePage);
+ });
+ prev.addEventListener('click', changePage);
+ next.addEventListener('click', changePage);
+ return wrapper;
+}
+
+/**
+ * Renders the search results as cards into the specified contianer
+ * @param parent
+ * @param results
+ */
+// eslint-disable-next-line import/prefer-default-export
+export function displayResults(parent, results) {
+ const cards = div({ class: 'search-results-cards property-list-cards' });
+ renderCards(cards, results.properties);
+ const pagination = div({ class: 'search-results-pagination' },
+ buildPagination(parseInt(results.pages, 10), parseInt(results.page, 10)),
+ );
+ parent.replaceChildren(cards, pagination);
+}
diff --git a/blocks/property-listing/cards/cards.css b/blocks/shared/property/cards.css
similarity index 96%
rename from blocks/property-listing/cards/cards.css
rename to blocks/shared/property/cards.css
index 3ac54d1f..cca4a1f2 100644
--- a/blocks/property-listing/cards/cards.css
+++ b/blocks/shared/property/cards.css
@@ -2,7 +2,6 @@
.property-list-cards {
display: flex;
- height: 400px;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
@@ -11,9 +10,6 @@
padding-bottom: 10px;
}
-.property-list-cards::-webkit-scrollbar {
- display: none;
-}
.property-list-cards .listing-tile {
display: flex;
@@ -55,7 +51,7 @@
.property-list-cards .listing-image-container {
position: relative;
- height: 100%;
+ padding-top: 73.5294%;
}
.property-list-cards .listing-image-container .property-image {
@@ -66,6 +62,7 @@
right: 0;
bottom: 0;
object-fit: cover;
+ object-position: center;
}
.property-list-cards .is-sold .listing-image-container .property-image {
@@ -148,7 +145,7 @@
justify-content: center;
}
-.property-list-cards .property-labels .property-label.open-house svg {
+.property-list-cards .property-labels .property-label.open-house img {
height: 24px;
width: 24px;
margin-right: 5px;
@@ -178,13 +175,13 @@
bottom: 0;
}
-.property-list-cards .listing-image-container .property-price {
- padding-top: 7px;
- padding-left: 10px;
+.property-list-cards .listing-image-container .property-price p {
+ display: inline-block;;
+ margin: 0;
+ padding: 7px 10px;
font-weight: var(--font-weight-bold);
font-size: var(--body-font-size-l);
line-height: var(--line-height-s);
- max-width: 70%;
background-color: var(--white);
color: var(--body-color);
}
@@ -265,7 +262,7 @@
left: 0;
}
-.property-list-cards .property-details .property-buttons .button-property svg {
+.property-list-cards .property-details .property-buttons .button-property img {
width: 24px;
height: 24px;
}
@@ -334,7 +331,6 @@
@media (min-width: 1200px) {
.property-list-cards {
display: grid;
- height: 820px;
grid-template: repeat(2, 1fr) / repeat(4, 1fr);
gap: 20px;
padding-bottom: 20px;
diff --git a/blocks/property-listing/cards/cards.js b/blocks/shared/property/cards.js
similarity index 67%
rename from blocks/property-listing/cards/cards.js
rename to blocks/shared/property/cards.js
index d690fc3e..10092676 100644
--- a/blocks/property-listing/cards/cards.js
+++ b/blocks/shared/property/cards.js
@@ -1,9 +1,6 @@
-import { propertySearch } from '../../../scripts/apis/creg/creg.js';
-import { decorateIcons } from '../../../scripts/aem.js';
-
function createImage(listing) {
if (listing.SmallMedia?.length > 0) {
- return ``;
+ return ``;
}
return 'no images available
';
}
@@ -41,7 +38,7 @@ export function createCard(listing) {
if (listing.PdpPath.includes('LuxuryTheme=true')) {
item.classList.add('is-luxury');
}
- const applicationType = listing.ApplicationType && listing.ApplicationType === 'For Rent' ? `${listing.ApplicationType}` : '';
+ const applicationType = listing.ListingType && listing.ListingType === 'For Rent' ? `${listing.ListingType}` : '';
if (listing.ClosedDate !== '01/01/0001') {
item.classList.add('is-sold');
@@ -49,7 +46,7 @@ export function createCard(listing) {
}
item.innerHTML = `
-
+
${createImage(listing)}
@@ -70,7 +67,7 @@ export function createCard(listing) {
${listing.mlsStatus}
- ${listing.ListPriceUS}
+
${listing.ListPriceUS}
@@ -79,7 +76,7 @@ export function createCard(listing) {
Closed: ${listing.ClosedDate}
-
+
${listing.StreetName}
${listing.City}, ${listing.StateOrProvince} ${listing.PostalCode}
@@ -89,13 +86,21 @@ export function createCard(listing) {
@@ -117,21 +122,13 @@ export function createCard(listing) {
/**
* Render the results of the provided search into the specified parent element.
*
- * @param {SearchParameters} searchParams
* @param {HTMLElement} parent
- * @return {Promise
}
+ * @param {Object[]} properties results from CREG
*/
-export async function render(searchParams, parent) {
- const list = document.createElement('div');
- list.classList.add('property-list-cards');
- parent.append(list);
-
- propertySearch(searchParams).then((results) => {
- if (results?.properties) {
- results.properties.forEach((listing) => {
- list.append(createCard(listing));
- });
- decorateIcons(parent);
- }
+export function render(parent, properties = []) {
+ const cards = [];
+ properties.forEach((listing) => {
+ cards.push(createCard(listing));
});
+ parent.replaceChildren(...cards);
}
diff --git a/blocks/property-listing/cards/luxury-collection-template.css b/blocks/shared/property/luxury-collection-template.css
similarity index 71%
rename from blocks/property-listing/cards/luxury-collection-template.css
rename to blocks/shared/property/luxury-collection-template.css
index 3d516487..0c7326cc 100644
--- a/blocks/property-listing/cards/luxury-collection-template.css
+++ b/blocks/shared/property/luxury-collection-template.css
@@ -1,11 +1,12 @@
.luxury-collection .property-list-cards .listing-image-container .property-image {
- z-index: 1;
+ z-index: 1;
}
+
.luxury-collection .property-list-cards .property-labels {
- background: initial;
+ background: initial;
}
.luxury-collection .property-list-cards .property-price {
- background: initial;
- z-index: 2;
-}
\ No newline at end of file
+ background: initial;
+ z-index: 2;
+}
diff --git a/blocks/shared/search-countries/search-countries.js b/blocks/shared/search-countries/search-countries.js
index 27fef4e5..ed7640d2 100644
--- a/blocks/shared/search-countries/search-countries.js
+++ b/blocks/shared/search-countries/search-countries.js
@@ -21,7 +21,7 @@ function addListeners(wrapper, cbs) {
wrapper.querySelector('.select-items .item.selected')?.classList.remove('selected');
e.currentTarget.classList.add('selected');
- wrapper.querySelector('select option[selected="selected"]')?.removeAttribute('selected');
+ wrapper.querySelectorAll('select option').forEach((o) => { o.selected = false; });
wrapper.querySelector(`select option[value="${selected}"]`).setAttribute('selected', 'selected');
wrapper.classList.toggle('open');
if (cbs) {
diff --git a/blocks/shared/search/suggestion.js b/blocks/shared/search/suggestion.js
new file mode 100644
index 00000000..e613be74
--- /dev/null
+++ b/blocks/shared/search/suggestion.js
@@ -0,0 +1,103 @@
+import {
+ getSelected as getSelectedCountry,
+} from '../search-countries/search-countries.js';
+import {
+ abort as abortSuggestions,
+ get as getSuggestions,
+} from '../../../scripts/apis/creg/suggestion.js';
+
+const MORE_INPUT_NEEDED = 'Please enter at least 3 characters.';
+const NO_SUGGESTIONS = 'No suggestions found. Please modify your search.';
+const SEARCHING_SUGGESTIONS = 'Looking up suggestions...';
+
+const updateSuggestions = (suggestions, target) => {
+ // Keep the first item - required character entry count.
+ const first = target.querySelector(':scope li');
+ target.replaceChildren(first, ...suggestions);
+};
+
+const buildSuggestions = (suggestions) => {
+ const lists = [];
+ suggestions.forEach((category) => {
+ const list = document.createElement('li');
+ list.classList.add('list-title');
+ list.textContent = category.displayText;
+ lists.push(list);
+ const ul = document.createElement('ul');
+ list.append(ul);
+ category.results.forEach((result) => {
+ const li = document.createElement('li');
+ li.setAttribute('category', category.searchType);
+ li.setAttribute('display', result.displayText.trim());
+ li.setAttribute('query', result.QueryString);
+ li.setAttribute('type', new URLSearchParams(result.QueryString).get('SearchType'));
+ li.textContent = result.SearchParameter;
+ ul.append(li);
+ });
+ });
+
+ return lists;
+};
+
+/**
+ * Handles the input changed event for the text field. Will add suggestions based on user input.
+ *
+ * @param {Event} e the change event
+ * @param {HTMLElement} target the container in which to add suggestions
+ */
+const inputChanged = (e, target) => {
+ const { currentTarget } = e;
+ const { value } = currentTarget;
+ const searchBar = currentTarget.closest('.search-bar');
+ if (value.length > 0) {
+ searchBar.classList.add('show-suggestions');
+ } else {
+ searchBar.classList.remove('show-suggestions');
+ searchBar.querySelector('input[name="query"]').value = '';
+ searchBar.querySelector('input[name="type"]').value = '';
+ }
+
+ if (value.length <= 2) {
+ abortSuggestions();
+ target.querySelector(':scope > li:first-of-type').textContent = MORE_INPUT_NEEDED;
+ updateSuggestions([], target);
+ } else {
+ target.querySelector(':scope > li:first-of-type').textContent = SEARCHING_SUGGESTIONS;
+ getSuggestions(value, getSelectedCountry(currentTarget.closest('form')))
+ .then((suggestions) => {
+ if (!suggestions) {
+ // Undefined suggestions means it was aborted, more input coming.
+ updateSuggestions([], target);
+ return;
+ }
+ if (suggestions.length) {
+ updateSuggestions(buildSuggestions(suggestions), target);
+ } else {
+ target.querySelector(':scope > li:first-of-type').textContent = NO_SUGGESTIONS;
+ }
+ });
+ }
+};
+
+const suggestionSelected = (e, form) => {
+ const query = e.target.getAttribute('query');
+ const keyword = e.target.getAttribute('display');
+ const type = e.target.getAttribute('type');
+ if (!query) {
+ return;
+ }
+ form.querySelector('input[name="keyword"]').value = keyword;
+ form.querySelector('input[name="query"]').value = query;
+ form.querySelector('input[name="type"]').value = type;
+ form.querySelector('.search-bar').classList.remove('show-suggestions');
+};
+
+export default function addEventListeners(form) {
+ const suggestionsTarget = form.querySelector('.suggester-input .suggester-results');
+ form.querySelector('.suggester-input input').addEventListener('input', (e) => {
+ inputChanged(e, suggestionsTarget);
+ });
+ suggestionsTarget.addEventListener('click', (e) => {
+ suggestionSelected(e, form);
+ });
+}
diff --git a/blocks/shared/search/util.js b/blocks/shared/search/util.js
new file mode 100644
index 00000000..832212ac
--- /dev/null
+++ b/blocks/shared/search/util.js
@@ -0,0 +1,168 @@
+import { getMetadata } from '../../../scripts/aem.js';
+
+export const BED_BATHS = [
+ { value: 1, label: '1+' },
+ { value: 2, label: '2+' },
+ { value: 3, label: '3+' },
+ { value: 4, label: '4+' },
+ { value: 5, label: '5+' },
+];
+
+export function getPlaceholder() {
+ const country = getMetadata('country') || 'US';
+ return country === 'US' ? 'Enter City, Address, Zip/Postal Code, Neighborhood, School or MLS#' : 'Enter City';
+}
+
+let bodyCloseListener;
+/**
+ * Helper function to close an expanded item when click events occur elsewhere on the page.
+ * @param {HTMLElement} root context for closing elements
+ */
+export function closeOnBodyClick(root) {
+ if (bodyCloseListener) {
+ document.body.removeEventListener('click', bodyCloseListener);
+ }
+ bodyCloseListener = (e) => {
+ // Don't close if we clicked somewhere inside of the context.
+ if (root.contains(e.target)) {
+ return;
+ }
+ root.classList.remove('open');
+ root.querySelectorAll('.open').forEach((open) => open.classList.remove('open'));
+ root.querySelectorAll('[aria-expanded="true"]').forEach((expanded) => expanded.setAttribute('aria-expanded', 'false'));
+ document.body.removeEventListener('click', bodyCloseListener);
+ bodyCloseListener = undefined;
+ };
+ document.body.addEventListener('click', bodyCloseListener);
+}
+
+/**
+ * Creates a Select dropdown for filtering search.
+ * @param {String} name name of select
+ * @param {String} placeholder label
+ * @param {Array[Object]} options max number of options
+ * @param {String} options.value value for option entry
+ * @param {String} options.label label for option entry
+ * @returns {HTMLDivElement}
+ */
+export function buildFilterSelect(name, placeholder, options) {
+ const wrapper = document.createElement('div');
+ wrapper.classList.add('select-wrapper', name);
+ wrapper.innerHTML = `
+
+ Any ${placeholder}
+
+ `;
+
+ const select = wrapper.querySelector('select');
+ const ul = wrapper.querySelector('ul');
+ options.forEach((option) => {
+ const ele = document.createElement('option');
+ const li = document.createElement('li');
+ li.setAttribute('role', 'option');
+ li.setAttribute('data-value', option.value);
+ ele.value = option.value;
+ // eslint-disable-next-line no-multi-assign
+ ele.textContent = li.textContent = `${option.label} ${placeholder}`;
+ select.append(ele);
+ ul.append(li);
+ });
+ return wrapper;
+}
+
+export function filterItemClicked(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ const value = e.currentTarget.getAttribute('data-value');
+ const wrapper = e.currentTarget.closest('.select-wrapper');
+ wrapper.querySelector('.selected span').textContent = e.currentTarget.textContent;
+ wrapper.querySelector('ul li.selected')?.classList.toggle('selected');
+ e.currentTarget.classList.add('selected');
+ wrapper.querySelectorAll('select option').forEach((o) => { o.selected = false; });
+ if (!value) {
+ wrapper.querySelector('select option[value=""]').selected = true;
+ } else {
+ wrapper.querySelector(`select option[value="${value}"]`).selected = true;
+ }
+ wrapper.classList.remove('open');
+ wrapper.querySelector('[aria-expanded="true"]')?.setAttribute('aria-expanded', 'false');
+}
+
+/**
+ * Creates a from/to range input field for the search filter
+ * @param name parameter name
+ * @param placeholder label
+ */
+export function buildDataListRange(name, placeholder) {
+ const wrapper = document.createElement('div');
+ wrapper.classList.add('range-wrapper', name);
+ wrapper.innerHTML = `
+ ${placeholder}
+
+ `;
+ return wrapper;
+}
+
+/**
+ * Creates a from/to range input field with selections for the options
+ * @param name parameter name
+ * @param placeholder placeholder
+ * @param boundaries boundaries for ranges.
+ */
+export function buildSelectRange(name, placeholder, boundaries) {
+ const wrapper = document.createElement('div');
+ wrapper.classList.add('range-wrapper', name);
+ wrapper.innerHTML = `
+ ${placeholder}
+
+
+
+
No Min
+
+
+
to
+
+
+
No Max
+
+
+
+ `;
+
+ wrapper.querySelectorAll(`#min-${name}, #max-${name}`).forEach((item) => {
+ boundaries.forEach((b) => {
+ const opt = document.createElement('option');
+ opt.value = b.value;
+ opt.textContent = b.label;
+ item.querySelector('select').append(opt);
+ const li = document.createElement('li');
+ li.setAttribute('data-value', b.value);
+ li.setAttribute('role', 'option');
+ li.textContent = b.label;
+ item.querySelector('ul').append(li);
+ });
+ });
+ return wrapper;
+}
diff --git a/icons/checkmark.svg b/icons/checkmark.svg
index b888e84f..db7e1ffc 100644
--- a/icons/checkmark.svg
+++ b/icons/checkmark.svg
@@ -1 +1,3 @@
-
\ No newline at end of file
+
diff --git a/icons/close-x-white.svg b/icons/close-x-white.svg
new file mode 100644
index 00000000..bfdb1439
--- /dev/null
+++ b/icons/close-x-white.svg
@@ -0,0 +1,11 @@
+
diff --git a/icons/close-x.svg b/icons/close-x.svg
new file mode 100644
index 00000000..f3d89559
--- /dev/null
+++ b/icons/close-x.svg
@@ -0,0 +1,11 @@
+
diff --git a/icons/filter-white.svg b/icons/filter-white.svg
new file mode 100644
index 00000000..52cc5962
--- /dev/null
+++ b/icons/filter-white.svg
@@ -0,0 +1,18 @@
+
diff --git a/icons/globe.png b/icons/globe.png
new file mode 100644
index 00000000..7717876f
Binary files /dev/null and b/icons/globe.png differ
diff --git a/icons/heartempty.svg b/icons/heartempty.svg
index 5c92e930..623bab0d 100644
--- a/icons/heartempty.svg
+++ b/icons/heartempty.svg
@@ -5,5 +5,5 @@
tabindex="-1"
viewBox="0 0 24 21">
+ fill="#AAA"/>
diff --git a/icons/heartemptydark.svg b/icons/heartemptydark.svg
index 9efbc173..e94647b4 100644
--- a/icons/heartemptydark.svg
+++ b/icons/heartemptydark.svg
@@ -1,9 +1,9 @@
-
diff --git a/icons/heartfull.svg b/icons/heartfull.svg
new file mode 100644
index 00000000..01c8256f
--- /dev/null
+++ b/icons/heartfull.svg
@@ -0,0 +1,9 @@
+
diff --git a/icons/maps/loader_opt.mp4 b/icons/maps/loader_opt.mp4
deleted file mode 100644
index 7e9da187..00000000
Binary files a/icons/maps/loader_opt.mp4 and /dev/null differ
diff --git a/icons/maps/loader_opt.webm b/icons/maps/loader_opt.webm
deleted file mode 100644
index 90ba4065..00000000
Binary files a/icons/maps/loader_opt.webm and /dev/null differ
diff --git a/icons/maps/map-reveal-marker-standard.png b/icons/maps/map-reveal-marker-standard.png
deleted file mode 100644
index cfb600ea..00000000
Binary files a/icons/maps/map-reveal-marker-standard.png and /dev/null differ
diff --git a/icons/pencil.svg b/icons/pencil.svg
new file mode 100644
index 00000000..c1fb1664
--- /dev/null
+++ b/icons/pencil.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/search.svg b/icons/search.svg
new file mode 100644
index 00000000..aa6794ae
--- /dev/null
+++ b/icons/search.svg
@@ -0,0 +1,11 @@
+
diff --git a/package-lock.json b/package-lock.json
index 2b02f397..77f02222 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,6 +18,7 @@
"eslint": "8.35.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-plugin-import": "2.27.5",
+ "mocha": "^10.2.0",
"sinon": "15.0.1",
"stylelint": "15.2.0",
"stylelint-config-standard": "30.0.1"
@@ -1425,6 +1426,15 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/ansi-escapes": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@@ -1703,6 +1713,12 @@
"node": ">=8"
}
},
+ "node_modules/browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
"node_modules/browserslist": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
@@ -2087,6 +2103,67 @@
"node": ">=8"
}
},
+ "node_modules/cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "node_modules/cliui/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/cliui/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/cliui/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
@@ -3304,6 +3381,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/flat": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+ "dev": true,
+ "bin": {
+ "flat": "cli.js"
+ }
+ },
"node_modules/flat-cache": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz",
@@ -3413,6 +3499,15 @@
"node": ">=6.9.0"
}
},
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
"node_modules/get-func-name": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
@@ -3702,6 +3797,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "bin": {
+ "he": "bin/he"
+ }
+ },
"node_modules/hosted-git-info": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@@ -4264,6 +4368,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-weakref": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@@ -4680,6 +4796,92 @@
"integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==",
"dev": true
},
+ "node_modules/log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-symbols/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/log-symbols/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/log-symbols/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/log-symbols/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/log-symbols/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/log-symbols/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/log-update": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz",
@@ -4959,6 +5161,171 @@
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"dev": true
},
+ "node_modules/mocha": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
+ "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-colors": "4.1.1",
+ "browser-stdout": "1.3.1",
+ "chokidar": "3.5.3",
+ "debug": "4.3.4",
+ "diff": "5.0.0",
+ "escape-string-regexp": "4.0.0",
+ "find-up": "5.0.0",
+ "glob": "7.2.0",
+ "he": "1.2.0",
+ "js-yaml": "4.1.0",
+ "log-symbols": "4.1.0",
+ "minimatch": "5.0.1",
+ "ms": "2.1.3",
+ "nanoid": "3.3.3",
+ "serialize-javascript": "6.0.0",
+ "strip-json-comments": "3.1.1",
+ "supports-color": "8.1.1",
+ "workerpool": "6.2.1",
+ "yargs": "16.2.0",
+ "yargs-parser": "20.2.4",
+ "yargs-unparser": "2.0.0"
+ },
+ "bin": {
+ "_mocha": "bin/_mocha",
+ "mocha": "bin/mocha.js"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mochajs"
+ }
+ },
+ "node_modules/mocha/node_modules/diff": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
+ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/mocha/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mocha/node_modules/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/mocha/node_modules/glob/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mocha/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/mocha/node_modules/minimatch": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
+ "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/mocha/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "node_modules/mocha/node_modules/nanoid": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
+ "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
+ "dev": true,
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/mocha/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/mocha/node_modules/yargs-parser": {
+ "version": "20.2.4",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
+ "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -5744,6 +6111,15 @@
"node": ">=8"
}
},
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
"node_modules/raw-body": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
@@ -5990,6 +6366,15 @@
"url": "https://github.com/sponsors/mysticatea"
}
},
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -6217,6 +6602,15 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/serialize-javascript": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
+ "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+ "dev": true,
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
"node_modules/set-function-length": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
@@ -7296,6 +7690,12 @@
"node": ">=8"
}
},
+ "node_modules/workerpool": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
+ "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
+ "dev": true
+ },
"node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
@@ -7395,12 +7795,39 @@
}
}
},
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
},
+ "node_modules/yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/yargs-parser": {
"version": "20.2.9",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
@@ -7410,6 +7837,42 @@
"node": ">=10"
}
},
+ "node_modules/yargs-unparser": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+ "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^6.0.0",
+ "decamelize": "^4.0.0",
+ "flat": "^5.0.2",
+ "is-plain-obj": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs-unparser/node_modules/decamelize": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/yargs-unparser/node_modules/is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/yauzl": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
diff --git a/package.json b/package.json
index da0ff83e..e452355c 100644
--- a/package.json
+++ b/package.json
@@ -2,8 +2,10 @@
"name": "hsf-commonmoves",
"private": true,
"version": "1.0.0",
+ "type": "module",
"description": "Starter project for Adobe Helix",
"scripts": {
+ "test": "mocha",
"lint:js": "eslint .",
"lint:css": "stylelint blocks/**/*.css styles/*.css",
"lint": "npm run lint:js && npm run lint:css"
@@ -21,13 +23,14 @@
"devDependencies": {
"@babel/core": "7.21.0",
"@babel/eslint-parser": "7.19.1",
+ "@esm-bundle/chai": "4.3.4-fix.0",
+ "@web/test-runner": "0.15.1",
+ "@web/test-runner-commands": "0.6.5",
"chai": "4.3.7",
"eslint": "8.35.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-plugin-import": "2.27.5",
- "@esm-bundle/chai": "4.3.4-fix.0",
- "@web/test-runner": "0.15.1",
- "@web/test-runner-commands": "0.6.5",
+ "mocha": "^10.2.0",
"sinon": "15.0.1",
"stylelint": "15.2.0",
"stylelint-config-standard": "30.0.1"
diff --git a/scripts/apis/creg/ApplicationType.js b/scripts/apis/creg/ApplicationType.js
deleted file mode 100644
index 950daa53..00000000
--- a/scripts/apis/creg/ApplicationType.js
+++ /dev/null
@@ -1,25 +0,0 @@
-export default class ApplicationType {
- constructor(type, label) {
- this.type = type;
- this.label = label;
- }
-}
-
-ApplicationType.FOR_SALE = new ApplicationType('FOR_SALE', 'For Sale');
-ApplicationType.FOR_RENT = new ApplicationType('FOR_RENT', 'For Rent');
-ApplicationType.PENDING = new ApplicationType('PENDING', 'Pending');
-ApplicationType.RECENTLY_SOLD = new ApplicationType('RECENTLY_SOLD', 'Recently Sold');
-
-/**
-* Returns the ApplicationType for the specified string.
-*
-* @param {string} type
-* @returns {ApplicationType} the matching type.
-*/
-export function applicationTypeFor(type) {
- const [found] = Object.getOwnPropertyNames(ApplicationType)
- .filter((t) => t.toLowerCase() === type.toLowerCase())
- .map((t) => ApplicationType[t]);
-
- return found;
-}
diff --git a/scripts/apis/creg/OpenHouses.js b/scripts/apis/creg/OpenHouses.js
deleted file mode 100644
index 89e24046..00000000
--- a/scripts/apis/creg/OpenHouses.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export default class OpenHouses {
- constructor(value, label) {
- this.value = value;
- this.label = label;
- }
-}
-
-OpenHouses.ONLY_WEEKEND = new OpenHouses(7, 'This Weekend');
-OpenHouses.ANYTIME = new OpenHouses(365, 'Anytime');
diff --git a/scripts/apis/creg/PropertyType.js b/scripts/apis/creg/PropertyType.js
deleted file mode 100644
index 7cad72b7..00000000
--- a/scripts/apis/creg/PropertyType.js
+++ /dev/null
@@ -1,13 +0,0 @@
-export default class PropertyType {
- constructor(id, label) {
- this.ID = id;
- this.Label = label;
- }
-}
-
-PropertyType.CONDO_TOWNHOUSE = new PropertyType(1, 'Condo/Townhouse');
-PropertyType.SINGLE_FAMILY = new PropertyType(2, 'Single Family');
-PropertyType.COMMERCIAL = new PropertyType(3, 'Commercial');
-PropertyType.MULTI_FAMILY = new PropertyType(4, 'Multi Family');
-PropertyType.LAND = new PropertyType(5, 'Lot/Land');
-PropertyType.FARM = new PropertyType(6, 'Farm/Ranch');
diff --git a/scripts/apis/creg/SearchParameters.js b/scripts/apis/creg/SearchParameters.js
deleted file mode 100644
index 2e964c0c..00000000
--- a/scripts/apis/creg/SearchParameters.js
+++ /dev/null
@@ -1,155 +0,0 @@
-import SearchType from './SearchType.js';
-import PropertyType from './PropertyType.js';
-import ApplicationType from './ApplicationType.js';
-
-const parseQuery = (queryString) => {
- const parsed = {};
- queryString.split('&').map((kvp) => kvp.split('=')).forEach((kv) => {
- parsed[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
- });
- return parsed;
-};
-
-export const SortDirections = Object.freeze({
- ASC: 'ASCENDING',
- DESC: 'DESCENDING',
-});
-
-export const SortOptions = Object.freeze({
- DATE: 'DATE',
- PRICE: 'PRICE',
- DISTANCE: 'DISTANCE',
-});
-
-export default class SearchParameters {
- static DEFAULT_PAGE_SIZE = 32;
-
- ListingStatus; // TODO: Find out what this means
-
- MinPrice;
-
- NewListing = false;
-
- OpenHouses;
-
- Page = 1;
-
- PageSize = 32;
-
- SearchInput = '';
-
- SearchType = '';
-
- #ApplicationType = ApplicationType.FOR_SALE.type;
-
- #PropertyType = [PropertyType.CONDO_TOWNHOUSE, PropertyType.SINGLE_FAMILY].join(',');
-
- #franchiseeCode;
-
- #isFranchisePage = false;
-
- #params = '';
-
- #sort = 'PRICE_DESCENDING';
-
- /**
- * Create a new instance of the SearchParameters.
- *
- * @param {SearchType} type the type of search to perform
- * @param {string} params the formatted params using the 'paramFormatterBuilder' from the type
- */
- constructor(type = SearchType.Empty, params = '') {
- this.SearchType = type.type;
- this.#params = params;
- }
-
- /**
- * Set the types of property status type to include in search.
- *
- * @param {ApplicationType[]} types
- */
- set applicationTypes(types) {
- this.#ApplicationType = types.map((t) => t.type).join(',');
- }
-
- /**
- * Set the franchisee ID for context search.
- *
- * @param id
- */
- set franchisee(id) {
- if (id) {
- this.#franchiseeCode = id.toUpperCase();
- this.#isFranchisePage = true;
- } else {
- this.#franchiseeCode = undefined;
- this.#isFranchisePage = false;
- }
- }
-
- /**
- * Set the property types to search against.
- *
- * @param {PropertyType[]} types
- */
- set propertyTypes(types) {
- this.#PropertyType = types.map((t) => t.ID).join(',');
- }
-
- /**
- * Set the sort type
- *
- * @param {('DATE'|'PRICE')} sort property on which to sort
- */
- set sortBy(sort) {
- const formatted = sort.toUpperCase();
- if (SortOptions[formatted]) {
- this.#sort = `${SortOptions[formatted]}_${this.#sort.split('_')[1]}`;
- }
- }
-
- /**
- * Set the sort direction
- *
- * @param {('ASC'|'DESC')} direction the direction of the sort
- */
- set sortDirection(direction) {
- const formatted = direction.toUpperCase();
- if (SortDirections[formatted]) {
- this.#sort = `${this.#sort.split('_')[0]}_${SortDirections[formatted]}`;
- }
- }
-
- /**
- * Populates this search parameter from the provided query string.
- *
- * @param {String} queryString the query string to parse
- */
- populate(queryString) {
- const query = parseQuery(queryString);
- Object.keys(this).forEach((p) => {
- if (query[p]) {
- this[p] = query[p];
- }
- });
- }
-
- /**
- * Converts this Search Parameter object into its URL query parameter equivalent
- *
- * @return {String} URL encoded representation of this
- */
- asQueryString() {
- let query = Object.keys(this).filter((k) => this[k]).map((k) => `${k}=${encodeURIComponent(this[k])}`).join('&');
- query += `&PropertyType=${this.#PropertyType}&ApplicationType=${this.#ApplicationType}`;
- query += `&Sort=${this.#sort}&isFranchisePage=${this.#isFranchisePage}`;
- if (this.#params) {
- query += `&${this.#params}`;
- }
- if (this.#franchiseeCode) {
- query += `&franchiseeCode=${this.#franchiseeCode}`;
- }
-
- return query;
- }
-}
diff --git a/scripts/apis/creg/SearchType.js b/scripts/apis/creg/SearchType.js
deleted file mode 100644
index ae22b6b9..00000000
--- a/scripts/apis/creg/SearchType.js
+++ /dev/null
@@ -1,55 +0,0 @@
-const defaultParameterBuilder = (v) => `SearchParameter=${encodeURIComponent(v)}`;
-
-const mapParameterBuilder = (minLat, maxLat, minLon, maxLon) => {
- const obj = {
- type: 'FeatureCollection',
- features: [{
- type: 'Feature',
- geometry: {
- type: 'Polygon',
- coordinates: [[
- [minLon, minLat], // Bottom left
- [minLon, maxLat], // Top left
- [maxLon, maxLat], // Top right
- [maxLon, minLat], // Bottom right
- [minLon, minLat], // Close the box
- ]],
- },
- }],
- };
- return `SearchParameter=${encodeURIComponent(JSON.stringify(obj))}`;
-};
-
-const radiusParameterBuilder = (lat, lon, radius) => `Latitude=${lat}&Longitude=${lon}&Distance=${radius}`;
-
-export default class SearchType {
- constructor(type, parameterBuilder) {
- this.type = type;
- this.paramBuilder = parameterBuilder;
- }
-}
-
-SearchType.Address = new SearchType('Address', defaultParameterBuilder);
-SearchType.City = new SearchType('City', defaultParameterBuilder);
-SearchType.Community = new SearchType('Community', mapParameterBuilder);
-SearchType.Empty = new SearchType('Empty', () => '');
-SearchType.Map = new SearchType('Map', mapParameterBuilder);
-SearchType.Neighborhood = new SearchType('Neighborhood', defaultParameterBuilder);
-SearchType.Radius = new SearchType('Radius', radiusParameterBuilder);
-SearchType.School = new SearchType('School', defaultParameterBuilder);
-SearchType.SchoolDistrict = new SearchType('School District', defaultParameterBuilder);
-SearchType.ZipCode = new SearchType('ZipCode', defaultParameterBuilder);
-SearchType.listingId = new SearchType('listingId', defaultParameterBuilder);
-
-/**
- * Returns the SearchType for the specified string.
- *
- * @param {string} type
- * @returns {SearchType} the matching type.
- */
-export function searchTypeFor(type) {
- const [found] = Object.getOwnPropertyNames(SearchType)
- .filter((t) => t.toLowerCase() === type.toLowerCase())
- .map((t) => SearchType[t]);
- return found;
-}
diff --git a/scripts/apis/creg/creg.js b/scripts/apis/creg/creg.js
index 4080f9e4..50a93e5b 100644
--- a/scripts/apis/creg/creg.js
+++ b/scripts/apis/creg/creg.js
@@ -1,92 +1,69 @@
/* Wrapper for all Creg API endpoints */
-// eslint-disable-next-line no-unused-vars
-import SearchParameters from './SearchParameters.js';
+// TODO: Use Sidekick Plugin for this
+import { getMetadata } from '../../aem.js';
const urlParams = new URLSearchParams(window.location.search);
export const DOMAIN = urlParams.get('env') === 'stage' ? 'ignite-staging.bhhs.com' : 'www.bhhs.com';
const CREG_API_URL = `https://${DOMAIN}/bin/bhhs`;
-let suggestionFetchController;
-
-const mapSuggestions = (json) => {
- const results = [];
- const { searchTypes, suggestions } = json;
-
- if (!suggestions) {
- return results;
- }
-
- const keys = Object.keys(suggestions);
- keys.forEach((k) => {
- if (!suggestions[k.toLowerCase()]) {
- suggestions[k.toLowerCase()] = suggestions[k];
- }
- });
- searchTypes.forEach((type) => {
- const name = type.searchType.replaceAll(/\s+/g, '').toLowerCase();
- if (suggestions[name] && suggestions[name].length) {
- results.push({
- ...type,
- results: suggestions[name],
- });
- }
- });
-
- return results;
-};
+/**
+ * @typedef {Object} SearchResults
+ * @property {Array