Wicket - How to reload/refresh reusable components correctly? - ajax

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);

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;
}
}

Tapestry 5.3: Component with form inside t:zone

I have a component for editing db entity. It has #CommitAfter on onSuccess(). When it is on the separate page it works fine. I click update button, it saves data and redirects to view page.
Now I want to reuse it on the list page. By clicking on item it should appear. After editing and clicking update button on the component it should save item to db and hide itself.
To achieve this I modified component so I can set zone id for its form. Put component inside zone on the list page, added links for each item with event onSelectItem, which sets zone id for component and returns body of the zone.
It did show component and I could edit fields and hit update button. It updated item but redirected whole page to view page. I tried to return null in onSuccess() – but in this case it didn’t save data to db and zone also wasn’t refreshed. I also tried call page class from a component by using #InjectPage and return page.zone.getBody() – this does reload zone but still doesn’t save data though all methods passed w/o exception. Also it too bad to call page specific code inside a component. My other custom ajax calls do save data to db in methods with #CommitAfter.
So my question what is the correct way of doing this in Tapestry?
ContactsList.tml
<html t:type="layout" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter">
<t:form>
... list of contacts linked to onSelectContact()
</t:form>
<t:zone id="editContact" t:id="editContactZone">
<t:if test="selectedContact">
<t:contactform t:id="contactForm" />
</t:if>
</t:zone>
</html>
ListPage.java
public class ContactsList{
#InjectComponent
private Zone editContactZone;
#InjectComponent("contactForm")
private ContactForm contactForm;
#Property
private Contact selectedContact;
public Object onSelectContact(#RequestParameter(value = "id", allowBlank = false) Integer id) {
selectedContact = getContactById(id);
if (selectedContact != null) {
contactForm.setContact(selectedContact);
contactForm.setFormZone(editContactZone.getClientId());
}
return editContactZone.getBody();
}
}
ContactForm.tml
<div xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd" xmlns:p="tapestry:parameter">
<t:form t:id="editContactForm" zone="prop:zone">
.... contact fields
</t:form>
</div>
ContactForm.java
public class ContactForm{
#InjectComponent("editContactForm")
private Form editContactForm;
#Property
private String zone;
#InjectPage
private ContactList listPage;
#InjectPage
private ViewContact viewContact;
#Property
protected Contact contact;
public void setContact(Contact contact) {
this.contact = contact;
}
public void setFormZone(String zone){
this.zone = zone;
}
#CommitAfter
public Object onSuccess() {
parseContact();
storeContact(); // calls DAO.store(contact)
return zone == null ? viewContact : listPage.onSelectContact(0); //don't like this in component code but at least it returns zone's body
}
}
You could pass a Block component parameter to the ContactForm containing the markup to render after #OnSuccess.
Or perhaps a better separation of concerns is to fire an event in the ContactForm which bubbles up to the page? Eg:
ContactForm.java
#Inject ComponentResources resources;
#CommitAfter
public void onSuccess() {
storeContact();
resources.triggerEvent("contactSaved", new Object[] { contact }, null);
}
ContactsList.java
#Inject AjaxResponseRenderer ajaxRenderer;
#Inject Zone someZone;
#Property Contact contact;
public void onContactSaved(Contact contact) {
this.contact = contact;
ajaxRenderer.addRender(someZone);
}
Also, why are you using #RequestParameter? Why not use event context?
ContactsList.tml
<t:loop source="contacts" item="contact">
<t:eventlink event="selectContact" context="contact">Edit ${contact.name}</t:eventlink>
</t:loop>
ContactList.java
Block onSelectContact(Contact contact) {
// doStuff
}

Wicket: form submit with reusable components and AjaxButton

