Pull Component Data from App Config
VertiGIS Studio Web has a powerful app configuration model which can be used to easily change the behavior of an application without modifying custom code. Using app config to power a components behavior increases its reusability and customizability.
By the end of this article, you'll have the knowledge to build a component that displays relevant news items at the top of your map. These news items will be populated from config, along with a value that tells the news component whether or not to be visible by default.
#
Prerequisites- Download and setup the VertiGIS Studio Web SDK.
- Check out the deployment instructions to learn more about deploying custom code to a VertiGIS Studio Web App.
#
Starting PointWe are going to add configuration to a custom component that displays news items. This component currently is following bad practices and does not treat the model as the source of truth for its data. We are going to move the newsItems
list to a configurable property on the model, and add a new configurable property, hideOnStartup
.
note
This example uses VertiGIS Studio Web layout components
- Component
- Empty Model
- CSS
- Component Index
- Registration
- Layout
- App Config
import React, { useState } from "react";import { LayoutElement } from "@vertigis/web/components";import List from "@vertigis/web/ui/List";import ListItem from "@vertigis/web/ui/ListItem";import TitleBar from "@vertigis/web/ui/TitleBar";import Button from "@vertigis/web/ui/Button";import DialogActions from "@vertigis/web/ui/DialogActions";import "./NewsFeed.css";
export default function NewsFeed(props) { const [hidden, setHidden] = useState(false);
const newsItems: string[] = [ "New fire hydrant installed at Main and 5th.", "Pipe burst at 4th and Broadview", "Fire hydrant reported as needs maintenance by citizen.", ];
return ( <LayoutElement {...props}> <List className="news-item-list"> <DialogActions> <TitleBar text="Recent News"></TitleBar> {hidden && ( <Button onClick={() => setHidden(false)}> Show News </Button> )} {!hidden && ( <Button onClick={() => setHidden(true)}> Hide News </Button> )} </DialogActions> {!hidden && newsItems.map((news, idx) => ( <ListItem key={idx}>{news}</ListItem> ))} </List> </LayoutElement> );}
import { ComponentModelBase, serializable,} from "@vertigis/web/models";
@serializableclass NewsFeedModel extends ComponentModelBase {}
export default NewsFeedModel;
.news-item-list { max-height: 200px; overflow: scroll;}
export { default } from "./NewsFeed";export { default as NewsFeedModel } from "./NewsFeedModel";
import NewsFeed, { NewsFeedModel } from "./components/NewsFeed";import { LibraryRegistry } from "@vertigis/web/config";
export default function (registry: LibraryRegistry) { registry.registerComponent({ name: "news-feed", namespace: "your.custom.namespace", getComponentType: () => NewsFeed, itemType: "news-feed-model", title: "News Feed", }); registry.registerModel({ getModel: (config) => new NewsFeedModel(config), itemType: "news-feed-model", });}
<?xml version="1.0" encoding="UTF-8"?><layout xmlns="https://geocortex.com/layout/v1" xmlns:custom="your.custom.namespace"> <map> <custom:news-feed slot="top-center"/> </map></layout>
{ "schemaVersion": "1.0", "items": []}
#
Define the Configurable PropertiesFirst, we need to create a NewsFeedModelProperties
interface which we use to inform the NewsFeedModel
about which properties it should populate from configuration.
import { ComponentModelBase, serializable, ComponentModelProperties, PropertyDefs,} from "@vertigis/web/models";import Collection from "esri/core/Collection";
interface NewsFeedModelProperties extends ComponentModelProperties { newsItems?: Collection<string>; hideOnStartup?: boolean;}
@serializableexport default class NewsFeedModel extends ComponentModelBase<NewsFeedModelProperties> { /** * Array of items to display in the news feed */ newsItems: Collection<string> = new Collection<string>();
/** * Whether or not the news ticker is initially hidden */ hideOnStartup: boolean;}
#
Participate in the ConfigurationNext, we have to inform VertiGIS Studio Web about how to serialize and deserialize these properties between the app config and the model, as well as provide default values. We do this by implementing the _getSerializableProperties
method.
import { ComponentModelBase, serializable, ComponentModelProperties, PropertyDefs,} from "@vertigis/web/models";import Collection from "esri/core/Collection";
interface NewsFeedModelProperties extends ComponentModelProperties { newsItems?: string[]; hideOnStartup?: boolean;}
@serializableexport default class NewsFeedModel extends ComponentModelBase<NewsFeedModelProperties> { /** * Array of items to display in the news feed */ newsItems: Collection<string> = new Collection<string>();
/** * Whether or not the news ticker is hidden */ hideOnStartup: boolean;
protected _getSerializableProperties(): PropertyDefs<NewsFeedModelProperties> { const props = super._getSerializableProperties(); return { ...props, newsItems: { serializeModes: ["initial"], default: ["No news."], serialize: () => this.newsItems.toArray(), deserialize: (newsItems) => { newsItems.forEach((newsItem) => this.newsItems.add(newsItem) ); }, }, hideOnStartup: { serializeModes: ["initial"], default: false, }, }; }}
#
Consume the Configuration in the ComponentFinally, we need to update the NewsFeed
component to treat the model as its single source of truth for data. First, we update the props passed into the component to include the relevant model.
export interface NewsFeedProps extends LayoutElementProperties<NewsFeedModel> {}
export default function NewsFeed(props: NewsFeedProps) { ...}
The model will initially populated with values from configuration or defaults. The component can use props.model
values to set the initial state, but we also want to update the model and re-render on model changes. Since the data state is contained within the model, we can't use the useState
React pattern.
To respond to model changes, we can do the following.
Upon user interaction that affects state,
- The component updates the model values.
- The component listens for changes on the model values and re-renders with the
useWatchAndRerender
function.
note
Learn more about the helper React Hook functions like useWatchAndRerender
.
export default function NewsFeed(props: NewsFeedProps) { const { model } = props;
useWatchAndRerender(model, "newsItems"); ...}
#
Complete ExampleFollowing is a complete example where news items are configured in the app.json
, populated into the NewsFeedModel
and finally consumed and presented by the NewsFeed
component.
- Component
- Model
- Css
- Layout
- App Config
- Component Export
- Registration
- UI
import React, { useState } from "react";import { LayoutElement, LayoutElementProperties,} from "@vertigis/web/components";import { useWatchAndRerender } from "@vertigis/web/ui";import List from "@vertigis/web/ui/List";import ListItem from "@vertigis/web/ui/ListItem";import TitleBar from "@vertigis/web/ui/TitleBar";import Button from "@vertigis/web/ui/Button";import DialogActions from "@vertigis/web/ui/DialogActions";import "./NewsFeed.css";import { NewsFeedModel } from ".";
export interface NewsFeedProps extends LayoutElementProperties<NewsFeedModel> {}
export default function NewsFeed(props: NewsFeedProps) { const { model } = props; const { hidden, setHidden } = useState(model.hideOnStartup);
/** * The use watch function handles observing a property on the model, * re-rendering on change, and cleaning up the subscription handle on unmount. * This helper function allows you to use the model as your component state. */ useWatchAndRerender(model, "newsItems");
return ( <LayoutElement {...props}> <List className="news-item-list"> <DialogActions> <TitleBar text="Recent News"></TitleBar> {model.hidden && ( <Button onClick={() => (model.hidden = false)} > Show News </Button> )} {!model.hidden && ( <Button onClick={() => (model.hidden = true)}> Hide News </Button> )} </DialogActions> {!model.hidden && model.newsItems .map((news, idx) => ( <ListItem key={idx}>{news}</ListItem> )) .toArray()} </List> </LayoutElement> );}
import { ComponentModelBase, serializable, ComponentModelProperties, PropertyDefs,} from "@vertigis/web/models";import Collection from "esri/core/Collection";
interface NewsFeedModelProperties extends ComponentModelProperties { newsItems?: string[]; hideOnStartup?: boolean;}
@serializableexport default class NewsFeedModel extends ComponentModelBase<NewsFeedModelProperties> { /** * Array of items to display in the news feed */ newsItems: Collection<string> = new Collection<string>();
/** * Whether or not the news ticker is hidden */ hideOnStartup: boolean;
protected _getSerializableProperties(): PropertyDefs<NewsFeedModelProperties> { const props = super._getSerializableProperties(); return { ...props, newsItems: { serializeModes: ["initial"], default: ["No news."], serialize: () => this.newsItems.toArray(), deserialize: (newsItems) => { newsItems.forEach((newsItem) => this.newsItems.add(newsItem) ); }, }, hideOnStartup: { serializeModes: ["initial"], default: false, }, }; }}
.news-item-list { max-height: 200px; overflow: scroll;}
<?xml version="1.0" encoding="UTF-8"?><layout xmlns="https://geocortex.com/layout/v1" xmlns:custom="your.custom.namespace"> <map> <custom:news-feed slot="top-center" config="default"/> </map></layout>
{ "schemaVersion": "1.0", "items": [ { "$type": "news-feed-model", "id": "default", "newsItems": [ "New fire hydrant installed at Main and 5th.", "Pipe burst at 4th and Broadview", "Fire hydrant reported as needs maintenance by citizen." ], "hideOnStartup": true } ]}
export { default } from "./NewsFeed";export { default as NewsFeedModel } from "./NewsFeedModel";
import Test, { TestModel } from "./components/Test";import NewsFeed, { NewsFeedModel } from "./components/NewsFeed";import { LibraryRegistry } from "@vertigis/web/config";
const LAYOUT_NAMESPACE = "custom.foo";
export default function (registry: LibraryRegistry) { registry.registerComponent({ name: "news-feed", namespace: "your.custom.namespace", getComponentType: () => NewsFeed, itemType: "news-feed-model", title: "News Feed", }); registry.registerModel({ getModel: (config) => new NewsFeedModel(config), itemType: "news-feed-model", });}

#
Next StepsCheck out the Component Reference
Take a deep dive into components in the VertiGIS Studio Web SDK