How can I create a TextField character mask that only allows numbers?

It isn’t difficult to validate a text field after it loses focus, but is there a way to capture key presses as they come in and reject anything that isn’t a number (or whatever)? I found a few things online, but so far none of them work. Here are some that I have found:


Connector and the Extension
(can’t get this to work at all)

NumberField
(I am running version 7.3.3, so this is out)
There are several more links using various technologies, but all come up short.

I quickly put together something using the TextChangeListener, but this is way to flaky to be production worthy.

Have any Vaadin extensions been developed to mimic this? I can probably build one, but this can take a day, which seems a waste of time given how fundamental this behavior is. If anyone has something, I would immenseley appreciate it.

There’s the
CSValidation add-on
for Vaadin 7.x-something, and it works at least. It’s not based on masks, but either JS or regexp validators.

Then there’s
OSGiMaskedTextField
, but I haven’t tried that.

Thanks Marko, I will give CSValidation a try in the morning. I am still having a hard time finding the information I need on Vaadin.

It seems that this isn’t exactly what I need. All this does is validate whether or not the entered characters are allowed, but what I would like is something that doesn’t allow the input of bad characters. For example, right now I need a number mask applied to some fields. Ideally, if someone tries typing an incorrect character the value never even makes it to the text field.

I have a rudimentary solution using the TextChangeListener, but you can actually see the character hit the textbox then I delete it after the fact. This is ugly, and buggy.

Any ideas?

I’m sorry, I just found the “PreventInvalidTyping” option. This works, but very poorly. For starters, it has the same defect that I mentioned in my previous post, you can see the letters hitting the field then get deleted. But worse, when initial values are loaded, if I try typing something that is invalid, it deletes the entire text string (including the initial value) rather than just the last letter. Maybe I am doing something wrong?

Yes, that’s how it works, it deletes the letters after they have been typed (on keyup events). The other solution would be to handle each keypress (keydown events) at low level, but it might require implementing cursor navigation, key repeat, editing, and perhaps even modifier keys from scratch, which is a very complex task with CSValidation’s validation model.

With a masked text field, where the validation model is more restricted, implementing the editing features is much easier, as I think it was in the
MaskedTextField
for Vaadin 6. I don’t know how the
OSGiMaskedTextField
works, as there doesn’t seem to be source code or demo links.

The behaviour with the initial values in CSValidation sounds like a bug. I’ll note the issue, but I can’t promise when it would get fixed.

It seems that I figured out how to use your extension, and it is a very nice piece of work. In the end, seeing the characters appear and disappear isn’t so bad, but the ‘bug’ is kind of strange.

