Listings on the map
The results page, pre-chewed: your listings as clustered markers with price pills, a base card with picture, price, title, place and link, and the search area drawn around them. You give the data, the map does the rest.
What it does
Listings are rendered as native map layers: white cluster discs ringed with your accent colour that split as the visitor zooms in, then a white price pill with an accent anchor dot per listing. Clicking a pill opens a card (the pill turns accent while it is open). If a search area is given, it is drawn under the listings and the map opens on it.
It is a base template: every field of a listing is optional (a listing without a price becomes a plain dot, without a picture the card has no image block, without a link no button), colours and radii follow your accent settings, every element has a yatmo-listing-* class, and the card can be replaced entirely.
Live demos
Two situations. First, plain results: your backend answered a classic search (city, price, bedrooms), the map simply shows them. Some listings have no price, no picture or no link on purpose, to show the base template adapting.
Second, results inside a search area: the polygon a visitor drew (or the one computed by a travel-time or lifestyle search) is passed back as listings.shape, the listings your backend found inside it are displayed with it. Zoom in to split the clusters, click a price.
yatmoConfig, so a page hosts one Yatmo map. The first demo above lives in an iframe for that reason: do the same when a page needs a second map.How to enable
Add a listings object with your results (already filtered by your backend) and, if there was one, the search area:
yatmoConfig = {
licenseKey: 'xxxxxxxxxxxxxxxxxx',
language: 'FR',
country: 'BE',
container: 'map',
center: [4.3517, 50.8503],
zoom: 12,
startsWithPoisHidden: true, // POI toggle available, hidden at load (or noPois: true to remove it)
accentColor: '#0a7cff',
listings: {
items: [
{ id: 4471, latitude: 50.8449, longitude: 4.3502, price: '264 000 €', title: 'Appartement · 1 chambre', subtitle: 'Bruxelles', image: 'https://.../4471.jpg', url: 'https://.../annonce/4471' },
{ id: 4472, latitude: 50.8511, longitude: 4.3617, price: '398 k€', title: 'Maison 3 chambres', subtitle: 'Schaerbeek', image: 'https://.../4472.jpg', url: 'https://.../annonce/4472' }
],
shape: JSON.parse(sessionStorage.getItem('searchShape')) // the polygon received from the search page
}
};
The listing object
| Name | In | Type | Description |
|---|---|---|---|
| latitude / longitude required | item | number | Position of the listing. Items without a valid position are skipped. |
| id | item | string | number |
Your identifier, handed back in onListingClick.
|
| price | item | string | Displayed as is in the pill and the card: format it your way ("264 000 €", "398 k€", "1 250 € / month"). Without it the listing is a plain dot and the card has no price line. |
| title | item | string | First line of the card, one line, truncated with an ellipsis. |
| subtitle | item | string | Second line with a place icon, typically the city or the neighbourhood. |
| image | item | string (URL) | Picture of the card, lazy-loaded, cropped to 280x170. Without it the card has no image block. |
| url | item | string (URL) |
Target of the "View listing" button, opened in a new tab with rel="noopener". Without it there is no button.
|
| anything else | item | any |
Kept untouched and handed to renderCard and onListingClick, so your own template can use your own fields.
|
Options
| Name | In | Type | Description |
|---|---|---|---|
| items | listings | array |
The listings to display at load. Can be empty and filled later with YatmoSearch.setListings.
|
| shape | listings | GeoJSON geometry |
The search area to draw under the listings (the one received from onSearch).
|
| fitTo | listings | string |
Initial view: auto (the shape if any, else the listings), shape, listings or none (keep center / zoom).
Default: auto. |
| cluster | listings | boolean |
Native clustering. Turn it off only for a few dozen listings.
Default: true. |
| maxVisiblePills | listings | integer |
Maximum number of HTML price pills on screen at once (the rest stays clustered). Raise it with care on mobile.
Default: 250. |
| renderCard | listings | function(item, close) |
Replaces the base card: return a DOM element or an HTML string. close() closes the card.
|
| onListingClick | listings | function(item) | Called when a pill is clicked (the card opens anyway). Analytics, side panel, whatever you like. |
Runtime API
Once the map is loaded, window.YatmoSearch lets your page update it without reloading, for instance after an AJAX search or a filter change:
YatmoSearch.setListings(items); // replaces the listings (clusters, pills and open card are refreshed)
YatmoSearch.clearListings();
YatmoSearch.setShape(geometry, true); // draws a search area, true = fit the map to it
YatmoSearch.clearShape();
YatmoSearch.getShape(); // the current area or null
Customizing the card
Two levels. For colours, fonts and spacing, restyle the classes with selectors more specific than the plugin's (they all start with yatmo-listing-: -marker, -pill, -dot, -card, -card-img, -card-close, -card-body, -card-price, -card-title, -card-sub, -card-open):
#map [data-yatmo-root] .yatmo-listing-pill { font-size: 13px; border: 1.5px solid var(--yatmo-accent); }
#map [data-yatmo-root] .yatmo-listing-card-price { font-size: 20px; color: #0a7cff; }
#map [data-yatmo-root] .yatmo-listing-card-open { border-radius: 0; text-transform: uppercase; }
For a different layout altogether (energy label, surface, favourite button, your own image carousel), return your own element from renderCard:
listings: {
items: results,
renderCard: function (item, close) {
var card = document.createElement('div');
card.className = 'my-card';
card.innerHTML =
'<img src="' + item.image + '" alt="">' +
'<strong>' + (item.price || 'Price on request') + '</strong> ' + item.surface + ' m² · EPC ' + item.epc +
'<a href="' + item.url + '">See the listing</a>';
card.querySelector('a').addEventListener('click', close);
return card; // or return an HTML string
}
}
textContent.End to end: search page to results page
Search page: the visitor draws an area or picks a travel time, your onSearch stores the polygon and navigates.
function runMySearch(shape, meta) {
sessionStorage.setItem('searchShape', JSON.stringify(shape));
location.href = '/results';
}
Results page: your backend has filtered the listings inside the polygon (see the SQL examples); the page renders them in yatmoConfig.listings.items and passes the polygon back as listings.shape so the visitor sees where they searched. For a single-page experience, keep the search panel and the listings on the same map: run the query in onSearch and call YatmoSearch.setListings(results) when it comes back.
Performance
- Clusters and listing points are WebGL layers with native clustering: tens of thousands of points cost nothing to pan and zoom.
- HTML pills exist only for the de-clustered points in the viewport, capped by
maxVisiblePills, and are recycled when the map moves. - Pictures are only requested when a card opens (lazy loading), so a results map never downloads 5 000 thumbnails.
- Hand the map light objects: position, price string, title, place, image URL, link. Keep the rest of your listing data on your side and look it up by
idinrenderCardoronListingClick.