I'm using wicket version 6.
I have a form. as contents of the form, I have a FormComponentPanel that contains two DateTimeField's (org.apache.wicket.extensions.yui.calendar).
In the class containing the form I have one AjaxButton, and one AjaxLink, both doing the same: reading the models, creating a new object and sending it to some server for processing.
Anyhow,
when clicking on the Link my new object is created with the correct values except those newly dates selected with the datepicker
when clicking the Button I get some error ([AjaxRequestHandler#1701777932 responseObject [org.apache.wicket.ajax.AjaxRequestHandler$1#3e1]) but no further information on the error
well, I addressed the first issue (Link) with trying to add ajax update behavior to it, as suggested here , but the selected date is not updated in the model
the AjaxButton is created and onSubmit overwritten with just calling another method and target.add(form); also setOutputMarkupId is set to true, but it seems that still something is missing
In order to get it to work I'd just need to solve one of the problems, but it would be great if someone has a solution for both problems. Thanks in advance.
edit
public MyPanelIncludingForm() {
// ...
form.add(getRangePanel()); // creates a new TimeRangePanel and returns the instance
form.add(getSubmitButton());
// ...
}
private FormComponent<String> getSubmitButton() {
FormComponent<String> submitBtn = new AjaxButton("submitBtn", form) {
private static final long serialVersionUID = 3005L;
#Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
System.out.println("submit QT ajax button");
setResponsePage(HomePage.class);
sendQuery();
target.add(form);
}
#Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
System.err.println("error occurred. " + target);
target.add(feedback);
}
};
submitBtn.setOutputMarkupId(true);
return submitBtn;
}
// separate FormComponentModel for timeRange
public class TimeRangePanel extends FormComponentPanel<MyRange> {
MyRange range;
PropertyModel<Date> dpFromPM = new PropertyModel<Date>(this, "range.start");
PropertyModel<Date> dpToPM = new PropertyModel<Date>(this, "range.stop");
public RangePanel(String id, IModel<MyRange> model) {
super(id, model);
dpFrom = new DateTimeField("dpFrom", dpFromPM) {
private static final long serialVersionUID = 3006L;
#Override
protected DateTextField newDateTextField(String id, PropertyModel<Date> model) {
DateTextField dtf = super.newDateTextField(id, model);
AjaxFormComponentUpdatingBehavior a = new AjaxFormComponentUpdatingBehavior("onChange") {
private static final long serialVersionUID = 3006L;
#Override
protected void onUpdate(AjaxRequestTarget target) {
System.out.println("here u " + dpFrom.getModelObject().toString());
}
};
dtf.add(a);
return dtf;
}
};
// second DateTimeField as dpFrom
} // end of constructor
#Override
protected void onBeforeRender() {
range = getModelObject();
super.onBeforeRender();
}
} // end of class
edit2
this is what the wicket ajax debug window is printing:
INFO: focus removed from
INFO: focus set on submitBtn11
INFO: Received ajax response (299 characters)
INFO:
<div wicket:id="feedbackQuery" class="feedback" id="feedbackQuery1c"><wicket:panel>
<ul wicket:id="feedbackul" class="feedbackPanel">
<li wicket:id="messages" class="feedbackPanelERROR">
<span wicket:id="message" class="feedbackPanelERROR"></span>
</li>
</ul>
</wicket:panel></div>
INFO: returned focused element: [object HTMLInputElement]
INFO: returned focused element: [object HTMLInputElement]
INFO: Response processed successfully.
INFO: refocus last focused component not needed/allowed
INFO: focus removed from submitBtn11
edit3
As I wrote in a comment:
I removed the re-usable components (FormComponentPanel) and now I don't get an error with the AjaxButton . anyhow, it's weird, I thought re-usable components should work, even with Ajax; also the models were assigned properly.
when clicking the Button I get some error
([AjaxRequestHandler#1701777932 responseObject
[org.apache.wicket.ajax.AjaxRequestHandler$1#3e1]) but no further
information on the error
=> You should run Wicket in DEVELOPMENT mode to get a more detailed trace.
You might simply add in your form a field called:
range.start
range.stop
and use the Object MyRange as property model instead create a new one for each field.
There is actually no need for that extra cycle. And I don't think Wicket will write the information back to the MyRange object if you create new PropertyModels for every attribute.
For example: http://svn.apache.org/viewvc/incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/web/components/admin/users/UserForm.java?view=markup
Line 276
and the corresponding HTML
http://svn.apache.org/viewvc/incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/web/components/admin/users/UsersPanel.html?view=markup
Line 82
Sebastian

How to update a List<T> in C#?

I'm taking a basic course in C# programming, have never programmed anything before. One of our exercises is to create a program that can rent out movies (i.e. a Videostore) from scratch.
One of my classes contains customers. I need a method where the user of the program can add customers to the first list and a separate method in which to display all customers, containing the newly added customers; or if no customers are added then the original ones.
This is what I´ve done so far:
I've created a List<T> for the original customers.
I have made a method that can add customers to the first list and display them in ONE method.
The problem is that I don´t know how to update the original list of customers with the ones the user adds. If I call the entire method it will obviously (even to me..) return the entire method and make the user add the customers over again. I´ve tried creating two List<T>s, but how can I make the original list update to include the customers the user adds?? I managed to call the first list from the second but the reverse doesn't work.
I have tried and tried and tried but I´ve simply run out of ideas! For me even getting this far has been quite the challenge. I thought about giving the whole thing up. Programming is not easy.
If anyone has any suggestions I would be very happy!
namespace MyNameSpace
{
public class Customers
{
public Customers()
{
}
public string Name
{
get;
set;
}
public string Tel
{
get;
set;
}
public List<Customers> CustomerList1() //Original customers
{
List<Customers> newCustomer = new List<Customers>
{
new Customers
{
Name="A",
Tel="1"
},
new Customers
{
Name="H",
Tel="2"
},
};
return newCustomer;
}
public List<Customers> CustomerList2() //User adds new customers
{
List<Customers> custList = CustomerList1();
Console.WriteLine("---------------------------");
Console.WriteLine("New Customer");
Console.WriteLine("---------------------------");
Console.WriteLine("Name:");
Console.WriteLine("Tel:");
List<Customers> addedCustomer = new List<Customers>
{
new Customers //There is most likely a better way...
{
Name=Console.ReadLine(),
Telephone=Console.ReadLine()
}
};
custList.AddRange(addedCustomer);
Console.WriteLine("***************List******************");
foreach (Customers c in custList)
{
Console.WriteLine();
Console.WriteLine(c.Name);
Console.WriteLine(c.Tel);
Console.WriteLine();
}
Console.WriteLine("******************************************");
return addedCustomer;
}
public void CustomerView() //This method only returns original list
{
List<Customers> customers = CustomerList1();
foreach (Customers c in customers)
{
Console.WriteLine();
Console.WriteLine(c.Name);
Console.WriteLine(c.Tel);
Console.WriteLine();
}
Console.WriteLine("*******************");
}
public void CustomerListAdd() //This is another method I´ve tried to add
{ customers..
List<Customers> customers = CustomerList1();
Console.WriteLine("");
Console.WriteLine("---------------------------");
Console.WriteLine("New Customer");
Console.WriteLine("---------------------------");
Customers customerAdd = new Customers();
Console.WriteLine("Name:");
customerAdd.Name = Console.ReadLine();
Console.WriteLine("Tel:");
customerAdd.Telephone = Console.ReadLine();
customers.Add(customerAdd);
Console.WriteLine();
foreach (Customers c in customers)
{
Console.WriteLine();
Console.WriteLine(c.Name);
Console.WriteLine(c.Tel);
Console.WriteLine();
}
Console.WriteLine("*******************");
}
}
}
Thank you in advance
Make your Customer list class level:
public class MyClass
{
// this is outside of a method, but inside the class
private List<Customer> customers;
public MyClass()
{
// instantiate the customer list inside the constructor
customers = new List<Customer>();
// add a default customer to the list by calling the AddCustomer
// method in the constructor.
AddCustomer(new Customer() { Name = "A", Tel="1" });
// You can also bypass the AddCustomer method below and just call
// customers.Add() here instead. If you have other things you want
// to do (like insert the customer into a database, for example)
// you might want to keep the method like I have below.
}
public void AddCustomer(Customer cust)
{
// add the customer to the existing list.
customers.Add(cust);
}
}

wicket - Implement Ajax add/remove items ListView

Im getting crazy about this issue. I implemented a ListView which you can add/remove TextField dinamically, but only the last TextField is removed.
An example:
// Object type which is used in the list
public class ExampleObject implements Serializable{
private String keyword;
public String getKeyword() {
return this.keyword;
}
public void setKeyword(String s) {
keyword = s;
}
}
//ListView
List<ExampleObject> keywordList = new ArrayList<ExampleObject>();
keywordList.add(new ExampleObject());
ListView keywordView = new ListView("keywordView", keywordList) {
#Override
protected void populateItem(final ListItem item) {
ExampleObject model = (ExampleObject) item.getModelObject();
item.add(new TextField("subKeyword", new PropertyModel(model, "keyword")));
// keyword remove link
AjaxSubmitLink removeKeyword = new AjaxSubmitLink("removeKeyword", myForm)
{
#Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
ExampleObject selected = (ExampleObject) item.getModelObject();
// I also tried deleting by index. println shows the
// selected object is the element I want to remove, so why always
// remove last object of the list?
keywordList.remove(selected);
if (target != null) {
target.addComponent(myForm);
}
}
};
item.add(removeKeyword);
// keyword add link
AjaxSubmitLink addKeyword = new AjaxSubmitLink("addKeyword", metadataForm)
{
#Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
keywordList.add(new ExampleObject());
if (target != null) {
target.addComponent(myForm);
}
}
};
item.add(addKeyword);
}
keywordView.setReuseItems(true);
metadataForm.add(keywordView);
Any help would be very appreciate, because I thing this issue is really a very stupid mistake but I cant get it!
Thanks
It might be as simple as getting rid of the line
keywordView.setReuseItems(true);
The reuseItems flag is an efficiency so that the page does not rebuild the ListView items unnecessarily, but it can lead to confusion such as what you're seeing.
ListView really wasn't made for use with forms though, and you'll probably be better off with another tactic entirely.
This blog entry on building a list editor form component might be useful. It will need some changes if you're not on Wicket 1.4, but similar stuff is definitely possible in Wicket 1.3, and the comments have some hints.
Read the javadoc of ListView#setReuseItems():
"But if you modify the listView model object, than you must manually call listView.removeAll() in order to rebuild the ListItems."
You can not use a ListView this way. Either use the members of ListView provided:
removeLink(java.lang.String id, ListItem<T> item)
and
newItem(int index)
but, I never used those. If I have to display a List and be able to add remove Items dynamically, I prefer the RefreshingView.
If you do use FormComponents inside a RefreshingView, make sure you set a Reusestartegy (setItemReuseStrategy())
Bert

Resources