# CM SDK: Embed your CleverMaps projects quickly and easily

**CleverMaps SDK** is a JavaScript library for embedding CleverMaps projects in a web iframe.  
It lets you inject the iframe with CleverMaps project into your web page, authenticate a user on the CleverMaps platform and manage a two-way communication between web page and CleverMaps view in the iframe.

## Index

1. Quick start
2. Installation
3. API Reference
4. Examples
5. Releases and changelog

## Quick start

Here is the simples way to start with sdk.  
Run `npm i clevermaps-js-sdk` in your project folder.  
Then inject the sdk js file from the `node_modules` folder into your static html file.

```
<script src="./node_modules/clevermaps-js-sdk/dist/index.js"><script>
```

And then instantiate the sdk:

```
<script>
    const sdk = cmSdk();
</script>
```

## Installation

Run one of the command below according to the package manager you use.

```
npm i clevermaps-js-sdk
```

```
yarn add clevermaps-js-sdk
```

The SDK is written using UMD.  
To import it into your ES2015 application import the default module:

```
import cmSdk from 'clevermaps-js-sdk';
```

Or require as CommonJS module:

```
const cmSdk = require('clevermaps-js-sdk');
```

## API Reference

### SDK Instance

**cmSDK**(hostUrl: _string_ = 'https://secure.clevermaps.io/')  
Returns an SDK instance.

```
import cmSdk from 'clevermaps-js-sdk';
// ...
const sdk = cmSdk();
```

Alternatively you can pass the host url:

```
const sdk = cmSdk('https://secure.clevermaps.io/');
```

Note that `cmSdk()` and `cmSdk('https://secure.clevermaps.io/')` will connect to exactly the same host, since `https://secure.clevermaps.io/` is the function default host url.

SDK object contains methods for managing iframe.

```
const {
    createIframe,
    renderIframe,
    removeIframe
} = cmSdk();
```

---

sdk.**createIframe**(viewUrlPath: _string_, options: _Options_)  
Returns an iframe instance.

```
const iframe = sdk.createIframe('h99r7aeqcsepjngk/map/points_overview_view', {});
```

Instead of `h99r7aeqcsepjngk/map/points_overview_view` use the right outermost part of your project view url, e.g. secure.clevermaps.io/#/**h99r7aeqcsepjngk/map/points_overview_view**.  
SDK lets you change the behaviour, visibility or interactivity of the iframe or of its content.
Here is a brief example:

```
const iframe = sdk.createIframe('h99r7aeqcsepjngk/map/points_overview_view', {
    componentSettings: {
        infoBox: {
            defaultExpanded: false,
        },
        tools: {
            search: false,
        }
    },
    theme: {
        colorPrimary: '#0055ff'
    },
    interactivity: {
        afterClick: false,
    },
    fullScreenButton: {
        enabled: false,
    },
    studioLinkButton: {
        enabled: false
    }
});
```

#### Options

- `interactivity`: _{ afterClick: boolean = true, text: string = 'Click to interact'}_  
  In default you interact with the frame only after you click into it. In a config object
  you can disable this behaviour, or you are able to change the description text.
- `fullScreenButton`: _{ enabled: boolean = true, title: string = 'Display in full screen' }_  
  This option lets you disable a full screen button in the top right area of the iframe.
- `studioLinkButton`: _{ enabled: boolean = true }_  
  This option lets you disable a Studio link button in the top right area of the iframe.
