I'm trying to create a dynamic table component in wicket to show a list of Objects. The component will receive a List of Objects and render it in a table form.
Assuming the following Product entity:
public class Product {
public static final long serialVersionUID = 1L;
//
private String id;
private String name;
private String description;
private Double price;
// getters and setters omitted for brevity
}
The following html/code pair will work:
(Note: Product entity will later be genericized so we can pass it a list of any POJO)
<table>
<tbody>
<tr wicket:id="row">
<td wicket:id="cell">
cell
</td>
</tr>
</tbody>
</table>
----------------------------------------------------------------
parameters:
final String[] fieldNames = new String[]{"id", "name", "description", "price"};
List<Product> productList = ....
----------------------------------------------------------------
ListView lvRows = new ListView("row", productList) {
#Override
protected void populateItem(ListItem item) {
Product product = (Product)item.getModelObject();
CompoundPropertyModel cpm = new CompoundPropertyModel(product);
//
RepeatingView cell = new RepeatingView("cell", cpm);
item.add(cell);
//
for (String fn : fieldNames) {
Label label = new Label(fn);
cell.add(label);
}
}
};
this.add(lvRows);
The problem is that the above code will result in a bunch of warnings (one for each Product in the list):
00:57:52.339 [http-apr-8080-exec-108] WARN
o.a.w.m.repeater.AbstractRepeater - Child component of repeater
org.apache.wicket.markup.repeater.RepeatingView:cell has a non-safe
child id of id. Safe child ids must be composed of digits only.
So my questions are:
am i doing it wrong?
if not, how do i get rid of the warnings?
Why does wicket require numeric child id in this instance? CompoundPropertyModel works fine in other situations, with the id linked to object attributes...
if i'm doing it wrong, what's the "proper" way to do it? Should i create the child ids uniquely, forfeit using CompoundPropertyModel, and feed the values directly via reflection? Not sure about the performance impact though, doing a reflection call for each cell can't be cheap. Something like this:
for(String fn:fieldNames} {
String s = ...; //find the value of Object O, property fn via Reflection
Label label = new Label(cell.newChildId(), s);
cell.add(label);
}
Thanks in advance.
You can avoid the warnings by adding an intermediate child in the RepeatingView, as explained by Igor Vaynberg in this post at the Wicket users list.
In other words, don't add the Labels with the non-numeric ids directly to the repeater, add a container with a numeric id instead, and add the Labels to the container:
RepeatingView cell = new RepeatingView("cell");
WebMarkupContainer container = new WebMarkupContainer(cell.newChildId(), cpm);
for (String fn : fieldNames) {
Label label = new Label(fn);
container.add(label);
}
cell.add(container);
item.add(cell);
The reasons for those warnings are also outlined in that post.
EDIT:
It seems that you'd need to model a ProductPanel and use it inside the RepeatingView instead of just a WebMarkupContainer. This means that the ProductPanel would probbaly have to explicitly enumerate properties (or wicket:ids) in its HTML.
You could also keep your current code and just do Label label = new Label(cell.newChildId(), new PropertyModel(product, fn)); and drop the CPM, if you want the HTML to be independent from the specific properties.
Related
In drop down I get like this. find image attached. Actually in coulmn of "Name" field both 'Name' and 'Description' are displaying as comma(,) separated.
final ComboBoxItem comboBoxItem = new ComboBoxItem("attributeTypeId","Attr. Type");
ListGridField nameField = new ListGridField("name", "Name");
ListGridField descField = new ListGridField("description","Description");
descField.setShowHover(true);
comboBoxItem.setPickListFields(nameField, descField);
comboBoxItem.setPickListWidth(200);
comboBoxItem.setFilterLocally(true);
comboBoxItem.setColSpan(2);
comboBoxItem.setAddUnknownValues(false);
comboBoxItem.setValueField(FieldNames.ID_FIELD);
comboBoxItem.setDisplayField(FieldNames.NAME_FIELD);
comboBoxItem.setAutoFetchData(true);
OptionListDataSource attrTypeds = OptionListDataSource.getInstance(FieldNames.ATTRIBUTE_TYPE_FIELD);
attrTypeds.fetchData(null, new DSCallback() {
#Override
public void execute(final DSResponse response, final Object rawData, final DSRequest request) {
Record[] recList = response.getData();
LinkedHashMap<String, String[]> dataLinkMap = new inkedHashMap<String,String[]>(); //LinkedHashMap<String,
dataLinkMap.put("0", new String[]{"Select",""});
for (Record record : recList) {
String attrId = record.getAttribute(FieldNames.ID_FIELD);
String attrName = record.getAttribute(FieldNames.NAME_FIELD);
String attrDesc = record.getAttribute(FieldNames.DESCRIPTION_FIELD);
dataLinkMap.put(attrId, new String[]{attrName,attrDesc});
}
comboBoxItem.setValueMap(dataLinkMap);
}
});
Screen Shot
Here is some sample code to achieve what I understand you want to achieve:
public class TestCases implements EntryPoint {
public void onModuleLoad() {
DataSource logDS = DataSource.get("yourDSName");
final DynamicForm form = new DynamicForm();
form.setWidth(550);
form.setNumCols(2);
ListGridField nameField = new ListGridField(FieldNames.NAME_FIELD);
ListGridField descriptionField = new ListGridField(FieldNames.NAME_DESCRIPTION);
LinkedHashMap<String,String> hashMap = new LinkedHashMap<String,String>();
hashMap.put("-1", "Select");
ComboBoxItem myItem = new ComboBoxItem();
myItem.setTitle("ComboBox");
myItem.setOptionDataSource(logDS);
myItem.setDisplayField("category");
myItem.setValueField(FieldNames.ID_FIELD);
myItem.setSpecialValues(hashMap);
myItem.setPickListWidth(300);
myItem.setPickListFields(nameField, descriptionField);
form.setItems(myItem);
form.draw();
}
}
Notice:
In order to display various fields, you need to use setPickListFields with the reference to those fields.
You don't need to call fetch() on the DataSource itself. This is done automatically for you when you use DataBound components like ComoBoxItem.
You can add additional empty values using setSpecialValues() without modifying your DSResponse data (which is why you don't need to use fetch() directly).
EDIT
The problem you are having is that the ValueMap, which is just a Map (in other words, just a group of key/value pairs), that you are providing to the ComboBoxItem is not the same as the Record[] object provided directly by the DataSource, which in essence is just a List made of several Maps, each representing a field name and its value. This way, besides the value field, you can provide several fields to the ComboBoxItem for display purposes, like Name and Description, in your particular case.
From looking at the API, it looks to me that you can't provide a Record[] manually to the ComboBoxItem, so either you get the data via DMI (which for me is the easiest) or other method that allows you to modify and return the required response from the server automatically to the ComboBoxItem by using the data binding capabilities, or you stick to showing just the "values" (which is what you are getting right now, but off course you could format the data better).
What I mean with formatting is that if you choose to go with your original approach of using setValueMap(), you need to provide a Map where each entry in the Map is just a value on the ComboBoxItem and its respective display "text", which can be any String combining the values of several other fields, and formatted as desired using String concatenation (for instance, you could make it
nameField + ": " + descriptionField
But this is as good as it gets with this approach.
Now, via a DMI you would need to define the server class that would provide the properly-formatted data in you datasource descriptor (ds.xml file):
<operationBindings>
<operationBinding operationType="fetch" serverMethod="fetchComboBoxData">
<serverObject lookupStyle="new" className="com.myApp.ComboBoxDMI"/>
</operationBinding>
</operationBindings>
And then create the class and method to provide what you need:
public class ComboBoxDMI {
public DSResponse fetchComboBoxData(DSRequest dsRequest) throws Exception {
DSResponse response = dsRequest.execute();
if (response.statusIsSuccess()) {
#SuppressWarnings("unchecked")
List<Map<String, Object>> recList = response.getRecords();
List<Map<String, Object>> comboBoxList = new ArrayList<Map<String,Object>>();
// Add here the new record... for each field in your DataSource, you need to set a Map
// with the key being the field name and the value being the field value. So you need
// 1 Map entry per field. All your Map entries form 1 record, and that's what you add
// to your List of Maps
return constructDSResponse(comboBoxList);
}
return response;
}
private DSResponse constructDSResponse(List<Map<String, Object>> comboBoxList) {
DSResponse response = new DSResponse();
int totalRows = comboBoxList.size();
response.setStartRow(totalRows > 0 ? 1 : 0);
response.setEndRow(totalRows);
response.setTotalRows(totalRows);
response.setData(comboBoxList);
return response;
}
}
Finally, you can follow the original approach I suggest in my original answer, but now you don't need to use the setSpecialValues API, which your version doesn't support.
I am attempting to build a web application using Wicket and OrientDB. I am trying to avoid writing/maintaining a flat Java class that represents each Class of vertex as a POJO (like an ORM). Rather, I am sending the vertices themselves all the way to the web layer. I access the properties via:
vertex.getProperty("propertyName");
Meaning that the properties themselves are not member variables of Vertex and thus cannot be accessed through normal getters/setters. I am running into an issue with Wicket because many components depend on a "PropertyModel" style implementation where you pass in a Model that represents one of the member variables of the class you're working with, and the data is stored in that member variable.
I have this DataView that pulls the properties of a vertex out into a Label and a TextField.
public VertexViewer(final PageParameters pageParameters, OrientVertex vertex)
this.vertex = vertex;
this.properties = this.vertex.getProperties();
List<String> keyList = new ArrayList<>();
keyList.addAll(this.vertex.getPropertyKeys());
final DataView<String> propertiesView = new DataView<String>("properties", new ListDataProvider<>(keyList)) {
#Override
protected void populateItem(Item item) {
String key = item.getModelObject().toString();
item.add(new Label("property_name", key));
item.add(new TextField<String>("edit_field", new Model<String>(properties.get(key).toString())));
}
};
add(propertiesViewer);
}
And the HTML:
<wicket:extend>
<div wicket:id="properties" style="display: table-row;">
<div wicket:id="property_name" style="display: table-cell;"></div>
<input wicket:id="edit_field" type="text" style="display: table-cell;"/>
</div>
</wicket:extend>
This renders exactly how I want it to, but does anyone have any recommendations on how I can save the data that is being changed in the TextFields? I can't use a model of a member variable like I would normally do on a Form because I don't ever know exactly what/how many properties are going to be in a vertex. Am I going about this in completely the wrong way? Any help is much appreciated.
Take a look at https://github.com/OrienteerDW/wicket-orientdb.
The developer of this library also created https://issues.apache.org/jira/browse/WICKET-5623, but this improvement didn't get much support. Please feel free to vote for it if you think it is needed.
So, after much trial and error (mostly error) I found a patttern that gets around Wicket's NoSQL deficiencies and doesn't involve manually creating member variables to make Models of.
I get the Map<String, Object> from vertex.getProperties() and then create a copy of it.
private Map<String, Object> realProperties = baseVertex.getProperties();
private Map<String, Object> dummyProperties = new HashMap<>();
dummyProperties.putAll(realProperties);
This allows me to use the values in the dummyProperties as Models to store data.
List<String> keyList = new ArrayList<>();
keyList.addAll(getDummyProperties().keySet());
final DataView<String> propertiesView = new DataView<String>("properties", new ListDataProvider<>(keyList)) {
#Override
protected void populateItem(Item item) {
String key = item.getModelObject().toString();
item.add(new Label("property_name", key));
item.add(new TextField<String>("edit_field", new PropertyModel<String>(getDummyProperties(), key)));
}
};
From there, I put the DataView on a Form with an AjaxButton that compares the potential new values in the TextFields to the original values through some convoluted looping.
AjaxButton saveButton = new AjaxButton("saveButton") {
#Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
super.onSubmit(target, form);
Map<String, Object> changedProperties = new HashMap<>();
// for each entry in the dummy values linked via PropertyModel to the TextField input
for (Map.Entry<String,Object> dummyEntry : getDummyProperties().entrySet()) {
// for each entry in the
for (Map.Entry<String,Object> realEntry : baseVertex.getProperties().entrySet()) {
// if the keys match
if (dummyEntry.getKey().equals(realEntry.getKey())) {
// if the value has changed
if (!dummyEntry.getValue().equals(realEntry.getValue())){
// value in textField differs from value in database
changedProperties.put(dummyEntry.getKey(),dummyEntry.getValue());
}
}
}
}
DBWorker worker = new DBWorker();
// perform the update
worker.updateVertex(recordID, changedProperties);
// pull the fresh vertex out and pass it to the page again
setResponsePage(new VertexViewer(new PageParameters(), new CustomVertex(worker.getVertexByID(recordID))));
}
};
This ends up evaluating the new values against the old, writing the new ones back to the database, pulling back the fresh vertex and calling a new ViewerPage that accepts the updated vertex as an argument.
This works as intended, is generic to the class of vertex/number of properties, and prevents me from having to maintain ORM style Classes for each vertex class.
I have the following properties to display in the Vaadin Table
public class Item
{
String itemName;
String itemSource;
String itemStatus;
...
}
Next, I retrieve my data using the ItemContainer, and connect it to the table:
ItemContainer container = new ItemContainer(Item.class, app);
table.setContainerDataSource(container);
The table will display the data based on Item class.
The question: can I change "somehow" the datatype of the fields on the "fly" ? So I want change the "itemStatus" to be Label since display HTML tags in correct styles.
For example the text in "itemStatus" is: <b>Status is:</b><i>Completed</i>. I want to see in the table formatted string Status is:Completed
You can override a container property in Table by adding a generated column with the same id:
tablet.addGeneratedColumn("itemStatus", new ColumnGenerator() {
#Override
public Object generateCell(Table source, Object itemId, Object columnId) {
Label label = new Label("" + source.getContainerProperty(itemId, columnId).getValue());
label.setContentMode(ContentMode.HTML);
label.setSizeUndefined();
return label;
}
});
I am new to JavaBeans and I need a little help to keep my first little JSF-project going.
I am writing a little web application where a user can search with certain criteria for buildings. So the user enters in the search form 'location', 'property type', 'asking price', 'number of rooms' and 'living space'.
My managed bean accept the requiry with setter/getter and now the data is to be transmitted to a SQL class, where they are processed and matching search results are returned. It sounds simple, but I can not find a solution.
My managed bean looks like this now:
package beans
//import statements
...
#ManagedBean
#RequestScoped
public class PropertySearchBean {
private String _place
private String _propertyType
private double _askingPrice
private int _rooms
private double _livingSpace
public ArrayList<SearchResults> results = new ArrayList<SearchResults>();
// empty constructor
...
// getter and setter for these 5 user inputs
...
public void initializeSearchResults() {
// do the SQL query, recieve search results
// add it as a new object of 'SearchResults'
SQLPropertySearch search = new SQLPropertySearch(_place, _propertyType,
_askingPrice, _rooms, _livingSpace);
ArrayList<Integer> idResults = search.getPropertyIDlist();
SQLProperty property;
if(!idResults.isEmpty()) {
for(int i=0; i<idResults.size(); i++) {
property = new SQLProperty(idResults.get(i));
results.add(new SearchResults(
property.getPropertyID(),
property.getPropertyName(),
// and so on..
));
}
}
}
public static class SearchResults {
int propertyID;
String propertyName;
// and so on..
public SearchResults(int propertyID, String propertyName) {
this.propertyID = propertyID;
this.propertyName = propertyName;
// and so on..
}
// getter and setter
public int getPropertyID() {
return propertyID;
}
public void setPropertyID(int propertyID) {
this.propertyID = propertyID;
}
// and so on..
}
public ArrayList<SearchResults> getResults() {
return results;
}
}
In my XHTML-file I go through each entry of my ArrayList results.
It looks like this:
<ui:repeat var="res" value="#{PropertySearchBean.results}">
<p>#{res.propertyID}</p>
<p>#{res.propertyName}</p>
</ui:repeat>
I don't have an idea how to initialize the ArrayList, because first thing to do is the search itself, with the user input.
I am thankful for any kind of help!
You've removed the getters and setters from your example to improve readability. I'll provide one implementation here to ensure a common understanding (especially regarding the leading underscores).
public String getPlace() {
return _place;
}
public void setPlace(String place) {
this._place = place;
}
The property 'place' will be accessible within your view by using the value binding #{propertySearchBean.place}(see below).
Your code is meant to perform a search. Therefore you'll have to transfer user input from your XHTML file (view) to your managed bean. To do so you need to add a form to your view. Each search query parameter is bound to your bean using a specific value binding. Additionally the form contains a <h:commandButton> tag which finally triggers initialization of the result list.
<h:form>
<h:outputLabel for="place" value="Place:" />
<h:inputText id="place" value="#{propertySearchBean.place}" />
<!-- Additional fields -->
<h:commandButton action="#{propertySearchBean.initializeSearchResults}"
value="Search"/>
</h:form>
Note: You've used the following code in your example
<ui:repeat var="res" value="#{PropertySearchBean.results}">
Make sure that the first letter of your bean name is lower-case (propertySearchBean instead of PropertySearchBean). So this needs to be updated to
<ui:repeat var="res" value="#{propertySearchBean.results}">
I need to validate something about several Wicket input fields of type TextField<BigDecimal> (namely that the sum of percentages is 100). There are one to many such input fields; thing is, I don't know in advance how many.
(simplified example)
private class PercentageValidator extends AbstractFormValidator {
#Override
public FormComponent<?>[] getDependentFormComponents() {
// ...
}
#Override
public void validate(Form<?> form) {
List<TextField<BigDecimal>> fields = // TODO
// the actual validation where the value of every field is needed
}
}
Java code for the ListView:
ListView<?> listView = new ListView<PropertyShare>("shares", shares) {
#Override
protected void populateItem(ListItem<PropertyShare> item) {
// ...
item.add(new TextField<BigDecimal>("share", ... model ...));
}
};
HTML:
<tr wicket:id="shares">
<td> ... </td>
<td>
<input wicket:id="share" type="text" size="4"> %
</td>
</tr>
I tried keeping every TextField in a collection on the Page, but this approach fails as the populateItem() method of the enclosing ListView gets called not only the the Page is first created, so duplicate fields get added to the collection. (I couldn't figure out an easy way to keep it duplicate-free.)
The fact that ListView is used also seems to somewhat complicate finding the fields from the form object in the validate() method. I suppose I need to get the ListView with form.get("shares") and iterate through its children?
What's the "right way" to access any number of fields enclosed by a repeater such as ListView?
An alternative approach would be to subclass TextField and then use a Visitor to pick out all the descendant components of your subclass.
This way you can avoid unchecked casting and you don't have to rely on the ids, which isn't a very robust approach.
Edit: in practice, it would look something like this:
The subclass:
private static class ShareField extends TextField<BigDecimal> {
// ...
}
Helper method that finds all ShareFields from the form:
private List<ShareField> findShareFields(Form form) {
final List<ShareField> fields = Lists.newArrayList();
form.visitChildren(ShareField.class, new IVisitor<ShareField>() {
#Override
public Object component(ShareField component) {
fields.add(component);
return CONTINUE_TRAVERSAL;
}
});
return fields;
}
Right, while writing the question, it dawned on me that simply looping through the children of form.get("shares") and getting the field with id "share" would probably work.
It indeed does. Here's a helper method that finds the "share" fields:
#SuppressWarnings("unchecked")
private List<TextField<BigDecimal>> findFields(Form form) {
List<TextField<BigDecimal>> fields = Lists.newArrayList();
MarkupContainer container = (MarkupContainer) form.get("shares");
for (Iterator<? extends Component> it = container.iterator(); it.hasNext();) {
MarkupContainer c = (MarkupContainer) it.next();
fields.add((TextField<BigDecimal>) c.get("share"));
}
return fields;
}
However, there are three somewhat ugly casts in the above method, and one of those (Component -> TextField<BigDecimal>) produces an "unchecked cast" warning.
If you can clean up this solution, or know of better approaches, feel free to comment or post other answers!
As far I see you did not set the reuse items property on the list view; from the java doc:
If true re-rendering the list view is more efficient if the windows doesn't get changed at all or if it gets scrolled (compared to paging). But if you modify the listView model object, than you must manually call listView.removeAll() in order to rebuild the ListItems. If you nest a ListView in a Form, ALLWAYS set this property to true, as otherwise validation will not work properly.
However you also can iterate over the children of the listview with a Visitor. Wicket always keeps track of the components you added of the view.