Connecting the API using JavaScript
Note
In Mappable Account, log in or create a new account. The main page will display a key that will work for any Mappable service.
The key is activated within 15 minutes after you receive it.
Regular connection
<!DOCTYPE html>
<html>
<head>
<!-- Replace YOUR_API_KEY with the real key -->
<script src="https://js.api.mappable.world/v3/?apikey=YOUR_API_KEY&lang=en_US"></script>
<script src="index.js"></script>
</head>
<body>
<div id="app" style="width: 600px; height: 400px"></div>
</body>
</html>
async function initMap() {
await mappable.ready;
const {MMap, MMapDefaultSchemeLayer} = mappable;
const map = new MMap(
document.getElementById('app'),
{
location: {
center: [25.229762, 55.289311],
zoom: 10
}
}
);
map.addChild(new MMapDefaultSchemeLayer());
}
initMap();
{
"devDependencies": {
"http-server": "14.1.1"
},
"scripts": {
"start": "npx http-server ."
}
}
Set dependencies and run a local server:
npm install
npm run start
Open the app
Alert
The API is guaranteed to work with browsers that have an audience coverage of at least 0.5% according to Browserslist.
Specifics of regular connection
- The JS API is distributed exclusively via a link that should be inserted in the document header.
- API components are always loaded asynchronously.
- Components exist only in the global scope in the
mappable
variable. - Components are available only after the
mappable.ready
promise is resolved.
API loading parameters
apikey |
Required parameter The key issued in the Mappable Account. Note The key is activated within 15 minutes after you receive it. |
lang |
Required parameter Locale, set as
|
ready promise
The mappable.ready
promise guarantees that all components of the core JavaScript API
module are loaded and the DOM
is built.
Alert
Handlers of events like document.ready
, window.onload
, or jQuery.ready
do not signal the end of JS API component loading.
Connecting via top-level-await (recommended)
<!DOCTYPE html>
<html>
<head>
<!-- replace YOUR_API_KEY with the real key -->
<script src="https://js.api.mappable.world/v3/?apikey=YOUR_API_KEY&lang=en_US"></script>
<script type="module" src="index.js"></script>
</head>
<body>
<div id="app" style="width: 600px; height: 400px"></div>
</body>
</html>
import { MMap, MMapDefaultSchemeLayer } from './lib/mappable.js'
const map = new MMap(
document.getElementById('app'),
{
location: {
center: [25.229762, 55.289311],
zoom: 10
}
}
);
map.addChild(new MMapDefaultSchemeLayer());
await mappable.ready;
export const {MMap, MMapDefaultSchemeLayer} = mappable;
{
"devDependencies": {
"http-server": "14.1.1"
},
"scripts": {
"start": "npx http-server ."
}
}
Set dependencies and run a local server:
npm install
npm run start
Open the app
Specifics
-
Specify the
type="module"
attribute in the "script" tag that loads the compiled project JS to enable ECMAScript Modules (ESM) and top-level-await support:<script type="module" src="index.js"></script>
ESM support when using bundlers
If you're building a project using a bundler (for example, Webpack), add
"type": "module"
topackage.json
-
In
lib/mappable.js
, wait until the JS API is fully loaded and then export the necessary map components so that they can be used in other parts of the project:await mappable.ready; export const {MMap, MMapDefaultSchemeLayer} = mappable;
-
The use of top-level-await in
lib/mappable.js
guarantees thatmappable.ready
is executed before the map components are imported, so the project code can be shorter and cleaner (the asynchronous initMap function is no longer needed):import { MMap, MMapDefaultSchemeLayer } from './lib/mappable.js' const map = new MMap({...});
Connecting with Webpack
- Is there a way to not connect the JS API in the
HTML
document header? - Can JS API components be called not from a global variable, but more practically, as if they were delivered via an
npm
package? - Is there a way to skip handling the
mappable.ready
promise resolution?
You can do this using Webpack
and its externals
option. There are three methods, each with its own advantages and limitations.
mappable
global variable)
Method 1 (without the <!DOCTYPE html>
<html>
<head>
<!-- replace YOUR_API_KEY with the real key -->
<script src="https://js.api.mappable.world/v3/?apikey=YOUR_API_KEY&lang=en_US"></script>
<script src="build/bundle.js"></script>
</head>
<body>
<div id="app" style="width: 600px; height: 400px"></div>
</body>
</html>
import * as mappable from 'mappable';
async function initMap() {
await mappable.ready;
const {MMap, MMapDefaultSchemeLayer} = mappable;
const map = new MMap(
document.getElementById('app'),
{
location: {
center: [25.229762, 55.289311],
zoom: 10
}
}
);
map.addChild(new MMapDefaultSchemeLayer());
}
initMap();
const path = require('path');
module.exports = {
mode: 'development',
entry: './index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build'),
},
externals: {
mappable: 'mappable'
},
devtool: 'cheap-source-map'
};
{
"devDependencies": {
"http-server": "14.1.1",
"webpack": "5.88.2",
"webpack-cli": "5.1.4"
},
"scripts": {
"build": "webpack",
"start": "npx http-server ."
}
}
Set dependencies, build a project, and run a local server:
npm install
npm run build
npm run start
Open the app
Specifics
-
On the
HTML
page, we still connect theJS API
loader:<head> <script src="https://js.api.mappable.world/v3/?apikey=YOUR_API_KEY&lang=en_US"></script> </head>
Successful script loading guarantees that the
mappable
variable becomes globally accessible. -
In
webpack.config.js
, declare the externalmappable
variable:module.exports = { // ... externals: { mappable: 'mappable' } };
This makes it possible to import
mappable
in the project code as if themappable
code were delivered not via a global variable, but via annpm
package:import * as mappable from 'mappable'; // ...
JS API
loader)
Method 2 (without the <!DOCTYPE html>
<html>
<head>
<script src="build/bundle.js"></script>
</head>
<body>
<div id="app" style="width: 600px; height: 400px"></div>
</body>
</html>
import * as mappable from 'mappable';
async function initMap() {
await mappable.ready;
const {MMap, MMapDefaultSchemeLayer} = mappable;
const map = new MMap(
document.getElementById('app'),
{
location: {
center: [25.229762, 55.289311],
zoom: 10
}
}
);
map.addChild(new MMapDefaultSchemeLayer());
}
initMap();
const path = require('path');
module.exports = {
mode: 'development',
entry: './index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build'),
},
externalsType: 'script',
externals: {
// replace YOUR_API_KEY with the real key
mappable: ['https://js.api.mappable.world/v3/?apikey=YOUR_API_KEY&lang=en_US', 'mappable']
},
devtool: 'cheap-source-map'
};
{
"devDependencies": {
"http-server": "14.1.1",
"webpack": "5.88.2",
"webpack-cli": "5.1.4"
},
"scripts": {
"build": "webpack",
"start": "npx http-server ."
}
}
Set dependencies, build a project, and run a local server:
npm install
npm run build
npm run start
Open the app
Specifics
This is a modification of the first method:
-
You no longer need to connect the
<script>
tag in theHTML
document header —Webpack
does this for you. -
In
webpack.config.js
, you also need to declare themappable
variable, but it should contain a path to the API loader:module.exports = { // ... externalsType: 'script', externals: { mappable: ['https://js.api.mappable.world/v3/?apikey=YOUR_API_KEY&lang=en_US', 'mappable'] } };
Limitations of methods 1 and 2
In both of the above methods, a lightweight loader is imported, not the ready-made API. Components become fully available only after the mappable.ready
promise is resolved. This means that if your client code is split into modules, and the JS API is used in several of your modules at once, you should wait for the mappable.ready
promise to be resolved in each of your modules.
For example, you want to display two maps on a page at once, each in its own container. It would be more convenient for you to describe this functionality not in a single large module, but in two different ones, for example, module-a.js
and module-b.js
. With the use of the above methods, the code of your modules will look something like this:
// module-a.js
import * as mappable from 'mappable';
async function initMap() {
// Wait until `mappable.ready` is resolved
await mappable.ready;
const {MMap} = mappable;
// The map is initialized in the first container
const map = new MMap({document.getElementById('first-map-container')});
}
initMap();
// module-b.js
import * as mappable from 'mappable';
async function initMap() {
// Wait until `mappable.ready` is resolved
await mappable.ready;
const {MMap} = mappable;
// The map is initialized in the second container
const map = new MMap({document.getElementById('second-map-container')});
}
initMap();
You have to wait for mappable.ready
to be resolved in each module. This is not convenient, a simpler code would be more desirable:
// module-a.js
import * as mappable from 'mappable';
const {MMap} = mappable;
const map = new MMap({document.getElementById('first-map-container')});
// module-b.js
import * as mappable from 'mappable';
const {MMap} = mappable;
const map = new MMap({document.getElementById('second-map-container')});
To achieve this, use the third method.
mappable.ready
promise resolution)
Method 3 (without the <!DOCTYPE html>
<html>
<head>
<script src="build/bundle.js"></script>
</head>
<body>
<div id="app" style="width: 600px; height: 400px"></div>
</body>
</html>
import * as mappable from 'mappable';
const {MMap, MMapDefaultSchemeLayer} = mappable;
const map = new MMap(
document.getElementById('app'),
{
location: {
center: [25.229762, 55.289311],
zoom: 10
}
}
);
map.addChild(new MMapDefaultSchemeLayer());
const path = require('path');
module.exports = {
mode: 'development',
entry: './index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build'),
},
externalsType: 'script',
externals: {
// replace YOUR_API_KEY with the real key
mappable: [
`promise new Promise((resolve) => {
if (typeof mappable !== 'undefined') {
return mappable.ready.then(() => resolve(mappable));
}
const script = document.createElement('script');
script.src = "https://js.api.mappable.world/v3/?apikey=YOUR_API_KEY&lang=en_US";
script.onload = () => {
mappable.ready.then(() => resolve(mappable));
};
document.head.appendChild(script);
})`
]
},
devtool: 'cheap-source-map'
};
{
"devDependencies": {
"http-server": "14.1.1",
"webpack": "5.88.2",
"webpack-cli": "5.1.4"
},
"scripts": {
"build": "webpack",
"start": "npx http-server ."
}
}
Set dependencies, build a project, and run a local server:
npm install
npm run build
npm run start
Open the app
Specifics
This method is almost the same as method 2, but the path to the API loader is specified slightly differently in webpack.config.js
when declaring the mappable
variable, and the mappable.ready
promise is resolved there as well.
In this case, fully loaded JS API modules become available and mappable.ready
execution is guaranteed in the built project JS file, so the project code can be shorter and cleaner:
import * as mappable from 'mappable';
const {MMap} = mappable;
const map = new MMap({...});
Alert
With this connection method, the project code will not be executed until the JS API components are fully loaded.
Limitations of method 3
Let's say you want to show a loading animation until the map components are loaded. You've connected the JS API using the third method and written the following code. Unfortunately, you will not solve the task:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as mappable from 'mappable';
// Execution of this string will start only after mappable is loaded and mappable.ready is resolved
const reactify = mappable.reactify.bindTo(React, ReactDOM);
const {MMap} = reactify.module(mappable);
function MapView() {
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
setLoading(false);
}, []);
// This code does nothing. Although it does get executed when the component is mounted in the DOM,
// mounting will be performed only after mappable is loaded and mappable.ready is resolved.
// So when mounting, the user will see the 'Loading...' status for a split second,
// the loading animation will almost immediately be canceled by the subsequent useEffect
if (loading) {
return <div>Loading...</div>;
}
return <MMap />;
}
function App() {
return (
<MapView />
);
}
The main disadvantage of the third method of connecting Webpack is that nothing will be executed in the project modules until the JS API components are fully loaded.
Don't use it if you want to show the loading animation:
- Switch to Webpack connection method 1 or 2.
- Inside
React.useEffect
, wait formappable.ready
and only then remove theloading
flag. - The code that uses the
Reactify
module must be placed inside theMapView
component so that theJS
interpreter can read these strings only aftermappable.ready
is resolved:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as mappable from 'mappable';
function MapView() {
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
mappable.ready.then(() => setLoading(false));
}, []);
if (loading) {
return <div>Loading...</div>;
}
const reactify = mappable.reactify.bindTo(React, ReactDOM);
const {MMap} = reactify.module(mappable);
return <MMap />;
}
function App() {
return (
<MapView />
);
}
Switching the language
Remember the language is set when connecting the API. If you are creating international websites, regardless of the connection method you are using, make sure the language is switched correctly.
For example, to connect with Webpack using the third method, you can do it as follows:
module.exports = {
//...
externals: {
mappable: [
`promise new Promise((resolve) => {
...
const language_region = ['tr', 'tr-TR'].includes(navigator.language) ? 'tr-TR' : 'en-US';
script.src = "https://js.api.mappable.world/v3/?apikey=YOUR_API_KEY&lang=" + lang;
...
})`
]
}
};
Connecting packages and modules
The JS API provides additional packages and modules that can be used to solve specific tasks. To load them, use the mappable.import
method.