I have simple Vaadin GUI which I would like connect with my Rest API on localhost:8080:
#Route("hello")
public class EmployeeGui extends VerticalLayout {
private final WebClient webClient = WebClient.create("http://localhost:8080");
public EmployeeGui() {
TextField textEmployee = new TextField("Give id of user");
Button buttonOK = new Button("OK");
Label label = new Label();
buttonOK.addClickListener(buttonClickEvent -> {
this.webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/employee/{id}")
.build(textEmployee.getValue()))
.retrieve()
.bodyToMono(EmployeeTo.class)
.subscribe(emp -> {
label.setText(emp.getName());
});
});
add(textEmployee,buttonOK, label);
}
}
On localhost:8080 works my backend application which give me REST API to retrive some data from DB.
In text field we can put id of user and then click OK-button. After that in label we set name of user. Unfortunately I got exception (in line label.setText(emp.getName());):
java.lang.IllegalStateException: Cannot access state in VaadinSession or UI without locking the session.
I understood it, but how can I omit this problem? How can I put user Id and then return User attributes to label after OK button was clicked?
When you react to the request from your API, the "request" from Vaadin is
already done. If you want to make that work you would have to enable #Push
(so Vaadin can send changes from the server when they happen) and make sure you
access the UI in a safe way (see "Asynchronous Updates" in the
docs for
details).
Or you actually don't need that to be async and
use the web client in a blocking way (so the client blocks and the label change
happens inside the "request" triggered by the button.
The direct answer to the question is using #Push (Doc 1, Doc 2) to update the ui when the main request has already been responded to (because your webClient.get() is asynchronous). The fix to your problem looks like this:
#Push
#Route("hello")
public class EmployeeGui extends VerticalLayout {
private UI ui;
private final WebClient webClient = WebClient.create("http://localhost:8080");
public EmployeeGui() {
TextField textEmployee = new TextField("Give id of user");
Button buttonOK = new Button("OK");
Label label = new Label();
// keep instance of UI in a field,
// and update it whenever the EmployeeGui is (re-)attached to the page
// (important when using #PreserveOnRefresh or RouterLayout)
addAttachListener(event -> {
this.ui = event.getUI();
});
buttonOK.addClickListener(buttonClickEvent -> {
this.webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/employee/{id}")
.build(textEmployee.getValue()))
.retrieve()
.bodyToMono(EmployeeTo.class)
.subscribe(emp -> {
// use ui.access to obtain lock on UI, perform updates within
getUI().access(() -> label.setText(emp.getName()));
});
});
add(textEmployee,buttonOK, label);
}
private UI getUI(){
return this.ui;
}
}
But depending on what you want to do with your application, I can recommend using Spring Security to make the user Log in, then you have easy and direct access to the current username.
Related
I am trying to implement the ad in my app with Custom Native Ad Format - https://developers.google.com/ad-manager/mobile-ads-sdk/android/native/custom-formats#java_1
So, according to the documentation I am going with the approach described there and creating the ad
...
private void setListeners() {
...
imageView.setOnClickListener(v -> {
nativeCustomFormatAd.performClick("IMAGE");
});
...
}
private NativeCustomFormatAd nativeCustomFormatAd;
AdLoader adLoader = new AdLoader.Builder(context, "/6499/example/native")
.forCustomFormatAd("10063170",
new NativeCustomFormatAd.OnCustomFormatAdLoadedListener() {
#Override
public void onCustomFormatAdLoaded(NativeCustomFormatAd ad) {
// Show the custom format and record an impression.
nativeCustomFormatAd = ad;
Drawable drawable = vm.nativeCustomFormatAd.getImage("IMAGE").getDrawable();
imageView.setDrawable(drawable);
}
},
new NativeCustomFormatAd.OnCustomClickListener() {
#Override
public void onCustomClick(NativeCustomFormatAd ad, String s) {
// Handle the click action
}
})
.withAdListener( ... )
.withNativeAdOptions( ... )
.build();
#SuppressLint("VisibleForTests")
AdManagerAdRequest adManagerAdRequest = new AdManagerAdRequest.Builder().build();
adLoader.loadAd(adManagerAdRequest);
...
So, it looks pretty simple I try to make a request for the ad then I got (in a callback) NativeCustomFormatAd, save it as a class member, and along with it get drawable and set it to the imageView (to present it in the UI). Once a user clicks on the imageView I get an event in the click listener and invoke nativeCustomFormatAd.performClick("IMAGE");.
The problem is that I expect that once I transfer the ad click to the SDK (by nativeCustomFormatAd.performClick("IMAGE");) SDK is supposed to open the external browser, but instead nothing happens.
P.S. I am sure that nativeCustomFormatAd.performClick("IMAGE"); getting invoked and also I see that SDK gets the click as I got a callback event here:
...
new NativeCustomFormatAd.OnCustomClickListener() {
#Override
public void onCustomClick(NativeCustomFormatAd ad, String s) {
// Handle the click action
}
})
...
What am I missing here?
According to the docs you linked:
When a click is performed on a custom format ad, there are three possible responses from the SDK, attempted in this order:
Invoke the OnCustomClickListener from AdLoader, if one was provided.
For each of the ad's deep link URLs, attempt to locate a content resolver and start the first one that resolves.
Open a browser and navigate to the ad's traditional Destination URL.
Also:
If you pass a listener object in, the SDK instead invokes its onCustomClick method and takes no further action.
Therefore, it seems you have to pass a null OnCustomClickListener.
I have the following component and my grid doesn't get refreshed when I receive a Spring event. Why is that?
#Component
#Scope(SCOPE_PROTOTYPE)
public class SearchResultGridView extends Grid<SearchResultModel> implements
ApplicationListener<FetchBySubscriptionNumberAction> {
private static final Logger log = LoggerFactory.getLogger(SearchView.class);
private final SumoCmMemberService sumoCmMemberService;
private List<SearchResultModel> searchResultModels = new ArrayList<>();
private final List<String> columnNames = List.of(
"subscriptionNumber",
"mnr",
"firstName",
"lastName",
"dateOfBirth",
"street",
"houseNumber",
"box",
"postalCode",
"locality",
"active"
);
public SearchResultGridView(SumoCmMemberService sumoCmMemberService) {
super(SearchResultModel.class, false);
this.sumoCmMemberService = sumoCmMemberService;
initGridColumns();
initStyle();
}
private void initGridColumns() {
columnNames.forEach(columnName -> addColumn(columnName).setAutoWidth(true));
}
private void initStyle() {
addThemeVariants(GridVariant.LUMO_NO_BORDER);
setHeightFull();
}
#Override
public void onApplicationEvent(FetchBySubscriptionNumberAction action) {
log.info(action.getSubscriptionNumber());
sumoCmMemberService
.fetchBySubscriptionNumber(action.getSubscriptionNumber())
.doOnError(e -> log.error("error in webflux: " + e.getMessage()))
.subscribe(sumoCmMemberDto -> {
SumoSubscriptionDto sumoSubscriptionDto = sumoCmMemberDto.getSumoSubscription();
SearchResultModel searchResultModel = SearchResultModel
.builder()
.mnr(sumoCmMemberDto.getMnumber())
.firstName(sumoSubscriptionDto.getFirstName())
.lastName(sumoSubscriptionDto.getLastName())
.street(sumoSubscriptionDto.getStreet())
.houseNumber(sumoSubscriptionDto.getHouseNumber())
.box(sumoSubscriptionDto.getBox())
.active(sumoCmMemberDto.isActive())
.build();
searchResultModels = List.of(searchResultModel);
setItems(searchResultModels);
});
}
}
I'm using a Mono from a service from which I subscribe, and I simply want to replace the items.
Note: I dislike the DataProvider api, and I wish not to use it.
My use case is simple; I want to subscribe to a Mono reactive stream and refresh the table.
So why doesn't this work? The simplest use case of the Grid Layout api makes believe that it should. No compiler errors and no runtime errors. Just nothing happens, and the grid doesn't display the data I provide to it. In debug modus, I see that the list is properly populated.
But the grid, nothing displays.
The problem is that there is no ongoing client request, so there is no way for Vaadin to let the browser know something has changed.
What you need to do is to configure #Push to enable two-way communication and then use ui.access() to handle the update:
UI ui = UI.getCurrent();
[...]
.subscribe(sumoCmMemberDto -> {
SumoSubscriptionDto sumoSubscriptionDto = sumoCmMemberDto.getSumoSubscription();
SearchResultModel searchResultModel = SearchResultModel
.builder()
.mnr(sumoCmMemberDto.getMnumber())
.firstName(sumoSubscriptionDto.getFirstName())
.lastName(sumoSubscriptionDto.getLastName())
.street(sumoSubscriptionDto.getStreet())
.houseNumber(sumoSubscriptionDto.getHouseNumber())
.box(sumoSubscriptionDto.getBox())
.active(sumoCmMemberDto.isActive())
.build();
searchResultModels = List.of(searchResultModel);
ui.access(()->setItems(searchResultModels)); // wrap setItems in ui.access()
});
Read more here in the docs: https://vaadin.com/docs/latest/flow/advanced/server-push/#push.access (there are small differences in V14 vs latest, so make sure you read docs for the correct version)
I've got a method called showNotification that shows notification
public static void showNotification(UI ui, String notificationMessage) {
Notification notification = new Notification(notificationMessage);
notification.setStyleName("custom-notification");
notification.show(ui.getPage());
}
And I've got a service that returns CompletableFuture.
In my controller I created method
#Override
public CompletableFuture<?> startProcessing() {
return processorService.start();
}
And on button click I want to show notification if start was successful.
My buttonClick event looks like this
event -> {
controller.startProcessing()
.thenAccept(aVoid -> UI.getCurrent().access(() -> {
showNotification(getUI(), "Started processing");
})
).handle((aVoid, throwable) -> {
showNotification(getUI(), "Failed to start processing");
return aVoid;
});
}
The problem is that until I invoke another event - like for example click on another button - it doesn't show notification.
What is the problem?
You need to add #Push to your UI subclass to make the browser open a two-directional connection with the server. Without that, only the browser can initiate communication, and it only does that when there's some new event to send to the server.
I am making a Java project with vaadin. Right now I have a user registration form looking like that:
public class RegistrationComponent extends CustomComponent implements View {
public static final String VIEW_NAME = "Registration";
public RegistrationComponent(){
Panel panel = new Panel("Registration Form");
panel.setSizeUndefined();
FormLayout content = new FormLayout();
CheckBox checkBox1, checkBox2, checkBox3;
checkBox1 = new CheckBox("Check Box 1");
checkBox2 = new CheckBox("Check Box 2");
checkBox3 = new CheckBox("Check Box 3");
checkBox1.setRequired(true);
checkBox2.setRequired(true);
TextField mailTextField = new TextField("Email Address");
TextField passwordTextField = new TextField("Password");
TextField confirmPasswordTextField = new TextField("Confirm Password");
final Button submitButton = new Button("Submit");
content.addComponent(mailTextField);
content.addComponent(passwordTextField);
content.addComponent(confirmPasswordTextField);
content.addComponent(checkBox1);
content.addComponent(checkBox2);
content.addComponent(checkBox3);
content.addComponent(submitButton);
content.setSizeUndefined(); // Shrink to fit
content.setMargin(true);
panel.setContent(content);
setCompositionRoot(panel);
//listeners:
submitButton.addClickListener(new Button.ClickListener() {
public void buttonClick(Button.ClickEvent event) {
//
}
});
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event){
//
}
}
Of course, the form doesn't do anything other than being displayed.
What I wanna do, is make Vaadin display error messages next to fields if some requirements are not met. The requirements themselves are not that important (lets say I want email field to contain at least 8 characters). What I wanna know, is: is there any simple built-in way to do that? I was here:
https://vaadin.com/api/com/vaadin/data/Validator.html
but I dont understand how to use a validator, or even if that is what I want to use. I've been looking all over google for usage examples, but so far with no success. Thanks for help!
Vaadin 7
The following applies to Vaadin 7. The validate() method has been removed in Vaadin 8.
All Field types in Vaadin implement the Validatable interface which has the addValidator method that accepts an implementation of Validator as parameter.
So to add a validator that checks the length of the value of a TextField, you would do this:
TextField textField = new TextField();
textField.addValidator(
new StringLengthValidator(
"Must be between 2 and 10 characters in length", 2, 10, false));
Vaadin fields have built-in functionality for displaying the validation errors to the user. By default, the field will be highlighted in red and an exclamation mark will appear next to the field, hovering over this will show a more detailed message to the user.
Automatic Validation
By default, the field will now validate on the next server request which contains a changed value for the field to the server. If the field is set to 'immediate', this will happen when the field looses focus. If the field is not immediate, validation will happen when some other UI action triggers a request back to the server.
Explicit Validation
Sometimes, you may want to exercise more control over when validation happens and when validation errors are displayed to the user. Automatic validation can be disabled by setting validationVisible to false.
textField.setValidationVisible(false);
When you are ready to validate the field (e.g. in a button click listener) you can explicitly call the validate (you can also use commit() if it is a buffered field) method on the TextField instance to trigger validation. validate will throw an InvalidValueException if the value is invalid. If you want to use the builtin display of validation errors included in the TextField component you will also have to set validationVisible back to true.
try {
textField.validate();
} catch (Validator.InvalidValueException ex) {
textField.setValidationVisible(true);
Notification.show("Invalid value!");
}
Note that once validationVisbible is set back to true, validation will happen implicitly so you must remember to set it back to false on the next request if you want to maintain explicit control over validation.
Validation Messages
Individual validation messages can be extracted from the instance of Validator.InvalidValueException which is thrown when validate() or commit() is called.
try {
textField.validate();
} catch (Validator.InvalidValueException ex) {
for (Validator.InvalidValueException cause: ex.getCauses()) {
System.err.println(cause.getMessage());
}
}
Validators
Validators implement the Validator interface and there are several useful validators shipped with Vaadin. Check out the API docs for more information on these: https://vaadin.com/api/7.4.5/com/vaadin/data/Validator.html
Custom validators are easy to implement, here is an example taken from the Book of Vaadin:
class MyValidator implements Validator {
#Override
public void validate(Object value)
throws InvalidValueException {
if (!(value instanceof String &&
((String)value).equals("hello")))
throw new InvalidValueException("You're impolite");
}
}
final TextField field = new TextField("Say hello");
field.addValidator(new MyValidator());
field.setImmediate(true);
layout.addComponent(field);
Problem solved,
Apparently I wasn't looking deep enough before. Here it comes:
field.addValidator(new StringLengthValidator("The name must be 1-10 letters (was {0})",1, 10, true));
all details here:
https://vaadin.com/book/-/page/components.fields.html
Vaadin 8
Using Vaadin 8 com.vaadin.data.Binder easily you can validate your fields. See Binding Data to Forms in the manual.
Create a TextField and a binder to validate the text field.
public class MyPage extends VerticalLayout{
TextField investorCode = new TextField();
Binder<MyBean> beanBinder = new Binder<MyBean>();
//Info : MyBean class contains getter and setter to store values of textField.
public MyPage (){
investorCode.addValueChangeListener(e->valueChange(e));
addComponent(investorCode);
bindToBean();
}
private void bindToBean() {
beanBinder.forField(investorCode)
.asRequired("Field cannot be empty")
.withValidator(investorCode -> investorCode.length() > 0,"Code shold be atleast 1 character long").bind(MyBean::getInvestorCode,MyBean::setInvestorCode);
}
//rest of the code .....
private void valueChange(ValueChangeEvent<String> e) {
beanBinder.validate();
}
}
Call validate() from binder will invoke the validation action.
beanBinder.validate();
to validate the filed. You can call this from anywhere in the page. I used to call this on value change or on a button click.
Is it possible to nest forms in Wicket that are independent of each other? I want to have a form with a submit button and a cancel button. Both buttons should direct the user to the same page (let's call it Foo). The submit button should send some info to the server first; the cancel button should do nothing.
Here's a really simplified version of my existing code:
Form form = new Form() {
public void onSubmit()
{
PageParameters params = new PageParameters();
params.put("DocumentID", docID);
setResponsePage(Foo.class, params);
}
};
DropDownChoice<String> ddc = new DropDownChoice<String>("name", new PropertyModel<String>(this, "nameSelection"), names);
ddc.setRequired(true);
final Button submitButton = new Button("Submit") {
public void onSubmit() { doSubmitStuff(true); }
};
final Button cancelButton = new Button("Cancel") {
public void onSubmit() { doSubmitStuff(false); }
};
form.add(ddc);
form.add(submitButton);
form.add(cancelButton);
form.add(new FeedbackPanel("validationMessages"));
The problem is, I just added a validator, and it fires even if I press the cancel button, since the cancel button is attached to the same form as everything else. This could be avoided if the cancel button were in a separate form. As far as I know, I can't create a separate form because — due to the structure of the HTML — the separate form would be under the existing form in the component hierarchy.
Can I make the forms separate somehow in spite of the hierarchy? Or is there some other solution I can use?
EDIT:
In response to Don Roby's comment, this is a bit closer to what my code looked like back when I was trying setDefaultFormProcessing():
Form<Object> theForm = new Form<Object>("theForm") {
public void onSubmit()
{
PageParameters params = new PageParameters();
params.put("DocumentID", docID);
setResponsePage(Foo.class, params);
}
};
final CheckBox checkbox = new CheckBox("checkbox", new PropertyModel<Boolean>(this, "something"));
checkbox.add(new PermissionsValidator());
theForm.add(checkbox);
final Button saveButton = new Button("Save") {
public void onSubmit()
{ someMethod(true); }
};
final Button cancelButton = new Button("Cancel") {
public void onSubmit()
{ someMethod(false); }
};
cancelButton.setDefaultFormProcessing(false);
theForm.add(saveButton);
theForm.add(cancelButton);
theForm.add(new FeedbackPanel("validationMessages"));
There is an even simpler solution: call the setDefaultFormProcessing method on the cancel button with false as a parameter:
cancelButton.setDefaultFormProcessing(false);
This way, clicking the cancel button will bypass the form validation (and model updating), directly calling the onSubmit function.
It is possible to "nest" forms in wicket.
See this wiki entry
for some notes on how it works and this wiki entry for how it interacts with validation.
But for what you're after, the answer from Jawher should have worked and is much simpler.
Look at this example code for hints on getting that working.
I'm wondering if you've simplified your code too far in this posting. Can you produce a sample small enough to post that definitely has the problem?