Primeros usuarios: 50% de descuento. Finaliza el 31 de mayo
Embedding

Widget.js Reference

Use the JavaScript widget for embedding maps with Shadow DOM.

Widget.js Reference

The Mapsemble widget is a lightweight JavaScript file that embeds a fully interactive map into any web page. It uses Shadow DOM so the map is isolated from the host page's styles and scripts.

Embedding is always done by calling Mapsemble.init() against a container element you place in the page. The build-map "Embed" panel generates a ready-to-paste snippet that wires this up — the rest of this page documents the options you can pass to init() and the methods you can call on the returned instance.

Setup — Mapsemble.init(options)

Drop the widget script in once, add a container <div>, and call Mapsemble.init() against it:

<script src="https://app.mapsemble.com/widget.js"></script>
<div id="my-map" style="width:100%;min-height:600px"></div>

<script>
  window.Mapsemble.init({
    container: '#my-map',
    mapId: 'YOUR_MAP_ID',
  });
</script>

For richer setups — initial filters, custom ordering, a custom card renderer, or a handle you can call back into later — pass more options:

const map = Mapsemble.init({
  container: '#my-map',      // CSS selector or HTMLElement
  mapId: 'YOUR_MAP_ID',
  embed: 'shadow',
  language: 'en',
  mode: 'light',
  filters: { category: 'hotel' },
  orderBy: 'price-asc',
});

// Later, update the map in response to user input:
map.setFilters({ category: 'hotel', price_max: '200' });
map.setOrderBy('rating-desc');

init() returns an instance object with methods bound to that specific map. On a page with multiple maps, call init() once per map and keep each handle — the instance methods always target the right one.

init() options

Option Type Description
container CSS selector or HTMLElement Required. The element the map will render into.
mapId string Required. The map's UUID in Mapsemble.
embed 'shadow' Embed mode. Default 'shadow' — renders into a shadow root and supports featureRenderer.
filters object Initial filter values. Seeded before the widget's first feature fetch, so the map loads already filtered. See Filters below.
orderBy string Initial order-by slug (e.g. 'price-asc', 'rating-desc'). Applied before the first feature fetch.
language string BCP-47 locale. Default 'en'.
mode 'light' | 'dark' Color mode. Default 'light'.
translations object Map of English phrase → localized phrase, applied to all widget text — including JS-rendered hints such as 'Tap to interact'. See Translations.
onlyZones string[] Render only these zones (e.g. ['primary-filters', 'map']). Useful for split embeds.
excludeZones string[] Hide these zones from an otherwise default layout.
searchUrl string Destination for the search button in filter-only embeds.
minHeight number | string Sets min-height on the container. Number → pixels; string → any CSS length.
preventScale boolean On mobile, injects user-scalable=no into the viewport meta.
scrollCapture 'mobile' | 'all' | 'none' | boolean See Scroll capture. Default 'none'.
scrollOffsetTop number | string Top reservation. Used by scrollCapture as the sticky-top distance and by fillVisibleViewport as the top breathing-room. Default '0px'. (scrollCaptureTop is a deprecated alias.)
scrollOffsetBottom number | string Bottom reservation. Used by fillVisibleViewport as the bottom breathing-room. Default '0px'.
fillVisibleViewport boolean When true, the widget sizes its container to the visible viewport height (minus scrollOffsetTop + scrollOffsetBottom) via the visualViewport API. See Fill visible viewport. Default true.
featureRenderer function Custom renderer for feature cards and popups. See Custom card rendering.
featureUnmount function Cleanup callback paired with featureRenderer.
onCount function Callback fired with the total feature count after each load.

queryParams is accepted as a deprecated alias for filters — it logs a console warning. Migrate to filters.

Instance methods

Each call to Mapsemble.init() returns an object with methods bound to that map.

map.setFilters(filters)

Replace the active filter set. The map immediately refetches features from the datasource with the new filters.

// Apply filters
map.setFilters({
  category: 'restaurant',
  price_max: '50',
});

// Clear all filters
map.setFilters({});

Key behavior:

  • Full replace — each call overwrites the previous filter set. To keep a filter, include it every call.
  • Immediate refetch — the map reloads as soon as filters change.
  • Merged with built-in filters — if the map has its own filter UI configured, both sets combine. Use distinct keys to avoid collisions.
  • Arbitrary keys — the object is passed through to your datasource endpoint. Your endpoint decides how to interpret it.
  • Forwarded to card renderers — the same values are passed as remoteQueryParams to featureRenderer, so custom cards can reflect the active filters.

map.setOrderBy(orderBy)

Change the active order-by. Drives the built-in order-by dropdown when it's visible and triggers a feature reload.