To help you at least identify the bug, I have copied the code below. It is very rough, and hasn’t been cleaned up, so that may attribute to the way it works. None the less, I will copy it below in its current form so that you can see what’s going on which may shed some light.

    private void applyFundsToTable() {
        String numReg = "-?[0-9]
*";
        String floatReg = "(\\+|-)?([0-9]
*(\\.{0,1}[0-9]
*))";

        List<ComplexFund> funds = filterFunds();
        complexTable.removeAllItems();

        if (funds != null) {
            for (final ComplexFund f : funds) {
                CSValidator tnaValidator = new CSValidator();
                tnaValidator.setPreventInvalidTyping(true);
                CSValidator navValidator = new CSValidator();
                navValidator.setPreventInvalidTyping(true);
                CSValidator sharesValidator = new CSValidator();
                sharesValidator.setPreventInvalidTyping(true);
                tnaValidator.setRegExp(numReg);
                navValidator.setRegExp(floatReg);
                sharesValidator.setRegExp(numReg);

                TextField tnaField = new TextField("");
                tnaValidator.extend(tnaField);
                tnaField.setWidth("140px");
                tnaField.addFocusListener(new FieldEvents.FocusListener() {
                    @Override
                    public void focus(FieldEvents.FocusEvent event) {
                        rowFundClicked(f.getFundid());
                        complexTable.select(f.getFundid());
                    }
                });
                if (f.getTna() != null)
                    tnaField.setValue(f.getTna());
                Button tnaButton = new Button("");
                tnaButton.setWidth("40px");
                tnaButton.addClickListener(new Button.ClickListener() {
                    @Override
                    public void buttonClick(Button.ClickEvent event) {
                        complexTable.select(f.getFundid());
                        rowFundClicked(f.getFundid());
                        if (tableEditable) {
                            ConfirmationWindow results = new ConfirmationWindow(dao, ConfirmationWindow.TNA_TYPE, f.getFundid(), f.getIob12());
                            UI.getCurrent().addWindow(results);
                        }
                    }
                });
                if (f.getTnac() != null)
                    tnaButton.setCaption(f.getTnac());

                TextField navField = new TextField("");
                navValidator.extend(navField);
                navField.setWidth("140px");
                navField.addFocusListener(new FieldEvents.FocusListener() {
                    @Override
                    public void focus(FieldEvents.FocusEvent event) {
                        rowFundClicked(f.getFundid());
                        complexTable.select(f.getFundid());
                    }
                });
                if (f.getNav() != null)
                    navField.setValue(f.getNav());
                Button navButton = new Button("");
                navButton.setWidth("40px");
                navButton.addClickListener(new Button.ClickListener() {
                    @Override
                    public void buttonClick(Button.ClickEvent event) {
                        complexTable.select(f.getFundid());
                        rowFundClicked(f.getFundid());
                        if (tableEditable) {
                            ConfirmationWindow results = new ConfirmationWindow(dao, ConfirmationWindow.NAV_TYPE, f.getFundid(), f.getIob12());
                            UI.getCurrent().addWindow(results);
                        }
                    }
                });
                if (f.getNavc() != null)
                    navButton.setCaption(f.getNavc());

                TextField sharesField = new TextField("");
                sharesValidator.extend(sharesField);
                sharesField.setWidth("140px");
                sharesField.addFocusListener(new FieldEvents.FocusListener() {
                    @Override
                    public void focus(FieldEvents.FocusEvent event) {
                        complexTable.select(f.getFundid());
                        rowFundClicked(f.getFundid());
                    }
                });
                if (f.getShares() != null)
                    sharesField.setValue(f.getShares());
                Button sharesButton = new Button("");
                sharesButton.setWidth("40px");
                sharesButton.addClickListener(new Button.ClickListener() {
                    @Override
                    public void buttonClick(Button.ClickEvent event) {
                        complexTable.select(f.getFundid());
                        rowFundClicked(f.getFundid());
                        if (tableEditable) {
                            ConfirmationWindow results = new ConfirmationWindow(dao, ConfirmationWindow.SHARES_TYPE, f.getFundid(), f.getIob12());
                            UI.getCurrent().addWindow(results);
                        }
                    }
                });
                if (f.getSharesc() != null)
                    sharesButton.setCaption(f.getSharesc());

                complexTable.addItem(new Object[]{new Label(""+f.getFundid()), new Label(f.getTicker()), new Label(f.getName()), tnaField, tnaButton, navField, navButton, sharesField, sharesButton,
                                 new Label(""+f.getClasstype()), new Label(""+f.getIob12()),
                                 new Label(""+f.getTna_source()), new Label(f.getTna_entry_user()), new Label(f.getTna_entry_date()), new Label(f.getTna_update_user()), new Label(f.getTna_update_date()),
                                 new Label(""+f.getNav_source()), new Label(f.getNav_entry_user()), new Label(f.getNav_entry_date()), new Label(f.getNav_update_user()), new Label(f.getNav_update_date()),
                                 new Label(""+f.getShares_source()), new Label(f.getShares_entry_user()), new Label(f.getShares_entry_date()), new Label(f.getShares_update_user()), new Label(f.getShares_update_date()),
                                        }, f.getFundid());
            }
        }
    }

Yes, I found what causes the bug. The previous valid text is kept in a variable “oldtext”, but it’s not initialized from the initial field value, but empty value. Thus, if you give an initial value and type something, it reverts to the empty value.

I’ll try to find time to fix that soon.

Oh, that would be awesome. Please let me know, I have been tinkering a bit to try and figure out a workaround. I will hold off for now, this project is huge and I have plenty of other things to play with.

… also, if you would like some help, I would be more than willing.

Hello Marko.

I am just curious if you have had any luck in resolving this issue. If not, is there something I can do to either help fix it or to get around it?

Hi,

I examined the problem quickly last week, but didn’t find an easy way to fix the problem. I hope that it is even possible, as the possibilities of an extension to hook into a TextField are a bit limited. I’ll try to find time to look into it better.

Hi, sorry for the delay. I finally managed to have time to look into this. It looks like I just made a silly error earlier. Now it should work…unless I missed something (really should have test cases in the project).

So,
there’s now
0.5.2, I hope it works.

Thenk you, Marko. I will give it a try and let you know.

Now there’s also 0.5.3; the 0.5.2 was accidentally built with JDK 1.8, which caused trouble.