com.extjs.gxt.ui.client.widget.form.SpinnerField Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gxt Show documentation
Show all versions of gxt Show documentation
Rich Internet Application Framework for GWT
/*
* Sencha GXT 2.3.1a - Sencha for GWT
* Copyright(c) 2007-2013, Sencha, Inc.
* [email protected]
*
* http://www.sencha.com/products/gxt/license/
*/
package com.extjs.gxt.ui.client.widget.form;
import java.util.ArrayList;
import java.util.List;
import com.extjs.gxt.ui.client.GXT;
import com.extjs.gxt.ui.client.event.ClickRepeaterEvent;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.FieldEvent;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.util.ClickRepeater;
import com.extjs.gxt.ui.client.util.Format;
import com.extjs.gxt.ui.client.util.KeyNav;
import com.extjs.gxt.ui.client.util.Size;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.i18n.client.constants.NumberConstants;
import com.google.gwt.user.client.Element;
/**
* Numeric text field that provides automatic keystroke filtering and numeric
* validation.
*
*
* When the field wraps any thing other than Double, either
* {@link #setPropertyEditorType(Class)} or
* {@link #setPropertyEditor(PropertyEditor)} should be called with the
* appropriate number type.
*
*
* SpinnerField field = new SpinnerField();
* field.setPropertyEditorType(Integer.class);
*
*
*
* - Inherited Events:
* - Field Focus
* - Field Blur
* - Field Change
* - Field Invalid
* - Field Valid
* - Field KeyPress
* - Field SpecialKey
*
*/
public class SpinnerField extends TwinTriggerField {
/**
* SpinnerField messages.
*/
public class SpinnerFieldMessages extends TextFieldMessages {
private String maxText;
private String minText;
private String nanText;
private String negativeText = GXT.MESSAGES.numberField_negativeText();
/**
* Returns the max error text.
*
* @return the error text
*/
public String getMaxText() {
return maxText;
}
/**
* Returns the minimum error text.
*
* @return the minimum error text
*/
public String getMinText() {
return minText;
}
/**
* Returns the not a number error text.
*
* @return the not a number error text
*/
public String getNanText() {
return nanText;
}
/**
* Returns the negative error text.
*
* @return the error text
*/
public String getNegativeText() {
return negativeText;
}
/**
* Error text to display if the maximum value validation fails (defaults to
* "The maximum value for this field is {maxValue}").
*
* @param maxText the max error text
*/
public void setMaxText(String maxText) {
this.maxText = maxText;
}
/**
* Sets the Error text to display if the minimum value validation fails
* (defaults to "The minimum value for this field is {minValue}").
*
* @param minText min error text
*/
public void setMinText(String minText) {
this.minText = minText;
}
/**
* Sets the error text to display if the value is not a valid number. For
* example, this can happen if a valid character like '.' or '-' is left in
* the field with no number (defaults to "{value} is not a valid number").
*
* @param nanText the not a number text
*/
public void setNanText(String nanText) {
this.nanText = nanText;
}
/**
* Sets the negative error text (defaults to 'The value must be greater or
* equal to 0').
*
* @param negativeText the error text
*/
public void setNegativeText(String negativeText) {
this.negativeText = negativeText;
}
}
protected List allowed;
protected NumberConstants constants;
protected String decimalSeparator = ".";
protected KeyNav keyNav;
private boolean allowDecimals = true;
private boolean allowNegative = true;
private String baseChars = "0123456789";
private Number increment = 1d;
private int lastKeyCode;
private Number maxValue = Double.MAX_VALUE;
private Number minValue = Double.NEGATIVE_INFINITY;
/**
* Creates a new number field.
*/
public SpinnerField() {
messages = new SpinnerFieldMessages();
propertyEditor = new NumberPropertyEditor();
constants = LocaleInfo.getCurrentLocale().getNumberConstants();
decimalSeparator = constants.decimalSeparator();
}
/**
* Returns true of decimal values are allowed.
*
* @return the allow decimal state
*/
public boolean getAllowDecimals() {
return allowDecimals;
}
/**
* Returns true if negative values are allowed.
*
* @return the allow negative value state
*/
public boolean getAllowNegative() {
return allowNegative;
}
/**
* Returns the base characters.
*
* @return the base characters
*/
public String getBaseChars() {
return baseChars;
}
/**
* Returns the field's number format.
*
* @return the number format
*/
public NumberFormat getFormat() {
return getPropertyEditor().getFormat();
}
/**
* Sets the increment value.
*
* @return the increment
*/
public Number getIncrement() {
return increment;
}
/**
* Returns the fields max value.
*
* @return the max value
*/
public Number getMaxValue() {
return maxValue;
}
@Override
public SpinnerFieldMessages getMessages() {
return (SpinnerFieldMessages) messages;
}
/**
* Returns the field's minimum value.
*
* @return the min value
*/
public Number getMinValue() {
return minValue;
}
@Override
public NumberPropertyEditor getPropertyEditor() {
return (NumberPropertyEditor) propertyEditor;
}
/**
* Returns the number property editor number type.
*
* @see NumberPropertyEditor#setType(Class)
* @return the number type
*/
public Class> getPropertyEditorType() {
return getPropertyEditor().getType();
}
/**
* Sets whether decimal value are allowed (defaults to true).
*
* @param allowDecimals true to allow negative values
*/
public void setAllowDecimals(boolean allowDecimals) {
this.allowDecimals = allowDecimals;
}
/**
* Sets whether negative value are allowed.
*
* @param allowNegative true to allow negative values
*/
public void setAllowNegative(boolean allowNegative) {
this.allowNegative = allowNegative;
}
/**
* Sets the base set of characters to evaluate as valid numbers (defaults to
* '0123456789').
*
* @param baseChars the base character
*/
public void setBaseChars(String baseChars) {
assertPreRender();
this.baseChars = baseChars;
}
/**
* Sets the cell's number formatter.
*
* @param format the format
*/
public void setFormat(NumberFormat format) {
getPropertyEditor().setFormat(format);
}
/**
* Sets the increment that should be used (defaults to 1d).
*
* @param increment the increment to set.
*/
public void setIncrement(Number increment) {
this.increment = increment;
}
/**
* Sets the field's max allowable value.
*
* @param maxValue the max value
*/
public void setMaxValue(Number maxValue) {
this.maxValue = maxValue.doubleValue();
if (rendered && maxValue.doubleValue() != Double.MAX_VALUE) {
getInputEl().dom.setAttribute("aria-valuemax", "" + maxValue);
}
}
/**
* Sets the field's minimum allowed value.
*
* @param minValue the minimum value
*/
public void setMinValue(Number minValue) {
this.minValue = minValue.doubleValue();
if (rendered && maxValue.doubleValue() != Double.NEGATIVE_INFINITY) {
getInputEl().dom.setAttribute("aria-valuemin", "" + minValue);
}
}
/**
* Specifies the number type used when converting a String to a Number
* instance (defaults to Double).
*
* @param type the number type (Short, Integer, Long, Float, Double).
*/
public void setPropertyEditorType(Class> type) {
getPropertyEditor().setType(type);
}
@Override
protected Size adjustInputSize() {
return new Size(isHideTrigger() ? 0 : trigger.getStyleSize().width, 0);
}
protected void afterRender() {
super.afterRender();
addStyleOnOver(trigger.dom, "x-form-spinner-overup");
addStyleOnOver(twinTrigger.dom, "x-form-spinner-overdown");
}
protected void doSpin(boolean up) {
if (!readOnly) {
Number n = getValue();
double d = n == null ? 0d : getValue().doubleValue();
if (up) {
setValue(Math.max(minValue.doubleValue(), Math.min(d + increment.doubleValue(), maxValue.doubleValue())));
} else {
setValue(Math.max(
minValue.doubleValue(),
Math.min(allowNegative ? d - increment.doubleValue() : Math.max(0, d - increment.doubleValue()),
maxValue.doubleValue())));
}
}
}
@Override
protected void onKeyDown(FieldEvent fe) {
super.onKeyDown(fe);
// must key code in key code as character returned in key press
lastKeyCode = getKeyCode(fe.getEvent());
}
@Override
protected void onKeyPress(FieldEvent fe) {
super.onKeyPress(fe);
char key = getChar(fe.getEvent());
if (fe.isSpecialKey(lastKeyCode) || fe.isControlKey()) {
return;
}
if (!allowed.contains(key)) {
fe.stopEvent();
}
}
@Override
protected void onRender(Element target, int index) {
super.onRender(target, index);
allowed = new ArrayList();
for (int i = 0; i < baseChars.length(); i++) {
allowed.add(baseChars.charAt(i));
}
if (allowNegative) {
allowed.add('-');
}
if (allowDecimals) {
for (int i = 0; i < decimalSeparator.length(); i++) {
allowed.add(decimalSeparator.charAt(i));
}
}
Listener listener = new Listener() {
public void handleEvent(ClickRepeaterEvent be) {
if (SpinnerField.this.isEnabled()) {
if (!hasFocus) {
focus();
}
if (be.getType() == Events.OnClick) {
if (be.getEl() == trigger) {
onTriggerClick(null);
} else if (be.getEl() == twinTrigger) {
onTwinTriggerClick(null);
}
} else if (be.getType() == Events.OnMouseDown) {
if (be.getEl() == trigger) {
trigger.addStyleName("x-form-spinner-clickup");
} else if (be.getEl() == twinTrigger) {
twinTrigger.addStyleName("x-form-spinner-clickdown");
}
} else if (be.getType() == Events.OnMouseUp) {
if (be.getEl() == trigger) {
trigger.removeStyleName("x-form-spinner-clickup");
} else if (be.getEl() == twinTrigger) {
twinTrigger.removeStyleName("x-form-spinner-clickdown");
}
}
}
}
};
ClickRepeater cr = new ClickRepeater(trigger);
cr.addListener(Events.OnClick, listener);
cr.addListener(Events.OnMouseDown, listener);
cr.addListener(Events.OnMouseUp, listener);
addAttachable(cr);
cr = new ClickRepeater(twinTrigger);
cr.addListener(Events.OnClick, listener);
cr.addListener(Events.OnMouseDown, listener);
cr.addListener(Events.OnMouseUp, listener);
addAttachable(cr);
addStyleName("x-spinner-field");
trigger.addStyleName("x-form-spinner-up");
twinTrigger.addStyleName("x-form-spinner-down");
setMaxValue(maxValue);
setMinValue(minValue);
getInputEl().dom.setAttribute("role", "spinbutton");
keyNav = new KeyNav(this) {
@Override
public void onDown(ComponentEvent ce) {
doSpin(false);
}
@Override
public void onUp(ComponentEvent ce) {
doSpin(true);
}
};
}
protected void onTriggerClick(ComponentEvent ce) {
super.onTriggerClick(ce);
// only do it from the ClickRepeater, not from onBrowserEvent
if (ce == null) {
doSpin(true);
}
}
protected void onTwinTriggerClick(ComponentEvent ce) {
super.onTwinTriggerClick(ce);
// only do it from the ClickRepeater, not from onBrowserEvent
if (ce == null) {
doSpin(false);
}
}
@Override
protected boolean validateValue(String value) {
// validator should run after super rules
Validator tv = validator;
validator = null;
if (!super.validateValue(value)) {
validator = tv;
return false;
}
validator = tv;
if (value.length() < 1) { // if it's blank and textfield didn't flag it then
// its valid it's valid
return true;
}
String v = value;
Number d = null;
try {
d = getPropertyEditor().convertStringValue(v);
} catch (Exception e) {
String error = "";
if (getMessages().getNanText() == null) {
error = GXT.MESSAGES.numberField_nanText(v);
} else {
error = Format.substitute(getMessages().getNanText(), v);
}
markInvalid(error);
return false;
}
if (d.doubleValue() < minValue.doubleValue()) {
String error = "";
if (getMessages().getMinText() == null) {
error = GXT.MESSAGES.numberField_minText(minValue.doubleValue());
} else {
error = Format.substitute(getMessages().getMinText(), minValue);
}
markInvalid(error);
return false;
}
if (d.doubleValue() > maxValue.doubleValue()) {
String error = "";
if (getMessages().getMaxText() == null) {
error = GXT.MESSAGES.numberField_maxText(maxValue.doubleValue());
} else {
error = Format.substitute(getMessages().getMaxText(), maxValue);
}
markInvalid(error);
return false;
}
if (!allowNegative && d.doubleValue() < 0) {
markInvalid(getMessages().getNegativeText());
return false;
}
if (validator != null) {
String msg = validator.validate(this, value);
if (msg != null) {
markInvalid(msg);
return false;
}
}
if (GXT.isAriaEnabled()) {
getInputEl().dom.setAttribute("aria-valuenow", "" + value);
}
return true;
}
// needed due to GWT 2.1 changes
private native char getChar(NativeEvent e) /*-{
return e.which || e.charCode || e.keyCode || 0;
}-*/;
// needed due to GWT 2.1 changes
private native int getKeyCode(NativeEvent e) /*-{
return e.keyCode || 0;
}-*/;
}