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

  1. The JS API is distributed exclusively via a link that should be inserted in the document header.
  2. API components are always loaded asynchronously.
  3. Components exist only in the global scope in the mappable variable.
  4. 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 aslang=language_region:

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

  1. 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" to package.json

  2. 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;
    
  3. The use of top-level-await in lib/mappable.js guarantees that mappable.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.

Method 1 (without the mappableglobal variable)

<!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

  1. On the HTML page, we still connect the JS 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.

  2. In webpack.config.js, declare the external mappable variable:

    module.exports = {
        // ...
        externals: {
            mappable: 'mappable'
        }
    };
    

    This makes it possible to import mappable in the project code as if the mappable code were delivered not via a global variable, but via an npm package:

    import * as mappable from 'mappable';
    // ...
    

Method 2 (without the JS APIloader)

<!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:

  1. You no longer need to connect the <script> tag in the HTML document header — Webpack does this for you.

  2. In webpack.config.js, you also need to declare the mappable 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.

Method 3 (without the mappable.readypromise resolution)

<!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:

  1. Switch to Webpack connection method 1 or 2.
  2. Inside React.useEffect, wait for mappable.ready and only then remove the loading flag.
  3. The code that uses the Reactify module must be placed inside the MapView component so that the JS interpreter can read these strings only after mappable.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.