Embedding a map and talking to it

Embedding a map and talking to it

How a page embeds a Mappia map in an iframe with mappia_io.js, applies a query into it at runtime, and exchanges messages with the query running inside — the envelope, the handshake, the queueing and the failure modes.

Mappia reference, section “Integration”. Page: https://mappia.earth/reference/embedding-and-messages/ Generated API entries: MappiaIO.MappiaIO, MappiaIO.applyQuery, MappiaIO.send, MappiaIO.addOnMessageCallback, MappiaIO.addReadyCallback, QUERY.setMappiaIoCallback, MappiaIO.postMessage, QUERY.setQueryGlobalProperties, QueryState, QUERY.addLayer, QUERY.removeLayer, URLProperties, URLOptions (https://mappia.earth/assets/api.json)

A Mappia map can be a page of its own, or a component inside somebody else’s application — an Angular dashboard, a plain HTML site, a CMS page. Embedded, it is an <iframe>, and the host talks to the map by exchanging messages with it through the small library the platform serves for that purpose, mappia_io.js.

Two halves have to agree: the parent page, which creates the connection and sends messages, and the query inside the iframe, which receives them and answers. This page describes both, and the contract between them. Worked examples - applying queries, both message directions, request and answer, start-up order, progress, files - a complete page you can copy (/mappia-io/host-page.html), sites that run on it and a live playground are on the MappiaIO tool page.

1. The two routes

URLWhat it is
{origin}/calculator/?queryid=<id>The end-user view: the map, its layers and its widgets
{origin}/editor/?queryid=<id>The same query in the developer view, with the query text and the editor tools

Both accept the same URL parameters (queryid, options, extent, lang, visiblelayers, …), documented in the API reference under Sharing Maps. The two pages do not share live state: applying a query in the editor does not change an open calculator page.

2. Connecting from the parent page

<iframe id="mappia" src="https://maps.csr.ufmg.br/calculator/?queryid=417&options=scale"></iframe>
<script src="https://maps.csr.ufmg.br/mappia_io.js"></script>
<script>
  var mappia = MappiaIO("mappia", true);
</script>

MappiaIO(target, keepAfterReady):

ArgumentMeaning
targetThe iframe’s id, the iframe element itself, or the window object returned by window.open — the map can equally well live in a separate window
keepAfterReadytrue keeps listening after the initial handshake. For an embed that keeps talking to the map, pass true; false is for a one-shot message

The returned object is the connection. Its methods, all chainable:

MethodWhat it does
send(message[, forceSend])Sends a message to the query. Until the iframe reports ready, messages are queued, not dropped — forceSend skips the queue and is rarely needed
applyQuery(text)Applies query text into the running iframe, replacing what it shows
applyQueryFromUrl(url)Fetches the text and applies it (subject to the usual cross-origin rules)
addOnMessageCallback(fn)Registers a handler for messages coming from the query
addReadyCallback(fn)Runs fn when the iframe is ready (immediately, if it already is)
isDomLoaded() / isDomAttached()The state of the connection
remove()Stops listening — call it when the component holding the iframe is destroyed
addOnRemoveCallback(fn)Notified when the connection is removed

noopener and noreferrer break the connection. They sever the window reference the messages travel on. Do not put them on the iframe, and do not pass them to window.open for a map window.

3. Loading a query into the frame

Two independent mechanisms, which can be combined:

MechanismHowWhen to use it
Saved query?queryid=<number> in the iframe URLThe query lives on the Mappia server
Runtime querymappia.applyQuery(text) from the parentThe query text belongs to the host: generated per user, per selection, kept in the host’s own repository

A common shape is both: the iframe opens a saved query so something is on screen at once, and the parent then applies a more specific query.

4. The message envelope

Every message is one JSON object. Only operation is structural; what it means is for the parent and the query to agree on.

{
  operation: "show_property",     // required: your own name for the action
  message: { id: 4711 },          // your payload
  type: "geojson",                // optional, your own
  overwrite: { title: "Farm 4711", visibility: true },  // optional layer overrides
  layerDefinitionsMode: false     // optional
}

The platform does not define an action vocabulary: show_property, clear_selection, export_png are names you invent and implement in the query. The only reserved names are the handshake ones in §6, all prefixed mappia__.

5. The query side

Inside the iframe, the query registers a handler and answers with postMessage:

