Connecting the API

Usual connection

<!DOCTYPE html>
<html>
    <head>
        <!-- Substitute the value of the real key instead of YOUR_API_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 ."
    }
}

Install the dependencies and start the local server:

npm install
npm run start

Open application

Alert

The API is guaranteed to work in browsers whose audience is more than 0.5% according to Browserslist.

Specificities of the usual connection

  1. The JS API is distributed exclusively by the link that needs to be connected in the header of the document.
  2. API components are always loaded asynchronously.
  3. Components exist only in the global scope in the variable mappable.
  4. Components are available only after the promise mappable.ready is resolved.

API loading parameters

apikey

Required parameter

The key issued in the Mappable Account.

Note

Key activation takes up to 15 minutes.

lang

Required parameter

Locale. Set as lang=language_region, where

Promise ready

The promise mappable.ready guarantees that all components of the main module Javascript API are loaded, and the DOM is built.

Alert

Event handlers like document.ready, window.onload, jQuery.ready do not signal the end of loading JS API components.

Connecting via top-level-await (recommended)

<!DOCTYPE html>
<html>
  <head>
    <!-- Substitute the value of the real key instead of YOUR_API_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 ."
  }
}

Install the dependencies and start the local server:

npm install
npm run start

Open application

Specificities

  1. In the script tag that loads the compiled project js, specify the attribute type="module" to activate support for ECMAScript Modules (ESM) and top-level-await:

    <script type="module" src="index.js"></script>
    
    ESM support when using bundles

    If a bundler is used to build the project, for example Webpack, then in the package.json file needs to add "type": "module"

  2. In the file lib/mappable.js we are waiting for the JS API to be fully loaded, after which we export the necessary map components for use in other parts of the project:

    await mappable.ready;
    
    export const {MMap, MMapDefaultSchemeLayer} = mappable;
    
  3. Using top-level-await in lib/mappable.js guarantees the execution of mappable.ready before importing map components, so the project code can be written shorter and neater (the asynchronous initMap function is no longer needed):

    import { MMap, MMapDefaultSchemeLayer } from './lib/mappable.js'
    
    const map = new MMap({...});
    

Connecting with Webpack

  • Is it possible not to connect the JS API in the header of the HTML document?
  • Is it possible to call JS API components not from a global variable, but more habitually, as if they are supplied via an npm package?
  • Is it possible to make it so that you don't have to process the resolving of the promise `mappable.ready'?

Using Webpack and its [externals] option (https://webpack.js.org/configuration/externals /) this can be done. There are three ways, each has both advantages and limitations.

Method 1

<!DOCTYPE html>
<html>
    <head>
        <!-- Substitute the value of the real key instead of YOUR_API_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 ."
    }
}

Install the dependencies, build the project and run the local server:

npm install
npm run build
npm run start

Open application

Specificities

  1. On the HTML page, we are still connecting the JS API loader:

    <head>
        <script src="https://js.api.mappable.world/v3/?apikey=YOUR_API_KEY&lang=en_US"></script>
    </head>
    

    Successful loading of the script ensures that the mappable variable appears in global access.

  2. In webpack.config.js declaring an external variable mappable:

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

    Thanks to this, the project code makes it possible to import mappable as if the mappable code is delivered not through a global variable, but through an npm package:

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

Method 2

<!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: {
        // Substitute the value of the real key instead of YOUR_API_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 ."
    }
}

Install the dependencies, build the project and run the local server:

npm install
npm run build
npm run start

Open application

Specificities

This is a modified first method:

  1. It is no longer necessary to connect the tag <script> in the header of the HTML page. This is done by webpack itself.

  2. In webpack.config.js the external variable mappable is also declared, but it specifies the 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 method 1 and 2

In both of the above methods, not a ready-made API is imported, but a lightweight loader. The components become fully accessible only after the mappable.ready promise is resolved. This means that if your client code is divided into its own 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 is more convenient for you to describe this functionality not in one large module, but in two different ones, for example with the names module-a.js and module-b.js. Then using the methods described above, the code of your modules will look something like this:

// module-a.js
import * as mappable from 'mappable';

async function initMap() {
    // Wait for the resolving mappable.ready
    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 for the resolving mappable.ready
    await mappable.ready;

    const {MMap} = mappable;

    // The map is initialized in the first container
    const map = new MMap({document.getElementById('second-map-container')});
}

initMap();

We have to wait for the mappable.ready resolving in each module. This is inconvenient, I would like to write code easier:

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

<!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: {
        // Substitute the value of the real key instead of YOUR_API_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 ."
    }
}

Install the dependencies, build the project and run the local server:

npm install
npm run build
npm run start

Open application

Specificities

This is almost the same method #2, only in webpack.config.js when declaring the variable mappable, the path to the API loader is specified somewhat differently and the promise mappable.ready is resolved there.

With this connection, fully loaded JS API modules become available in the assembled project js file and the execution of mappable.ready is guaranteed, so the project code can be written shorter and neater:

import * as mappable from 'mappable';

const {MMap} = mappable;
const map = new MMap({...});

Alert

With this connection, the project code will not start executing until the JS API components are fully loaded.

Limitations of method 3

For example, you want to show the loading animation until the map components have loaded. We connected the JS API in method #3 and wrote this code for this. Unfortunately, you will not be able to complete the task:

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as mappable from 'mappable';

// Execution of this line will start only after loading mappable and the mappable.ready resolving
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 is useless. Of course, it will be executed when mounting the component in the DOM, but
    // mounting will happen only after loading mappable and resolving mappable.ready.
    // Therefore, when mounting, the user will see the status 'Loading...' for a fraction of a second,
    // and then useEffect will immediately happen, which will cancel the loading animation.
    if (loading) {
        return <div>Loading...</div>;
    }

    return <MMap />;
}

function App() {
    return (
        <MapView />
    );
}

The main disadvantage of webpack connection #3 is that nothing will be executed in the project modules until the JS API components are fully loaded.

To show the loading animation, method #3 will have to be abandoned:

  1. Switch to webpack connection #1 or #2.
  2. Inside React.useEffect, you need to wait for mappable.ready and only then uncheck loading.
  3. The code using the reactify module must be placed inside the MapView component so that the JS interpreter can "read" these lines only after the mappable.ready resolving:
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 />
    );
}

Language switching

Remember that when connecting the API, language is specified. If you are creating international sites, make sure that you switch it with any connection method.

For example, to connect with Webpack in the third way, you can do this:

module.exports = {
  //...
  externals: {
    mappable: [
      `promise new Promise((resolve) => {
          ...
          const lang = ['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 are useful for solving specific tasks. The mappable.import method is provided for uploading them. Read more in the documentation of packages and modules.