Vaadin (Flow): Navigating to destination with a shared object - java

Problem:
I currently have a grid that displays content of type SomeModel.
When I click an entry of that Grid I would like to navigate to a view that takes an object as its input to display the entries content.
Implementation:
To achive this behaviour I created a DetailLayout like this:
public DetailLayout extends FlexLayout implements HasUrlParameter<SomeModel>{
/* skipped some details */
#Override
public void setParameter(BeforeEvent event, Host parameter) {
/* This is where I expected to be able to handle the object */
}
}
From within the Grid I tried to navigate like this:
addSelectionListener((event) -> {
event.getFirstSelectedItem().ifPresent(somemodel -> {
getUI().ifPresent(ui -> {
ui.navigate(DetailLayout.class, somemodel);
});
});
});
But unfortunately this behaviour is not supported by Vaadin even tho its syntax is perfectly fine.
Question:
Do you know of another way to pass an object while navigation or did I miss a certain part of the official documentation documentation ?
Thank you in advance

Key-Value collection
As discussed in the comments on the other Answer, if you do not wish to expose the ID value as part of the URL, then work behind the scenes by using the key-value collection provided by Vaadin.
Vaadin actually provides key-value collections at three levels of scope:
ContextYour entire web-app at runtime
SessionEach user
UIEach web browser window/tab, as Vaadin supports multi-window web-apps
The app-wide key-value collection is available on the VaadinContext, via getAttribute & setAttribute methods.
VaadinService.getCurrent().getContext().setAttribute( key , value ) ;
The per-user key-value collection is available on the VaadinSession, via getAttribute & setAttribute methods.
VaadinSession.getCurrent().setAttribute( key , value ) ;
➥ The per-browser-window/tab collection (what you want for your needs in this Question) is not quite so readily available. You have to go through an indirect step. On the ComponentUtil class, call setData & getData methods. In addition to passing your key and your value, pass the current UI object.
Component c = UI.getCurrent() ;
String key = "com.example.acmeapp.selectedProductId" ;
Object value = productId ;
ComponentUtil.setData( c , key , value ) ;
Please vote for my ticket # 6287, a feature-request to add setAttribute/getAttribute methods on UI class, to match those of VaadinSession and VaadinContext.

Instead of giving the whole somemodel object as parameter of navigate(), you can pass its id
ui.navigate(DetailLayout.class, somemodel.getId());
And in the DetailLayout.setParameter() you can load the somemodel by its id
#Override
public void setParameter(BeforeEvent beforeEvent, Long someModelId) {
if(someModelId == null){
throw new SomeModelNotFoundException("No SomeModel was provided");
}
SomeModel someModel = someModelService.findById(someModelId);
if(someModel == null){
throw new SomeModelNotFoundException("There is no SomeModel with id "+someModelId);
}
// use someModel here as you wish. probably use it for a binder?
}

If you are using Spring with Vaadin Flow then you could create a #UIScoped bean and add your own fields storing state related to the current browser window/tab. The bean will be available as long as the UI is present.

Related

How to listen for local changes in data class object, so remote Firebase document is updated automatically

