Freitag, 7. Februar 2014

FXController with CDI managed DynamicObjectAdapter - Part 2

Based on my blog article part 1 we want to integrate the DynamicObjectAdapter into an JavaFX application. To remember: We have an interface called DemoLogic with two methods. add(..) and sub(..) The implementation is really simple, but at runtime we could switch the implementation partially. For this we needed an adapter.
The biggest tdifference compared to the default CDI decorator was, that we need no definition inside the beans.xml. The implementation is smaller and no need for Qualifiers and other biolder-plate code.
This DemoLogic we want to use now inside an JavaFX application. For this we ned first an fxml.file called DemoPane.fxml. Here we will define a few elements.
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.CheckBox?>
<fx:root type="javafx.scene.layout.AnchorPane"
            xmlns:fx="http://javafx.com/fxml">
    <children>
        <VBox>
            <children>
                <Button fx:id="button" text="Hello World" />
                <CheckBox fx:id="checkbox" text="switch context"/>
                <TextField fx:id="textFieldA"/>
                <TextField fx:id="textFieldB"/>
                <Label fx:id="label"/>
            </children>
        </VBox>

    </children>

</fx:root>
With the button we will activate the calculation, with the checkbox we will switch the context. To have a small as possible implementation, there is no error checking.
The next will be the controller called DemoController. This is a normal JavaFX Controller, but CDI managed. How to get this? Well there is a good blog article here ;-) CDI JavaFX bootstrapping.
Inside the DemoController we will get an Instance of the DemoLogic and the Context.
@Inject
    @DynamicDecoratorTest
    Instance<DemoLogic> demoLogicInstance;

    @Inject
    Context context;
The full implementation will be like the following.
@DynamicDecoratorTest
public class DemoController implements Initializable{

    @FXML public TextField textFieldA;
    @FXML public TextField textFieldB;
    @FXML public Button button;
    @FXML public Label label;
    @FXML public CheckBox checkbox;

    @Inject
    @DynamicDecoratorTest
    Instance<DemoLogic> demoLogicInstance;

    @Inject
    Context context;

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        button.setText("klick me");
        button.setOnAction(actionEvent -> {

            final DemoLogic demoLogic = demoLogicInstance.get();

            final String textFieldAText = textFieldA.getText();
            final Integer a = Integer.valueOf(textFieldAText);

            final String textFieldBText = textFieldB.getText();
            final Integer b = Integer.valueOf(textFieldBText);

            final int result = demoLogic.add(a, b);
            label.setText(result+"");

        });

        checkbox.setOnAction(actionEvent -> {
            context.original = checkbox.isSelected();
        });
    }
}
Inside the method initialize you will find the connection between the GUI logic and the business logic. This is now clean devided.
To test this I was using Arquillian. LAter we will see how we could do this with TestFX. We are working on the CDI Support.
@RunWith(Arquillian.class)
public class DemoLogicTest {
    @Deployment
    public static JavaArchive createDeployment() {
        return ShrinkWrap.create(JavaArchive.class)
                .addPackages(true, "org.rapidpm.demo")
                .addPackages(true, "junit.org.rapidpm.demo")
                .addPackages(true, "demo")
                .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
    }

    @Inject @DynamicDecoratorTest
    Instance<DemoController> demoControllerInstance;
    @Inject
    Context context;

    static final FXMLLoader loader = new FXMLLoader();

    @Test
    public void testDemoLogicJavaFXTest() throws Exception {
        loader.setControllerFactory(param -> demoControllerInstance.get());
        Application.launch(DemoApp.class);
    }


    public static class DemoApp extends Application {
        @Override
        public void start(Stage stage) throws Exception {

            final URL resource = getClass()
                    .getClassLoader()
                    .getResource("DemoPane.fxml");
            loader.setLocation(resource);
            final DemoController controller = (DemoController) loader
                    .getControllerFactory()
                    .call(DemoController.class);
            try {

                loader.setController(controller);
                loader.setRoot(new AnchorPane());
                final Parent root = (Parent) loader.load();

                stage.setScene(new Scene(root));
                stage.setTitle("Custom Control");
                stage.setWidth(300);
                stage.setHeight(200);
                stage.show();
            } catch (IOException exception) {
                throw new RuntimeException(exception);
            }

        }

        public static void main(String[] args) {
            launch(args);
        }
    }
}

You never need the decorator from CDI anymore. ;-)