Wicket - Load method in LoadableDetachableModel is not called after changing the value of the dropdown list - wicket

I am facing a problem with the update of a list that is filtered depending on the value of a dropdown.
This is a description of my model:
I have a list of users
When I click on a user, another list of orders of this user is
displayed
The list of orders is filtered according to the value of a dropdown
containig a list of status
Please, see the image below:
Users With orders
The filter is working well, but the problem that I am facing is that once I choose an element from the dropdown, the list of orders is no longer updated when the user is changed.
This is a snippet of my code:
Construction of the Order Panel and instantiation of the list:
public OrdersPanel(String id)
{
super(id);
this.setOutputMarkupId(true);
setOutputMarkupPlaceholderTag(true);
IModel<List<Order>> orderListModel = new OrderListModel();
orderListView = new orderListView("orderListView", orderListModel);
//...
}
OrderListModel:
private final class OrderListModel extends LoadableDetachableModel<List<Order>> {
private static final long serialVersionUID = 1L;
#Override
protected List<Order> load() {
//...
//We set the variable allOrders in order to be used later in the filtering process
//...
}
}
Construction of the dropdown:
private class StatusDropDown extends CustomDropDown<String> implements IAjaxIndicatorAware {
private static final long serialVersionUID = 1L;
private StatusDropDown(String id) {
super(id);
this.setNullValid(true);
StatusListModel statusModel = new StatusListModel();
setChoices(statusModel);
setChoiceRenderer(new StatusChoiceRenderer(statusModel));
}
#Override
protected void onUpdate(AjaxRequestTarget target) {
super.onUpdate(target);
if (target != null) {
new StatusDropDownRefreshEvent(this, target).fire();
target.addComponent(this);
}
}
/**
* disable ajax marker for the form fields
*/
public String getAjaxIndicatorMarkupId() {
return null;
}
}
CustomDropDown (Must be used by the context of the project on which I am working):
public class CustomDropDown<V> extends DropDownChoice<V> {
private static final long serialVersionUID = 1L;
public CustomDropDown(String id) {
this(id, id);
}
public CustomDropDown(String id, String property) {
super(id);
setOutputMarkupId(true);
setModel(new CustomComponentPropertyModel<V>(property));
add(new AjaxFormComponentUpdatingBehavior("onchange") {
private static final long serialVersionUID = 1L;
#Override
protected void onUpdate(AjaxRequestTarget target) {
new UpdateEvent(CustomDropDown.this, target).fire();
if (target != null) {
target.addComponent(CustomDropDown.this);
}
CustomDropDown.this.onUpdate(target);
}
});
}
#Override
protected void onComponentTag(ComponentTag tag) {
super.onComponentTag(tag);
if (!isValid()) {
tag.append("class", "invalid", " ");
FeedbackMessage message = getFeedbackMessage();
if (message != null) {
tag.put("title", message.getMessage().toString());
message.markRendered();
}
} else if (isRequired()) {
tag.append("class", "red-background", " ");
}
}
public void setWidth(String width) {
this.add(new AttributeAppender("style", true, new Model<String>("width:" + width), ";"));
}
public CustomDropDown<V> setChoices(V... choices) {
this.setChoices(Arrays.asList(choices));
return this;
}
protected void onUpdate(AjaxRequestTarget target) {
}
}
StatusDropDownRefreshEvent listener:
#AjaxUpdateEventListener
public void statusDropDownRefreshPanel(StatusDropDownRefreshEvent event){
if (event.getTarget() != null) {
orderListView.setList(getOrdersByStatus(allOrders));
event.getTarget().addComponent(this);
}
}
Changing of the user:
When the user is changed, an update event is fired from the users panel, and then cached in the orders panel:
#AjaxUpdateEventListener
public void refreshPanel(CustomerOrderRefreshEvent event) {
if (event.getTarget() != null) {
event.getTarget().addComponent(this);
this.onBeforeRender();
}
}
onBeforeRender() to determin the visibility of the panel (if no order is available then the orders panel is not visible)
#Override
public void onBeforeRender() {
setVisibilityAllowed(checkVisibility());
super.onBeforeRender();
}
Finally, the checkVisibility Method:
private boolean checkVisibility() {
if (isUserChanged()) {
List<Order> src = orderListView.getModelObject();
statusDropDown.setDefaultModelObject(null);
return CollectionUtils.isNotEmpty(src);
}
return true;
}
My main problem is that the changing of the selected user doesn't update the list of orders once a status is chosen from the list.
Thank you very much for your replies and your time.
Best regards.