- `theme`: _{ logoSrc: string, colorPrimary: string }_  
  CleverMaps platform lets you whitelabel it according to your brand.  
  `colorPrimary` will affect all links and controls in the iframe.  
  `logoSrc` will change the app loading logo. It requires a full url string of the `svg`, `jpg` or `png` image. Note that host
  of the image url must be `secure.clevermaps.io`. For more information please contact [CleverMaps support](https://www.clevermaps.io/).
- `componentSettings`: _Record<string, Record<string, boolean>>_  
  This option lets you hide/show some controls in the CleverMaps platform view or change its default behaviour.  
  Here are all available options with default values:

```
{
    controls: {
        menu: false
    },
    tools: {
        measure: true,
        search: true,
        compare: true,
        filters: true
    },
    infoBox: {
        viewSwitch: false,
        defaultExpanded: true,
        share: true,
        bookmark: false,
        export: true,
        globalFilters: true
    },
    legend: {
      defaultExpanded: true,
    }
}
```

---

sdk.**renderIframe**(containerId: _string_, iframeInstance: _Iframe_)  
Injects iframe into the dom element with given id. You should pass `iframeInstance` returned by `sdk.createIframe` as a second parameter.  
If dom element is not found, it will do nothing.

```
<main>
    <div id="frameRoot"></div>
<main>
```

```
const iframe = sdk.createIframe('h99r7aeqcsepjngk/map/points_overview_view'', {});
sdk.renderIframe('frameRoot', iframe);
```

will result into following dom injection:

```
<main>
    <div id="frameRoot">
        <iframe src="https://secure.clevermaps.io/#/h99r7aeqcsepjngk/map/points_overview_view">
            <!-- ... -->
        </iframe>
    </div>
<main>
```

---

sdk.**removeIframe**(containerId: _string_, iframeInstance: _Iframe_)  
Remove iframe from the dom element with given id. In other words, it will do exactly vice versa as `sdk.renderIframe`.  
You should pass `iframeInstance` returned by `sdk.createIframe` as a second parameter.  
If iframe or dom element are not found, it will do nothing.

```
sdk.removeIframe('frameRoot', iframe);
```

---

### Iframe Instance

Iframe instance is returned by `sdk.createIframe`.

```
const {
    setState,
    message,
} = sdk.createIframe('h99r7aeqcsepjngk/map/points_overview_view', {});
```

The `message` object contains function for two-way communication between the host
website and embedded iframe.

iframe.**setState**(viewUrlPath: _string_)  
Immediately changes iframe view according to the url path. You should use the
same url share as in `sdk.createIframe`

```
iframe.setState('h99r7aeqcsepjngk/map/points_overview_view');
```

---

iframe.message.**addFilter**(definitionId: _string_, values: _Values_, instanceId: _string_)  
Adds a filter instance into the CleverMaps platform view.  
`definitionId` is filtered dataset property or indicator link in case of indicator filter.  
You can set arbitrary `instanceId`. The only prerequisite is uniqueness. Don't forget
to store it into the variable since `instanceId` is a parameter of `setFilter` and `removeFilter` described below.

```
iframe.message.addFilter('clients.sex_name', {values: ['Male']}, 'myId0001');
```

You should input `Values` according the filter type:

```
type FilterValues = FilterValuesMultiSelect | FilterValuesSingleSelect |
    FilterValuesFeature | FilterValuesHistogram |
    FilterValuesDate | FilterValuesIndicator;


type FilterValuesMultiSelect = {
    values?: Array<string | number | null>;
}
type FilterValuesSingleSelect = {
    value?: string | number | null;
}
type FilterValuesFeature = {
    values?: Array<string | number>;
}
type FilterValuesHistogram = {
    values?: [number | null, number | null];
    nullFiltered?: boolean;
}
type FilterValuesDate = {
    startDate?: FilterDefinitionDateValue | FilterDefinitionDateFunction;
    endDate?: FilterDefinitionDateValue | FilterDefinitionDateFunction;
}
type FilterDateSimpleValues = {
    startDate: FilterDefinitionDateValue;
    endDate: FilterDefinitionDateValue;
}
type FilterDefinitionDateValue = {
    value: string;
}
type FilterValuesIndicator = {
    values?: [number | null, number | null];
    granularity?: string;
}

type FilterDefinitionDateFunction = {
    function: FilterDefinitionDateValueFunction;
}
type FilterDefinitionDateValueFunction = {
    type: FilterDefinitionDateValueFunctionType;
    value?: number;
    content?: Array<FilterDefinitionDateValueFunction>;
    options?: {
        interval?: FilterDefinitionDateUnit;
    };
}
enum FilterDefinitionDateValueFunctionType {
    Today = 'function_today',
    DateTrunc = 'function_date_trunc',
    Interval = 'function_interval',
    Minus = 'function_minus',
    Plus = 'function_plus',
    Number = 'number'
}
enum FilterDefinitionDateUnit {
    Day = 'day',
    Week = 'week',
    Month = 'month',
    Quarter = 'quarter',
    Year = 'year'
}
```

---

iframe.message.**setFilter**(instanceId: _string_, values: _Values_)  
Sets values of already instanced filter.

```
const myUniqueInstanceId = 'myId0001';
iframe.message.addFilter('clients.sex_name', {values: ['Male']}, myUniqueInstanceId);
iframe.message.setFilter(myUniqueInstanceId, {values: ['Female']});
```

---

iframe.message.**removeFilter**(instanceId: _string_)  
Removes filter instance from the view.

```
const myUniqueInstanceId = 'myId0001';
iframe.message.addFilter('clients.sex_name', {values: ['Male']}, myUniqueInstanceId);
iframe.message.removeFilter(myUniqueInstanceId);
```

---

iframe.message.**resetFilter**(instanceId: _string_)  
Reset filter instance to the default state with default values.

```
const myUniqueInstanceId = 'myId0001';
iframe.message.addFilter('clients.sex_name', {values: ['Male']}, myUniqueInstanceId);
iframe.message.resetFilter(myUniqueInstanceId);
```

---

iframe.message.**openBookmarkModal**()  
Opens a dialog in the frame with bookmark related options.

```
iframe.message.openBookmarkModal();
```

---

iframe.message.**openExportModal**()  
Opens a dialog in the frame with list of exports.

```
iframe.message.openExportModal();
```

---

iframe.message.**toggleFitAll**()  
Center the map in the view to show all filtered data.

```
iframe.message.toggleFitAll();
```

---

iframe.message.**addAddFilterListener**(listener: () => void)  
iframe.message.**removeAddFilterListener**(listener: () => void)  
Lets you add and remove a listener to a add filter event in the view.

```
const listener = (
    type: string,
    params:
        [
            instanceId: string,
            values: Values, // see definition above
            filterDefinition: FilterDefinition
        ]
) => {
    console.log(`Filter event of type: ${type}`);
    console.log(`A filter with ${params.instanceId} instance id was added!`);
}

useEffect(() => {
    iframe.message.addAddFilterListener(listener);

    return () => {
        iframe.message.removeAddFilterListener(listener);
    };
});
```

`FilterDefiniton` can be of following types:

```
type FilterDefinition =
    FilterDefinitionMultiSelect |
    FilterDefinitionSingleSelect |
    FilterDefinitionFeature |
    FilterDefinitionHistogram |
    FilterDefinitionDate |
    FilterDefinitionGlobalDate |
    FilterDefinitionIndicator;

type FilterDefinitionMultiSelect = {
    type: FilterType.MultiSelect;
    property: string;
    orderBy?: Array<OrderBy>;
}

type FilterDefinitionSingleSelect = {
    type: FilterType.SingleSelect;
    property: string;
    orderBy?: Array<OrderBy>;
}

type FilterDefinitionFeature = {
    type: FilterType.Feature;
    dataset: string;
}

type FilterDefinitionHistogram = {
    type: FilterType.Histogram;
    property: string;
    format?: Format;
}

type FilterDefinitionDate = {
    type: FilterType.Date;
    property: string;
}

type FilterDefinitionGlobalDate = {
    type: FilterType.GlobalDate;
    property: string;
}

type FilterDefinitionIndicator = {
    type: FilterType.Indicator;
    indicator: string;
    filterSelection?: boolean;
}
```

---

iframe.message.**addSetFilterListener**(listener: () => void)  
iframe.message.**removeSetFilterListener**(listener: () => void)  
Lets you add and remove a listener to a set filter event in the view.

```
const listener = (
    type: string,
    params:
        [
            instanceId: string,
            values: Values, // see definition above
            filterDefinition: FilterDefinition
        ]
) => {
    // ...
}
iframe.message.addSetFilterListener(listener);
iframe.message.removeSetFilterListener(listener);
```

---

iframe.message.**addRemoveFilterListener**(listener: () => void)  
iframe.message.**removeRemoveFilterListener**(listener: () => void)  
Lets you add and remove a listener to a remove filter event in the view.

```
const listener = (
    type: string,
    params:
        [
            instanceId: string,
            filterDefinition: FilterDefinition
        ]
) => {
    // ...
}
iframe.message.addRemoveFilterListener(listener);
iframe.message.removeRemoveFilterListener(listener);
```

---

## Examples

### Instantiate sdk and render iframe

```
const sdk = cmSdk();
const iframe = sdk.createIframe('lmpijr4jf9sjm6b9/map/catchment_area_view', {});
sdk.renderIframe('div_a', iframe);
```

### Customize iframe interactivity

```
const options = {
    interactivity: {
        afterClick: true,
        text: 'Click to interact'
    },
    fullScreenButton: {
        enabled: true,
        title: 'Expand to full screen'
    },
};
const iframe = sdk.createIframe('lmpijr4jf9sjm6b9/map/catchment_area_view', options);
sdk.renderIframe('div_a', iframe);
```

### Customize view controls

```
const options = {
    componentSettings: {
        tools: {
            search: false,
            compare: false,
            measure: false,
        },
        infoBox: {
            defaultExpanded: false,
            share: false,
            export: false
        }
    }
};
const iframe = sdk.createIframe('lmpijr4jf9sjm6b9/map/catchment_area_view', options);
sdk.renderIframe('div_a', iframe);
```

### Handle filter events

```
const iframe = sdk.createIframe('lmpijr4jf9sjm6b9/map/catchment_area_view', options);
sdk.renderIframe('div_a', iframe);
iframe.message.addFilter('clients.sex_name', ['Female'], 'filterId1')

const setFilter = () => iframe.message.setFilter('filterId1', ['Male']);
const resetFilter = () => iframe.message.resetFilter('filterId1');
const removeFilter = () => iframe.message.removeFilter('filterId1');

//...
<button onClick="resetFilter">Reset filter</button>
//...
```

### Listen to filter events in useEffect hook

```
const iframe = sdk.createIframe('lmpijr4jf9sjm6b9/map/catchment_area_view', options);
sdk.renderIframe('div_a', iframe);
const listener = (...params) => console.log(params);

useEffect(() => {
    iframe1.message.addSetFilterListener(listener);
    return () => {
        iframe1.message.removeSetFilterListener(listener);
    }
});
```

## Releases and changelog

### 2.6.0

Adds access token to iframe url and iframe set access token message

### 2.5.0

Lets user collapse legend block on load

### 2.4.1

Adds Studio link button fallback font and sets font weight

### 2.4.0

Adds Studio link button

### 2.3.9

Adds missing documentation functions listener parameter

### 2.3.8

Adds new options for hidding and disabling filters

### 2.3.7

Deletes visibility tool related code

### 2.3.6

Checks if iframe element and contentWindow is defined before calling postMessage

### 2.3.5

Adds better dom manipulation bug handling

### 2.3.4

Updates untrusted message warning

### 2.3.3

Fix callbacks firing on message from not connected frame

### 2.3.2

Updates readme docs

### 2.3.1

Bug fixes

### 2.3.0

Lets you whitelabel and customize the iframe and adds interactive controls to iframe

### 2.2.0

Adds ability to listen to view filter events

### 2.1.0

Adds new message functions to interact with fit all, bookmark and export modal dialog

### 2.0.2

Bug fixes

### 2.0.1

Bug fixes

### 2.0.0

Brings completely new api to interact with filters

### 1.3.1

Updates readme docs

### 1.3.0

Adds iframe message object with ability to interact with view filters

### 1.2.0

Adds ability to authenticate

### 1.1.0

Enhances url handling

### 1.0.2

Bug fixes

### 1.0.1

Bug fixes

### 1.0.0

Initial version lets to inject an iframe with CleverMaps platform view into host website