I have a custom data class in Kotlin that is to be synced with Firebase as per the manual.
data class Data(
var description: String? = null,
var user_created_id: String? = null,
var user_responded_id: String? = null,
var status: Long? = null,
)
That manual provides a way to listen for remote changes in Firebase via adding addSnapshotListener to the Firebase document reference.
However I would like to set up a listener in the opposite way also: when something changes in at least one field in my local data object, the Firebase database should update automatically.
I assume Delegates.observable should work that way. However, when I add it to the data object, it runs code on listener only after the object is created. After I change its fields, nothing happens...
override var data : Data? by Delegates.observable(Data()) { property, oldValue, newValue -> { log("something was changed in the object") }
Right now I can think of the following two ways to implement this.
Instead of updating the fields, update the entire object with new fields.
Example: To update the description, you can do:
data?.description = "New description" // won't notify the observer
OR
data = data?.copy(description = "Another description") // will notify the observer
If you go this way, you can make your fields val instead of var.
If you don't need your class to be a data class, use a normal class and override setters for the fields.
class Data(val onDataChange: (Data) -> Unit) {
var description: String? = null
set(value) {
field = value
onDataChange(this)
}
// Similarly for other fields
}
Here, instead of overriding the setter, you could have also used Delegates.observable for each field.
If you really want an observer pattern you can adopt one of these two approaches (the first one looks nicer to me).

How to include all parameters of a standard HST component in child component?

A lot of my custom components extends the EssentialsListComponent. The same standard HST component has a lot of useful parameters such as pageSize and sortOrder (input through the Console) which I currently have to handle individually in my classes. This process is tedious and prone to human error.
How can I apply all the standard parameters at once to my HST query in my custom HST component? For example, something like the following would be lovely:
#Override
protected <T extends EssentialsDocumentListComponentInfo> HstQuery buildQuery(HstRequest request, T paramInfo,
HippoBean scope) {
scope = request.getRequestContext().getSiteContentBaseBean();
try {
HstQuery hstQuery = request.getRequestContext().getQueryManager().createQuery(scope);
hstQuery.applyParameters(paramInfo);// paramInfo should already includes pageSize, sortOrder etc. right?
} catch (Exception e) {
}
}
You can extend the EssentialsDocumentListComponentInfo interface, suppose MyDocumentListComponentInfo which is also an interface.
On the MyDocumentListComponentInfo interface override the methods you want to have default values such as pageSize and sortOrder.
Lets say you want the pageSize to have a default value of 20 instead of 10. For this you would use the code below:
#Parameter(name = "pageSize", required = true, defaultValue = "20", displayName = "Page size", description = "Nr of items per page")
int getPageSize();
With this you do not need to declare the property in the Console, unless you want a custom value.
Now in the components you extend, declare the following annotation above the class declaration:
#ParametersInfo(type = MyDocumentListComponentInfo.class)
Now your code should work fine and you have less parameters to configure every time.
see: http://www.onehippo.org/library/concepts/component-development/hstcomponent-parametersinfo-annotation.html
If you extend a component you inherit its parameters. |If you set it up to inherit in the hst configuration you can access those parameters. You can also reuse or extend existing parameterinfo interfaces to make cleaner code. If a parameter isn't included in the parameterinfo then you can still refer to it by name.
Note the documentation link is for version 10. If you need documentation on 7.9 or older then click the history link at the top of the article.

Dynamically creating a JSF form with java reflection

I'm trying to create a simple crud form to insert data into a database with hibernate, without knowing what the object type is. The ultimate goal is to only have one insert form for every table in the database. So far i get the methods that the current object has, check to see if it has any set methods and create a text input for every field that has a set.
UIViewRoot viewRoot = FacesContext.getCurrentInstance().getViewRoot();
HtmlPanelGrid hpg = (HtmlPanelGrid) viewRoot.findComponent("panel");
for (Method method : declaredFields) {
String name = method.getName();
if (name.contains("set")) {
HtmlOutputText hot = new HtmlOutputText();
HtmlInputText hit = new HtmlInputText();
hot.setValue(name.substring(3));
try {
hit.setValue(newObject.getClass().getMethod(name, String.class));
} catch (Exception ex) {
Logger.getLogger(ReflectController.class.getName()).log(Level.SEVERE, null, ex);
}
hpg.getChildren().add(hot);
hpg.getChildren().add(hit);
}
}
Here newObject is the object that is going to be inserted into the database later with hibernate. My problem is this:
How do assign a certain field from that object to the text input that is being created at the moment. So far if I put the method in the value like I'm doing above, it will just print out the method in the value attribute for that input. what i want is that when this form is submited, for to assign the value in that text box to the property with that name.
I can give you a partial answer - You need to create a ValueExpression dynamically
Application app = FacesContext.getCurrentInstance().getApplication();
hit.setValueExpression("value", app.getExpressionFactory().createValueExpression(FacesContext.getCurrentInstance().getELContext(), "#{bean.item}", Item.class));
The hard part will be creating the valueExpression that will actually map to a field within your object's value. That requires a great deal more thought but you will for sure need the dynamic valueExpression. As written, this will result in the execution of your bean's setItem();method with a parameter of type Item. You will require something a little more complex.
In JSF, binding input components to properties is accomplished with EL-expressions. You can create one programmatically as Steve shows, but that syntax is really ugly. On a related note, programmatic manipulation of the component tree is a rather unorthodox way of using JSF. The orthodox way to tackle your requirement would be something like:
<ui:repeat var="prop" value="#{genericEditorBean.propertyNames}">
<h:outputLabel value="#{prop}" for="input"/>
<h:inputText id="input" value="#{genericEditorBean.object[prop]}"/>
</ui:repeat>
where
public List<String> getPropertyNames() {
List<String> propertyNames = new ArrayList<>();
BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass());
for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
propertyNames.add(pd.getName());
}
return propertyNames;
}
(There really is no reason to reimplement scanning for Java Bean properties when the Java API offers a class for that very purpose. Unlike your home-grown version, this will also handle properties inherited from a super class ...)
I once used an open-source library named MetaWidget to do this.
It was a few years ago, but it worked well and was easy to set up.
It looks like the project is still active:
http://metawidget.sourceforge.net/index.php

