package fr.openwide.core.wicket.more.util.listener; import java.io.Serializable; import org.apache.wicket.Component; import org.apache.wicket.MetaDataKey; import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior; import org.apache.wicket.application.IComponentInstantiationListener; import org.apache.wicket.application.IComponentOnAfterRenderListener; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.FormComponent; import org.apache.wicket.markup.html.form.IFormSubmitter; import org.apache.wicket.markup.html.form.validation.IFormValidator; import org.apache.wicket.protocol.http.WebApplication; public final class FormProcessedListener implements IComponentInstantiationListener, IComponentOnAfterRenderListener { private static final FormProcessedListener INSTANCE = new FormProcessedListener(); private static final MetaDataKey<Serializable> IS_PROCESSED = new MetaDataKey<Serializable>() { private static final long serialVersionUID = 1L; private Object readResolve() { return IS_PROCESSED; } }; private static final IFormValidator PROCESSING_LISTENER = new IFormValidator() { private static final long serialVersionUID = 1L; @Override public FormComponent<?>[] getDependentFormComponents() { return null; } @Override public void validate(Form<?> form) { form.setMetaData(IS_PROCESSED, IS_PROCESSED); } private Object readResolve() { return PROCESSING_LISTENER; } }; public static boolean isProcessed(Form<?> form) { return form.getMetaData(IS_PROCESSED) != null; } /** * Finds the processed {@link Form} for a given {@link FormComponent}. * * <h4>Implementation details</h4> * <p>There is a slight difference between a form being submitted and a form being processed: * when a button submits a nested form, it actually submits the whole form (all the data is transfered to the server) * but only triggers the processing of the nested form (data submitted for the root form, for instance, is ignored). * <p>We cannot rely on the isSubmitted() method to determine if a form has been processed, * because it may return true for every sub-form of a root form, even though only one * sub-form has been processed, due to the behavior of AjaxFormSubmitBehavior * that always call <code>form.getRootForm().onFormSubmitted()</code> and not * <code>form.onFormSubmitted()</code>. * @see AjaxFormSubmitBehavior#onEvent(Component, org.apache.wicket.event.IEvent) */ public static Form<?> findProcessedForm(FormComponent<?> formComponent) { final Form<?> form = formComponent.getForm(); if (form == null) { return null; } final Form<?> rootForm = form.getRootForm(); IFormSubmitter submitter = rootForm.findSubmittingButton(); if (submitter == null) { return form; } else { final Form<?> targetedForm = submitter.getForm(); if (targetedForm == null) { throw new IllegalStateException("submitting component must not return 'null' on getForm()"); } if (targetedForm.equals(rootForm)) { // the submitting component points at the root form => so let's // just go with // root, everything else will be processed with it anyway. return rootForm; } else { // a different form was targeted. let's find the outermost form // that wants to be processed. Form<?> formThatWantsToBeProcessed = targetedForm; Form<?> current = targetedForm.findParent(Form.class); while (current != null) { if (isProcessed(current)) { formThatWantsToBeProcessed = current; } current = current.findParent(Form.class); } return formThatWantsToBeProcessed; } } } public static void init(WebApplication application) { application.getComponentInstantiationListeners().add(INSTANCE); application.getComponentOnAfterRenderListeners().add(INSTANCE); } @Override public void onInstantiation(Component component) { if (component instanceof Form) { Form<?> form = (Form<?>) component; form.add(PROCESSING_LISTENER); } } @Override public void onAfterRender(Component component) { if (component instanceof Form) { // Nettoyage des metadonnées ajoutées par PROCESSING_LISTENER component.setMetaData(IS_PROCESSED, null); } } }