Add a new plugin extension

This section shows how to add a plugin using the extension support in MapStore without re-compilation of the bundle of the geonode-mapstore-cient applications. This new extensibility should be used for simple plugins.

Setup extension

Navigate to the /opt/geonode-project/my_geonode/src/my_geonode/ directory

cd /opt/geonode-project/my_geonode/src/my_geonode/

Run the create script using the mapstore project

npx @mapstore/project create extension

These are the properties requested by the create script. MyGeoNodeExtension is the name of the extension and it should be a unique identifier

- Name of extension (default Extension): MyGeoNodeExtension
- Name of project (default mapstore-extension): client
- Run npm install after creation setup (yes/no default yes): no

Create an index.json file inside the /opt/geonode-project/my_geonode/src/my_geonode/static/mapstore/extensions directory

mkdir /opt/geonode-project/my_geonode/src/my_geonode/static/mapstore
mkdir /opt/geonode-project/my_geonode/src/my_geonode/static/mapstore/extensions
touch /opt/geonode-project/my_geonode/src/my_geonode/static/mapstore/extensions/index.json
/opt/geonode-project/my_geonode/src/
|-- ...
|-- my_geonode/
|    |-- ...
|    |-- client/
|    +-- static/
|         |-- ...
|         +-- mapstore/
|              |-- ...
|              +-- extensions/
|                   |-- ...
|                   +-- index.json
|-- ...

Configure the extensions/index.json file to include the new extension

vim /opt/geonode-project/my_geonode/src/my_geonode/static/mapstore/extensions/index.json

Paste this configuration inside the index.json

{
    "MyGeoNodeExtension": {
        "bundle": "MyGeoNodeExtension/index.js",
        "translations": "MyGeoNodeExtension/translations",
        "assets": "MyGeoNodeExtension/assets"
    }
}

Navigate to the extension client folder

cd /opt/geonode-project/my_geonode/src/my_geonode/client/

Add license file if missing LICENSE.txt

Add the postCompile.js script file to move the compiled bundle in the correct directory

touch /opt/geonode-project/my_geonode/src/my_geonode/client/postCompile.js
vim /opt/geonode-project/my_geonode/src/my_geonode/client/postCompile.js
const fs = require('fs-extra');
const path = require('path');
const extensionsPath = path.join(__dirname, '..', 'static', 'mapstore', 'extensions');
const extensionIndexPath = path.join(extensionsPath, 'index.json');
const extensionIndex = fs.existsSync(extensionIndexPath) ? require(extensionIndexPath) : {};

const EXTENSION_NAME = 'MyGeoNodeExtension';

let error = false;
Object.keys(extensionIndex).forEach(extensionName => {
    const endpoints = extensionIndex[extensionName];
    Object.keys(endpoints).forEach(key => {
        if (endpoints[key].match('http://localhost:8082/extension')) {
            error = true;
            console.error('');
            console.error('//////////////////////////////////////');
            console.error('/// Error: dev url ' + key + ': ' + endpoints[key]);
            console.error('/// -> please replace http://localhost:8082/extension with /static/mapstore/extensions/' + extensionName);
            console.error('//////////////////////////////////////');
            console.error('');
        }
    });
});

if (!error) {
    fs.moveSync(path.resolve(__dirname, 'dist'), path.resolve(extensionsPath, EXTENSION_NAME), { overwrite: true });
}

Modify the package.json to use the new postCompile.js script and to target the mapstore version 2022.01.xx.

vim /opt/geonode-project/my_geonode/src/my_geonode/client/package.json
"scripts": {
-   "compile": "mapstore-project compile extension",
+   "compile": "mapstore-project compile extension && node ./postCompile.js",
    "lint": "eslint js --ext .jsx,.js",
    "start": "mapstore-project start extension",
    "test": "mapstore-project test extension",
    "test:watch": "mapstore-project test:watch extension"
},
...
"dependencies": {
-   "mapstore": "git+https://github.com/geosolutions-it/MapStore2.git#master"
+   "mapstore": "git+https://github.com/geosolutions-it/MapStore2.git#2022.01.xx"
},

Install all needed dependencies

cd /opt/geonode-project/my_geonode/src/my_geonode/client/
npm install

Compile the bundle to verify if all the scripts are configured correctly

cd /opt/geonode-project/my_geonode/src/my_geonode/client/
npm run compile

After the compile command executes, a new folder is generated inside the extensions directory

/opt/geonode-project/my_geonode/src/
|-- ...
|-- my_geonode/
|    |-- ...
|    +-- static/
|         |-- ...
|         +-- mapstore/
|              |-- ...
|              +-- extensions/
|                   |-- MyGeoNodeExtension/
|                   +-- index.json
|-- ...

Edit the /opt/geonode-project/my_geonode/src/my_geonode/templates/geonode-mapstore-client/_geonode_config.html template to include this new extension

vim /opt/geonode-project/my_geonode/src/my_geonode/templates/geonode-mapstore-client/_geonode_config.html
<!-- _geonode_config.html file in the my_geonode project -->
{% extends 'geonode-mapstore-client/_geonode_config.html' %}
{% block override_local_config %}
<script>
    window.__GEONODE_CONFIG__.overrideLocalConfig = function(localConfig) {
        localConfig.plugins.map_viewer.push({ "name": "MyGeoNodeExtension" });
        return localConfig;
    };