map.setOrderBy('price-desc');
map.setOrderBy(null);        // clear (equivalent to 'default')

map.setVisibleIds(ids, options)

Show only the given feature IDs. Empty array shows all. The optional second argument accepts:

  • orderBy — order-by slug to apply alongside the visibility change.
  • queryParams — arbitrary params forwarded to remote card / popup / marker endpoints (handy for filters that affect rendering but not the visible set).
map.setVisibleIds(['feature-1', 'feature-2']);
map.setVisibleIds([], { orderBy: 'price-asc' });
map.setVisibleIds(['feature-1'], { queryParams: { highlight: 'true' } });

map.setMainIds(ids)

Mark the given features as "main" (full-size markers with labels); everything else is dimmed. Client-side only — no reload triggered.

map.setMainIds(['feature-1', 'feature-2']);

map.setHoverId(id)

Highlight a single marker (e.g. when the user hovers a related list item). Pass null to clear.

map.setHoverId('feature-1');
map.setHoverId(null);

map.setTranslations(translations)

Merge additional translations into the widget. Existing translations are preserved.

map.setTranslations({
  'Show results': 'Ergebnisse anzeigen',
  'Tap to interact': 'Zum Interagieren tippen',
});

map.destroy()

Tear the widget down and remove it from the container. Call this only if you're rebuilding the map from scratch — regular updates should go through setFilters / setOrderBy.

Static shortcuts

For simple pages with exactly one instance of a given mapId, the static methods work directly with the map ID:

Mapsemble.setFilters('YOUR_MAP_ID', { category: 'hotel' });
Mapsemble.setOrderBy('YOUR_MAP_ID', 'price-asc');

Every instance method has a static counterpart (Mapsemble.setFilters, Mapsemble.setOrderBy, Mapsemble.setVisibleIds, Mapsemble.setMainIds, Mapsemble.setHoverId, Mapsemble.setTranslations). The first argument can be a map ID string, a container element, or an instance.

If more than one instance of a given map ID exists on the page, the static methods target the first match and log a warning. Prefer the instance API in that case.

Consumer integration guide

This walkthrough shows how to wire Mapsemble into an app that drives filters, pagination, and ordering from its own UI, and serves features from its own backend.

1. Point Mapsemble at your datasource

Configure the map's datasource URL in Mapsemble's admin to point at your endpoint. Mapsemble will issue GET requests for features, sending query parameters including:

Parameter Description
extent WKT POLYGON(...) bounding box
filters JSON-encoded filter object (your filters merged with built-in filters)
orderBy Current order-by slug
offset, limit Pagination
language BCP-47 locale (e.g. en, nl)
mapId UUID of the map issuing the request

See the Remote Datasource reference for the full API contract, including the response format and how filter values are encoded.

Respond with a GeoJSON FeatureCollection:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "id": "42",
      "geometry": { "type": "Point", "coordinates": [4.8952, 52.3702] },
      "properties": { "price": "€ 450", "rating": 8.5, "title": "..." }
    }
  ],
  "properties": { "total": 150 }
}

2. Initialize with your initial state

const map = Mapsemble.init({
  container: document.querySelector('#map'),
  mapId: 'YOUR_MAP_ID',
  embed: 'shadow',
  filters: readFiltersFromUrl(),
  orderBy: readOrderByFromUrl(),
});

Passing filters and orderBy to init() guarantees the very first feature fetch uses them. The widget seeds its internal state from these options before the first request fires.

3. Push filter changes from your UI

document.querySelector('#price-filter').addEventListener('change', (e) => {
  map.setFilters({
    ...currentFilters,
    price_max: e.target.value,
  });
});

Each setFilters call triggers one feature reload. Debounce if your UI emits changes rapidly.

4. Customize card rendering (optional)

If you want to render feature cards and popups with your own components, pass a featureRenderer:

Mapsemble.init({
  container: '#map',
  mapId: 'YOUR_MAP_ID',
  embed: 'shadow',
  featureRenderer: (el, feature, filters, location) => {
    // el is a slot inside a shadow root — your CSS needs to be injected here.
    injectStyleOnce(el.getRootNode(), '/your-app.css');

    el.innerHTML = `
      <article>
        <h3>${feature.properties.title}</h3>
        <p>${feature.properties.price}</p>
      </article>
    `;
  },
  featureUnmount: (el) => { /* tear down any listeners/frameworks */ },
});

The filters argument to featureRenderer is the widget's current filter state — use it so cards stay consistent with the map.

location is one of 'listDesktop', 'listMobile', 'selectedDesktop', 'selectedMobile'.

