Solution for Numeric Text Field in GWT
Asked Answered
O

6

14

I need a text field very similar in behavior to Gxt's NumberField. Unfortunately I am not using Gxt in my application and GWT 2.0 does not have a Numeric text field implementation as yet.

So that currently leaves me with an option to simulate a NumberField by filtering out non-numeric keystrokes using a keyboardHandler.

Is this the the best way to approach the problem? Does anyone here have a better solution/approach in mind?

Thanks in advance :)

Othella answered 19/5, 2010 at 12:57 Comment(2)
Guess, this is a very frequent requirement in CRUD applications. Would like to know how you guys have implemented it.Othella
Filtering via one of the Key*Handlers seems pretty straightforward - what else do you need/expect? You could also check GXT's source code to see how they implemented their NumberField - reimplementing it in GWT should be a breeze.Linter
W
14

Here you can find the code that I use in one of my classes. The features are much more limited that those of GXT, but should put you in the proper track.

It's a really basic widget, but does what I need to.

public class ValueTextBox extends TextBox {

    private int min = 0;
    private int max = 100;
    private boolean minConstrained = true;
    private boolean maxConstrained = true;
    private int minDigits = 1;
    private int step = 1;

    private KeyUpHandler keyUpHandler = new KeyUpHandler() {

        @Override
        public void onKeyUp(KeyUpEvent event) {
            if (isReadOnly() || !isEnabled()) {
                return;
            }

            int keyCode = event.getNativeEvent().getKeyCode();

            boolean processed = false;

            switch (keyCode) {
            case KeyCodes.KEY_LEFT:
            case KeyCodes.KEY_RIGHT:
            case KeyCodes.KEY_BACKSPACE:
            case KeyCodes.KEY_DELETE:
            case KeyCodes.KEY_TAB:
                if (getText().isEmpty()) {
                    setValue(formatValue(min));
                }
                return;
            case KeyCodes.KEY_UP:
                if (step != 0) {
                    increaseValue();
                    processed = true;
                }
                break;
            case KeyCodes.KEY_DOWN:
                if (step != 0) {
                    decreaseValue();
                    processed = true;
                }
                break;
            }

            if (processed) {
                cancelKey();
            }
        }

    };

    private KeyPressHandler keyPressHandler = new KeyPressHandler() {
        @Override
        public void onKeyPress(KeyPressEvent event) {

            if (isReadOnly() || !isEnabled()) {
                return;
            }

            int keyCode = event.getNativeEvent().getKeyCode();

            switch (keyCode) {
            case KeyCodes.KEY_LEFT:
            case KeyCodes.KEY_RIGHT:
            case KeyCodes.KEY_BACKSPACE:
            case KeyCodes.KEY_DELETE:
            case KeyCodes.KEY_TAB:
            case KeyCodes.KEY_UP:
            case KeyCodes.KEY_DOWN:
                return;
            }

            int index = getCursorPos();
            String previousText = getText();
            String newText;
            if (getSelectionLength() > 0) {
                newText = previousText.substring(0, getCursorPos())
                        + event.getCharCode()
                        + previousText.substring(getCursorPos()
                                + getSelectionLength(), previousText.length());
            } else {
                newText = previousText.substring(0, index)
                        + event.getCharCode()
                        + previousText.substring(index, previousText.length());
            }
            cancelKey();

            setValue(newText, true);
        }
    };

    public ValueTextBox(int value) {
        this(value, 0, 100);
    }

    public ValueTextBox(int value, int min, int max) {
        this(value, min, max, true);
    }

    public ValueTextBox(int value, int min, int max, boolean constrained) {
        this(value, min, max, constrained, constrained);
    }

    public ValueTextBox(int value, int min, int max, boolean minConstrained,
            boolean maxConstrained) {
        super();

        addKeyPressHandler(keyPressHandler);
        addKeyUpHandler(keyUpHandler);

        this.min = min;
        this.max = max;
        this.minConstrained = minConstrained;
        this.maxConstrained = maxConstrained;

        setValue(formatValue(value), false);
        setTextAlignment(TextBoxBase.ALIGN_CENTER);
        setStyleName(Resources.INSTANCE.css().fwFormEntry());
    }

    public void setMinDigits(int minDigits) {
        if (minDigits > 0) {
            this.minDigits = minDigits;

            String value = getText();
            long newValue = parseValue(value);

            setText(formatValue(newValue));
        }
    }

    public void setSteps(int step) {
        this.step = step;
    }

    protected void increaseValue() {
        if (step != 0) {
            String value = getText();
            long newValue = parseValue(value);
            newValue += step;
            if (maxConstrained && (newValue > max)) {
                return;
            }
            setValue(formatValue(newValue));
        }
    }

    protected void decreaseValue() {
        if (step != 0) {
            String value = getText();
            long newValue = parseValue(value);
            newValue -= step;
            if (minConstrained && (newValue < min)) {
                return;
            }
            setValue(formatValue(newValue));
        }
    }

    /**
     * @param value
     *            the value to format
     * @return the formatted value
     */
    protected String formatValue(long value) {
        String newValue = String.valueOf(value);

        if (minDigits > newValue.length()) {
            String leading = StringUtils.repeat("0", (minDigits - newValue
                    .length()));
            newValue = leading + newValue;
        }

        return newValue;
    }

    @Override
    public void setValue(String value) {
        setValue(value, false);
    }

