In my current Apache Wicket project I have a search form for querying the database and displaying the query results in a ListView. The search input box is on the same page as the ListView with the results, and that ListView is filled with query results from a DAO, during invocation of the onSubmit() method of the form.
Everything works fine, but I need to display the number of search results. I tried to create a Label that is filled with the value of the size() method of the list got by the getList() method of the ListView instance, but no luck.
Thank you for any help in advance.
Depending on how you have built this form, you might only need to do label.setModelObject(listResults.size()). It's difficult to tell without seeing how are you doing it.
By what you're telling in your question, probably you're creating your Label like this new Label(labelId, listView.getList().size(). This won't work, you're setting the Label's Model at construction time with a constant value, that's the size of the list at construction time. You need to get that value inside a Model's getObject() to make the value "dynamic". Like, for instance,
AbstractReadOnlyModel sizeModel = new AbstractReadOnlyModel(){
public getObject(){
return listView.getList().getSize();
}
}
new Label(labelId, sizeModel);
With this, every time the page renders, sizeModel().getObejct() will be called to retrieve the value for the Label. In that other way, the Label has got a Model with a constant value.
You could even do label.setModelObject(list.size()) in the onSubmit() method.
From my ignorance on how you have built this form, I'll show you how would I do this. The List of results would be retrieved with a LoadableDetachableModel. That would be the Model of the ListView. Then, the Label can have for instance an AbstractReadOnlyModel that uses the ListViews modelObject to get its size.
public class MyForm extends Form {
private LoadableDetachableModel resultsModel;
private IModel searchModel;
public MyForm(){
searchModel = new Model();
TextField searchTextField = new TextField("search", searchModel);
resultsModel = new LoadableDetachableModel(){
protected Object load(){
return myService.get(searchModel.getModelObject());
}
}
ListView lv = new ListView("list", resultsModel){
// ...
}
Label resultsCount = new Label("count", new AbstractReadOnlyModel(){
public Object getObject(){
return ((List) resultsModel.getObject()).size();
}
})
SubmitButton button = new SubmitButton(){
public void onSubmit(){
//... No actions needed, really
}
}
// add's...
}
}
Using a LoadableDetachableModel for the ListView has the advantage of automatically detaching the Model, and therefore avoiding the whole List of results to get serialized into the Session.
Related
I'm trying to use a ListView as an Editor for Strings, that come out of a custom data model. I use TextFieldListCells with an appropriate StringConverter for the cells.
There is an add button next to the ListView that calls this method on action:
#FXML
private void addElement() {
WordListItem newItem = new WordListItem(-1, "");
wordListItems.add(newItem);
wordListView.setEditable(true);
wordListView.getSelectionModel().select(wordListItems.indexOf(newItem));
wordListView.edit(wordListItems.indexOf(newItem));
wordListView.setEditable(false);
}
Where wordListView is the ListView and wordListItems is the ObservableList containing the data for the wordListView.
This does work, except for when the list is empty (not null), and I couldn't quite explain why, so I inspected the Java source code for help.
Here's what I found out so far: the edit(int) call on ListView changes the ListViews internal editIndex value, which is supposed to call the EDIT_START Event. The editIndex is an ReadOnlyIntegerWrapper in which I found some weird code that I can't quite understand and I'm not sure if thats actually producing a bug or I just can't see why they did it:
#Override
protected void fireValueChangedEvent() {
super.fireValueChangedEvent();
if (readOnlyProperty != null) {
readOnlyProperty.fireValueChangedEvent();
}
}
This method is called whenever the editIndex property of ListView is changed. The problem: readOnlyProperty is null, because it's not set anywhere. The only place I could find where it got set is in the getter:
public ReadOnlyIntegerProperty getReadOnlyProperty() {
if (readOnlyProperty == null) {
readOnlyProperty = new ReadOnlyPropertyImpl();
}
return readOnlyProperty;
}
(ReadOnlyIntegerImpl is an inner private class and readOnlyProperty is it's type)
Now to my actual question: Is this a bug or am I overseeing something? Is there a reason why I can't add and edit a newly created Element in my list like that when it's empty, or is it really just this getter not being called yet?
The source code you found just is code for lazy initializing the property.
Unless new value is assigned to the property or the property itself is requested, null can be used as the property to avoid unnecessary creation of property objects. This is not an issue here.
The issue seems to be the ListView cells not being updated before edit is called. This happens during layout, so "manually" calling layout before starting the edit should work:
private void addElement() {
WordListItem newItem = new WordListItem(-1, "");
wordListItems.add(newItem);
wordListView.setEditable(true);
wordListView.layout();
wordListView.edit(wordListItems.size()-1);
wordListView.setEditable(false);
}
I am worried that I might end up with a lot of "undead" Objects inside my Listener List
that still get notified even though they could simply be removed.
Assume the following classes:
This is my ListView, a UI component where I use the addItem method to populate the list with objects.
After a while I might call clear to remove every Item in the List:
//Displays some Model objects
ListView
{
//Creates a new Cell via createCell and adds the Cell to the ItemList
public void addItem(MyModelObject obj) { ... }
//Simply cleares the ItemList
public void clear() { ... }
...
//Creates a new Cell to be added to the ListView
private ListCell createCell(MyModelObject obj)
{
//Create the Controller for the Cell and return the Cell
return new ListCellController(obj).getCellUI();
}
}
This is the ListCellController seen in the createCell method above. It holds the reference to a Model Object and is responsible for setting the correct contents in the ListCell it controls.
The Controller sets itself as a Listener in the Model Object:
//Handles the Content that is displayed in a ListCell
ListCellController implements MyModelObjectListener
{
private final ListCell _ui = ...
public ListCellController(MyModelObject obj)
{
obj.addListener(this); //Get informed if Model Object changes
_ui.setText(obj.getName()); //Control what is displayed in the Cell
}
//Returns the UI Object that is controlled by this Controller
public ListCell getCellUI() { return _ui; }
...
}
This is the Model Object, it does some stuff and occasionally notifies its listeners if it changed.
//My Model Object that informs listeners if it changed
MyModelObject
{
private List<MyModelObjectListener _listeners = ...;
...
public void addListener(MyModelObjectListener listener) { ... }
}
Now, what happens if I add some Items to the ListView and then clear it? The ListView never holds any references to Controller objects, only to ListCells. If the ListView is cleared, all the references to ListCells are discarded.
The ListCells do not know if they are currently displayed or already discarded and of course the Controller cannot know either.
I assume, that now, even though the ListView is empty, the Model Object still holds the references to the Controllers in the _listeners List. Because of that, the Controllers are not garbage-collected and thus, the ListCells are neither.
Does that mean that, if I add and remove a lot of Items from my ListView I will end up with a huge List of Listeners? How can I avoid this If I do not know if a ListCell is displayed or not?
TL;DR:
Will my _listeners List prevent garbage-collection of Controllers and ListCells?
I'm sorry, but I'm not understanding well what is your problem, but for what I know about garbage collection, even if two object refer to each other, if none of them is referenced in runtime, they will be deleted anyway.
So, if your problem is about this, if you have at least one listener referenced in runtime, it will not be garbage collected, therefore, you would have to remove it manually.
I hope I could be of any help.
Inside my Wicket webpage, I have a WebMarkupContainer which contains a ListView:
notifications = new ArrayList<Notification>(...);
ListView listView = new ListView("notification", notifications) {
#Override
protected void populateItem(ListItem item) {
...
}
};
container = new WebMarkupContainer("container");
container.setOutputMarkupId(true);
container.add(listView);
this.add(container);
The WebMarkupContainer is in place in order to let me dynamically update the list of items shown to the user onscreen. This is possible when the user clicks on a link or by adding the container to incoming AjaxRequestTarget.
Now I'm required to update the list without having an Ajax request:
public void refresh() {
List<Notification> newNotifications = ...
notifications.addAll(0, newNotifications);
}
This method is called in a run-time environment and the list of notifications, which is a private field of my webpage (same one as last code), will contain new objects. I want these new items displayed to the user. Is it possible to update (or re-render) the container?
I'm new to Wicket so if you have a better way to achieve the same results, I would appreciate if you could share it with me.
You would have to do it on a timer. Use AjaxSelfUpdatingTimerBehavior to do so. Just set some sensible duration and add your container to target in 'onTimer()' method.
EDIT:
If your 'refresh()' function is only called when new notifications appear, you could set a flag on your page (define boolean variable on page and change it to true when new notification appears and to false once listView is refreshed). Then you can set short duration on the behavior and 'onTimer()' would look something like that:
onTimer(AjaxRequestTarget target) {
if(newNotifications) {
target.add(container);
newNotifications = false;
}
}
And refresh
public void refresh() {
List<Notification> newNotifications = ...
notifications.addAll(0, newNotifications);
newNotifiactions = true;
}
That way container won't be refreshed too often (which might cause strange effects) and will refresh every time new notification appears.
I need to use a editable label or inline-label in a ListView and after change the
value of this component I want to know how can I update the property of
the Object displayed in this listView
add(new ListView[SomeObject]("listSomeObject", listData) {
override protected def onBeforeRender() {
...
super.onBeforeRender()
}
def populateItem(item: ListItem[SomeObject]) = {
var objValue = item.getModelObject()
item.add(new Label("total", objValue.toString(getFormatter())))
}
}
})
In the code above, the object SomeObject has a property called total, the listView
shows a set of SomeObject, when the label total is changed in some line of the
listview the corresponding object SomeObject should be updated with the new
value of the label total.
Someone can provide some useful example for help me with this task?
Thanks
You should use a model to display your property. For example a propertyModel. The method getObject() will get called on display. A PropertyModel will call the getter for the selected property. You can have your object have a getter that retrieves the formatted value that you are interested in.
item.add(new Label("total", new PropertyModel(item.getModel(), "formattedValue")))
I have a :
Client Class
ListView
TextField
I need to populate my ListView in order to form a table:
WORKING CODE:
clientModel = new LoadableDetachableModel() {
#Override
protected Object load() {
return Client.getClientListByCompanyName(searchClientInput.getValue());
}
};
searchClientInput.setModel(new Model<String>());
searchClientInput.add(new AjaxFormComponentUpdatingBehavior("onkeyup") {
#Override
protected void onUpdate(AjaxRequestTarget target) {
target.add(clientListViewContainer);
}
});
clientListView = new ListView<Client>(CLIENT_ROW_LIST_ID, clientModel) {
#Override
protected void populateItem(ListItem<Client> item) {
Client client = item.getModelObject();
item.add(new Label(CLIENT_ROW_COMPANY_CNPJ_ID, client.getCompanyName()));
item.add(new Label(CLIENT_ROW_COMPANY_NAME_ID, client.getCompanyCnpj()));
}
};
clientListViewContainer.setOutputMarkupId(true);
clientListViewContainer.add(clientListView);
add(clientListViewContainer);
Now, in my HTML, I have a TextField. Whenever an user types something in this TextField, a select will be made in the database with whatever he typed. So for each word, a select is made, and the table needs to be updated. I am guessing I will need to use AJAX and possibly a Model. I'm kind of lost about how I can do this, if someone can provide me examples I would be very grateful.
EDIT: New code that is throwing exception: Last cause: Attempt to set model object on null model of component: searchClientForm:searchClientInput
EDIT 2: Ok so the exception was that my TextField didn't had a model to bind data to. So what I did was: searchClientInput.setModel(new Model<String>());
I also had a problem with the event. Using onkeydown was working, but not as intended. I had Company Name 1-4. If I typed Company Name 1, I would need to press one key again so the table would get updated. With onkeyup this don't happens. Thanks for the help.
You could give the ListView a LoadableDetachableModel which provides the selected clients matching your TextField's value.
Use an AjaxFormComponentUpdatingBehavior on your TextField which add a parent of the ListView to the request target (don't forget #setOutputMarkupId().
I believe the best way to perform what you want (which is repainting a table/list at each input change --> DB access) is with a DataView and a DataProvider.
A DataView is just like the ListView component except it uses an IDataProvider to get the data you want to present. You are able to implement the DataProvider so it accesses your DB, and you can add restrictions (where clauses) to the DataProvider.
[this is more like pseudo-code]
public final class MyDataProvider<T> extends SortableDataProvider<T> {
// ...
Set filters;
// filters is the set where the restrictions you want to apply are stored
...
#Override
public Iterator<T> iterator(int first, int count) {
// DAO (Data Access Object) access to DB
// ...
return dao.findByRestrictions(filters).iterator();
}
...
}
Now on the ajax event on your input component you are able to update the filter being used in the DataProvider, and in the the next repaint of the DataView, the provider will "pull" the data matching the restrictions defined in the filter.
Hope it helps. Best regards.