I have an Entity which is persisted and which provides data for rows in a Vaadin Grid. This part works.
I now want to display a boolean value as an Image. For this I added a getter to the entity. This part only works when the record has been persisted to the database recently, which I think means that the getter is persisted, which is not desired. For rows persisted to the database before introduction of the getter, the field stays blank in the vaadin grid due to enabledIcon property returning something empty.
I tried to disable this behavior by adding a #Transient annotation, but for some reason then the eureka client has errors, and it also does not help with the empty fields.
I am considering creating a new wrapper class by composition instead of inheritance, but then I have to create all these getters and setters manually which seems to me to be bad design.
Any answer is welcome, even the ones which tell me to go with a wrapper class by composition.
package com.xxx.bpspkpibpcheck.model;
import javax.persistence.Entity;
//import javax.persistence.Transient;
import com.xxx.common.model.KPI;
import com.vaadin.server.ThemeResource;
#Entity
public class KPIBusiness extends KPI {
private static final String IMAGES = "images/";
private static final String IMAGE_STATUS_RED = IMAGES + "LED_red_24.png";
private static final String IMAGE_STATUS_GREEN = IMAGES + "LED_green_24.png";
private static final String IMAGE_STATUS_GRAY = IMAGES + "LED_gray_24.png";
private ThemeResource redStatus = new ThemeResource(IMAGE_STATUS_RED);
private ThemeResource greenStatus = new ThemeResource(IMAGE_STATUS_GREEN);
private ThemeResource grayStatus = new ThemeResource(IMAGE_STATUS_GRAY);
//#Transient
//ThemeResource enabledIcon = greenStatus;
//#Transient
public ThemeResource getEnabledIcon()
{
return getEnabled() != 0 ? greenStatus : grayStatus;
}
}
I might have missed something important here but here is my approach to this one.
To me the problems seems to be that in this case model - your entity - and view / presenter stuff have been mixed in your entity class. And you usually do not want to persist view related stuff.
You should separate this image stuff out of your entity and add the image column as generated column to the grid.
See this as an example: How to add a generated column to Vaadin 8 Grid?
UPDATE: provided example link is not about component column which is needed here so as an example:
Add a component column:
grid.addComponentColumn(statusProvider).setCaption("Status").setId("status");
where statusProvider is like:
ValueProvider<GridEntity, Layout> statusProvider = gridEntity -> {
AbsoluteLayout al = new AbsoluteLayout();
al.setSizeUndefined();
al.addStyleName("status");
String styleName = (gridEntity.isStatusOk()) ? "green" : "red";
al.addStyleName(styleName);
return al;
};
So in my version the "traffic lights" are implemented by cascading css stuff with addStyleName(...) (using the default mytheme.scss) but of course the LEDs images can alos be used either in css or as you originally planned.
.status {
width: 30px;
height: 30px;
border-radius: 15px;
margin: 0;
padding: 0;
}
.green {
background-color: green;
}
.red {
background-color: red;
}
Related
Good evening, I tell you my problem:
In the ZK Framework I need to use the onSelect method of a dynamically rendered Combobox within a Listbox that is also rendered.
When I select one of the Combobox options, its content should be saved in the observaciones variable of the DocumentoVinculado class. But the onSelect don't work! I appreciate any help. Attached code:
.zul
<zk>
<window id="myWindow" apply="com.curso.controller.NewFileComposer" title="Help">
<listbox id="myListbox">
<listhead>
<listheader label="NroGEBI"></listheader>
<listheader label="Observaciones"></listheader>
</listhead>
</listbox>
<label id="myLabel"></label>
</window>
</zk>
Composer / Controller
public class NewFileComposer extends BaseController {
private Window myWindow;
private Listbox myListbox;
private Combobox myCombobox0;
private Combobox myCombobox1;
private Label myLabel;
public void onSelect$myCombobox0() { myLabel.setValue(myCombobox0.getValue()); }
public void onSelect$myCombobox1() { myLabel.setValue(myCombobox1.getValue()); }
public void onCreate$myWindow() {
ListModelList<DocumentoVinculado> modelo = new ListModelList<>(crearLista());
myListbox.setModel(modelo);
myListbox.setItemRenderer(new NewFileRender());
}
private List<DocumentoVinculado> crearLista() {
List<DocumentoVinculado> docVinculados = new ArrayList<>();
docVinculados.add(new DocumentoVinculado("123GEBI1", " "));
docVinculados.add(new DocumentoVinculado("123GEBI2", " "));
return docVinculados;
}
}
Render
public class NewFileRender implements ListitemRenderer {
#Override
public void render(Listitem item, Object data, int i) throws Exception {
DocumentoVinculado docVinculado = (DocumentoVinculado) data;
Listcell nroGebiCell = new Listcell(docVinculado.getNroGEBI());
nroGebiCell.setParent(item);
Listcell opcionesCell = new Listcell();
opcionesCell.appendChild(comboboxObservaciones(i));
item.appendChild(opcionesCell);
}
private Combobox comboboxObservaciones(int i) {
Combobox combobox = new Combobox();
List<String> listaDeOpciones = listaDeOpciones();
for(String opcion : listaDeOpciones) {
Comboitem myComboitem = new Comboitem();
myComboitem.setLabel(opcion);
myComboitem.setParent(combobox);
}
combobox.setId("myCombobox" + i);
return combobox;
}
private List<String> listaDeOpciones() {
List<String> opciones = new ArrayList<>();
opciones.add(" ");
opciones.add("Opcion1");
opciones.add("Opcion2");
return opciones;
}
}
Thank you for reading. Cheers!
From your syntax, it looks like you are using GenericForwardComposer as the super class for your BaseController.
Is that correct?
Depending on how much you have already done, I'd recommend checking if you could switch to SelectorComposer instead.
They work the same way (wire components, and listen to events), but the SelectorComposer is much more explicit in how things are wired. GenericForwardComposer can be somewhat error-prone unless you are very clear about its lifecycle.
Regarding why the onSelect would not be firing in this case:
ZK Composers works in phases. One important phase is the "compose" phase, during which the components are created, events listeners are put in places, etc.
At the end of that phase, the "afterCompose" phase takes place. During afterCompose, "magic" stuff (like the wiring of the private components in your current composer, or the auto-forwarding of the onSelect$myCombobox0 in the same class) takes place. It will also attempt to rewire stuff that was missing again just before triggering onCreate on its root anchor component.
Now, if your comboboxes are created dynamically after that step (for example, during an "onCreate$myWindow" event which happens after all of the above is finished), the composer will have already done all of the wiring and forwarding, and will not know to re-check the components for additional wiring.
With all of that explained, what can you do about it?
First, you can consider moving the onCreate code to a doAfterCompose method.
Instead of waiting for onCreate to generate the Listbox content, if you do it during doAfterCompose (override from genericFowardComposer), you should be early enough that auto-wiring will trigger on these components.
That should look like this:
#Override
public void doAfterCompose(Component comp) {
ListModelList<DocumentoVinculado> modelo = new ListModelList<>(crearLista());
myListbox.setModel(modelo);
myListbox.setItemRenderer(new NewFileRender());
}
2nd, if you move from genericForwardComposer to SelectorComposer, you can actually tell the composer to rewire itself "on demand" by using the Selectors.wireComponents method. Of course, that's only valid if your application can be refactored for this change.
I use Vaadin 14 and would know whether it is possible to report changes in the nested list to objects in the main view.
A rough example is shown in the picture. Above you can see the sum as size (here 2), if I press Delete it should change to 1.
Is that possible and how?
concept
I don't have any code yet, it's a thought where I would like to have a hint about what would be possible, e.g. Observer Pattern or something, but code could look something like this
code:
#Rout("")
public class MainView extends VerticalLayout {
private List<CustomDetails> customDetails = new ArrayList<>();
public MainView(){
final var form = new FormLayout();
customDetails.forEach(form::add);
add(H1("Header"), form)
}
}
public class CustomDetails extends Details{
private CustomForm customForm;
private final Service service;
public CustomDetails(){
customForms = new CustomForm(service.getListOfObjects());
this.setContent(customForms)
}
}
public class CustomForm extend FormLayout{
private FormLayout formLayout = new FormLayout();
private List<Object> objects = new LinkedList<>();
public CustomForm(List<Object> list){
this.objects = list;
setUp();
add(new Paragraph("SUM: "+ list.size()), layout);
}
private void setUp(){
objects.forEarch(o->{
....
layout.add(...)
})
}
}
In Vaadin there is an utility class Binder which is used to bind data to forms. If your use case is related to this, i.e. your so called nested layout is in fact a form and objects you refer to are data beans you want bind into that form. I recommend to study that first.
If you have list editor, I would also investigate if it fits your application to implement it with Grid or IronList/VirtualList, which is backed by DataProvider. Say you edit one item, and after saving the item, you can call dataProvider.refreshItem(item) to update the view.
Observer Pattern or something...
Yes, that is a solution. It is a lot of work and has been done before.
One such library is Beanbag.
Note: I wrote this (or rather, I started writing it a day ago).
EDIT:
As of this edit, we have the ObservableCollection interface. You can use it like so:
// You have a collection called "strings".
// Wrap it in an ObservableCollection.
final ObservableCollection<String, Collection<String>, BasicObservableCollection.Default<String, Collection<String>>> observableStrings = ObservableCollections.observableCollection(strings);
// Add a removed observer.
observableStrings.addElementRemovedObserver(observation -> System.out.println("\"" + observation.getValue() + "\" was removed.");
// Remove an element.
observableStrings.remove("hello");
If you need the wrapper to have List methods, just wait until tomorrow evening EST. I'll have the code up by then and will update this post accordingly.
Because this is a question about design I'll start by saying what i have and what i want.
I have a design that uses composition. A Cell object holds a Shape and a Background objects (custom made ones for this example). Each of these 2 have their own data that defines them. here is the example in code:
class Cell {
Shape shape;
Background background;
class Shape {
int size;
Color color;
Point location;
//...
}
class Background {
Color color;
String name;
CoverType type;
//...
}
}
I also have a GUI that needs to represent many cells and i have written how to do it (how to use color, size etc. to create what i want on the screen). It includes classes such as CellRepresentation, ShapeRepresentation and BackgroundRepresentation that have their display properties bound to the the data properties (i think this is called Model and View).
I want to be able to represent changes in the GUI by changing the above data:
a user can (for example) right-click on a shape and set its color. So the data above changes and the change needs to be reflected in the GUI.
a user can also change the whole shape (for example copy-paste it from another cell). Or even the whole cell. These changes also need to reflect in the GUI.
My question is which of the class members need to be JavaFX properties that I bind to.
Here is what I am thinking: the "leaf" properties (size, color, location...) must be properties so I can bind to them the GUI property. But do I need to make the shape and background objects properties too? Only their properties have "Actual" representation on the screen. Ideally i would have liked it that if Shape changes then all of its properties tell their bindings that they could have changed (maybe the color didn't but size did). But it doesn't work this way - even though the Color of a Shape can change when the Shape changes the Color property won't tell whatever is bound to it that it changed.
The same goes for making Cell a property in the lager picture where there are many cells and so on: properties of properties delegating changes.
So I thought of making the Shape and Background also properties and registering an InvalidationListener to them updates their properties. This just doesn't seem right because i would think that with all the support for properties there would be a way to do what i want.
Can someone suggest a way to do this?
Using just the standard JavaFX API you can leverage the Bindings.selectXXX methods to observe a "property of a property".
So for example:
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.paint.Color;
public class Cell {
private final ObjectProperty<Shape> shape = new SimpleObjectProperty<>(new Shape());
public final ObjectProperty<Shape> shapeProperty() {
return this.shape;
}
public final Cell.Shape getShape() {
return this.shapeProperty().get();
}
public final void setShape(final Cell.Shape shape) {
this.shapeProperty().set(shape);
}
public static class Shape {
private final IntegerProperty size = new SimpleIntegerProperty(0);
private final ObjectProperty<Color> color = new SimpleObjectProperty<>(Color.BLACK);
public final IntegerProperty sizeProperty() {
return this.size;
}
public final int getSize() {
return this.sizeProperty().get();
}
public final void setSize(final int size) {
this.sizeProperty().set(size);
}
public final ObjectProperty<Color> colorProperty() {
return this.color;
}
public final javafx.scene.paint.Color getColor() {
return this.colorProperty().get();
}
public final void setColor(final javafx.scene.paint.Color color) {
this.colorProperty().set(color);
}
}
public static void main(String[] args) {
Cell cell = new Cell();
Bindings.selectInteger(cell.shapeProperty(), "size").addListener(
(obs, oldSize, newSize) -> System.out.println("Size changed from "+oldSize+" to "+newSize));
cell.getShape().setSize(10);
cell.setShape(new Shape());
Shape s = new Shape();
s.setSize(20);
cell.setShape(s);
}
}
Will produce the (desired) output
Size changed from 0 to 10
Size changed from 10 to 0
Size changed from 0 to 20
This API has a bit of a legacy feel to it, in that it relies on passing the property name as a string, and consequently is not typesafe and cannot be checked at compile time. Additionally, if any of the intermediate properties are null (e.g. if cel.getShape() returns null in this example), the bindings generate annoying and verbose warning messages (even though this is supposed to be a supported use case).
Tomas Mikula has a more modern implementation in his ReactFX library, see this post for a description. Using ReactFX, you would do:
public static void main(String[] args) {
Cell cell = new Cell();
Var<Number> size = Val.selectVar(cell.shapeProperty(), Shape::sizeProperty);
size.addListener(
(obs, oldSize, newSize) -> System.out.println("Size changed from "+oldSize+" to "+newSize));
cell.getShape().setSize(10);
cell.setShape(new Shape());
Shape s = new Shape();
s.setSize(20);
cell.setShape(s);
}
Finally, if you are creating a list of cells, you can create an ObservableList specifying an extractor. The extractor is a function mapping each element in the list (each Cell) to an array of Observables. If any of those Observables changes, the list fires an update event. So you could do
ObservableList<Cell> cellList =
FXCollections.observableArrayList(cell -> new Observable[] {Bindings.selectInteger(cell.shapeProperty(), "size")});
using the standard API, or
ObservableList<Cell> cellList =
FXCollections.observableArrayList(cell -> new Observable[] {Val.selectVar(cell.shapeProperty(), Shape::sizeProperty)});
using ReactFX. Then just add a ListChangeListener to the list, and it will be notified if the size changes (or if the shape changes to a new shape with a different size). You can add as many observables that are properties (or properties of properties) of the cell in the returned array as you need.
I have the following java and html code:
this.leakageModel = new PropertyListView<Leakage> ( "leakage", new ArrayList<Leakage> ()) {
private static final long serialVersionUID = 1L;
#Override
protected void populateItem (final ListItem<Leakage> item) {
Link<String> brandLink = new Link<String> ("brandLink") {
private static final long serialVersionUID = -480222850475280108L;
#Override
public void onClick () {
//change another model in the page to update
//another table when the link is clicked
}
};
brandLink.add (new Label ("brand"));
item.add (brandLink);
} };
add (this.leakageModel);
html file:
<tr wicket:id="leakage" class="testClass">
<td class="testClass">
<a wicket:id="brandLink" href="#">
<span wicket:id="brand"></span>
</a>
</td>
</tr>
What I want to do is to be able to call a javascript function from inside the onClick() method.
The model update that I currently do inside the onClick method works well and updates another table on the page.
However everything I have tried to call a javascript function or change the css style has failed.
For instance:
Adding a css class:
add (new AttributeAppender("class", new Model("anotherclass"), " "));
Using an AjaxLink type instead, and a number of other things I have tried to no avail.
On a related note, my original intention is to hide all rows in the table except the one I have clicked. Maybe I can do this just from the Java code and have no need for Javascript at all, but updating the css as above doesn't work.
Any suggestions as to what am I doing wrong?
On a related note, my original intention is to hide all rows in the
table except the one I have clicked.
Instead of answering your question, I will try to provide a solution to your problem :).
It makes perfect sense to hide the table row via javascript. I would suggest doing it with Jquery as described in Hiding all but first table row with jQuery:
$("#myTbl tr:not(nth-child(3))").hide();
Now, you have to execute the above javascript snippet each time a user clicks your Wicket link. For this, you can for example create your own link class like this:
public class JavascriptLink extends Label{
public JavascriptLink(String id, String label) {
super(id, label);
add(new AttributeAppender("onclick", "...your javascript here..."));
}
}
I leave it to you to combine the jquery with the JavascriptLink to meet your requirements. It should work going in this direction.
I have been trying to create a custom textfield in tapestry which will render some javascript when it gains focus. But I have been having trouble trying to find an example of this.
Here is some of the code i have started off with:
package asc.components;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.Field;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.ComponentDefaultProvider;
public class DahserTextField implements Field {
#Parameter (defaultPrefix = "literal")
private String label;
#Inject
private ComponentResources resources;
#Inject
private ComponentDefaultProvider defaultProvider;
#Parameter
private boolean disabled;
#Parameter
private boolean required;
String defaultLabel(){
return defaultProvider.defaultLabel(resources);
}
public String getControlName() {
return null;
}
public String getLabel() {
return label;
}
public boolean isDisabled() {
return disabled;
}
public boolean isRequired() {
return required;
}
public String getClientId() {
return resources.getId();
}
}
I have been unsure on what to do next. I do not know what to put into the .tml file. I would be grateful if anyone could help or point me in the right direction.
There is no need to replicate any of TextField's functionality in your own component, instead you should create a component mixin. Mixins are designed to add behaviour to existing components.
From the Tapestry 5 docs:
Tapestry 5 includes a radical feature,
component mixins. Component mixins are
a tricky concept; it basically allows
a true component to be mixed together
with special limited components called
mixins. The component plus its mixins
are represented as just a single tag
in the component template, but all the
behavior of all the elements.
You would use the mixin like this:
<input type="text" t:type="TextField" t:mixins="MyMixin" t:someParam="foo" />
A mixin stub:
#IncludeJavaScriptLibrary("MyMixin.js")
public class MyMixin {
/**
* Some string param.
*/
#Parameter(required = true, defaultPrefix = BindingConstants.LITERAL)
private String someParam;
#Environmental
private RenderSupport renderSupport;
#InjectContainer
private AbstractTextField field;
#AfterRender
void addScript() {
this.renderSupport.addScript("new MyJavascriptClass('%s', '%s');",
this.field.getClientId(), this.someParam);
}
}
Note the #InjectContainer annotation, which injects the containing TextField into your Mixin. In this case, we want the TextField's clientId.
Also note the #IncludeJavaScriptLibrary("MyMixin.js") annotation, which includes the required Javascript file.
The Javascript could look like this:
MyJavascriptClass = Class.create({
initialize: function(textField, someParam)
{
this.textField = $(textField);
this.someParam = someParam;
this.textField.observe('focus', this.onFocus.bindAsEventListener(this));
},
onFocus: function(event)
{
//do something
}
}
The key difference to your approach is that this involves defining your own JS class and using Tapestry's built-in facilities to load and initialize the JS. The use of mixins is also relatively light-weight and elegant in comparison to creating your own components.
The .tml
<t:textfield onfocus="somethingCool()" />
The Java should probably extent TextField? It will need to import a new stylesheet too probably.
--
Pages are actually components, so you would build a component just like you would have any other page. You can embed any other component into them. I hope this is a good starting point for you.