</script>
{% endblock %}

After these steps a box with a message is visible on the map viewer.

Develop the extension

Temporarily edit the /opt/geonode-project/my_geonode/src/my_geonode/static/mapstore/extensions/index.json file using the dev endpoints instead of the static ones (remember to restore this value to the static one)

vim /opt/geonode-project/my_geonode/src/my_geonode/static/mapstore/extensions/index.json
{
    "MyGeoNodeExtension": {
        "bundle": "http://localhost:8082/extension/index.js",
        "assets": "http://localhost:8082/extension/assets",
        "translations": "http://localhost:8082/extension/translations"
    }
}

Add the development configuration to the /opt/geonode-project/my_geonode/src/my_geonode/templates/geonode-mapstore-client/_geonode_config.html template

vim /opt/geonode-project/my_geonode/src/my_geonode/templates/geonode-mapstore-client/_geonode_config.html
{% extends 'geonode-mapstore-client/_geonode_config.html' %}
{% block override_local_config %}
<script>
    window.__GEONODE_CONFIG__.overrideLocalConfig = function(localConfig) {
        localConfig.plugins.map_viewer.push({ "name": "MyGeoNodeExtension" });

+       // IMPORTANT!!! REMOVE AFTER DEVELOPMENT
+       // temporary changes to develop extension
+       // use absolute extensions folder path to correctly get http://localhost:8082 endpoint
+       localConfig.extensionsFolder = '';
+       // ensure that the http://localhost:8082 endpoint will not use proxy but direct requests
+       localConfig.proxyUrl.useCORS.push('http://localhost:8082');

        return localConfig;
    };
</script>
{% endblock %}

Navigate to the /opt/geonode-project/my_geonode/src/my_geonode/client/ folder and start the extension endpoints

cd /opt/geonode-project/my_geonode/src/my_geonode/client/
npm start

Replace the content of the /opt/geonode-project/my_geonode/src/my_geonode/client/js/extension/plugins/Extension.jsx with a new extension. An example could be a floating box that shows the value of the center of the map

vim /opt/geonode-project/my_geonode/src/my_geonode/client/js/extension/plugins/Extension.jsx
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { mapSelector } from '@mapstore/framework/selectors/map';

function MyGeoNodeExtension({
    center
}) {
    return (
        <div
            className="shadow"
            style={{
                position: 'absolute',
                zIndex: 100,
                bottom: 35,
                margin: 8,
                left: '50%',
                backgroundColor: '#ffffff',
                padding: 8,
                textAlign: 'center',
                transform: 'translateX(-50%)'
            }}
        >
            <div>
                <small>
                    Map Center ({center.crs})
                </small>
            </div>
            <div>
                x: <strong>{center?.x?.toFixed(6)}</strong>
                {' | '}
                y: <strong>{center?.y?.toFixed(6)}</strong>
            </div>
        </div>
    );
}

MyGeoNodeExtension.propTypes = {
    center: PropTypes.object
};

MyGeoNodeExtension.defaultProps = {
    center: {}
};

const MyGeoNodeExtensionPlugin = connect(
    createSelector([
        mapSelector
    ], (map) => ({
        center: map?.center
    })),
    {}
)(MyGeoNodeExtension);

export default {
    name: __MAPSTORE_EXTENSION_CONFIG__.name,
    component: MyGeoNodeExtensionPlugin,
    containers: {},
    epics: {},
    reducers: {}
};

Build the extension

Restore the /opt/geonode-project/my_geonode/src/my_geonode/static/mapstore/extensions/index.json points to MyGeoNodeExtension static path for the extension

vim /opt/geonode-project/my_geonode/src/my_geonode/static/mapstore/extensions/index.json
{
    "MyGeoNodeExtension": {
        "bundle": "MyGeoNodeExtension/index.js",
        "assets": "MyGeoNodeExtension/assets",
        "translations": "MyGeoNodeExtension/translations"
    }
}

Remove the development configuration from the _geonode_config.html template

vim /opt/geonode-project/my_geonode/src/my_geonode/templates/geonode-mapstore-client/_geonode_config.html
{% extends 'geonode-mapstore-client/_geonode_config.html' %}
{% block override_local_config %}
<script>
    window.__GEONODE_CONFIG__.overrideLocalConfig = function(localConfig) {
        localConfig.plugins.map_viewer.push({ "name": "MyGeoNodeExtension" });

-       // IMPORTANT!!! REMOVE AFTER DEVELOPMENT
-       // temporary changes to develop extension
-       // use absolute extensions folder path to correctly get http://localhost:8082 endpoint
-       localConfig.extensionsFolder = '';
-       // ensure that the http://localhost:8082 endpoint will not use proxy but direct requests
-       localConfig.proxyUrl.useCORS.push('http://localhost:8082');

        return localConfig;
    };
</script>
{% endblock %}

Compile the bundle

cd /opt/geonode-project/my_geonode/src/my_geonode/client/
npm run compile

Now the new extension will be available in the map_viewer page with the changes applied during the development