I found a solution to my problem.
The list of orders wasn't updated because The method getObject was called on the wrong object.
The call of the load() method can be done via getObject(), but the condition is: The object must be detached (See the implementation of getObject() at this link)
The detached object in my case is the orderListModel and not the orderListView, so this is what I added to my code:
//Set the content of the list model
List<Order> orders = orderListModel.getObject(); //This invokes the load method
//Update the content of the list
orderListView.setList(orders);

Related

apache wicket - column link inside DataTable on table change (lets say filter and getting 0 rows) the opened link page fail

scenario : I am using data table with columns
one of the columns is link .
on run time I calculate the link created
the problem start when:
1. opennig table page P1
2. I clicked on link open in new tab
2.new tab is created with this URL
mit:8080/backoffice/?4-1.ILinkListener-MainPanelComponentWrapper-MainPanelComponent-table-gridForm-grid-body-rows-3-cells-2-cell-link
which is a component on P1 ,this tab generate P2.
changing P1 ,filtering on ajax and the table is empty so table-gridForm has no data
refreshing P2
getting exception
org.apache.wicket.WicketRuntimeException: Component 'MainPanelComponentWrapper:MainPanelComponent:table:gridForm:grid:body:rows:1:cells:2:cell:link' has been removed from page.
i need to create a redirected link that the new page woul be linked to .
how can i achive it ?
public class LinkPropertyColumn<T extends IEntity> extends BOPropertyColumn<T, String> implements IBOExportableColumn<T, String, Object> {
private static final long serialVersionUID = 1L;
private String headerTilte;
private GridViewType navigateTo;
private String routingByProperty;
private String entityId;
private String navigateToDynamicFunction;
private Map<String, String> filterByMap;
protected BOLinkPanel<T> linkPanel ;
public LinkPropertyColumn(String displayModel, String propertyToSortBy, String propertyExpression, String entityId,
String routingByProperty, String headerTilte, String navigateToDynamic) {
super(Model.of(displayModel), propertyToSortBy, propertyExpression);
this.headerTilte = headerTilte;
this.entityId = entityId;
this.navigateToDynamicFunction = navigateToDynamic;
this.routingByProperty = routingByProperty;
}
#Override
public Component getHeader(String componentId) {
return new Label(componentId, headerTilte);
}
#Override
public void populateItem(Item<ICellPopulator<T>> item, String componentId, final IModel<T> rowModel) {
linkPanel = new BOLinkPanel<T>(componentId, rowModel, getPropertyExpression()) {
private static final long serialVersionUID = 1L;
#Override
void onLinkClicked() {
LinkPropertyColumn.this.onLinkClicked(rowModel);
}
};
item.add(linkPanel);
}
public void onLinkClicked(IModel<T> rowModel) {
doing stuff...
params.add(HomePage.ENTITY_ID, idProperty);
final Object routingProperty = routingByProperty == null ? idProperty : BeanUtils.getProperty(object, routingByProperty);
params.set(HomePage.ROUTING_PROPERTY, routingProperty);
HomePage homePage = new HomePage(params);
final RequestCycle requestCycle = RequestCycle.get();
requestCycle.setResponsePage(homePage);
}
}
}
and :
public abstract class BOLinkPanel<T> extends Panel {
private static final long serialVersionUID = 1L;
/**
* #param id
*/
public BOLinkPanel(String id, IModel<T> model, String propertyExpression) {
super(id);
AbstractLink link = getLink();
link.add(new Label("caption", new PropertyModel<String>(model.getObject(), propertyExpression)));
add(link);
}
protected AbstractLink getLink() {
Link<Void> link = new AjaxFallbackLink<Void>("link") {
private static final long serialVersionUID = 1L;
#Override
public void onClick(AjaxRequestTarget target) {
// TODO Auto-generated method stub
BOLinkPanel.this.onLinkClicked();
}
// #Override
// public void onClick() {
// BOLinkPanel.this.onLinkClicked();
//
// }
};
return link;
}
abstract void onLinkClicked();
}
i did :
requestCycle.setResponsePage(HomePage.class, params);

