Custom Page¶
MapStore uses the hash router of the react-router library to manage the page rendering. The hash part of the url is the path that the single page application is detecting to render the current page component. MapStore is providing by default different pages for the home, manager, map viewer, context, dashboard and geostory. We can extend the available pages of a MapStore project by including new entries in the pages configuration of the app. Here below an example that shows how to extend the available pages.
Setup a new page¶
Create a new folder named pages
to contains all the custom pages inside js/
directory
Add a new file called CustomViewer.jsx
inside js/pages/
folder with the following content
import React from 'react';
import PropTypes from 'prop-types';
import Page from '@mapstore/containers/Page';
function CustomViewer({
plugins,
match,
mode
}) {
return (
<Page
id={mode}
includeCommon={false}
plugins={plugins}
params={match.params}
/>
);
}
CustomViewer.propTypes = {
match: PropTypes.object,
plugins: PropTypes.object,
mode: PropTypes.string
};
CustomViewer.defaultProps = {
match: {},
mode: 'custom-viewer'
};
export default CustomViewer;
Create a new appConfig.js
file in the js/
folder with the following content
import appConfig from '@mapstore/product/appConfig';
import CustomViewer from '@js/pages/CustomViewer';
const projectPages = [
{
name: 'custom-viewer',
path: '/custom-viewer',
component: CustomViewer
}
];
export default {
...appConfig,
pages: [
...appConfig.pages,
...projectPages
]
};
Replace the import of the default of appConfig config with the new one from the project inside the js/app.jsx
file
- const appConfig = require('@mapstore/product/appConfig').default;
+ import appConfig from '@js/appConfig';
Configure the new page in the configs/localConfig.json
under the plugins
object property adding at least one plugin
{
// ... ,
"plugins": {
// ... ,
"custom-viewer": [
{
"name": "Map"
}
]
}
}
At this point we should be able to access http://localhost:8081/#/custom-viewer page and see a spinning loader that represent the map waiting for configuration
Connect the page to state¶
This example is using the map plugin this means we need to retrieve a map config to be rendered.
We can connect the Custom Viewer to load the new.json
static map configuration on mount using the loadMapConfig
action from the MapStore framework
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import Page from '@mapstore/containers/Page';
import { getConfigUrl } from '@mapstore/utils/ConfigUtils';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { loadMapConfig } from '@mapstore/actions/config';
function CustomViewer({
plugins,
match,
mode,
onMount
}) {
useEffect(() => {
const mapId = 'new';
const { configUrl } = getConfigUrl({ mapId });
onMount(configUrl);
}, []);
return (
<Page
id={mode}
includeCommon={false}
plugins={plugins}
params={match.params}
/>
);
}
CustomViewer.propTypes = {
match: PropTypes.object,
plugins: PropTypes.object,
mode: PropTypes.string
};
CustomViewer.defaultProps = {
match: {},
mode: 'custom-viewer'
};
const ConnectedCustomViewer = connect(
createSelector([], () => ({})),
{
onMount: loadMapConfig
}
)(CustomViewer);
export default ConnectedCustomViewer;
Now a map with the new.json configuration should be available in the page
We can also extend the pages paths to read additional parameters than could be used to request map configuration from geostore based on a numerical id
Add a new path to the js/appConfig.js
custom-viewer page with the following structure '/custom-viewer/:id'
where :id
will be used as parameter to request a map configuration
import appConfig from '@mapstore/product/appConfig';
import CustomViewer from '@js/pages/CustomViewer';
const projectPages = [
{
name: 'custom-viewer',
- path: '/custom-viewer',
+ path: [ '/custom-viewer', '/custom-viewer/:id' ],
component: CustomViewer
}
];
export default {
...appConfig,
pages: [
...appConfig.pages,
...projectPages
]
};
Now also the http://localhost:8081/#/custom-viewer/1
will render the page and it will add the id the match.params prop object.
Changing the CustomViewer as follow will allows us to get map config from geostore
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import Page from '@mapstore/containers/Page';
import { getConfigUrl } from '@mapstore/utils/ConfigUtils';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { loadMapConfig } from '@mapstore/actions/config';
function CustomViewer({
plugins,
match,
mode,
onMount
}) {
useEffect(() => {
const mapId = match?.params?.id || 'new';
const { configUrl } = getConfigUrl({ mapId });
onMount(configUrl);
}, [match?.params?.id]);
return (
<Page
id={mode}
includeCommon={false}
plugins={plugins}
params={match.params}
/>
);
}
CustomViewer.propTypes = {
match: PropTypes.object,
plugins: PropTypes.object,
mode: PropTypes.string
};
CustomViewer.defaultProps = {
match: {},
mode: 'custom-viewer'
};
const ConnectedCustomViewer = connect(
createSelector([], () => ({})),
{
onMount: loadMapConfig
}
)(CustomViewer);
export default ConnectedCustomViewer;
Custom layout component¶
The Page component provided by MapStore accept a component
prop that can be used to compose the layout of the page.
First step is to create a ViewerLayout.jsx layout component in the js/components
folder with this content
import React from 'react';
function ViewerLayout({
children
}) {
return (
<div
style={{
position: 'absolute',
width: '100%',
height: '100%'
}}
>
{children}
</div>
);
}
export default ViewerLayout;
This simple component gets all the children are render them in the body. Inside tha page all the children will be the plugins listed in the localConfig plugin section
Now we need to add the layout component to the CustomViewer page
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import Page from '@mapstore/containers/Page';
import { getConfigUrl } from '@mapstore/utils/ConfigUtils';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { loadMapConfig } from '@mapstore/actions/config';
import ViewerLayout from '@js/components/ViewerLayout';
function CustomViewer({
plugins,
match,
mode,
onMount,
component
}) {
useEffect(() => {
const mapId = match?.params?.id || 'new';
const { configUrl } = getConfigUrl({ mapId });
onMount(configUrl);
}, [match?.params?.id]);
return (
<Page
id={mode}
includeCommon={false}
plugins={plugins}
params={match.params}
component={component}
/>
);
}
CustomViewer.propTypes = {
match: PropTypes.object,
plugins: PropTypes.object,
mode: PropTypes.string,
component: PropTypes.oneOfType([
PropTypes.object,
PropTypes.func
])
};
CustomViewer.defaultProps = {
match: {},
mode: 'custom-viewer',
component: ViewerLayout
};
const ConnectedCustomViewer = connect(
createSelector([], () => ({})),
{
onMount: loadMapConfig
}
)(CustomViewer);
export default ConnectedCustomViewer;
If we reload the page we should not see differences from the previous layout
We could now modify our layout component to accept new prop that represent additional section of the layout, for example a left panel section
import React from 'react';
function ViewerLayout({
children,
leftPanel
}) {
return (
<div
style={{
position: 'absolute',
width: '100%',
height: '100%',
display: 'flex'
}}
>
<div
style={{
position: 'relative',
width: 400,
height: '100%'
}}>
{leftPanel}
</div>
<div
style={{
position: 'relative',
flex: 1
}}
>
{children}
</div>
</div>
);
}
export default ViewerLayout;
Now the page should show an empty left side in the page.
It is possible to inject a plugin inside that section by using the cfg.containerPosition property defining the name of the prop section
{
// ... ,
"plugins": {
// ... ,
"custom-viewer": [
{
"name": "Map",
"cfg": {
"containerPosition": "leftPanel"
}
},
{
"name": "BackgroundSelector"
}
]
}
}
In the example above we will see tha map rendered in the left panel section while the background selector in the main container.
Warning: at least one plugin must be rendered in the main children container