How to allow users to customize the UI
Idea
Take advantage of the declarative design pattern of JavafX/FXML and allow users to customize a certain view without any coding just by opening it with e.g. SceneBuilder to re-arrange the layout or add new controls or even change the style according to the users needs.
The FXML file + CSS can be basically be placed whereever they are reachable via a URL.
The user must only know the interface/methods of the assigned controller class inside the FXML.
RemoteController
Assuming this simple demo controller class provides methods to remotely control devices and to send MQTT messages, a user is able to customize his own remote control.
public class RemoteController{
@FXML
public void onTest(){
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setContentText("");
alert.setHeaderText("WORKS!");
alert.show();
}
public void onTest(String value){
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("WORKS!");
alert.setContentText(value);
alert.show();
}
public void onSwitch(String houseCode, int groudId, int deviceId, String command){
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Switch!");
alert.setContentText(String.format("Command: send %s %d %d %s", houseCode, groudId, deviceId, command));
alert.show();
}
}
remote.fxml and remote.css
Note the referenced de.jensd.shichimifx.demo.ext.RemoteController and remote.css.
So basically controller actions can be called via
onAction="#onTest".
Nice:
If you add
<?language javascript?>
to FXML, it’s also possible to pass parameters by a JavaScript call via the controller-instance.
onAction=controller.onTest('OFF')
onAction=controller.onSwitch('a',1,1,'ON')
Unfortunately I can’t find more documentation about this feature than -> this, but somehow it magically it works ;-). Its even possible to pass different types of parameters.
<?xml version="1.0" encoding="UTF-8"?>
<?language javascript?>
<?import javafx.geometry.*?>
<?import java.lang.*?>
<?import java.net.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox alignment="TOP_CENTER" prefHeight="400.0" prefWidth="600.0" spacing="20.0" styleClass="main-pane" stylesheets="@remote.css" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.jensd.shichimifx.demo.ext.RemoteController">
<children>
<Label styleClass="title-label" text="Universal Remote" />
<HBox alignment="CENTER_RIGHT" spacing="20.0">
<children>
<Label layoutX="228.0" layoutY="96.0" styleClass="sub-title-label" text="Light Frontdoor" />
<Button layoutX="43.0" layoutY="86.0" mnemonicParsing="false" onAction="#onTest" prefWidth="150.0" styleClass="button-on" text="ON" />
<Button layoutX="411.0" layoutY="86.0" mnemonicParsing="false" onAction="#onTest" prefWidth="150.0" styleClass="button-off" text="OFF" />
</children>
<padding>
<Insets left="10.0" right="10.0" />
</padding>
</HBox>
<HBox alignment="CENTER_RIGHT" spacing="20.0">
<children>
<Label layoutX="228.0" layoutY="96.0" styleClass="sub-title-label" text="Light Garden" />
<Button layoutX="43.0" layoutY="86.0" mnemonicParsing="false" onAction="controller.onTest('ON')" prefWidth="150.0" styleClass="button-on" text="ON" />
<Button layoutX="411.0" layoutY="86.0" mnemonicParsing="false" onAction="controller.onTest('OFF')" prefWidth="150.0" styleClass="button-off" text="OFF" />
</children>
<padding>
<Insets left="10.0" right="10.0" />
</padding>
</HBox>
<HBox alignment="CENTER_RIGHT" spacing="20.0">
<children>
<Label layoutX="228.0" layoutY="96.0" styleClass="sub-title-label" text="Light Garden" />
<Button layoutX="43.0" layoutY="86.0" mnemonicParsing="false" onAction="controller.onSwitch('a', 1,1,'ON')" prefWidth="150.0" styleClass="button-on" text="ON" />
<Button layoutX="411.0" layoutY="86.0" mnemonicParsing="false" onAction="controller.onTest('OFF')" prefWidth="150.0" styleClass="button-off" text="OFF" />
</children>
<padding>
<Insets left="10.0" right="10.0" />
</padding>
</HBox>
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</VBox>
Based on this example a user is able to simple open the FXMl with SceneBuilder and to add new Button calling the controller.onSwitch() method to control different/new devices installed for home automation.
FxmlUtils
The next release of ShichimiFX will contain a new Utilily class to load FXML as shown in the ExternalFXMLDemoController. Note that the loaded Pane is added to the center of the externalPane (BorderPane) of the Demo-Application via onLoadExternalFxml():
public class ExternalFXMLDemoController {
@FXML
private ResourceBundle resources;
@FXML
private BorderPane externalPane;
@FXML
private TextField fxmlFileNameTextField;
@FXML
private Button chooseFxmlFileButton;
@FXML
private Button loadFxmlFileButton;
private StringProperty fxmlFileName;
public void initialize() {
fxmlFileNameTextField.textProperty().bindBidirectional(fxmlFileNameProperty());
loadFxmlFileButton.disableProperty().bind(fxmlFileNameProperty().isEmpty());
}
public StringProperty fxmlFileNameProperty() {
if (fxmlFileName == null) {
fxmlFileName = new SimpleStringProperty("");
}
return fxmlFileName;
}
public String getFxmlFileName() {
return fxmlFileNameProperty().getValue();
}
public void setFxmlFileName(String fxmlFileName) {
this.fxmlFileNameProperty().setValue(fxmlFileName);
}
@FXML
public void chooseFxmlFile() {
FileChooser chooser = new FileChooser();
chooser.setTitle("Choose FXML file to load");
if (getFxmlFileName().isEmpty()) {
chooser.setInitialDirectory(new File(System.getProperty("user.home")));
} else {
chooser.setInitialDirectory(new File(getFxmlFileName()).getParentFile());
}
File file = chooser.showOpenDialog(chooseFxmlFileButton.getScene().getWindow());
if (file != null) {
setFxmlFileName(file.getAbsolutePath());
}
}
@FXML
public void onLoadExternalFxml() {
try {
Optional<URL> url = FxmlUtils.getFxmlUrl(Paths.get(getFxmlFileName()));
if (url.isPresent()) {
Pane pane = FxmlUtils.loadFxmlPane(url.get(), resources);
externalPane.setCenter(pane);
} else {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setContentText(getFxmlFileName() + " could not be found!");
alert.show();
}
} catch (IOException ex) {
Dialogs.create().showException(ex);
}
}
}
Have you tried to load external fxml file on iOS? Script-handler will not work out of the box (see my question in robovm-group: http://groups.google.com/group/robovm/t/85e1c47faabc9b8d?utm_source=digest&utm_medium=email), but it still would be really cool to customize iOS ui on the fly.
Thanks for the very nice work.
What if we want to add a new field with a new event in the controller which doesn’t exist yet in the controller. How can we do it ?
Thank
Tkp
Hi Je ne
Where can I download a copy source code of the remote app please ?
Thanks
TkP