Commands and Operations

When writing custom components you may want to take advantage of Geocortex 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.

tip

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.

note

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.

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}'`
);
}
}

@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.

note

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
});
tip

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",
});
// ...
}
// ...
}