Component Reference
The following is an example of a custom component that has been created using the VertiGIS Studio Web SDK. In general, VertiGIS Studio Web components are functional React components extended with a few new behaviors and patterns.
import React, { useState } from "react";
import {
LayoutElement,
LayoutElementProperties,
} 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 { useWatchAndRerender } from "@vertigis/web/ui";
import DialogActions from "@vertigis/web/ui/DialogActions";
import ExampleComponentModel from "./ExampleComponentModel";
export default function ExampleComponent(
props: LayoutElementProperties<ExampleComponentModel>
) {
const { model } = props;
const [hidden, setHidden] = useState(false);
useWatchAndRerender(model, "items");
return (
<LayoutElement {...props}>
<List>
<DialogActions key="dialog-actions">
<TitleBar text="Some Title"></TitleBar>
{hidden && (
<Button onClick={() => setHidden(false)}>
Show Component
</Button>
)}
{!hidden && (
<Button onClick={() => setHidden(true)}>
Hide Component
</Button>
)}
</DialogActions>
{!hidden &&
model.items
.map((item, idx) => (
<ListItem key={idx}>{item}</ListItem>
))
.toArray()}
</List>
</LayoutElement>
);
}
Component Registration
All components need to be registered with the VertiGIS Studio Web API in order to be used in a layout.
export default function (registry: LibraryRegistry) {
// ... other item registrations
registry.registerComponent({
// The name used to identify the component in the layout
name: "example",
// The namespace used to identify the component in the layout.
namespace: "custom.abc123",
// The class corresponding to the React Functional Component
getComponentType: () => ExampleComponent,
// The model type this component is bound to.
itemType: "example-model",
title: "Example Component",
});
Components and Layout
Once a component has been registered, it can be used in a layout by referring it by name and namespace.
<?xml version="1.0" encoding="UTF-8"?>
<layout xmlns="https://geocortex.com/layout/v1" xmlns:custom="custom.abc123">
<custom:example/>
</layout>
Component Anatomy
At its core, each custom component is a React component that takes in props and renders a DOM element. However, custom components can implement a couple of patterns which are specific to VertiGIS Studio Web.
Rendering
VertiGIS Studio Web custom components return a DOM element, like any other React component, but with one restriction. The root DOM element must be a <LayoutElement/>
node with all the layout attributes passed as props.
export default function ExampleComponent(
props: LayoutElementProperties<ExampleComponentModel>
) {
return (
<LayoutElement {...props}>
<p>Some Content</p>
</LayoutElement>
);
}
Props
Custom components take in a special type of props, LayoutElementProperties
. These props include the React props but also include props specific to the SDK, such as;
- The model associated with the component,
- The ID of the component in the layout
- The width and height, along with other presentation attributes in the layout
The LayoutElementProperties
interface takes in ComponentModelBase
type class corresponding to the model associated with the component. This will give the props.model
property the correct type.
export default function ExampleComponent(
props: LayoutElementProperties<ExampleComponentModel>
) {
const { model, id, width, height, slot, stretch } = props;
...
}
State
VertiGIS Studio Web uses two patterns to manage state in a component. First, any purely local UI or presentation logic state should be captured using the React state hook pattern. For data that comes from configuration or other sources, like a service, the components model should be treated as the state.
export default function ExampleComponent(
props: LayoutElementProperties<ExampleComponentModel>
) {
// Presentation logic state
const [hidden, setHidden] = useState(false);
// Business Data / Objects
useWatchAndRerender(props.model, "modelProperty")
...
}
Models
Every component is bound to specific item type, and each item type is bound to a specific model.
In this snippet, the ExampleComponent
is bound to the example-model
item type. The ExampleComponentModel
is registered as the example-model
item type, meaning that an ExampleComponentModel
will be injected into the props of each ExampleComponent
.
export default function (registry: LibraryRegistry) {
// ... other item registrations
registry.registerComponent({
name: "example",
namespace: LAYOUT_NAMESPACE,
getComponentType: () => ExampleComponent,
itemType: "example-model",
title: "Example Component",
});
registry.registerModel({
getModel: (config) => new ExampleComponentModel(config),
itemType: "example-model",
});
}
It's best practice to use the model to define configurable or persistent state, and use the component hooks to interact with the model. For UI specific transient state, like an active selection, it is recommended you use the React state hooks.
For example, the following component delegates the management of the list item content to its model. The component does not have to concern itself with whether those list items come from config, dynamic application events, or other sources.
This example uses the useWatchAndRerender
component hook in order to dynamically update the component when the model changes.
- Component
- Model
export default function ExampleComponent(
props: LayoutElementProperties<ExampleComponentModel>
) {
const { model } = props;
useWatchAndRerender(model, "items");
return (
<LayoutElement {...props}>
<List>
{model.items
.map((item, idx) => (
<ListItem key={idx}>{item}</ListItem>
))
.toArray()}
</List>
</LayoutElement>
);
}
export default class ExampleComponentModel extends ComponentModelBase {
items: Collection<string> = new Collection<string>();
// ... populate the items list somehow
}
Configuration
VertiGIS Studio Web provides a suite of flexible, highly configurable components, and this flexibility is powered in a large part by the relationship between app config, models, and components. Each model has the potential to consume static app config, which it can deserialize and transform before providing the data to its associated component for rendering.
The following model defines a simple configurable property items
.
- The shape of this property in the app config JSON file is defined by the
items
property in theExampleComponentModelProperties
interface. - The shape of this property in the model is defined by the
items
property in theExampleComponentModel
class.
The ExampleComponentModel
class consumes the configuration by extending the ComponentModelBase<ExampleComponentProperties>
interface, using the @serializable
class decorator, and then defining serialization logic in an override of the _getSerializableProperties()
method. You can learn more about app config serialization in this article.
- Model
- App Config
interface ExampleComponentModelProperties
extends ComponentModelProperties {
items?: string[];
}
@serializable
export default class ExampleComponentModel extends ComponentModelBase<ExampleComponentModelProperties> {
items: Collection<string> = new Collection<string>();
protected _getSerializableProperties(): PropertyDefs<ExampleComponentModelProperties> {
const props = super._getSerializableProperties();
return {
...props,
items: {
serializeModes: ["initial"],
default: ["Default Value"],
serialize: () => this.items.toArray(),
deserialize: (items) => {
this.items.removeAll();
this.items.addMany(items);
},
},
};
}
}
{
"schemaVersion": "1.0",
"items": [
...
{
"$type": "example-component",
"id" : "example-component",
"items" : [
"Item 1",
"Item 2",
"Item 3"
]
},
...
]
}
Component Defaults
Most components support the config
attribute in the layout, which links a component model to configuration in the app config JSON. However, many component models have default values they can supply for initialization instead of relying on configuration. This means that if you omit the config
property for certain components, the component model will attempt to create itself with its default values. An example of defining these default values can be seen in this example. It's also the mechanism that powers the default map displayed by this layout.
<?xml version="1.0" encoding="utf-8" ?>
<layout xmlns="https://geocortex.com/layout/v1">
<map/> <!-- The map component is populated with values from the MapModel's `_getSerializableProperties` function. -->
</layout>
Initialization and Teardown
Component models have an initialization method, which can be used to perform asynchronous startup logic, and a teardown method, which can be used to free resources when a component is removed from the layout.
Always call super._onInitialize()
when overriding the _onInitialize
method, and super._onDestroy()
when overriding the _onDestroy
method.
export default class ExampleComponentModel extends ComponentModelBase {
items: Collection<string> = new Collection<string>();
protected async _onInitialize(): Promise<void> {
super._onInitialize();
this.items.add("1");
this.items.add("2");
this.items.add("3");
this.items.add("4");
// ...other initialization logic
}
protected async _onDestroy(): Promise<void> {
await super._onDestroy();
this.items.destroy();
// ...free up resources
}
}
Component Lifecycle
When a VertiGIS Studio Web Application boots up, the set of components which are initially active in the layout are created and initialized. Components like the Panel
will only activate their first child. All initially inactive components will only be created when they are activated in the layout.
Activation and Deactivation
Custom code can listen and react to a components activation or deactivation by subscribing to the ui.activated
or ui.deactivated
event. The ui.*
events contain various events relating to the component lifecycle.
The UIService
contains implements the commands ui.activate
and ui.deactivate
and exposes methods like getActiveChildComponentIds()
which can be used to examine and manipulate the activation state of components.
@serializable
export default class ExampleComponentModel extends ComponentModelBase<ExampleComponentModelProperties> {
private readonly _handles: IHandle[] = [];
protected async _onInitialize(): Promise<void> {
super._onInitialize();
this._handles.push(
this.messages.events.ui.activated.subscribe(() => {
// ...on activated logic here
})
);
this._handles.push(
this.messages.events.ui.deactivated.subscribe(() => {
// ...on deactivated logic here
})
);
}
protected async _onDestroy(): Promise<void> {
await super._onDestroy();
// Make sure to clean up subscriptions when the model is destroyed.
this._handles.forEach((h) => h.remove());
}
}
Next Steps
Learn how to use Commands and Operations with Components
Learn how to run and implement commands and operations with custom components
Learn about Component Interactions
Learn about how components can interact with each other through their models
Create a Component with a Complex UI
Follow along with a more in depth component example
Create a Component that Consumes App Config
Learn more about writing components that consume configuration values.