Skip to content

Latest commit

 

History

History
448 lines (353 loc) · 14.1 KB

README.asciidoc

File metadata and controls

448 lines (353 loc) · 14.1 KB

UI (User Interface)

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.

Depends on

Addon Exported Optional

org.jboss.forge.furnace.container:cdi

no

no

convert

yes

no

facets

yes

no

ui-spi

yes

no

Setup

This addon requires the following installation steps.

Add configuration to pom.xml

To use this addon, you must add it as a dependency in the pom.xml of your forge-addon classified artifact:

<dependency>
   <groupId>org.jboss.forge.addon</groupId>
   <artifactId>ui</artifactId>
   <classifier>forge-addon</classifier>
   <version>${version}</version>
</dependency>

Features

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.

UI Components

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);
Tip

If your addon uses a container that does not support "@Inject" annotations, services such as the InputComponentFactory may also be accessed via the AddonRegistry:

AddonRegistry registry = ...
Imported<InputComponentFactory> imported = registry.getServices(InputComponentFactory.class);
InputComponentFactory factory = imported.get();

Create a UICommand

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.

  1. 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());
       }
    
    }
  2. Add inputs to your command.

    public class ExampleCommand extends AbstractUICommand implements UICommand {
       @Inject
       private UIInput<String> name;
    }
  3. Ensure that you have initialized the UIBuilder with all required UIInput instances, and performed any UIInput configuration (if necessary).

    @Override
    public void initializeUI(UIBuilder builder) throws Exception {
       // Configure inputs here
       builder.add(name);
    }
  4. Implement functionality to be executed.

    @Override
    public Result execute(UIExecutionContext context) throws Exception {
       // Do the work here
       return Results.success("Hello,"+ name.getValue());
    }

Implement a multi-step wizard

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.

  1. Follow the same basic steps as if you were implementing a simple UICommand; however, in this case we must also implement the UIWizard interface.

    public class MyInitialPage extends AbstractUICommand implements UIWizard {
    }
  2. Notice that the next(UINavigationContext context) method must be implemented in addition to the standard UICommand 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 a NavigationResult, 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();
       }
    }
  3. Create a UIWizardStep implementation, similar to UIWizard. 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 implement UIWizard.

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

UICommand execution lifecycle

  1. Retrieve instance of selected UICommand

  2. Call .initializeUI(UIBuilder builder)

  3. UI provider gathers input values from user.

  4. UI provider calls .validate(UIValidationContext context)

    • if inputs are valid, proceed, if not, return to step #3

  5. UI provider converts user supplied values (if necessary) and populates input components.

  6. UI provider calls .execute(UIContext context)

UIWizard execution lifecycle

  1. Retrieve instance of selected UIWizard

  2. Call initializeUI(UIBuilder builder)

  3. UI provider gathers input values from user.

  4. UI provider calls .validate(UIValidationContext context)

    • if inputs are valid, proceed, if not, return to step #3

  5. UI provider converts user supplied values (if necessary) and populates input components.

  6. UI provider calls .next(UIContext context)

    • if NavigationResult is contains a UIWizard or UIWizardStep 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.

Use CommandScoped objects to store model state

There are two ways to pass state between wizard steps:

  1. Add state in UIContext using UIContext.getAttributeMap().put

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

Use UIOutput to display messages to the user

The underlying UIProvider provides a method getOutput() returning a UIOutput. This object provides two methods:

  1. PrintStream out();

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

UICommandTransformer feature

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
   }
}

Use PrerequisiteCommandsProvider when you need some commands to be executed prior to your comand

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.

  1. Implement PrerequisiteCommandsProvider in your command

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

@Inject UIContextProvider for classes that need UIContext

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 UIContextProvider may also be accessed via the AddonRegistry:

AddonRegistry registry = ...
Imported<UIContextProvider> imported = registry.getServices(UIContextProvider.class);
UIContextProvider factory = imported.get();

Specializing inputs

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

NavigationResultTransformer feature

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