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 .. code-block:: javascript import React from 'react'; import PropTypes from 'prop-types'; import Page from '@mapstore/containers/Page'; function CustomViewer({ plugins, match, mode }) { return ( ); } 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 .. code-block:: javascript 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 .. code-block:: diff - 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 .. code-block:: javascript { // ... , "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 .. code-block:: javascript 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 ( ); } 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 .. code-block:: diff 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 .. code-block:: javascript 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 ( ); } 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 .. code-block:: javascript import React from 'react'; function ViewerLayout({ children }) { return (
{children}
); } 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 .. code-block:: javascript 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 ( ); } 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 .. code-block:: javascript import React from 'react'; function ViewerLayout({ children, leftPanel }) { return (
{leftPanel}
{children}
); } 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 .. code-block:: javascript { // ... , "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