    @Override
    public void setValue(String value, boolean fireEvents) {
        try {
            long newValue = parseValue(value);
            if ((maxConstrained && (newValue > max))
                    || (minConstrained && (newValue < min))) {
                return;
            }
            String prevText = getValue();
            super.setText(formatValue(newValue));
            if (fireEvents) {
                ValueChangeEvent.fireIfNotEqual(this, getValue(), prevText);
            }
        } catch (Exception ex) {
            // Do Nothing
            System.out.println(ex.getMessage());
        }
    }

    /**
     * @param value
     *            the value to parse
     * @return the parsed value
     */
    protected long parseValue(String value) {
        return Long.valueOf(value);
    }
}

Update: The code is available in https://github.com/ctasada/GWT-Eureka

Wilhelm answered 25/5, 2010 at 14:3 Comment(3)
Thanks Carlos, I had implemented my version on NumberField along similar lines entirely in KeyPressHandler since I didn't need the keyUp and KeyDown - spinner like feature. I specially like your formatValue technique, since it also subtly conveys the max limit of the field - planning to borrow your idea ;) I shall post my version as soon as I get to my development workstation. Thanks :)Othella
That helps a lot - may I commercially use that code? And if so, under which conditions?Perennate
@slartidan. Just vote up if it helped you ;) Just contact me if you need some extra help.Wilhelm
F
5

Here is a simple KeyPressHandler to allow the user to input decimal numbers;

public void onKeyPress(KeyPressEvent event){
    TextBox sender = (TextBox)event.getSource();

    if (sender.isReadOnly() || !sender.isEnabled()) {
        return;
    }

    Character charCode = event.getCharCode();
    int unicodeCharCode = event.getUnicodeCharCode();

    // allow digits, '.' and non-characters
    if (!(Character.isDigit(charCode) || charCode == '.' || unicodeCharCode == 0)){
        sender.cancelKey();
    }
}
Filia answered 12/11, 2011 at 22:3 Comment(1)
This solution permits multiple decimal points, which produces an invalid decimal point number.Caught
I
4

Don't know when these classes were added to GWT, but they work fine for me without any extra code:

com.google.gwt.user.client.ui.DoubleBox
com.google.gwt.user.client.ui.IntegerBox
com.google.gwt.user.client.ui.LongBox 

For more advanced validation you may want to overwrite their base class ValueBox with some custom Parser...

Inmesh answered 5/11, 2013 at 15:5 Comment(0)
B
1

Carlos Tasada answer works, but contains a bug: you should add event.isShiftKeyDown() check in onKeyPress handler before switch/case block. It will pass some symbols like '(' otherwise.

Belga answered 14/5, 2012 at 6:57 Comment(1)
But it will still pass ' and . symbols, so you need to check event.getCharCode() alsoBelga
V
1

Based on Julian Downes answer you can do this:

text.addKeyPressHandler(new KeyPressHandler() {

        @Override
        public void onKeyPress(KeyPressEvent event) {
            TextBox sender = (TextBox) event.getSource();

            if (sender.isReadOnly() || !sender.isEnabled()) {
                return;
            }
            if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER){
                return;
            }

            Character charCode = event.getCharCode();

            try{
                Double.parseDouble(sender.getText().concat(charCode.toString()));
            }
            catch(Exception e){
                sender.cancelKey();
            }
        }
    });
Viticulture answered 24/4, 2015 at 8:52 Comment(0)
O
0

Here is my implementation of NumberField. Very similar in functionality to Carlos's version, but with additional support for decimal input and non-numeric key filtering.

public class NumberBox extends TextBox
{
private boolean isDecimal = false;

public NumberBox( )
{
}

public boolean isDecimal( )
{
    return isDecimal;
}

public void setDecimal( boolean isDecimal )
{
    this.isDecimal = isDecimal;
}

public Integer getIntegerValue( )
{
    return ( StringUtil.isEmpty( getSanitizedValue( ) ) ) ? null : Integer.parseInt( getSanitizedValue( ) );
}

@Override
protected void initialize( )
{
    super.initialize( );
    addStyleName( "number" );

    this.addKeyPressHandler( new KeyPressHandler( )
    {
        public void onKeyPress( KeyPressEvent event )
        {
            if ( !isEnabled( ) || isReadOnly( ) )
                return;

            int keyCode = event.getNativeEvent( ).getKeyCode( );

            // allow special keys
            if ( ( keyCode == KeyCodes.KEY_BACKSPACE )
                    || ( keyCode == KeyCodes.KEY_DELETE )
                    || ( keyCode == KeyCodes.KEY_ENTER ) || ( keyCode == KeyCodes.KEY_ESCAPE ) || ( keyCode == KeyCodes.KEY_RIGHT )
                    || ( keyCode == KeyCodes.KEY_LEFT ) || ( keyCode == KeyCodes.KEY_TAB ) )
                return;

            // check for decimal '.'
            if ( isDecimal( ) && '.' == (char)keyCode && !getValue( ).contains( "." ) )
                return;

            // filter out non-digits
            if ( Character.isDigit( charCode ) )
                return;

            cancelKey( );
        }
    } );
}

}


PS: Superclass TextBox is a custom class extending GWT TextBox with some additional application specific features. The method initialize() is basically invoked inside the TextBox constructor, and getSanitizedValue does some basic sanity checks with trimming.

Othella answered 26/5, 2010 at 7:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.