Commands and Operations
When writing custom components you may want to take advantage of VertiGIS Studio Web's large built-in suite of command and operations or your own custom commands and operations.
Running Commands and Operations
Commands and operations can be run in any custom service or component. Components can run commands and operations in two ways; through the component model, or through the React component.
It is best practice to run commands and operations through the components model, and express the result through a change in the models data rendered by the component.
Command and Operations in a Model
Commands and operations can be run through the messages
property of the model.
@serializable
export default class ExampleComponentModel extends ComponentModelBase {
async displayConfirmDialog(){
const didConfirm = await this.messages.operations.ui.confirm.execute({
title: "Confirm me"
})
...
}
...
}
Command and Operations in a Component
Commands and operations can be run through the UIContext
of a component.
export default function CustomComponent(props) {
const { operations, commands } = useContext(UIContext);
const displayConfirmDialog = async () => {
const result = await operations.ui.confirm.execute({
title: "Confirm Me",
});
...
};
...
}
Commands and Operations in a Service
Services can run commands and operations through the messages
property.
export default class ExampleService extends ServiceBase {
protected async someMethod(): Promise<void> {
const { commands, command, operations, operation } =
this.messages;
// Execute a named, built-in, command.
commands.ui.setTheme.execute("dark");
// Execute a custom command by its string ID. If you are executing
// a custom command with arguments, you must provide the correct generic typings.
command<string>(
"my-custom.command-that-takes-a-string"
).execute("some arg");
// Execute a named, built-in, operation.
const opResult = await operations.ui.confirm.execute({
message: "hey",
});
// Execute a custom operation by its string ID.
// You must provide the correct generic typings <input, output>.
const customOpResult = await operation<string, boolean>(
"my-custom.operation-that-returns-a-bool"
).execute("some arg");
}
}
Implementing Commands and Operations
Components and services can also implement commands and operations. Command and operation implementations are defined in a component's model or in a service. By decorating a method with @command
or @operation
, components and services can define an implementation for a command or operation. The command or operation must also be registered through the registerOperation
or registerCommand
methods in the library registry with the same item type of the model or the ID of the service.
The following example demonstrates a component model defines an implementation for a custom command that takes a custom argument type, and a service that implements a custom operation that returns a number.
It's best practice to expose the arguments and return values for commands and operations as public interfaces, as this allows consumers of the command or operation to have the appropriate typing.
- Component Model
- Service
- Registration
export interface CustomCommandArg {
value: string;
}
@serializable
export default class CustomModel extends ComponentModelBase {
@command("custom.my-command")
protected _myCommand(arg: CustomCommandArg): void {
console.log(
`Executing 'custom.my-command' with argument '${arg.value}'`
);
}
}
export default class CustomService extends ServiceBase {
@operation("custom.my-operation")
protected _myOperation(): number {
return 42;
}
}
export default function (registry: LibraryRegistry) {
...
registry.registerCommand({
name: "custom.my-command",
itemType: "custom-model"
});
registry.registerModel({
getModel: (config) => new CustomModel(config),
itemType: "custom-model",
});
registry.registerOperation({
name: "custom.my-operation",
serviceId: "custom-service"
});
registry.registerService({
getService: () => new CustomService(),
id: "custom-service",
});
}
@command
and @operation
Decorator Options
Both the @command
and @operation
decorator functions can take an optional second argument specifying parameters that affect the behavior of the command or operation.
The targetInactive
option applies to commands and operations implemented through a component model. The default value is false. If targetInactive
is set to true, the command or operation will be executed even if the associated component is not currently active in the layout
@command("custom.my-command", { targetInactive: true })
protected _myCommand() {
console.log(
`Executing 'custom.my-command even if the associated
component is not activated in the layout.`
);
}
The parallel
option applies to commands only. The default value is false. By default, if a command has multiple implementations, each implementation is executed sequentially in the order it was registered in the library, and multiple component model implementations are executed in layout order. Setting parallel
to true causes the command implementation to be run in parallel with other implementations marked as parallel
.
If there is a mix of parallel and sequential command implementations, then the parallel implementations will run concurrently after all sequential implementations have finished executing.
@command("custom.my-command", { parallel: true })
protected _myCommand() {
console.log(
`This implementation will be executed in parallel
with other command implementations marked as parallel.`
);
}
canExecute
Hook
Commands and services can optionally implement a hook function which indicates whether it is possible to execute the command. This is accomplished by implementing a "canExecute" method in the same class as the command implementation. This method is decorated with @canExecute
and returns a boolean indicating whether the command can or cannot run based on the given argument.
@command("custom.divide-ten-by-x")
protected _divideTenByX(x: number): void {
this.messages.commands.ui.alert.execute({
message: `10 / ${x} is ${10 / x}`,
});
}
@canExecute("custom.divide-ten-by-x")
protected _canExecuteDivideTenByX(x: number): boolean {
return x !== 0;
}
If the canExecute
hook for the command implementation returns true, the execute
hook will be called. If the function returns false, the execute
hook will not be called. Some components, like the I Want To Menu, use the canExecute
hook to disable buttons when no implementations of a command can be executed. This is accomplished through the canExecuteChanged
event. This event can be published or subscribed to in order to react to changing command execution conditions.
this.messages
.command("custom.command-with-can-execute")
.canExecuteChanged.publish();
this.messages
.command("custom.command-with-can-execute")
.canExecuteChanged.subscribe(() => {
// .. react to can execute status changing
});
For a full example of a command with a dynamic execution status, check out the canExecute
tutorial, or this live SDK sample.
Running Custom Commands
You can run the custom commands or operations you define from another component, model, or service by locating them by ID and providing the appropriate type arguments.
@serializable
export default class ExampleComponentModel extends ComponentModelBase {
async runCustomCommandsAndOperations() {
await this.messages
.command<CustomCommandArg>("custom.my-command")
.execute({
someProp: "some arg",
});
const result = await this.messages
.operation<InputArgType, OutputArgType>(
"custom.my-operation"
)
.execute({
some: "arg",
});
// ...
}
// ...
}