How can we filter the table viewer in JFace based on the entered text

I have created a table using table viewer and now i need to filter based on the text entered in the text box so how can we filter the table the code to create table is as follows
TableViewerColumn message = new TableViewerColumn(viewer, SWT.NONE);
message.getColumn().setWidth(800);
message.getColumn().setText("Message");
message.setLabelProvider(new ColumnLabelProvider()
{
#Override
public void update(ViewerCell cell)
{
Object element = cell.getElement();
if(element instanceof MyObject)
{
MyObject obj = (MyObject) element;
cell.setText(obj.getMessage());
}
}
});
}
private static class MyObject
{
private String first;
private String second;
private String message;
public MyObject(String first, String second,String message)
{
this.first = first;
this.second = second;
this.message = message;
}
public String getFirst()
{
return first;
}
public void setFirst(String first)
{
this.first = first;
}
public String getSecond()
{
return second;
}
public void setSecond(String message)
{
this.second = second;
}
public String getMessage()
{
return message;
}
public void setMessage(String message)
{
this.message = message;
}
so now how can we filter the table. Please help me as I am new to jface table viewer
Use a class derived from ViewerFilter to add a filter:
class MyFilter extends ViewerFilter
{
#Override
public boolean select(Viewer viewer, Object parentElement, Object element)
{
MyObject obj = (MyObject)element;
// TODO return true to include the object, false to exclude
}
}
Add this to the table with:
viewer.addFilter(new MyFilter());
Call
viewer.refresh();
to get the viewer to rerun the filter when the text changes.

Struts 2 ModelDriven Action suporting both a list and individual items

I have inherited some struts2 REST-plugin based code, and the following construct puzzles me:
#Namespace("/merchants/{id}")
public class MerchantAction extends ActionSupport implements ModelDriven<Object> {
private Merchant merchant = new Merchant(); // A Model
private Iterable<Merchant> merchants; // A list of models
....
public HttpHeaders index() {
merchants = merchantService.findAllMerchants();
return new DefaultHttpHeaders("index");
}
#Override
public Object getModel() {
return (merchant != null ? merchant : merchants);
}
public void setId(String id) {
merchant = merchantService.findMerchant(id));
}
In other words, it seems to be toggling between returning a list and returning an individual item in the getModel() call. Is this kosher ? Looks a bit strange to me
I've considered your approach, but finally gave it up. IMO, it lost the advantage of strong typed action.
My solution is, creating a ViewModel for each action. In the view models, there can be the single model, the list of the model, and other items for pages usage, such as items for drop down list or radio buttons.
So the UserViewModel is like:
public class UserViewModel implements IViewModel<User> {
private User model;
private List<User> list;
public void setModel(User user) {
this.model = user;
}
public User getModel() {
return model;
}
public void setList(List<User> list) {
this.list = list;
}
public List<User> getList() {
return list;
}
}
And the actions are like:
public class UserController implements ModelDriven<UserViewModel> {
private int id;
private UserViewModel model = new UserViewModel();
public String index() {
return "success";
}
public String show() {
return "success";
}
public void setId(int id) {
this.id = id;
}
public int getId() {
return this.id;
}
#Override
public UserViewModel getModel() {
return model;
}
}
But in this way, I still lose the shortcut way in jsp files. I should write long model.userName instead of short userName.
I'm still finding the best solution of it.

JFace Databinding with TreeViewer

I am trying to build a view in my RCP application, the view just contains a TreeViewer.
The tree can contain folders or leafs, a folder can contain folders and leafs. when I add a folder to the back-end data model of the root folder, the UI is updated automatically, but if I add a folder to any branch folder, the UI will not be updated automatically. please tell me what's wrong with my code.
The model class:
public class TreeNode extends BindableObject {
private Folder parent;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
super.firePropertyChange("name", this.name, this.name = name);
}
public Folder getParent() {
return parent;
}
public void setParent(Folder parent) {
this.parent = parent;
}
}
public class Folder extends TreeNode {
private List<TreeNode> children = new ArrayList<TreeNode>();
public List<TreeNode> getChildren() {
return children;
}
public void setChildren(List<TreeNode> children) {
this.children = children;
}
public void add(TreeNode node){
children.add(node);
}
}
the view :
public class ExplorerView extends ViewPart {
private WritableList data;
private TreeViewer treeViewer;
public WritableList getData() {
return data;
}
public TreeViewer getViewer(){
return treeViewer;
}
public ExplorerView() {
// TODO Auto-generated constructor stub
}
#Override
public void createPartControl(Composite parent) {
parent.setLayout(new FillLayout(SWT.HORIZONTAL));
treeViewer = new TreeViewer(parent, SWT.BORDER);
treeViewer.setContentProvider(new ObservableListTreeContentProvider(new ExplorerObservableFactory(), new ExploerTreeStructureAdvisor()));
treeViewer.setLabelProvider(new ExplorerTreeLabelProvider());
ArrayList<TreeNode> list = new ArrayList<TreeNode>();
list.add(new ExplorerDataModel().getElements()[0]);
data = new WritableList(list, TreeNode.class);
treeViewer.setInput(data);
}
#Override
public void setFocus() {
}
}
the ObservableFactory:
public class ExplorerObservableFactory implements IObservableFactory {
#Override
public IObservable createObservable(Object target) {
System.out.println(target.getClass().getName());
if(target instanceof WritableList){
return (WritableList)target;
}
else if(target instanceof Folder){
List<TreeNode> children = ((Folder)target).getChildren();
return new WritableList(children, TreeNode.class);
}
return null;
}
}
the TreeStructureAdvisor:
public class ExploerTreeStructureAdvisor extends TreeStructureAdvisor {
#Override
public Object getParent(Object element) {
return ((TreeNode)element).getParent();
}
#Override
public Boolean hasChildren(Object element) {
if(element instanceof Folder){
return true;
}else{
return false;
}
}
}
the data :
public class ExplorerDataModel {
public TreeNode[] getElements() {
Folder f1 = new Folder();
f1.setName("Database Connections");
Folder f11 = new Folder();
f11.setName("Credit Test");
TreeNode t1 = new TreeNode();
t1.setName("bank#localhost");
f11.add(t1);
t1.setParent(f11);
TreeNode t2 = new TreeNode();
t2.setName("credit#localhost");
f11.add(t2);
t2.setParent(f11);
Folder f12 = new Folder();
f12.setName("Credit Product");
TreeNode t3 = new TreeNode();
t3.setName("nbcbcredit#localhost");
f12.add(t3);
t3.setParent(f12);
TreeNode t4 = new TreeNode();
t4.setName("nbcbcredit_bak#localhost");
f12.add(t4);
t4.setParent(f12);
f1.add(f11);
f11.setParent(f1);
f1.add(f12);
f12.setParent(f1);
return new TreeNode[] { f1 };
}
}
the test command handler:
public Object execute(ExecutionEvent event) throws ExecutionException {
ExplorerView v =(ExplorerView) HandlerUtil.getActiveWorkbenchWindow(event).getActivePage().findView("com.amarsoft.dmp.explorer.explorerView");
Folder f = new Folder();
f.setName("ODA Flat Files");
v.getData().add(f);
Folder f1 = (Folder) v.getData().get(0);
f1.setName("Database Connections (3)");
Folder f2 = new Folder();
f2.setName("Report Test");
f1.add(f2);
return null;
}
if I execute above command, the added folder "ODA Flat Files" will appear in the tree immediately, but the added folder "Report Test" will not be there, if call TreeViewer#refresh() everything is ok, but I want to know why.
Modifying your model does not notify your tree. Refresh is one way of telling the tree that the data has changed and it needs to update. If you go through the java doc for jface viewers in eclipse you will find the following quote
To handle structural changes, use the refresh methods instead.
It seems you misconfigured the data binding...
Have a look at the official snippets:
With a List Factory
With a Set Factory
With a more than a list as Factory
Have fun! (without any refresh ;-) )
ps. for EMF just look at this: http://tomsondev.bestsolution.at/2009/06/08/galileo-emf-databinding-%E2%80%93-part-3/
In your ExplorerObservableFactory replace
... else if(target instanceof Folder){
List<TreeNode> children = ((Folder)target).getChildren();
return new WritableList(children, TreeNode.class);
}
by following
else if(target instanceof Folder){
return BeansObservables.observeList(target, "children");
}
If you return WritableList here, the contentProvider's listener is registered on it (it should be registered on the Folder bean instead)

