This addon exports services for use in other addons. The 'ui' addon enables the creation of host-agnostic user interface commands and wizards that run in any API compliant environment.
In practice, that means that your command and wizard code will run in any UI provider without requiring functional changes.
The UI addon is directly used by UI providers (Eclipse, IDEA, Netbeans, Shell) to render Wizards and single dialog boxes. The Eclipse UI provider follows the Eclipse User Interface Guidelines
Note
|
Implementations of UICommand are displayed in the Forge Quick Assist menu in Eclipse (When Ctrl+5 is pressed). Additionally, the Forge shell automatically makes UICommand instances available as functions; tab completion proposals are supported for command names, option names, and option arguments. |
This addon requires the following installation steps.
- Consistent programming experience
-
Because the UI API provides an abstract model for creating commands and wizards, it is the standard approach for creating user interfaces in all addons.
There are four input types which can be used to gather input from a user:
-
UIInput : Prompts for a single value where the set of valid values has not been pre-determined..
-
UIInputMany : Prompts for multiple values where the set of valid values has not been pre-determined.
-
UISelectOne : Prompts for selection of a single value from a collection of pre-determined valid values.
-
UISelectMany : Prompts for selection of multiple values from a collection of pre-determined valid values.
Input components may accept any Java object type. Note that for simple types such as String
, Integer
, and other
built in language types, simple conversion will occur automatically; however, for complex or custom data types, a
Converter
from the convert addon will be required.
Components may be created either via dependency injection, or via programmatic instantiation through the InputComponentFactory
.
- Create components via dependency injection
-
@Inject private UIInput<String> name; @Inject private UIInputMany<File> name; @Inject private UISelectOne<Integer> name; @Inject private UISelectMany<CustomType> name;
- Create components programmatically
-
If the number of inputs are unknown at compile time, it is possible to create inputs using InputComponentFactory:
@Inject private InputComponentFactory factory; ... UIInput<String> firstName = factory.createUIInput("firstName", String.class); UIInput<String> lastName = factory.createUIInput("lastName", String.class);
TipIf your addon uses a container that does not support "@Inject" annotations, services such as the
InputComponentFactory
may also be accessed via theAddonRegistry
:AddonRegistry registry = ... Imported<InputComponentFactory> imported = registry.getServices(InputComponentFactory.class); InputComponentFactory factory = imported.get();
- Implement a simple dialog box
-
When interaction does not present a complex work-flow, a single command is typically enough to perform trivial or independent tasks.
-
Create a class that implements
UICommand
and implement the required methods. You may also extend from org.jboss.forge.addon.ui.AbstractUICommand to eliminate some boilerplate configuration.public class ExampleCommand extends AbstractUICommand implements UICommand { @Inject private UIInput<String> name; @Override public void initializeUI(UIBuilder builder) throws Exception { builder.add(name); } @Override public Result execute(UIExecutionContext context) throws Exception { return Results.success("Hello,"+ name.getValue()); } }
-
Add inputs to your command.
public class ExampleCommand extends AbstractUICommand implements UICommand { @Inject private UIInput<String> name; }
-
Ensure that you have initialized the
UIBuilder
with all requiredUIInput
instances, and performed anyUIInput
configuration (if necessary).@Override public void initializeUI(UIBuilder builder) throws Exception { // Configure inputs here builder.add(name); }
-
Implement functionality to be executed.
@Override public Result execute(UIExecutionContext context) throws Exception { // Do the work here return Results.success("Hello,"+ name.getValue()); }
-
When interaction is complex and presents a considerable number of arguments, you may find it necessary to gather input via a wizard flow, rather than a single command implementation. Wizards allow for multi-page, multi-path commands to be created, where the path through a flow may differ based on user input provided in each step.
-
Follow the same basic steps as if you were implementing a simple
UICommand
; however, in this case we must also implement theUIWizard
interface.public class MyInitialPage extends AbstractUICommand implements UIWizard { }
-
Notice that the
next(UINavigationContext context)
method must be implemented in addition to the standardUICommand
methods. Be sure to store relevant values as context attributes so that they may be accessed via subsequent wizard steps.The
next
method also returns aNavigationResult
, which is where you will specify the next wizard step (if any) to execute.public class MyInitialPage extends AbstractUICommand implements UIWizard { @Inject private UIInput<String> firstName; @Override public void initializeUI(UIBuilder builder) throws Exception { builder.add(firstName); } @Override public NavigationResult next(UINavigationContext context) throws Exception { context.getUIContext().getAttributeMap().put("firstName", firstName.getValue()); return Results.navigateTo(MyNextStep.class); } @Override public Result execute(UIExecutionContext context) throws Exception { return Results.success(); } }
-
Create a
UIWizardStep
implementation, similar toUIWizard
.UIWizardStep
implementations cannot be used as standalone commands, or as entry points to a wizard flow. If your wizard step would function independently of prior wizard steps, then it may simply implementUIWizard
.public class MyNextStep extends AbstractUICommand implements UIWizardStep { @Inject @WithAttributes(label="Last Name", required=true) private UIInput<String> lastName; @Override public void initializeUI(UIBuilder builder) throws Exception { builder.add(lastName); } @Override public NavigationResult next(UINavigationContext context) throws Exception { // End of interaction, return null return null; } @Override public Result execute(UIExecutionContext executionContext) throws Exception { String firstName = (String) executionContext.getUIContext().getAttributeMap().get("firstName"); String fullName = firstName + " " + lastName.getValue(); return Results.success("Hello,"+ fullName); } }
-
Retrieve instance of selected
UICommand
-
Call
.initializeUI(UIBuilder builder)
-
UI provider gathers input values from user.
-
UI provider calls
.validate(UIValidationContext context)
-
if inputs are valid, proceed, if not, return to step #3
-
-
UI provider converts user supplied values (if necessary) and populates input components.
-
UI provider calls
.execute(UIContext context)
-
Retrieve instance of selected
UIWizard
-
Call
initializeUI(UIBuilder builder)
-
UI provider gathers input values from user.
-
UI provider calls
.validate(UIValidationContext context)
-
if inputs are valid, proceed, if not, return to step #3
-
-
UI provider converts user supplied values (if necessary) and populates input components.
-
UI provider calls
.next(UIContext context)
-
if
NavigationResult
is contains aUIWizard
orUIWizardStep
type instance, repeat from step #1 for the next result type. -
if
NavigationResult
is null, UI provider calls.execute(UIContext context) for each visited step, in the order in which they were visited.
-
There are two ways to pass state between wizard steps:
-
Add state in UIContext using UIContext.getAttributeMap().put
-
Create a class with @CommandScoped and add the necessary attributes on it
CommandScoped classes are available during user interaction. The scope is destroyed when a command (or a wizard including its steps) is run or cancelled
The underlying UIProvider provides a method getOutput() returning a UIOutput. This object provides two methods:
-
PrintStream out();
-
PrintStream err();
Any information printed to these PrintStream objects will be displayed in the configured UI out/err streams. How it is displayed is up to the UI Provider implementation.
When a command needs to be transformed before execution (returning a different object than what was requested), you should implement a UICommandTransformer.
import org.jboss.forge.addon.ui.command.UICommandTransformer;
public class MyTransformer implements UICommandTransformer {
public UICommand transform(UIContext context, UICommand original) {
// original is the command invoked. It should be returned if no changes are needed
}
}
In some scenarios, it is important that other commands/wizards be executed before the invoked command. This is true in cases where you have the setup command that adds library dependencies to the current project and the current command/wizard creates classes that use classes from these libraries.
-
Implement PrerequisiteCommandsProvider in your command
-
Return a NavigationResult with the list of the prerequisite commands to be executed based on the current UIContext in the getPrerequisiteCommands method.
Example:
public class MyCommand extends AbstractProjectCommand implements PrerequisiteCommandsProvider
{
@Override
public NavigationResult getPrerequisiteCommands(UIContext context)
{
NavigationResultBuilder builder = NavigationResultBuilder.create();
Project project = getSelectedProject(context);
if (project != null)
{
if (!project.hasFacet(CDIFacet.class))
{
builder.add(CDISetupCommand.class);
}
}
return builder.build();
}
...
}
Note
|
The PrerequisiteCommandsProvider feature is implemented using a UICommandTransformer |
We highly recommend that operations involving UIContext
start from UICommands.
However, some services might require the current UIContext
and changing the API may not be possible.
@Inject
private UIContextProvider contextProvider;
...
// This may be null if no UI interaction started
UIContext context = contextProvider.getUIContext();
Tip
|
If your addon uses a container that does not support "@Inject" annotations, services such as the AddonRegistry registry = ... Imported<UIContextProvider> imported = registry.getServices(UIContextProvider.class); UIContextProvider factory = imported.get(); |
The UI addon provides an easy way to reuse the settings for the existing inputs by providing decorators for each InputComponent
type:
public interface TargetPackage extends UIInput<String>
{
}
Implement this interface by doing:
public class TargetPackageImpl extends AbstractUIInputDecorator<String> implements TargetPackage
{
@Inject
@WithAttributes(label = "Target Package", type = InputType.JAVA_PACKAGE_PICKER)
private UIInput<String> targetPackage;
@Override
protected UIInput<String> createDelegate()
{
return targetPackage;
}
}
And in the command class:
@Inject
private TargetPackage targetPackage;
...
public void initializeUI(UIBuilder builder)
{
builder.add(targetPackage);
}
If you need to add a new step to an existing command, you should implement a NavigationResultTransformer
. Forge will automatically pick any published NavigationResultTransformer
service.
import org.jboss.forge.addon.javaee.jpa.ui.setup.JPASetupWizard;
import org.jboss.forge.addon.ui.context.UINavigationContext;
import org.jboss.forge.addon.ui.result.NavigationResult;
import org.jboss.forge.addon.ui.result.navigation.NavigationResultBuilder;
import org.jboss.forge.addon.ui.result.navigation.NavigationResultTransformer;
public class JPANavigationResultTransformer implements NavigationResultTransformer {
@Override
public boolean handles(UINavigationContext context) {
// Is this command the 'JPA: Setup' wizard?
return context.getCurrentCommand() instanceof JPASetupWizard;
}
@Override
public NavigationResult transform(UINavigationContext context, NavigationResult currentFlow) {
// Add my new wizard step to the end of the flow
return NavigationResultBuilder.create(currentFlow).add(MyCustomWizardStep.class).build();
}
@Override
public int priority() {
return 100;
}
}