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 Point
We 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
.
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";
@serializable
class 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 Properties
First, 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;
}
@serializable
export 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 Configuration
Next, 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;
}
@serializable
export 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 Component
Finally, 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.
Learn more about the helper React Hook functions like useWatchAndRerender
.
export default function NewsFeed(props: NewsFeedProps) {
const { model } = props;
useWatchAndRerender(model, "newsItems");
...
}
Complete Example
Following 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;
}
@serializable
export 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 Steps
Check out the Component Reference
Take a deep dive into components in the VertiGIS Studio Web SDK