Thursday, June 07, 2007

Embedding a form inside a Wicket AjaxTabbedPanel

It wasn't that obvious to me how to get input fields to save their values when switching tabs in a Wicket tabbed panel that is embedded in a form, so I thought I'd capture it here. The key is to add an Ajax onblur behavior to each input field. As mentioned in the Wicket-User forum, you can't use AjaxFormValidatingBehavior.addToAllFormComponents() with a tabbed panel, at least not as of Wicket 1.2.6. So this is how I chose to do it. I created a reusable subclass called UpdatingTextField. I started with Wicket Examples TabbedPanelPage sample code.

TabbedPanelPage.html


<html>
<head>
<title>QuickStart</title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<form wicket:id="formy">
<div wicket:id="tabs" class="tabpanel">[tabbed panel will be
here]</div>
</form>
</body>
</html>


TabbedPanelPage.java


/*
* ==============================================================================
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package wicket.quickstart;

import java.util.ArrayList;
import java.util.List;

import wicket.ajax.AjaxRequestTarget;
import wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import wicket.extensions.ajax.markup.html.tabs.AjaxTabbedPanel;
import wicket.extensions.markup.html.tabs.AbstractTab;
import wicket.markup.html.WebPage;
import wicket.markup.html.form.Form;
import wicket.markup.html.form.TextField;
import wicket.markup.html.panel.Panel;
import wicket.model.CompoundPropertyModel;
import wicket.model.IModel;
import wicket.model.Model;

/**
* Tabbed panel demo.
*
* @author ivaynberg
*/
public class TabbedPanelPage extends WebPage
{
private String text1 = "";
private String text2 = "";
private String text3 = "";

public TabbedPanelPage()
{
final Form form = new Form("formy", new CompoundPropertyModel(this));

// create a list of ITab objects used to feed the tabbed panel
List tabs = new ArrayList();
tabs.add(new AbstractTab(new Model("first tab"))
{
public Panel getPanel(String panelId)
{
Panel p = new TabPanel1(panelId, form.getModel());
TextField field = new UpdatingTextField("text1");
p.add(field);
return p;
}
});

tabs.add(new AbstractTab(new Model("second tab"))
{
public Panel getPanel(String panelId)
{
Panel p = new TabPanel2(panelId, form.getModel());
TextField field = new UpdatingTextField("text2");
p.add(field);
return p;
}
});

tabs.add(new AbstractTab(new Model("third tab"))
{
public Panel getPanel(String panelId)
{
Panel p = new TabPanel3(panelId, form.getModel());
TextField field = new UpdatingTextField("text3");
p.add(field);
return p;
}
});

form.add(new AjaxTabbedPanel("tabs", tabs));
add(form);
}

/**
* Panel representing the content panel for the first tab.
*/
private static class TabPanel1 extends Panel
{
public TabPanel1(String id, IModel model)
{
super(id, model);
}
};

/**
* Panel representing the content panel for the second tab.
*/
private static class TabPanel2 extends Panel
{
public TabPanel2(String id, IModel model)
{
super(id, model);
}
};

/**
* Panel representing the content panel for the third tab.
*/
private static class TabPanel3 extends Panel
{
public TabPanel3(String id, IModel model)
{
super(id, model);
}
}

public String getText1()
{
return text1;
}

public void setText1(String text1)
{
this.text1 = text1;
}

public String getText2()
{
return text2;
}

public void setText2(String text2)
{
this.text2 = text2;
}

public String getText3()
{
return text3;
}

public void setText3(String text3)
{
this.text3 = text3;
}

private static class UpdatingTextField extends TextField
{
public UpdatingTextField(String id)
{
super(id);
add(new AjaxFormComponentUpdatingBehavior("onblur")
{
protected void onUpdate(AjaxRequestTarget target)
{
}
});
}
}
}


Sample tab: TabbedPanelPage$TabPanel1.html


<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<wicket:panel>
<br />
This is tab-panel 1
<p>
<input type="text" wicket:id="text1" />
</p>
</wicket:panel>
</html>

11 comments:

Scott said...

Julian, do you have a LIVE example for this? I am struggling a bit trying to figure out how to have Tabbed pages with individual FORMS on some of the tabs -- in both a HOME and RESULTS html page.

In addition, I *would* like to retain FORM values when leaving and returning to a TAB... which (I think) is the real emphasis of your demo.

I sense that maybe you have had to build the same? [ie. multiple TABS with different FORMS on some of the TABS]

BTW, I'm a newbie to: java, OOP and wicket, so the more detailed an answer, the better. ;-)

Thank You Sir!

Scott

Julian Sinai said...

Scott, unfortunately at this point I don't have a live example to show you. But it's quite easy to embed forms inside tabs. Rather than adding the tabbed panel to the form, you would instead create one form per tab, then add each form to that tab, then add the tabbed panel to the page. In this case, Wicket will automatically retain your form values if you click around from one tab to another. Hope this helps.

Kenny S said...

Thanks for this post. I'm doing the same thing... a form that contains an AjaxTabbedPanel. Your sample code allowed me to get this working and allows a user to modify text field values while switching back and forth between the different tabs. When they are done modifying the data on all the tabs, they can submit the entire form. However, I have not achieved the same behavior as the UpdatingTextField with other components; specifically a CheckBoxMultipleChoice.

Is it possible for you to explain why the UpdatingTextField component allows its value to be changed and still retain that value even after switching between different tabs? This is great, but the UpdatingTextField code does not make it apparent on how this is so. I've tried to reproduce the same behavior for a CheckBoxMultipleChoice component, but after the "onblur" event fires, all of the checkboxes are unselected. Probably has something to do with me using a LoadableDetachableModel for the CheckBoxMultipleChoice, but not sure.

PaweĊ‚ Szulc said...

Kenny what you need to do is to ovveride newLink method in AjaxTabbedPanel

Luis said...

Hi i tried your solution with TextField and DropDownChoice components, it works fine, good job :-) But now i can't figure out how to make it work with RadioChoice ones.Can anybody help me please?Thank you so much.

Luis

Samu said...

I think this solution may be even better: http://apache-wicket.1842946.n4.nabble.com/Keep-state-of-values-between-AjaxTabbedPanel-tab-change-td1860721.html#a1860723

Luis said...

If you want to spread your form on two or more tabs, i'm agree that would be much better to use a Javascript solution (i worked my problem out with jQuery) against Wicket one. I guess Wicket tabs are advisable especially for showing datas.

Julian Sinai said...

Samu:

Thanks for the pointer.

Luis:

I actually use jQuery tabs for the case where I want to embed tabs inside the form, rather than the other way around.

oggie said...

Does anyone have a recent working example of using jquery and tabs with one form?

Or at least a working example of submitting tabs with forms?

Timaar said...

http://it-essence.xs4all.nl/roller/technology/entry/tabbed_forms_in_wicket

Here is a great answer to the problem.

I have not tested this with an ajax-tab.

Dominic Joas said...

Thank you for this nice snippet.
I've searched a lot to solve this Problem!