Poor mans input constraints

For me the best way to prevent the user from annoying errors and error messages is simply in the first line to try not to allow the user e.g. to enter invalid data or to execute actions while data in incomplete (disabled buttons/menu entries).
If only numbers allowed in an input field I think its a better approach to restict user input to numbers only instead of validating and popping up with error messages after applying or “OK”.

Swing has a JFormattedTextField which allows setting a format pattern to verify the input data. Sadly the current JavaFX API doesn’t contain such kind of control but thers is a way by adding an EventHandler for KEY_TYPED-Events and to consume the event at certain conditions. Consuming the event will end up the event bubbling phase. (see Handling JavaFX Events for more informations).
Clearly this can be implemented in a new control or by creating a subclass of TextField. But there is also a simple approach by attaching the wanted constraints to existing instances of standard controls. As you my know the editor controls of Cells and ComboBoxes are also TextFields. Thus such a handler can also be attached e.g. to a ComboBox.

Limitation/Disclaimer:
Don’t sue me for this very simple approach ;-)!
As this is just a limitaion of the INPUT while TYPING it can be bypassed e.g. by copy-paste.
Clearly in the long run a JFormattedTextField like control supporting pattern matching, validation and user feedback is needed!

Runnable demo and code can be found here.

InputConstraintsDemo

        InputConstraints.noBlanks(noBlanksField);
        InputConstraints.noLeadingBlanks(noLeadingBlanksField);
        InputConstraints.numbersOnly(numbersOnlyField);
        InputConstraints.lettersOnly(lettersOnlyField);

        InputConstraints.noLeadingBlanks(valueComboBox.getEditor());
        InputConstraints.noLeadingBlanks(noLeadingBlanksComboBox.getEditor());
        InputConstraints.noBlanks(noBlanksComboBox.getEditor());
        InputConstraints.numbersOnly(numbersOnlyComboBox.getEditor());
        InputConstraints.lettersOnly(lettersOnlyComboBox.getEditor());
public class InputConstraints{

    private InputConstraints() {
        // not needed here
    }

    public static void noLeadingAndTrailingBlanks(final TextField textField) {
        textField.addEventFilter(KeyEvent.KEY_TYPED, createNoLeadingBlanksInputHandler());
        textField.focusedProperty().addListener((Observable observable) -> {
            textField.setText(textField.getText().trim());
        });
    }

    public static void noLeadingBlanks(final TextField textField) {
        textField.addEventFilter(KeyEvent.KEY_TYPED, createNoLeadingBlanksInputHandler());
    }

    public static void noBlanks(final TextField textField) {
        textField.addEventFilter(KeyEvent.KEY_TYPED, createNoBlanksInputHandler());
        textField.focusedProperty().addListener((Observable observable) -> {
            textField.setText(textField.getText().trim());
        });
    }

    public static void numbersOnly(final TextField textField) {
        numbersOnly(textField, Integer.MAX_VALUE);
    }

    public static void numbersOnly(final TextField textField, final Integer maxLenth) {
        textField.addEventFilter(KeyEvent.KEY_TYPED, createNumbersOnlyInputHandler(maxLenth));
        textField.focusedProperty().addListener((Observable observable) -> {
            textField.setText(textField.getText().trim());
        });
    }

    public static void lettersOnly(final TextField textField) {
        lettersOnly(textField, Integer.MAX_VALUE);
    }

    public static void lettersOnly(final TextField textField, final Integer maxLenth) {
        textField.addEventFilter(KeyEvent.KEY_TYPED, createLettersOnlyInputHandler(maxLenth));
        textField.focusedProperty().addListener((Observable observable) -> {
            textField.setText(textField.getText().trim());
        });
    }

    public static EventHandler<KeyEvent> createNoLeadingBlanksInputHandler() {
        return (KeyEvent event) -> {
            if (event.getSource() instanceof TextField) {
                TextField textField = (TextField) event.getSource();
                if (" ".equals(event.getCharacter()) && textField.getCaretPosition() == 0) {
                    event.consume();
                }
            }
        };
    }

    public static EventHandler<KeyEvent> createNumbersOnlyInputHandler(final Integer maxLength) {
        return createPatternInputHandler(maxLength, "[0-9.]");
    }

    public static EventHandler<KeyEvent> createLettersOnlyInputHandler(final Integer maxLength) {
        return createPatternInputHandler(maxLength, "[A-Za-z]");
    }

    public static EventHandler<KeyEvent> createNoBlanksInputHandler() {
        return (KeyEvent event) -> {
            if (event.getSource() instanceof TextField) {
                if (" ".equals(event.getCharacter())) {
                    event.consume();
                }
            }
        };
    }

    public static EventHandler<KeyEvent> createPatternInputHandler(final Integer maxLength, String pattern) {
        return (KeyEvent event) -> {
            if (event.getSource() instanceof TextField) {
                TextField textField = (TextField) event.getSource();
                if (textField.getText().length() >= maxLength || !event.getCharacter().matches(pattern)) {
                    event.consume();
                }
            }
        };
    }
}

Leave a Comment