I have a problem with reading property in my webcomponent. I don't understand why it's not working. I create simple example and after click the button I should get value of property but it's null. I don't know why ? In my others test, setProperty works OK, but then getProperty always get same value what I set in setProperty. I also try change property in a browser. PropertyChangeListener also is never triggered after change value on client side. Spend a lot time for this. Can somebody tell me what's happening?
HelloWorld.class
import com.vaadin.flow.component.DomEvent;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.littemplate.LitTemplate;
#Tag("hello-world")
#JsModule("./components/hello-world.ts")
public class HelloWorld extends LitTemplate {
#DomEvent("click")
public static class ClickEvent extends ComponentEvent<HelloWorld> {
public ClickEvent(HelloWorld source, boolean fromClient) {
super(source, fromClient);
}
}
public HelloWorld() {
setId("hello-world");
getElement().addPropertyChangeListener("value", "change", e -> {
System.out.println("change value: " + e.getValue());
});
addListener(ClickEvent.class, e -> System.out.println("getValue(): " + getValue()));
}
public void setValue(String value) {
getElement().setProperty("value", value);
}
public String getValue() {
return getElement().getProperty("value");
}
}
hello-world.ts
import { LitElement, html, property} from 'lit-element';
export class HelloWorld extends LitElement {
#property({type: String}) value = 'unset';
render() {
return html`
<div>${this.value}</div>
<button #click=${this._click}>Button</button>
`;
}
_click() {
this.value = 'Clicked';
let click = new Event('click');
this.dispatchEvent(click);
}
}
customElements.define("hello-world", HelloWorld);
You have set up the property value to be synchronized back to the server when a change event is fired in the client, but no such event is fired. There is, however, a click event so you might want to change addPropertyChangeListener to use that DOM event name instead.
Related
Edit:
I first voted to close as a duplicate after finding this answer by James_D, which sets a TextFormatter on a TextField. But then firstly I found that (in a TableView context) the method TextFieldTableCell.forTableColumn() does not in fact draw a TextField when it starts editing, but instead a LabeledText, which does not subclass TextInputControl, and therefore does not have setTextFormatter().
Secondly, I wanted something which acted in a familiar sort of way. I may have produced the "canonical" solution in my answer: let others judge.
This is a TableColumn in a TableView (all Groovy):
TableColumn<Person, String> ageCol = new TableColumn("Age")
ageCol.cellValueFactory = { cdf -> cdf.value.ageProperty() }
int oldAgeValue
ageCol.onEditStart = new EventHandler(){
#Override
public void handle( Event event) {
oldAgeValue = event.oldValue
}
}
ageCol.cellFactory = TextFieldTableCell.forTableColumn(new IntegerStringConverter() {
#Override
public Integer fromString(String value) {
try {
return super.fromString(value)
}
catch ( NumberFormatException e) {
// inform user by some means...
println "string could not be parsed as integer..."
// ... and cancel the edit
return oldAgeValue
}
}
})
Excerpt from class Person:
public class Person {
private IntegerProperty age;
public void setAge(Integer value) { ageProperty().set(value) }
public Integer getAge() { return ageProperty().get() }
public IntegerProperty ageProperty() {
if (age == null) age = new SimpleIntegerProperty(this, "age")
return age
}
...
Without the start-edit Handler, when I enter a String which can't be parsed as an Integer NumberFormatException not surprisingly gets thrown. But I also find that the number in the cell then gets set to 0, which is likely not to be the desired outcome.
But the above strikes me as a pretty clunky solution.
I had a look at ageCol, and ageCol.cellFactory (as these are accessible from inside the catch block) but couldn't see anything better and obvious. I can also see that one can easily obtain the Callback (ageCol.cellFactory), but calling it would require the parameter cdf, i.e. the CellDataFeatures instance, which again you'd have to store somewhere.
I'm sure a validator mechanism of some kind was involved with Swing: i.e. before a value could be transferred from the editor component (via some delegate or something), it was possible to override some validating mechanism. But this IntegerStringConverter seems to function as a validator, although doesn't seem to provide any way to revert to the existing ("old") value if validation fails.
Is there a less clunky mechanism than the one I've shown above?
Edit
NB improved after kleopatra's valuable insights.
Edit2
Overhauled completely after realising that the best thing is to use the existing default editor and tweak it.
I thought I'd give an example with a LocalDate, slightly more fun than Integer. Given the following class:
class Person(){
...
private ObjectProperty<LocalDate> dueDate;
public void setDueDate(LocalDate value) {
dueDateProperty().set(value);
}
public LocalDate getDueDate() {
return (LocalDate) dueDateProperty().get();
}
public ObjectProperty dueDateProperty() {
if (dueDate == null) dueDate = new SimpleObjectProperty(this, "dueDate");
return dueDate;
}
Then you create a new editor cell class, which is exactly the same as TextFieldTreeTableCell (subclass of TreeTableCell), which is used by default to create an editor for a TreeTableView's table cell. However, you can't really subclass TextFieldTreeTableCell as, for example, its essential field textField is private.
So you copy the code in full from the source* (only about 30 lines), and you call it
class DueDateEditor extends TreeTableCell<Person, LocalDate> {
...
You then have to create a new StringConverter class, subclassing LocalDateStringConverter. The reason for subclassing is that if you don't do that it is impossible to catch the DateTimeParseException thrown by fromString() when an invalid date is received: if you use LocalDateStringConverter the JavaFX framework unfortunately catches it, without any frames in the stack trace involving your own code. So you do this:
class ValidatingLocalDateStringConverter extends LocalDateStringConverter {
boolean valid;
LocalDate fromString(String value) {
valid = true;
if (value.isBlank()) return null;
try {
return LocalDate.parse(value);
} catch (Exception e) {
valid = false;
}
return null;
}
}
Back in your DueDateEditor class you then rewrite the startEdit method as follows. NB, as with the TextFieldTreeTableCell class, textField is actually created lazily, when you first edit.
#Override
void startEdit() {
if (! isEditable()
|| ! getTreeTableView().isEditable()
|| ! getTableColumn().isEditable()) {
return;
}
super.startEdit();
if (isEditing()) {
if (textField == null) {
textField = CellUtils.createTextField(this, getConverter());
// this code added by me
ValidatingLocalDateStringConverter converter = getConverter();
Callable bindingFunc = new Callable(){
#Override
Object call() throws Exception {
// NB the return value from this is "captured" by the editor
converter.fromString( textField.getText() );
return converter.valid? '' : "-fx-background-color: red;";
}
}
def stringBinding = Bindings.createStringBinding( bindingFunc, textField.textProperty() );
textField.styleProperty().bind( stringBinding );
}
CellUtils.startEdit(this, getConverter(), null, null, textField);
}
}
NB don't bother trying to look up CellUtils: this is package-private, the package in question being javafx.scene.control.cell.
To set things up you do this:
Callback<TreeTableColumn, TreeTableCell> dueDateCellFactory =
new Callback<TreeTableColumn, TreeTableCell>() {
public TreeTableCell call(TreeTableColumn p) {
return new DueDateEditor( new ValidatingLocalDateStringConverter() );
}
}
dueDateColumn.setCellFactory(dueDateCellFactory);
... the result is a nice, reactive editor cell: when containing an invalid date (acceptable pattern yyyy-mm-dd; see other LocalDate.parse() variant for other formats) the background is red, otherwise normal. Entering with a valid date works seamlessly. You can also enter an empty String, which is returned as a null LocalDate.
With the above, pressing Enter with an invalid date sets the date to null. But overriding things to prevent this happening (i.e. forcing you to enter a valid date, or cancel the edit, e.g. by Escape) is trivial, using the ValidatingLocalDateStringConverter's valid field:
#Override
void commitEdit( LocalDate newDueDate ){
if( getConverter().valid )
super.commitEdit( newDueDate );
}
* I couldn't find this online. I extracted from the javafx source .jar file javafx-controls-11.0.2-sources.jar
i am fetching value from data base which is overriding the old value , how could in know when the variable value has changed.
while(result.next())
{
String rteCd = result.getString("Rte_Cd")
}
every time the rteCd will get get overridden with the database value, i want to check at which point it has changed becoz it can be same also, i need to perform some action when it changes
Npte:- i cant change the database and the query can return same value multiple times
Java.util.Observable might be of help.
The java.util.Observable.hasChanged() method returns if this object has changed.
Here's a sample code you can check out
import java.util.Observable;
import java.util.Observer;
class ObservedObject extends Observable {
private String watchedValue;
public ObservedObject(String value) {
watchedValue = value;
}
public void setValue(String value) {
// if value has changed notify observers
if(!watchedValue.equals(value)) {
watchedValue = value;
// mark as value changed
setChanged();
}
}
}
public class ObservableDemo implements Observer {
public String name;
public ObservableDemo(String name) {
this.name = name;
}
public static void main(String[] args) {
// create watched and watcher objects
ObservedObject watched = new ObservedObject("Original Value");
// watcher object listens to object change
ObservableDemo watcher = new ObservableDemo("Watcher");
// add observer to the watched object
watched.addObserver(watcher);
// trigger value change
System.out.println("setValue method called...");
watched.setValue("New Value");
// check if value has changed
if(watched.hasChanged())
System.out.println("Value changed");
else
System.out.println("Value not changed");
}
public void update(Observable obj, Object arg) {
System.out.println("Update called");
}
}
Output
setValue method called...
Value changed
Declare two more variable outside the while loop and then see the old and new value like this
String oldvalue="";
String newvalue="";
while(result.next())
{
oldvalue=rteCd;
String rteCd = result.getString("Rte_Cd")
newvalue=rteCd;
}
//Now Display them
println("old value"+oldvalue);
println("new value"+newvalue);
String prevRteCd = null;
String rteCd = null;
while(result.next())
{
prevRteCd = rteCd;
rteCd = result.getString("Rte_Cd");
if(prevRteCd! =null && !prevRteCd.equals(rteCd))
{
itChanged(prevRteCd, rteCd);
}
}
Normally if we have some textField in GWT we can add a BlurHandler by the following code:
textField.addBlurHandler(new BlurHandler() {
#Override
public void onBlur(BlurEvent event) {
//what we need
}
});
But if we use UiBinder and our textField is annotated by #UiField and it is mentioned in our ui.xml file we can add BlurHandler by this code as well:
#UiHandler("textField")
protected void createBlurHandler(BlurEvent event) {
}
I guess I am right here because it works like this. So, the question is, can we actually define BlurHandler inside ui.xml file?
For example, it is possible to add inputMaxLength and some other attributes there, does GWT has some possibility like onChange method or are these ways that I described the only possibilities?
I would like to have something like this:
<g:TextBox ui:field="textField" onBlur="methodName" />
Is it possible?
I am pretty sure what you are asking is not possible. The problem is that you wouldn't be able to use reflection to figure out which method you want to call. However you can extends the TextBox class and use that inside your template. The extended class could have it's own properties that can be set in the template. An example is as follows where I set the default test on my own DefaultTextBox.
public class DefaultTextBox extends TextBox {
/**
* The text color used when the box is disabled and empty.
*/
private static final String TEXTBOX_DISABLED_COLOR = "#AAAAAA";
private final String defaultText;
public #UiConstructor
DefaultTextBox(final String defaultText) {
this.defaultText = defaultText;
resetDefaultText();
// Add focus and blur handlers.
addFocusHandler(new FocusHandler() {
#Override
public void onFocus(FocusEvent event) {
getElement().getStyle().clearColor();
getElement().getStyle().clearFontStyle();
if (defaultText.equals(getText())) {
setText("");
}
}
});
addBlurHandler(new BlurHandler() {
#Override
public void onBlur(BlurEvent event) {
if ("".equals(getText())) {
resetDefaultText();
}
}
});
}
public String getDefaultText() {
return defaultText;
}
#Override
public void setText(String text) {
if (text == null) {
super.setText(getDefaultText());
} else {
getElement().getStyle().clearColor();
getElement().getStyle().clearFontStyle();
super.setText(text);
}
}
public String getText() {
return super.getText();
}
/**
* This is override so that the editor framework will not get the default
* value but the actual null value when the default text is in the box.
*/
#Override
public String getValue() {
try {
return getValueOrThrow();
} catch (ParseException e) {
return null;
}
}
#Override
public void setValue(String value) {
setText(value);
}
/**
* This is overridden from the parent class because this is
* how the editor gets the value.
*/
public String getValueOrThrow() throws ParseException {
if (defaultText.equals(super.getValueOrThrow())) {
return null;
}
return super.getValueOrThrow();
}
/**
* Reset the text box to the default text.
*/
public void resetDefaultText() {
setText(defaultText);
getElement().getStyle().setColor(TEXTBOX_DISABLED_COLOR);
getElement().getStyle().setFontStyle(FontStyle.ITALIC);
}
}
Then in the template you can set properties like this.
<w:DefaultTextBox defaultText="name" ui:field="nameTextBox" />
This will also work with setters, you can set properties without having to use the #UiConstructor but in my case I wanted to make sure that there was no empty constructor for this class.
I am using a Vaadin text field and I want to restrict it to support numbers only in it. I tried to override setValue() and return without calling super. setValue() if text is not a number. But it doesn't seems to be working. How can I correct this?
I am using Vaadin 7. And I think it doesn't support NumberField as well.
If I understand you question correct, you want to have a field that ignores all inputs that are not a number and not only mark the field as invalid. Vaadins architecture is designed that every field in the browser has its representation on the server. In my opinion the cleanest way to achieve this would be to have a browser field, that permits input of letters and other wrong characters. I couldn't find such a field in Vaadin 7. There seems to be an add-on for vaadin 6 called Number Field for that, but I didn't test it.
You have multiple options:
Port this add-on to vaadin 7 or ask the author to do it
Write your own field. Maybe extending VTextField and TextFieldConnector
Do everything on the server side and accept the delays and the traffic (IMHO ugly)
Since I think option 3 is not the way to go, I probably shouldn't show this code, but it's the quickest way to implement this.
public class IntegerField extends TextField implements TextChangeListener {
String lastValue;
public IntegerField() {
setImmediate(true);
setTextChangeEventMode(TextChangeEventMode.EAGER);
addTextChangeListener(this);
}
#Override
public void textChange(TextChangeEvent event) {
String text = event.getText();
try {
new Integer(text);
lastValue = text;
} catch (NumberFormatException e) {
setValue(lastValue);
}
}
}
Vaadin 7 allows to extend their built in widgets (if you want to have more knowledge on this I really recommend this post) here is a solution which uses that mechanism.
It is composed of two classes:
Connector and the Extension
The Extension
package com.infosystem.widgets.vaadin;
import com.vaadin.server.AbstractClientConnector;
import com.vaadin.server.AbstractExtension;
import com.vaadin.ui.TextField;
public class NumberField extends AbstractExtension {
public static void extend(TextField field) {
new NumberField().extend((AbstractClientConnector) field);
}
}
Connector:
package com.infosystem.widgets.vaadin.client.numberField;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.infosystem.widgets.vaadin.NumberField;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ServerConnector;
import com.vaadin.client.extensions.AbstractExtensionConnector;
import com.vaadin.client.ui.VTextField;
import com.vaadin.shared.ui.Connect;
#Connect(NumberField.class)
public class NumberFieldConnector extends AbstractExtensionConnector {
private static final long serialVersionUID = -737765038361894693L;
private VTextField textField;
private KeyPressHandler keyPressHandler = new KeyPressHandler() {
#Override
public void onKeyPress(KeyPressEvent event) {
if (textField.isReadOnly() || !textField.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:
case KeyCodes.KEY_SHIFT:
return;
}
if (!isValueValid(event)) {
textField.cancelKey();
}
}
};
#Override
protected void extend(ServerConnector target) {
textField = (VTextField) ((ComponentConnector) target).getWidget();
textField.addKeyPressHandler(keyPressHandler);
}
private boolean isValueValid(KeyPressEvent event) {
String newText = getFieldValueAsItWouldBeAfterKeyPress(event.getCharCode());
try {
parseValue(newText);
return true;
} catch (Exception e) {
return false;
}
}
protected long parseValue(String value) {
return Long.valueOf(value);
}
private String getFieldValueAsItWouldBeAfterKeyPress(char charCode) {
int index = textField.getCursorPos();
String previousText = textField.getText();
StringBuffer buffer = new StringBuffer();
buffer.append(previousText.substring(0, index));
buffer.append(charCode);
if (textField.getSelectionLength() > 0) {
buffer.append(previousText.substring(index + textField.getSelectionLength(),
previousText.length()));
} else {
buffer.append(previousText.substring(index, previousText.length()));
}
return buffer.toString();
}
}
To use the code above you need to add it to your current widget set.
Afterwards the use of this is as follows:
TextField field = new TextField();
NumberField.extend(field);
In Vaadin 7, you can use a TextField and set a validator to allow only numbers:
TextField textField;
textField.addValidator(new RegexpValidator("[-]?[0-9]*\\.?,?[0-9]+"), "This is not a number!");
Change the regex to fit your needs.
Remember that still is handling Strings and therefore you still need to convert the returning value of the TextField:
Long.parseLong(textField.getValue())
With Vaadin 8, you can use Binder:
Binder<YouBean> binder = new Binder<>();
binder.forField(textField)
.withConverter(new StringToIntegerConverter("Must be Integer"))
.bind(YouBean::getter, YouBean::setter);
binder.setBean(bean); //optional
A TextField is a component that always has a value of type String. When binding a property of another type to a text field, the value is automatically converted if the conversion between the two types is supported.
public class MyBean {
private int value;
public int getValue() {
return value;
}
public void setValue(int integer) {
value = integer;
}
}
The property named "value" from a BeanItem constructed from MyBean will be of type Integer. Binding the property to a TextField will automatically make validation fail for texts that can not be converted to an Integer.
final MyBean myBean = new MyBean();
BeanItem<MyBean> beanItem = new BeanItem<MyBean>(myBean);
final Property<Integer> integerProperty = (Property<Integer>) beanItem
.getItemProperty("value");
final TextField textField = new TextField("Text field", integerProperty);
Button submitButton = new Button("Submit value", new ClickListener() {
public void buttonClick(ClickEvent event) {
String uiValue = textField.getValue();
Integer propertyValue = integerProperty.getValue();
int dataModelValue = myBean.getValue();
Notification.show("UI value (String): " + uiValue
+ "\nProperty value (Integer): " + propertyValue
+ "\nData model value (int): " + dataModelValue);
}
});
addComponent(new Label("Text field type: " + textField.getType()));
addComponent(new Label("Text field type: " + integerProperty.getType()));
addComponent(textField);
addComponent(submitButton);
With this example, entering a number and pressing the button causes the value of the TextField to be a String, the property value will be an Integer representing the same value and the value in the bean will be the same int. If e.g. a letter is entered to the field and the button is pressed, the validation will fail. This causes a notice to be displayed for the field. The field value is still updated, but the property value and the bean value are kept at their previous values.
This is an update (2017 with vaadin 8) for #raffael answer:
public class DoubleField extends TextField implements ValueChangeListener<String> {
public String lastValue;
public DoubleField() {
setValueChangeMode(ValueChangeMode.EAGER);
addValueChangeListener(this);
lastValue="";
}
#Override
public void valueChange(ValueChangeEvent<String> event) {
String text = (String) event.getValue();
try {
new Double(text);
lastValue = text;
} catch (NumberFormatException e) {
setValue(lastValue);
}
}
NumberField is available for Vaadin 7 and 8 by now.
I have a GWT application that loads a product when the page is loaded. I am using PropertyChangeEvent on the product object (and its sub-objects) to update the values of fields, whenever a change happens.
Of course, I do not want this PropertyChangeEvent to raise when the product is loaded for the first time. For this, I am setting the raisePropertyChange value to false, but it doesn't seem to work. Please find below the code base:
// Class ProductBaseImpl
public abstract class PropChangeImpl {
// The raise property change event, should be turned off conditionally
private boolean raisePropertyChangeEvent = true;
protected boolean getRaisePropertyChangeEvent() {
return this.raisePropertyChangeEvent;
}
protected void setRaisePropertyChangeEvent(final boolean value) {
this.raisePropertyChangeEvent = value;
}
protected void raisePropertyChangeEvent(String fieldName, Object oldValue, Object newValue) {
if (this.raisePropertyChangeEvent ) {
// --> HERE IS THE PROBLEM <--
// This IF loop must not be true when loading the product first time
System.out.println("Property change event raised!");
// the update operations go here
} else {
System.out.println("Property change event not raised!");
}
}
}
// Class ProductBaseImpl
public abstract class ProductBaseImpl extends PropChangeImpl {
private static HandlerRegistration productChangeBeginRegistration;
private static HandlerRegistration productChangeEndRegistration;
protected E instance;
protected ProductBaseImpl(final E instance) {
this.instance = instance;
// Stop updates when a new product loads
if (ProductBaseImpl.productChangeBeginRegistration == null) {
ProductBaseImpl.productChangeBeginRegistration = Core.getEventBus().addHandler(ProductChangeBeginEvent.TYPE, new ProductChangeBeginEventEventHandler() {
#Override
public void onProductChangeBegin(final ProductChangeBeginEvent event) {
ProductBaseImpl.this.raisePropertyChangeEvent(false);
}
});
}
if (ProductBaseImpl.productChangeEndRegistration == null) {
ProductBaseImpl.productChangeEndRegistration = Core.getEventBus().addHandler(ProductChangeEndEvent.TYPE, new ProductChangeEndEventtHandler() {
#Override
public void onProductChangeEnd(final ProductChangeEndEvent event) {
ProductBaseImpl.this.raisePropertyChangeEvent(true);
}
});
}
}
}
// Class ProductSubObj1
public class ProductSubObj1 extends ProductBaseImpl {
public ProductSubObj1 (final E instance) {
super(instance);
// some other operations
}
}
// similar to above, I have classes ProductSubObj1, ProductSubObj2 ...
// Class ProductProvider, that fetches the product from service to UI
public class ProductProvider {
// some properties and members
public void fetchProduct(String productId) {
// Let listeners know the product is about to change
Core.getEventBus().fireEvent(new ProductChangeBeginEvent(productId));
// Call the service to get the product in Json data
// After processing the data to be available for the UI (and scheduleDeferred)
Core.getEventBus().fireEvent(new ProductChangeEndEvent(productId));
}
}
As commented inline in the code, the control always goes within the
if (this.raiseDataChangeEvent)
block which I don't want to happen when the product is loaded for the first time.
Could you please advise what am I doing wrong?
Thanks.
Can you just do this:?
protected void raisePropertyChangeEvent(String fieldName, Object oldValue, Object newValue) {
if (this.raisePropertyChangeEvent && oldValue != null /*Or whatever your default unloaded value is*/) {
// --> HERE IS THE PROBLEM <--
// This IF loop must not be true when loading the product first time
System.out.println("Property change event raised!");
// the update operations go here
} else {
System.out.println("Property change event not raised!");
}
}