Wicket: form submit with reusable components and AjaxButton - ajax

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

Related

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
}

How to handle changes in a form with a model-update using Wicket and AJAX

I have a form with many input-fields and need to handle a change to any of those input-fields; so I add a AjaxEventBehavior to the form, like:
Form<MyX> myForm = new Form<>("X", getModel());
myForm.add(new AjaxEventBehavior("onchange") {
#Override
protected void onEvent(AjaxRequestTarget target) {
handleFormChange(...);
}
});
The method handleFormChange gets called everytime I change some content in the input-fields of the form. But the model is not getting updated with the new value of the changed input-field of the form.
How can I get thoose model-updates? I tried AjaxFormComponentUpdatingBehavior. It updates the model, but I cannot use it for forms, just for FormComponents.
Does anybody has an ideas how to handle that? TIA!
With AjaxFormSubmitBehavior you can submit the whole form on each change.
First for on change use the dedicated OnChangeAjaxBehavior.
Then you can use the Iterator of the form to get all children and add then add the OnChangeAjaxBehavior to all FormComponents which will call your handleFormChange() on every change like this:
for (Iterator it = form.iterator(); it.hasNext();) {
Object o = it.next();
if (o instanceof FormComponent) {
((FormComponent) o).add(new OnChangeAjaxBehavior() {
#Override
protected void onUpdate(AjaxRequestTarget target) {
handleFormChange(...);
}
});
}
}

Wicket - Update item inside listview with ajax timer

There is an example of a world clock on wicket-example http://www.wicket-library.com/wicket-examples/ajax/world-clock?0 .
I tried the same, but inside a list-view.
final DataView<LongRunningTask> dataView = new DataView<LongRunningTask>("sorting", adp) {
private static final long serialVersionUID = 1L;
#Override
protected void populateItem(final Item<LongRunningTask> item) {
...
final Label clock = new Label("timer", getCounter().getObject());
item.add(clock);
clock.setOutputMarkupId(true);
add(new AbstractAjaxTimerBehavior(Duration.seconds(1)) {
#Override
protected void onTimer(AjaxRequestTarget target) {
target.add(clock);
}
});
...
}
}
};
But this is not working. If I do
add(new AjaxSelfUpdatingTimerBehavior(Duration.seconds(1)));
inside the Page itself, then it works. But I don't want to refresh the page every second, I just want to refresh the one item (clock) inside the listView.
Any chance how I can make this work?
The simplest solution to this problem is to add the Behavior to the clock instead of the surrounding container.
You're adding the behavior to the DataView once per row. Since DataViews can't be updated via Ajax, this is bound to fail. Updating the clock component itself instead works.
final DataView<LongRunningTask> dataView = new DataView<LongRunningTask>("sorting", adp) {
private static final long serialVersionUID = 1L;
#Override
protected void populateItem(final Item<LongRunningTask> item) {
...
final Label clock = new Label("timer", getCounter().getObject());
item.add(clock);
clock.setOutputMarkupId(true);
clock.add(new AbstractAjaxTimerBehavior(Duration.seconds(1)) {
#Override
protected void onTimer(AjaxRequestTarget target) {
target.add(clock);
}
});
...
}
}
};
After having a second look at this, I think there is another culprit:
final Label clock = new Label("timer", getCounter().getObject());
I don't know exactly what getCounter().getObject() does but chances are that it doesn't return a model...
final Label clock = new Label("timer", new PropertyModel(this, "counter"));
should be a better fit here. You might habe to replace "this" with an approprately prefixed version to make sure the container defining getCounter() is adressed.
If this doesn't work, have a look at your AjaxDebugWindow. You should see data coming in every second. If it doesn't your component isn't updated. If the data doesn't change, your model isn't updated. Armed with this, you should be able to fix this easily.
You have to refresh the DataView, todo this add it to a WebMarkupContainer and target the WebMarkupContainer with the ajax timer behavior.
A second way could be to add the label and ajax timer behavior to a new Panel. Then add that panel to your DataView. Im not sure if this way would work, as i've never tried it before but its worth a go.

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 6.1 AjaxEventBehavior and form components with Models

final Address address = new Addres();
Form form = new Form("addressInputForm");
this.add(form);
form.setOutputMarkupId(true);
FormComponent fc;
fc = new RequiredTextField("street", new AddressModel(new Model(address), AddressModelType.STREET_MODEL));
fc.add(StringValidator.maximumLength(30));
fc.setLabel(new ResourceModel("label.street"));
form.add(fc);
form.add(new AjaxButton("submitAddressInput", form){
#Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form){
System.out.println(address.toString());
}
#Override
protected void onError(AjaxRequestTarget target, Form<?> form){
//
}
});
AjaxEventBehavior behavior = new AjaxEventBehavior("keyup"){
#Override
protected void onEvent(AjaxRequestTarget target) {
System.out.println(address.toString());
}
};
form.add(behavior);
I created a little example about the problem.
Basically when the person inserts a value into the field called "street" then as you can expect from the code it activates behavior and runs the method onEvent(). onEvent() there will be print out of variable address. Unfortunately address.street value is NULL. So it prints out "Street: NULL".
But when the person clicks on the ajax button, which submits the data, then Wicket loads all the data from the form through AddressModel into address variable. In there the end result will be for example "Street: Bond street".
My question is that how I could init the process of saving data into the address variable in AjaxEventBehavior case?
Use AjaxFormComponentUpdatingBehavior which will submit the component's value.

Resources