Pass data to a #Finally annotated method in Play Framework

In Play!, I want to log something after I executed a controller action, using the #Finally annotation. However, I need some data from the database I sent to my view. Is it possible to access this data in the #Finally annotated method?
This is the method in particular:
#Finally
private static void logSomething() {
//System.out.println("User: " + u.first_name);
System.out.println(response);
for (String key : response.headers.keySet()) {
System.out.println(key);
}
}
How can I pass parameters to this? If I put a parameter in the definition, it's always null (cause how should this method even know what data to pass?).. so is it possible?
If you sent it to your view, then it will be available from the renederArgs map.
So, assuming you called your render method in some way like this...
User user = User.findById(someId);
render(user);
Then you should be able to access it in renderArgs as follows
User user = (User)renderArgs.get("user");

Does GWT RequestFactory support implementation of optimistic concurrency control?

In a GWT app I present items that can be edited by users. Loading and saving the items is perfomed by using the GWT request factory. What I now want to achive is if two users concurrently edit an item that the user that saves first wins in the fashion of optimistic concurrency control. Meaning that when the second user saves his changes the request factory backend recognizes that the version or presence of the item stored in the backend has changed since it has been transfered to the client and the request factory/backend then somehow prevents the items from being updated/saved.
I tried to implement this in the service method that is used to save the items but this will not work because request factory hands in the items just retrieved from the backend with applied user's changes meaning the versions of these items are the current versions from the backend and a comparison pointless.
Are there any hooks in the request factory processing I coud leverage to achieve the requested behaviour? Any other ideas? Or do I have to use GWT-RPC instead...
No: http://code.google.com/p/google-web-toolkit/issues/detail?id=6046
Until the proposed API is implemented (EntityLocator, in comment #1, but it's not clear to me how the version info could be reconstructed from its serialized form), you'll have to somehow send the version back to the server.
As I said in the issue, this cannot be done by simply making the version property available in the proxy and setting it; but you could add another property: getting it would always return null (or similar nonexistent value), so that setting it on the client-side to the value of the "true" version property would always produce a change, which guaranties the value will be sent to the server as part of the "property diff"; and on the server-side, you could handle things either in the setter (when RequestFactory applies the "property diff" and calls the setter, if the value is different from the "true" version, then throw an exception) or in the service methods (compare the version sent from the client –which you'd get from a different getter than the one mapped on the client, as that one must always return null– to the "true" version of the object, and raise an error if they don't match).
Something like:
#ProxyFor(MyEntity.class)
interface MyEntityProxy extends EntityProxy {
String getServerVersion();
String getClientVersion();
void setClientVersion(String clientVersion);
…
}
#Entity
class MyEntity {
private String clientVersion;
#Version private String serverVersion;
public String getServerVersion() { return serverVersion; }
public String getClientVersion() { return null; }
public void setClientVersion(String clientVersion) {
this.clientVersion = clientVersion;
}
public void checkVersion() {
if (Objects.equal(serverVersion, clientVersion)) {
throw new OptimisticConcurrencyException();
}
}
}
Note that I haven't tested this, this is pure theory.
We came up with another workaround for optimistic locking in our app. Since the version can't be passed with the proxy itself (as Thomas explained) we are passing it via HTTP GET parameter to the request factory.
On the client:
MyRequestFactory factory = GWT.create( MyRequestFactory.class );
RequestTransport transport = new DefaultRequestTransport() {
#Override
public String getRequestUrl() {
return super.getRequestUrl() + "?version=" + getMyVersion();
}
};
factory.initialize(new SimpleEventBus(), transport);
On the server we create a ServiceLayerDecorator and read version from the RequestFactoryServlet.getThreadLocalRequest():
public static class MyServiceLayerDecorator extends ServiceLayerDecorator {
#Override
public final <T> T loadDomainObject(final Class<T> clazz, final Object domainId) {
HttpServletRequest threadLocalRequest = RequestFactoryServlet.getThreadLocalRequest();
String clientVersion = threadLocalRequest.getParameter("version") );
T domainObject = super.loadDomainObject(clazz, domainId);
String serverVersion = ((HasVersion)domainObject).getVersion();
if ( versionMismatch(serverVersion, clientVersion) )
report("Version error!");
return domainObject;
}
}
The advantage is that loadDomainObject() is called before any changes are applied to the domain object by RF.
In our case we're just tracking one entity so we're using one version but approach can be extended to multiple entities.

Categories