5. Two-way sync with the URL (optional)

Keep the map and your URL in sync so filters survive a reload:

window.addEventListener('popstate', () => {
  map.setFilters(readFiltersFromUrl());
  map.setOrderBy(readOrderByFromUrl());
});

Scroll capture

On mobile the widget can integrate with host-page scroll. When enabled, the widget becomes a sticky section — the user scrolls the host page, and once the card list is in view the scroll transfers to the internal card list. When the list reaches the end, the page resumes scrolling.

Mapsemble.init({
  container: '#my-map',
  mapId: 'YOUR_MAP_ID',
  embed: 'shadow',
  scrollCapture: true,
  scrollCaptureTop: '84px',
});
  • scrollCapture ('mobile' | 'all' | 'none' | boolean) — enable the sticky scroll-through behavior. Default 'none' (off). 'mobile' (or boolean true) only activates when container width < 768px; 'all' activates on every viewport.
  • scrollOffsetTop (string | number) — CSS top offset for the sticky widget. Default '0px'. Use to clear a fixed header. (scrollCaptureTop is a deprecated alias.)

Fill visible viewport

On mobile, CSS 100dvh does not update continuously while the address bar animates on iOS Safari and Android Chrome — the widget stays at its initial size and doesn't grow into freed space. fillVisibleViewport (default true) makes the widget set its container height from window.visualViewport.height, which tracks the actual visible area in real time across both browsers.

Mapsemble.init({
  container: '#my-map',
  mapId: 'YOUR_MAP_ID',
  embed: 'shadow',
  scrollOffsetTop: '15px',      // sticky-top distance AND top breathing-room
  scrollOffsetBottom: '15px',   // bottom breathing-room
  // fillVisibleViewport: true, // default — opt out with `false`
});

The applied height is visualViewport.height - scrollOffsetTop - scrollOffsetBottom. The two offsets reserve space above and below the widget when filling the viewport — for example, to leave a peek of the host page's content visible above and below the map.

Opt out with fillVisibleViewport: false if the host page sizes the container itself (flex layout, CSS Grid, or its own height calc). The widget will not touch the container's inline height in that case.

The listener is unconditional — no width-based mobile gate. On desktop visualViewport.height equals window.innerHeight, the listener is effectively idle (it doesn't fire on document scroll, only on viewport changes), and the cost is one rAF per resize. The unconditional approach also correctly handles iPad Safari at widths ≥ 1024px where a width gate would mis-classify it as desktop and skip the fix.

Falls back gracefully on browsers without window.visualViewport (very old): the widget early-returns without setting any inline height, and the host's CSS takes over.

Translations

Every piece of widget text is translatable — filter labels, buttons, empty states, and JavaScript-rendered hints like the 'Tap to interact' pill shown when Scroll capture is enabled. The translation that ends up on screen is resolved in this order:

  1. init({ translations }) — values you pass to Mapsemble.init(). Highest priority; overrides everything else for that embed.
  2. Map admin translations — values the map owner entered in the map's Translations panel, keyed by the widget language.
  3. English fallback — the original phrase, used when neither source provides a translation.

The keys are the original English phrases:

Mapsemble.init({
  container: '#my-map',
  mapId: 'YOUR_MAP_ID',
  language: 'nl',
  translations: {
    'Tap to interact': 'Tik om in te zoomen',
    'No results found': 'Geen resultaten gevonden',
    'More Filters': 'Meer filters',
  },
});

Use init({ translations }) when the embedding page needs phrasing that's specific to the host context (a Dutch real-estate site might want different copy than a Dutch travel site, even for the same map). For per-map translations that should apply to every embed, set them in the map's Translations panel instead.

To update translations after init, see map.setTranslations().

Callbacks

onCount(total)

Fires after every feature load with the total matching count (accounting for filters and extent). Use it to update an element outside the widget:

Mapsemble.init({
  container: '#my-map',
  mapId: 'YOUR_MAP_ID',
  embed: 'shadow',
  onCount: (count) => {
    document.querySelector('#property-count').textContent = `${count} properties`;
  },
});

Migrating from older versions

  • Mapsemble.init({ queryParams }) is a deprecated alias for filters and will be removed in a future release. Update your init call to use filters.
  • Pre-seeding data-shadow-embed-*-value attributes on the container is no longer required — init({ filters, orderBy }) handles seeding internally.
  • Reaching into container.shadowRoot to drive the order-by dropdown is no longer required — call map.setOrderBy() instead.
  • init({ sort }) and setSort() were renamed to init({ orderBy }) and setOrderBy() to align with the rest of the API. No backwards-compatible alias is provided.