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.

One map per page
The plugin reads one global 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
    }
}
Escape your own data in renderCard
The base template writes every field as text, never as HTML. If your template builds HTML strings from listing data, escape titles and places yourself or use 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