Programmatically refresh a Gwt CellTree

I want to fire the "open root node" event on my current working CellTree, which now has the following behaviour:
#Override
public <T> NodeInfo<?> getNodeInfo(final T value) {
return new DefaultNodeInfo<Categoria>(
(value instanceof Categoria) ?
createBranchDataProvider((Categoria)value) :
rootDataProvider,
new CategoriaCell()
);
}
private AsyncDataProvider<Categoria> createRootDataProvider() {
AsyncDataProvider<Categoria> dataProvider = new AsyncDataProvider<Categoria>() {
#Override
protected void onRangeChanged(HasData<Categoria> display) {
AsyncCallback<Categoria[]> cb = new AsyncCallback<Categoria[]>() {
#Override
public void onSuccess(Categoria[] result) {
updateRowCount(result.length, true);
updateRowData(0, Arrays.asList(result));
}
#Override
public void onFailure(Throwable caught) {
Window.alert(caught.toString());
}
};
rpcService.getCategorie(cb);
}
};
return dataProvider;
}
How can I fire that "onRangeChanged" event, to refresh my level-1 nodes?
What is my convenience method missing?
private void updateTree() {
TreeNode rootTreeNode = cellTree.getRootTreeNode();
for (int i = 0; i < rootTreeNode.getChildCount(); i++) {
rootTreeNode.setChildOpen(i, false);
}
// HOW TO REFRESH LEVEL-1 NODES?
}
Working example. Add reference to DataProvider (and parent Node) (MyMenuItem and MyCell with DataProvider in my code). After adding element refresh parent.
public class MyMenuItem {
private String name;
private String action; //some data
private int level; //if needed
private ArrayList<MyMenuItem> list; //nodes childrens
private MyMenuItem parent; //track internal parent
private MyCell cell; //for refresh - reference to visual component
public void setCell(MyCell cell) {
this.cell = cell;
}
public void refresh() {
if(parent!=null) {
parent.refresh();
}
if (cell!=null) {
cell.refresh(); //refresh tree
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public MyMenuItem(String name, String action) {
super();
parent = null;
level = 0;
this.name = name;
this.action = action;
list = new ArrayList<MyMenuItem>();
}
public MyMenuItem(String name) {
this(name, "");
}
public void addSubMenu(MyMenuItem m) {
m.level = this.level+1;
m.parent = this;
list.add(m);
}
public boolean hasChildrens() {
return list.size()>0;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public ArrayList<MyMenuItem> getList() {
return list;
}
public MyMenuItem getParent() {
return parent;
}
}
public class MyTreeModel implements TreeViewModel {
private MyMenuItem officialRoot; //default not dynamic
private MyMenuItem studentRoot; //default not dynamic
private MyMenuItem testRoot; //default not dynamic
private MyMenuItem root;
public MyMenuItem getRoot() { // to set CellTree root
return root;
}
public MyTreeModel() {
root = new MyMenuItem("root");
// Default items
officialRoot = new MyMenuItem("Official"); //some basic static data
studentRoot = new MyMenuItem("Student");
testRoot = new MyMenuItem("Test");
root.addSubMenu(officialRoot);
root.addSubMenu(studentRoot);
root.addSubMenu(testRoot);
}
//example of add add logic
private void addNew(MyMenuItem myparent, String name, String uid) {
myparent.addSubMenu(new MyMenuItem(name, uid));
myparent.refresh(); //HERE refresh tree
}
#Override
public <T> NodeInfo<?> getNodeInfo(T value) {
ListDataProvider<MyMenuItem> dataProvider;
MyMenuItem myValue = null;
if (value == null) { // root is not set
dataProvider = new ListDataProvider<MyMenuItem>(root.getList());
} else {
myValue = (MyMenuItem) value;
dataProvider = new ListDataProvider<MyMenuItem>(myValue.getList());
}
MyCell cell = new MyCell(dataProvider); //HERE Add reference
if (myValue != null)
myValue.setCell(cell);
return new DefaultNodeInfo<MyMenuItem>(dataProvider, cell);
}
#Override
public boolean isLeaf(Object value) {
if (value instanceof MyMenuItem) {
MyMenuItem t = (MyMenuItem) value;
if (!t.hasChildrens())
return true;
return false;
}
return false;
}
}
public class MyCell extends AbstractCell<MyMenuItem> {
ListDataProvider<MyMenuItem> dataProvider; //for refresh
public MyCell(ListDataProvider<MyMenuItem> dataProvider) {
super("keydown","dblclick");
this.dataProvider = dataProvider;
}
public void refresh() {
dataProvider.refresh();
}
#Override
public void onBrowserEvent(Context context, Element parent, MyMenuItem value,
NativeEvent event, ValueUpdater<MyMenuItem> valueUpdater) {
if (value == null) {
return;
}
super.onBrowserEvent(context, parent, value, event, valueUpdater);
if ("click".equals(event.getType())) {
this.onEnterKeyDown(context, parent, value, event, valueUpdater);
}
if ("dblclick".equals(event.getType())) {
this.onEnterKeyDown(context, parent, value, event, valueUpdater);
}
}
#Override
public void render(Context context, MyMenuItem value, SafeHtmlBuilder sb) {
if (value == null) {
return;
}
sb.appendEscaped(value.getName());
//add HERE for better formating
}
#Override
protected void onEnterKeyDown(Context context, Element parent,
MyMenuItem value, NativeEvent event, ValueUpdater<MyMenuItem> valueUpdater) {
Window.alert("You clicked "+event.getType()+" " + value.getName());
}
}
in module add
treeModel = new MyTreeModel();
tree = new CellTree(treeModel,treeModel.getRoot());
The Level-1 nodes (I suppose you the mean below the root node) can not be refreshed the way you are doing it.
You have to store the instance of your dataProvider for the level-1 nodes somewhere.
Later when you refresh your list you have to update your stored dataProvider for your level-1 nodes.
The nodes below the level-1 can be refreshed the way you are doing it. Because as soon as you close the level 1 nodes (that is what you are doing in the updateTree method) and the next time you open it getNodeInfo will be called and the updated Subcategories will be retrieved and displayed in the CellTree.
UPDATE
For refreshing the CellWidgets which is attached to AsyncDataProvider you will probably have to extend the AsyncDataProvider and either extract the RPC call to a getData() method which is called in the onRangeChanged() method or create an interface with a refresh method and implement it in your custom AsyncDataProvider which calls the protected onRangeChanged() method.