package de.flower.rmt.ui.markup.html.form.field;
import de.flower.common.ui.Css;
import de.flower.common.ui.model.StateSavingModel;
import de.flower.common.util.Check;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.feedback.ComponentFeedbackMessageFilter;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.IMarkupFragment;
import org.apache.wicket.markup.MarkupElement;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.validation.IValidator;
/**
* @author flowerrrr
*/
public abstract class AbstractFormFieldPanel extends Panel {
// must match the wicket:for attribute of the label
public final static String ID = "input";
public static final String LABEL_KEY = "labelKey";
private boolean isValidated = false;
private final FormComponent formComponent;
private boolean validationEnabled = true;
public AbstractFormFieldPanel(String id, FormComponent fc) {
super(id);
setOutputMarkupId(true);
add(AttributeModifier.append("class", id));
formComponent = fc;
add(formComponent);
formComponent.add(AttributeModifier.append("class", getCssClassModel()));
// set css class of border according to validation result.
IModel<String> cssClassModel = new AbstractReadOnlyModel<String>() {
@Override
public String getObject() {
if (formComponent.getFeedbackMessage() != null) {
return Css.ERROR;
} else if (isValidated) {
return Css.VALID;
} else {
return "";
}
}
};
add(new AttributeAppender("class", cssClassModel, " "));
add(new FeedbackPanel("feedback", new ComponentFeedbackMessageFilter(formComponent)) {
@Override
public boolean isVisible() {
return formComponent.getFeedbackMessage() != null;
}
});
// use form's compoundpropertymodel by default but let component override it if desired.
if (formComponent.getModel() == null) {
// set model of form component. form.getForm not available at constructor time, so we have
// to use wrapping model to defer lookup of form.
IModel<?> formModelWrapperModel = new AbstractReadOnlyModel<IModel<?>>() {
@Override
public IModel<?> getObject() {
return formComponent.getForm().getModel();
}
};
formComponent.setModel(new PropertyModel(formModelWrapperModel, this.getId()));
}
if (useStateSavingModel()) {
final StateSavingModel<?> cachingModel = new StateSavingModel(formComponent.getModel());
formComponent.setModel(cachingModel);
}
// add validation
if (isValidationEnabled()) {
formComponent.add(getValidator(formComponent));
if (isInstantValidationEnabled()) {
formComponent.add(new AjaxFormComponentUpdatingBehavior("onchange") {
@Override
protected void onUpdate(AjaxRequestTarget target) {
isValidated = true;
target.add(AbstractFormFieldPanel.this);
onChange(target);
}
@Override
protected void onError(AjaxRequestTarget target, RuntimeException e) {
if (e != null) {
throw e;
}
isValidated = true;
target.add(AbstractFormFieldPanel.this);
}
});
}
}
formComponent.setLabel(new LoadableDetachableModel<String>() {
/**
* Have to delay lookup of resourceKey until component is rendered.
*/
@Override
protected String load() {
String resourceKey = getLabelKey();
return (resourceKey == null) ? "" : new ResourceModel(resourceKey).getObject();
}
});
}
@Override
public void onDetach() {
// reset state of component.
isValidated = false;
super.onDetach();
}
@Override
protected void onComponentTag(ComponentTag tag) {
// replace whatever tag the user has given.
// user can still write <input wicket:id="name" /> and it will be replaced with a div tag.
tag.setName("div");
// need to set twitter-bootstrap class 'control-group'
tag.put("class", "control-group");
tag.remove(LABEL_KEY);
tag.setNamespace(null);
super.onComponentTag(tag);
}
public String getLabelKey() {
String labelKey;
final IMarkupFragment markup = getMarkup();
final MarkupElement markupElement = markup.get(0);
labelKey = ((ComponentTag) markupElement).getAttribute(LABEL_KEY);
return labelKey;
}
protected String getCssClass() {
final IMarkupFragment markup = getMarkup();
final MarkupElement markupElement = markup.get(0);
return ((ComponentTag) markupElement).getAttribute("class");
}
protected IModel<String> getCssClassModel() {
return new AbstractReadOnlyModel<String>() {
@Override
public String getObject() {
return getCssClass();
}
};
}
public FormComponent getFormComponent() {
return formComponent;
}
public void addValidator(final IValidator<?> validator) {
formComponent.add(validator);
}
/**
* Subclass can override if they need special handling.
*
* @param fc
* @return
*/
protected IValidator<?> getValidator(FormComponent<?> fc) {
return new org.wicketstuff.jsr303.validator.PropertyValidator(fc);
}
/**
* Override this method to disable standard instant validation behavior.
*
* @return
*/
protected boolean isInstantValidationEnabled() {
return true;
}
/**
* Override this method to fully disable validation for this component.
*
* @return
*/
protected boolean isValidationEnabled() {
return validationEnabled;
}
public AbstractFormFieldPanel setValidationEnabled(boolean enabled) {
this.validationEnabled = enabled;
return this;
}
@SuppressWarnings("SameReturnValue")
protected boolean useStateSavingModel() {
return true;
}
public StateSavingModel<?> getStateSavingModel() {
Check.isTrue(useStateSavingModel(), "Form component does not use state saving model");
return (StateSavingModel<?>) formComponent.getModel();
}
/**
* Override this method to get notified when ajax-callback fires for onChange event
* of component.
*
* @param target
*/
protected void onChange(AjaxRequestTarget target) {
}
}