ExtjsUtils.QUERY.setQueryGlobalProperties({
  handleParentMessage: function (msg) {
    if (msg.operation === "show_property") {
      ExtjsUtils.QUERY.addLayer({
        name: "CSR:municipios",
        title: (msg.overwrite && msg.overwrite.title) || "Municipalities",
        visibility: true,
      });
      // answer the parent
      ExtjsUtils.QUERY.postMessage({ operation: "property_shown", message: msg.message });
    }
  },
}) &&
  ExtjsUtils.QUERY.setMappiaIoCallback(window.handleParentMessage) && [
    { name: "CSR:estados", visibility: true },
  ];
  • ExtjsUtils.QUERY.setMappiaIoCallback(fn) registers the handler. (setMessageCallback is an older alias of the same function.)
  • ExtjsUtils.QUERY.postMessage(obj) sends to the parent — to the containing page when embedded, to the opener when the map is a separate window. Objects are serialized for you.
  • The parent receives it in every handler registered with addOnMessageCallback.

Because the handler is a function the query needs by name, it belongs in the query’s globals rather than in a top-level variable — see the query language §3.

6. The handshake, and why nothing is lost

The two sides synchronise themselves before application messages flow. These operation values are the platform’s own; never send them from application code:

mappia__checkDOM, mappia__confirmDOM, mappia__checkQueryListening, mappia__confirmQueryListening, mappia__applyQuery, mappia__queryApplied.

What the two sides guarantee:

  • Parent side: send before the iframe reports ready is queued and flushed on ready.
  • Query side: a message that arrives while a query is being applied is queued and handed to the handler once the load finishes.

So applyQuery(...) immediately followed by send(...) is safe: the message is delivered after the new query is in place, not against the old one. Neither side silently drops messages — they delay them.

Two of those platform messages also reach the parent’s addOnMessageCallback handlers, as plain strings: mappia__queryApplied after every applyQuery — the way to know the new query is in place — and mappia__confirmQueryListening whenever a query registers its callback. Handlers should skip strings starting with mappia__ they do not use.

mappia__queryApplied also arrives when the query did not run (a syntax or runtime error, or a value that is not a list of layers), leaving an empty map. Such a query is announced first by an object, {action: "mappia__queryError", error: {name, message, line, column}} — line and column in the text you sent, when the browser gives them (it does for runtime errors, not for syntax errors). Check for it before trusting the confirmation:

mappia.addOnMessageCallback(function (msg) {
  if (msg && msg.action === "mappia__queryError") showProblem(msg.error.message, msg.error.line);
  else if (msg === "mappia__queryApplied") markApplied();
});

A page that must not depend on it (the message is new in 2026-10: an older map server sends only the confirmation) parses the text first, with the same wrapper as the platform — new Function("return (function(){return true && " + text + "\n})()") throws on a syntax error — and treats an empty map after a confirmation as suspect. The waiting mechanism is the same one described in the execution model §5.

7. A complete parent-side integration

var mappia = MappiaIO("mappia", true);

// Receive first, so nothing sent below can outrun the handler.
mappia.addOnMessageCallback(function (msg) {
  if (msg.operation === "property_shown") {
    document.getElementById("status").textContent = "Showing " + msg.message.id;
  }
});

mappia.addReadyCallback(function () {
  fetch("/queries/properties.js")
    .then(function (response) { return response.text(); })
    .then(function (queryText) {
      mappia.applyQuery(queryText);
      mappia.send({ operation: "show_property", message: { id: 4711 } });
    });
});

// When the component is destroyed
// mappia.remove();

Worth wrapping send in one small module per integration, with a function per operation name, so the host’s UI code never repeats a raw string and the contract is written down in one place.

A complete, copyable page in this shape - its own buttons driving the map, the map’s panel button and clicks reporting back, a log of every message - is published at /mappia-io/host-page.html.

8. Failure modes

SymptomCause
Nothing arrives, in either directionnoopener / noreferrer on the iframe or the opened window (§2)
Messages arrive before the query is ready, and are ignoredThe query registered no handler — setMappiaIoCallback is what makes it listen (§5)
The map answers the first message and then goes quietThe connection was created with keepAfterReady = false
Re-used iframe never answers againThe connection was removed and the iframe was not loaded afresh; create the connection again after a real load
A handler runs with the previous query’s layersThe message was sent without waiting; the handshake covers applyQuery, but a message sent during a user-triggered reload of the whole frame is a different race — wait for ready (§2)
A query that registers no callback still answers like the previous oneThe callback set by setMappiaIoCallback is kept when another query is applied, until a query registers its own. A handler should check that what it works on still exists
The parent receives raw events it cannot readMessages travel wrapped; read them through the callback rather than listening on window yourself

Operation names seen in an existing integration are that integration’s choices, not platform API. When in doubt, the reserved prefix is mappia__, and everything else is yours.