How can I right-align a cell in a Wicket table column? - datatable

I'd like to have a PropertyColumn of a DataTable right-aligned. But if I try to add a new SimpleAttributeModifier("align", "right") to the cell item, it is added to a span within the td, rather than the td itself.
public class AssetSizeColumn<T> extends PropertyColumn<T> {
...
#SuppressWarnings("unchecked")
public void populateItem(Item<ICellPopulator<T>> item, String componentId, IModel<T> rowModel) {
IModel<Long> model = (IModel<Long>) createLabelModel(rowModel);
Component label = new Label(componentId, model.getValue().toString());
label.add(new SimpleAttributeModifier("align", "right"));
item.add(label);
}
Can I get at the td to set the alignment?

The trick is that the td is the parent of the item as soon as it is added to the ICellPopulator, so that we can add a modifier to it straight away.
public void populateItem(Item<ICellPopulator<T>> item, String componentId, IModel<T> rowModel) {
IModel<Long> model = (IModel<Long>) createLabelModel(rowModel);
Component label = new Label(componentId, model.getObject().toString());
item.add(label);
label.getParent().add(new SimpleAttributeModifier("align", "right"));
}

Related

Vaadin Select - fields changed inside binder's apply do not write changes to bean from item

Using Vaadin 14.7.0.
Inside a CRUD editor (Enhanced CRUD Editor) I'm building various fields, amongst which I have a Select.
The Select is initialized with a list of options but I'm also trying to change the items from CRUD form edit to CRUD form edit depending on changes from my underlying database so that the user can select new values.
BindingBuilder<Item, SelectOption> bindingBuilder = binder.forField(s);
if (prop.isMandatory()) {
bindingBuilder.asRequired(requiredI18n);
}
bindingBuilder.bind(new ValueProvider<Item, SelectOption>() {
private static final long serialVersionUID = 1L;
#Override
public SelectOption apply(final Item item) {
ListPropertyDefinition lp = ((ListPropertyDefinition)prop);
Serializable currentValue = item.get(lp.getName());
Collection<SelectOption> sOptions = null;
if (lp.getSelectOptions() != null) {
ListDataProvider<SelectOption> ldp = (ListDataProvider)s.getDataProvider();
sOptions = ldp.getItems();
} else {
sOptions = getNewOptions(item, prop.getName());
s.setItems(sOptions);
}
return new SelectOption("N/A", currentValue);
}
}, new Setter<Item, SelectOption>() {
private static final long serialVersionUID = 1L;
#Override
public void accept(final Item bean, final SelectOption fieldvalue) {
bean.set(prop.getName(), fieldvalue != null ? fieldvalue.getValue() : null);
}
});
Now, if the s.setItems(sOptions) branch is being called then the Select field gets populated with the new values sent by the backend but when I'm saving the item the value that I get is null, regardless of what I select in the select field.
This does not happen when I do not change the items in the select field (i.e. if branch).
I did some debugging for comparing 2 select fields - one that changes its values on the fly and one that has values that don't change... from what I could see the field that has values changing on the fly has a null buffered value as seen in the attached image:
vs the field that does not have its values modified in the binder's apply method:
Not sure if what I'm doing is the right way of "refreshing" a select field's values and / or what should I do so that I get the selected value back in the bean on CRUD form save.
I think you are doing things in overly complicated manner. Based on your code I think your principal challenge is how to set empty selection to be "N/A"
For that you simply need to enable empty selection to be allowed. You need to have one placeholder item for empty selection, for which you generate "N/A" as caption. Then you can just do:
Binder<Item> binder = new Binder<>();
Select<SelectOption> select = new Select<>();
...
select.setEmptySelectionAllowed(true);
select.setEmptySelectionCaption("N/A");
binder.forField(s).bind(Item::getProperty,Item::setProperty);
public class SelectOption {
...
}
// Make the item bean also to follow POJO convention
public class Item {
private SelectOption property;
public SelectOption getProperty() {
return property;
}
public void setProperty(SelectOption property) {
this.property = property;
}
}

JavaFX Tableview displaying Image#Hashcode rather than the Image itself from display model

I'm trying to display an Image for an object in a table view, I have created a DisplayModel class in order to display the relevant values in the table columns from an observable collection, I have 2 columns containing string values and one integer all of which are displaying fine, I also have an image of the Car I wish to display but it will only display the Image#Hashcode rather than the image itself.
The implementation I currently have is as follows:
The display model class:
private ObjectProperty<Image> image;
private SimpleStringProperty make;
private SimpleStringProperty model;
private SimpleDoubleProperty price;
public DisplayModel(Image image, String make, String model, Double price) {
this.image = new SimpleObjectProperty<>(image);
this.make = new SimpleStringProperty(make);
this.model = new SimpleStringProperty(model);
this.price = new SimpleDoubleProperty(price);
}
public Image getImage() {
return image.get();
}
public void setImage(Image displayImage) {
this.image = new SimpleObjectProperty<>(displayImage);
}
The implementation in the initialise method on the controller for the FXML file:
try {
OutputStream targetFile = new FileOutputStream(s + "\\" + imageUrl);
targetFile.write(fileBytes);
targetFile.close();
File f = new File(s + imageUrl);
image = new Image(f.toURI().toString());
} catch (IOException e) {
e.printStackTrace();
}
Using the ImageView controller and trying to display the same image presents it fine so I'm wondering whether it's do with the ObjectProperty implementation or the TableView control.. I've recently faced a similar and far more common issue of needing to override the toString method to return the actual value rather than the String#HashValue, I wonder whether there is a similar way to resolve this issue.
Any advice would be greatly appreciated, thanks for your time.
This has to do with the updateItem implementation of the TableCell returned by the default cellFactory. This is implemented as follows (ignoring empty cells):
If the item is a Node, display it using setGraphic with the item as parameter. This adds the Node to the scene inside the TableCell.
Otherwise convert the object to text using it's toString method and use setText to display the resulting value as text in the TableCell.
Image is not a subclass of Node; therefore you get the latter behavior.
To change this behavior, you need to use a custom cellFactory that deals with the item type properly:
ItemImage.setCellFactory(col -> new TableCell<Item, Image>() { // assuming model class named Car here; type parameters match the TableColumn
private final ImageView imageView = new ImageView();
{
// set size of ImageView
imageView.setFitHeight(50);
imageView.setFitWidth(80);
imageView.setPreserveRatio(true);
// display ImageView in cell
setGraphic(imageView);
}
#Override
protected void updateItem(Image item, boolean empty) {
super.updateItem(item, empty);
imageView.setImage(item);
}
});
Note that usually you don't keep the graphic for empty cells. I decided to do this in this case to keep the cell size consistent.

Dynamically generated columns in radgrid disappear after postback

Am using radgrid and creating it in aspx but on certain action i add more GridTemplateColumns to the grid.
private void CreateDateColumns(List<DateTime> occurenceList)
{
if (occurenceList != null && occurenceList.Count > 0)
{
int index = 1;
foreach (DateTime occurence in occurenceList)
{
string templateColumnName = occurence.Date.ToShortDateString();
GridTemplateColumn templateColumn = new GridTemplateColumn();
templateColumn.ItemTemplate = new MyTemplate(templateColumnName, index);
grdStudentAttendanceList.MasterTableView.Columns.Add(templateColumn);
templateColumn.HeaderText = templateColumnName;
templateColumn.UniqueName = templateColumnName;
index++;
}
}
}
private class MyTemplate : ITemplate
{
protected RadComboBox rcbAttendance;
private string colname;
private int _index;
public MyTemplate(string cName, int index)
{
colname = cName;
_index = index;
}
public void InstantiateIn(System.Web.UI.Control container)
{
rcbAttendance = new RadComboBox();
rcbAttendance.Items.Add(new RadComboBoxItem("---Select---", "-1"));
rcbAttendance.Items.Add(new RadComboBoxItem("Present", "1"));
rcbAttendance.Items.Add(new RadComboBoxItem("Absent", "2"));
rcbAttendance.Items.Add(new RadComboBoxItem("Leave", "3"));
rcbAttendance.ID = "rcbAttendance" + _index;
container.Controls.Add(rcbAttendance);
}
}
All are fine with creation but when i press save button or any combobox make postback the only dynamically generated columns content disappear and the other columns stay.
What i noticed that columns still in place with headertext but only content are disappeared (in my case content are comboboxes)
After enabling viewstate for grid only header text appear.
What should i do to keep columns contents after postback and get their values ?
When creating template columns programmatically, the grid must be generated completely in the code-behind using the Page_Init event. Then, you must create the templates dynamically in the code-behind and assign them to the ItemTemplate and EditItemTemplate properties of the column. To create a template dynamically, you must define a custom class that implements the ITemplate interface. Then you can assign an instance of this class to the ItemTemplate or EditTemplateTemplate property of the GridTemplateColumn object.
Blockquote
Column templates must be added in the Page_Init event handler, so that the template controls can be added to the ViewState.
Blockquote
Source: Telerik
Basicly, you need to create all GridTemplateColumns in the Page_Init. We had the same problem and this approach fixed it.

Wicket - How to reload/refresh reusable components correctly?

I have a java class:
public Task {
private int id;
private Company sender;
private Company receiver;
//Getter and Setter
...
}
As you can see, I have 2 other custom classes in the task class. And a company has for example Adress and Directory.
I have a CompanyPanel which will reusable be used on the Page. Here is some code from the panel.
public class CompanyPanel extends Panel {
protected List<Company> companies;
public CompanyPanel(String id, IModel<Company> model) {
super(id,new CompoundPropertyModel<Company>(model));
companies = new ArrayList<Company>();
Company company_1 = new Company();
//Setting default predefined values for the company, so I can select it from the dropdown and to set fields automatically
company_1.setFtpAdress("adress1.com");
company_1.setFtpDir("/MusterDir/");
companies.add(company_1);
//SAME for another company
...
companies.add(comany_2);
...
final DropDownChoice<Company> companyList = new DropDownChoice<Company>("companies", model,
new LoadableDetachableModel<List<Company>>() {
#Override
protected List<Company> load() {
return companies;
}
}){
protected boolean wantOnSelectionChangedNotifications() {
return true;
}
};
add(companyList);
final TextField<String> ftpAdress = new TextField<String>("ftpAdress");
ftpAdress.setOutputMarkupId(true);
add(ftpAdress);
final TextField<String> ftpDir = new TextField<String>("ftpDir");
ftpDir.setOutputMarkupId(true);
add(ftpDir);
//added Ajax to dropdown to update textfields automatically, based on selection of dropdown
companyList.add(new AjaxFormComponentUpdatingBehavior("onchange")
{
#Override
protected void onUpdate(AjaxRequestTarget target)
{
target.add(ftpAdress);
target.add(ftpDir);
}
});
}
}
In the Page I use reuseable CompanyPanels.
...
CompanyPanel senderPanel = new CompanyPanel("senderPanel", new PropertyModel(task,"sender"));
senderPanel.setOutputMarkupId(true);
form.add(senderPanel);
CompanyPanel receiverPanel = new CompanyPanel("receiverPanel", new PropertyModel(task,"receiver"));
receiverPanel.setOutputMarkupId(true);
form.add(receiverPanel);
...
When I submit the form I do:
public void onSubmit(AjaxRequestTarget target, Form<?> form) {
//doSomething
target.add(senderPanel);
target.add(receiverPanel);
}
The problem: The company panel is not being rerendered. And I don't really know why.
Workflow:
I select a company from the dropdown panel
The TextFields(which are inside the companyPanel) will be set correctly, based on the dropdown
I modify a textField (which belongs to a company)
I submit the form
I change the company from the dropdown list
I change back to the first company -> PROBLEM: the modified textfields displays still the modified text inside. It was not reseted to the default values.
Any help very appreciated.
Of course they will display the modified values. You create a list of companies in the CompanyPanel constructor. When you modify a company's data, the object is modified inside that list.
A quick way to fix this would be to replace the CompanyPanel panel with a new instance of CompanyPanel in your onSubmit method. That would recreate the list of companies with your default values. You would of course lose the modified values.
Another possibly better fix is to move the companies list creation into the loadabledetachablemodel:
final DropDownChoice<Company> companyList = new DropDownChoice<Company>("companies", model,
new LoadableDetachableModel<List<Company>>() {
#Override
protected List<Company> load() {
List<Company>companies = new ArrayList<Company>();
Company company_1 = new Company();
//Setting default predefined values for the company, so I can select it from the dropdown and to set fields automatically
company_1.setFtpAdress("adress1.com");
company_1.setFtpDir("/MusterDir/");
companies.add(company_1);
//SAME for another company
...
companies.add(comany_2);
...
return companies;
}
This way the list of companies is recreated on every request with the default values.
Make sure you implement a proper equals() and hashCode() method in Company though for DropDownChoice to show the proper selected element though - because in this scenario the object in your model and the objects in the list may never be ==.
You have to provide more code. If you submit the correctly so that the model changes try:
senderPanel.modelChanged();
receiverPanel.modelChanged();
target.add(senderPanel);
target.add(receiverPanel);

Wicket datatable footer

I want to add footer to a datatable. It will do some statistic work like "sum". Can anyone show me how to do this? It will be great if you show me some examples. Thanks!
Regards
Here is an example. In this example we will create page with table with invoices. One invoice in each row. At the bottom of the table will be toolbar in which will be shown the sum of all invoices.
Invoice.java (Object that represents one invoice)
public class Invoice implements Serializable {
private String description;
private int sum;
public Invoice() {}
public Invoice(String description, int sum) {
this.description = description;
this.sum = sum;
}
// setters and getters
}
InvoiceDataProvider.java (Data provider for the table)
public class InvoiceDataProvider implements IDataProvider {
private List<Invoice> invoices;
public InvoiceDataProvider(IModel<Double> sumModel) {
invoices = new ArrayList<Invoice>();
invoices.add(new Invoice("for rent", 550));
invoices.add(new Invoice("for phone", 25));
invoices.add(new Invoice("for insurance", 10));
// reset sumModel
sumModel.setObject(0.0);
// count the sum of invoices
for (Invoice invoice : invoices) {
sumModel.setObject(sumModel.getObject() + invoice.getSum());
}
}
#Override
public void detach() {
}
#Override
public Iterator iterator(long first, long count) {
return invoices.iterator();
}
#Override
public IModel model(final Object object) {
return new AbstractReadOnlyModel<Invoice>() {
#Override
public Invoice getObject() {
return (Invoice) object;
}
};
}
#Override
public long size() {
return invoices.size();
}
}
In the data provider we get data to be shown in the table (invoices list). Than we count sum of the invoices in the list and add the result to the model.
InvoicesToolbar.java (The toolbar for the table. Here the total sum of invoices will be shown)
public class InvoicesToolbar extends AbstractToolbar {
private IModel<Double> sumModel;
public InvoicesToolbar(DataTable table, IModel sumModel) {
super(table);
this.sumModel = sumModel;
add(new Label("sumText", Model.of("total sum")));
add(new Label("sumNumber", sumModel));
}
}
We save sumModel and create two labels. The label 'sumText' will show text 'total sum' and label 'sumNumber' will show the value stored in the model (sumModel).
InvoicesToolbar.html (The toolbar is panel so the appropriate html file should be created)
<?xml version="1.0" encoding="UTF-8" ?>
<wicket:panel xmlns:wicket="http://wicket.apache.org">
<tr>
<td><span wicket:id="sumText"></span></td>
<td><span wicket:id="sumNumber"></span></td>
</tr>
</wicket:panel>
We create one table row with two cells (the table will have two columns) and add html elements with wicket id to render there some text.
DataTablePage.java (The web page with the table)
public class DataTablePage extends WebPage {
public DataTablePage() {
List<IColumn> columns = new ArrayList<IColumn>();
columns.add(new PropertyColumn(Model.of("description"), "description"));
columns.add(new PropertyColumn(Model.of("sum"), "sum"));
IModel<Double> sumModel = new Model<Double>(0.0);
DataTable table = new DataTable("table", columns, new InvoiceDataProvider(sumModel), 10);
table.addTopToolbar(new HeadersToolbar(table, null));
table.addBottomToolbar(new InvoicesToolbar(table, sumModel));
add(table);
}
}
We create list of columns (PropertyColumn - value will be the property of the object). We create model (sumModel). In this model will be lately stored the total sum of the invoices. And we create table. To this table (to the top) we add header toolbar (headers for the columns) and (to the bottom) we add our InvoiceToolbar.
DataTablePage.html (Html markup for the web page)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Table</title>
</head>
<body>
<table wicket:id="table"></table>
</body>
</html>
Above the wicket-core dependency we also need wicket-extensions dependency. The code was tested using wicket 6.18.0.
When populating the data table, say for instance with a ListView, you should increment an Integer Model that will be used later in a Label.
Model sumModel = new Model(0);
listview = new ListView(...) {
... populate(ListItem item) {
int N = // whatever value you need
sumModel.setObject(sumModel.getObject() + N);
}
}
add(listview);
// ...
Label sumFooter = new Label("footer", sumModel);
add(sumFooter);
PS: I have not tested this.
edit
You should check CountLabel component aswell
(http://opensource.55minutes.com/apidocs/fiftyfive-wicket-all/4.0-SNAPSHOT/fiftyfive/wicket/basic/CountLabel.html)

Resources