0

I have a custom component representing a form field. It has a label, a text field and an error message that may be shown after input validation.

<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="109.0" prefWidth="512.0" spacing="10.0" styleClass="vbox" type="VBox" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <Label fx:id="fieldLabel" text="Lorem ipsum dolor sit amet"></Label>
      <TextField fx:id="textField" promptText="Lorem ipsum dolor sit amet"></TextField>
      <Label fx:id="errorLabel" text=""></Label>
   </children>
</fx:root>
public class FormField extends VBox {

    @FXML private TextField textField;
    @FXML private Label fieldLabel;
    @FXML private Label errorLabel;

    private String fieldLabelText;
    private String promptText;

    public FormField(@NamedArg("fieldLabelText") String fieldLabelText,
                     @NamedArg("promptText") String promptText,
                     @NamedArg("isPasswordField") boolean isPasswordField) {

        FXMLLoader loader = new FXMLLoader(getClass().getResource("../../resources/fxml/form-field.fxml"));
        loader.setRoot(this);
        loader.setController(this);

        this.fieldLabelText = fieldLabelText;
        this.promptText = promptText;

        try {
            loader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }

    @FXML
    public void initialize() {
        this.fieldLabel.setText(fieldLabelText);
        this.textField.setPromptText(promptText);
    }

Now what I want to know is how would I go about making an extension of this component that has a PasswordField instead of a TextField? Or passing an argument such as boolean isPasswordField that let's FormField decide if it should render a TextField or a PasswordField? If TextField had an obscureText(true) method in it's API it would be good enough since this is all I'm looking for, but I couldn't find any.

All I could find about JavaFX inheritance was in the sense of "extending" the object by adding new components to it, not by changing it's existing elements.

Allan Juan
  • 2,048
  • 1
  • 18
  • 42
  • 4
    As an aside, note that the path to your FXML is incorrect (it will not work when your application is packaged). See https://stackoverflow.com/questions/61531317/how-do-i-determine-the-correct-path-for-fxml-files-css-files-images-and-other. – James_D Jun 24 '20 at 11:17

1 Answers1

4

One option is to place both a TextField and a PasswordField in the UI, stacked on top of each other in a StackPane, with only one of them visible:

<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="109.0" prefWidth="512.0" spacing="10.0" styleClass="vbox" type="VBox" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <Label fx:id="fieldLabel" text="Lorem ipsum dolor sit amet"></Label>
      <StackPane>
        <TextField fx:id="textField" promptText="Lorem ipsum dolor sit amet"></TextField>
        <PasswordField fx:id="passwordField" visible="false"/>
      </StackPane>
      <Label fx:id="errorLabel" text=""></Label>
   </children>
</fx:root>

Then in the controller decide which is visible based on the parameter that was passed:

public class FormField extends VBox {

    @FXML private TextField textField;
    @FXML private PasswordField passwordField;
    @FXML private Label fieldLabel;
    @FXML private Label errorLabel;

    private String fieldLabelText;
    private String promptText;
    private boolean isPasswordField;

    public FormField(@NamedArg("fieldLabelText") String fieldLabelText,
                     @NamedArg("promptText") String promptText,
                     @NamedArg("isPasswordField") boolean isPasswordField) {

        // path is copied from OP, but is incorrect:
        FXMLLoader loader = new FXMLLoader(getClass().getResource("../../resources/fxml/form-field.fxml"));
        loader.setRoot(this);
        loader.setController(this);

        this.fieldLabelText = fieldLabelText;
        this.promptText = promptText;
        this.isPasswordField = passwordField;

        try {
            loader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }

    @FXML
    public void initialize() {
        this.fieldLabel.setText(fieldLabelText);
        this.textField.setPromptText(promptText);
        this.passwordField.promptTextProperty().bind(
            this.textField.promptTextProperty());
        this.passwordField.setVisible(isPasswordField);
        this.textField.setVisible(!isPasswordField);
    }

}

Other variations are possible, e.g. your FXML could just define the StackPane with no content; then in the controller code add either a TextField or PasswordField as needed.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • 1
    propagating that incorrect parameter in resource lookup .. doohh *tongue-in-cheek :) – kleopatra Jun 24 '20 at 12:36
  • As an addition to this, If he needs to hide or show the text later, He can use a boolean property instead of a boolean, for whether or not the text is hidden, and bind visible properties of the fields to it, and maybe bind the text properties of the fields to each other (bidirectional) – SDIDSA Jun 25 '20 at 23:11