Last active
December 16, 2015 11:09
-
-
Save sody/5425817 to your computer and use it in GitHub Desktop.
Form separation in Tapestry
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<fieldset> | |
<legend>${message:section.common}</legend> | |
<t:label for="first" class="control-label"/> | |
<t:textfield t:id="first" value="firstName" validate="required,maxLength=50" | |
label="message:label.first-name"/> | |
<t:label for="last" class="control-label"/> | |
<t:textfield t:id="last" value="lastName" validate="required,maxLength=50" | |
label="message:label.last-name"/> | |
</fieldset> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@SupportsInformalParameters | |
public class FormFieldSet { | |
@Parameter(required = true, defaultPrefix = BindingConstants.LITERAL) | |
private String title; | |
@Inject | |
private ComponentResources resources; | |
@Environmental | |
private ValidationTracker tracker; | |
@BeginRender | |
void renderTitle(final MarkupWriter writer) { | |
writer.element("fieldset"); | |
resources.renderInformalParameters(writer); | |
// render legend | |
final Element el = writer.element("legend"); | |
if (tracker.getHasErrors()) { | |
el.addClassName("text-error"); | |
} | |
writer.write(title); | |
writer.end(); | |
} | |
@AfterRender | |
void end(final MarkupWriter writer) { | |
writer.end(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class FormSection { | |
private static final ComponentAction<FormSection> SETUP_TRACKER = new SetupTracker(); | |
private static final ComponentAction<FormSection> CLEANUP_TRACKER = new CleanupTracker(); | |
private static final ComponentAction<FormSection> PROCESS_SUBMISSION = new ProcessSubmission(); | |
@Persist(PersistenceConstants.FLASH) | |
private ValidationTracker tracker; | |
private ValidationTracker wrapper; | |
@Inject | |
private ComponentResources resources; | |
@Environmental | |
private FormSupport formSupport; | |
@Environmental | |
private TrackableComponentEventCallback eventCallback; | |
@Inject | |
private Environment environment; | |
@SetupRender | |
void storeSetupAction() { | |
formSupport.storeAndExecute(this, SETUP_TRACKER); | |
} | |
@CleanupRender | |
void storeCleanupAction() { | |
formSupport.storeAndExecute(this, CLEANUP_TRACKER); | |
formSupport.store(this, PROCESS_SUBMISSION); | |
} | |
private void processSubmission() { | |
// defer validation to be executed after all field values are populated | |
formSupport.defer(new Runnable() { | |
public void run() { | |
environment.push(ValidationTracker.class, wrapper); | |
validateSection(); | |
environment.pop(ValidationTracker.class); | |
} | |
}); | |
} | |
private void setupValidationTracker() { | |
// get or create inner validation tracker | |
// it will save validation state for fields within this form section | |
final ValidationTracker innerTracker = tracker != null ? tracker : new ValidationTrackerImpl(); | |
// get original validation tracker | |
final ValidationTracker outerTracker = environment.pop(ValidationTracker.class); | |
// add error recording hook to original validation tracker | |
// that will save inner validation tracker in session flash attribute | |
final ValidationTracker outerTrackerWrapper = new ValidationTrackerWrapper(outerTracker) { | |
private boolean saved = false; | |
private void save() { | |
if (!saved) { | |
tracker = innerTracker; | |
saved = true; | |
} | |
} | |
@Override | |
public void recordError(final Field field, final String errorMessage) { | |
super.recordError(field, errorMessage); | |
save(); | |
} | |
@Override | |
public void recordError(final String errorMessage) { | |
super.recordError(errorMessage); | |
save(); | |
} | |
}; | |
// replace original validation tracker with its hooked version | |
environment.push(ValidationTracker.class, outerTrackerWrapper); | |
// create composite validation tracker that will record errors and input values | |
// in both inner and original validation trackers | |
wrapper = new ValidationTrackerWrapper(innerTracker) { | |
@Override | |
public void recordError(final Field field, final String errorMessage) { | |
super.recordError(field, errorMessage); | |
outerTrackerWrapper.recordError(field, errorMessage); | |
} | |
@Override | |
public void recordError(final String errorMessage) { | |
super.recordError(errorMessage); | |
outerTrackerWrapper.recordError(errorMessage); | |
} | |
@Override | |
public void recordInput(final Field field, final String input) { | |
super.recordInput(field, input); | |
outerTrackerWrapper.recordInput(field, input); | |
} | |
}; | |
// push composite tracker to environment | |
// to be accessible by all inner components | |
environment.push(ValidationTracker.class, wrapper); | |
} | |
private void cleanupValidationTracker() { | |
// pop composite tracker from environment | |
environment.pop(ValidationTracker.class); | |
} | |
private void validateSection() { | |
try { | |
// trigger validate event | |
// it will record error if validation exception occurs | |
resources.triggerEvent(EventConstants.VALIDATE, null, eventCallback); | |
} catch (RuntimeException ex) { | |
final ValidationException ve = ExceptionUtils.findCause(ex, ValidationException.class); | |
if (ve != null) { | |
wrapper.recordError(ve.getMessage()); | |
return; | |
} | |
throw ex; | |
} | |
} | |
static class SetupTracker implements ComponentAction<FormSection> { | |
public void execute(final FormSection component) { | |
component.setupValidationTracker(); | |
} | |
@Override | |
public String toString() { | |
return "FormSection.SetupTracker"; | |
} | |
} | |
static class CleanupTracker implements ComponentAction<FormSection> { | |
public void execute(final FormSection component) { | |
component.cleanupValidationTracker(); | |
} | |
@Override | |
public String toString() { | |
return "FormSection.CleanupTracker"; | |
} | |
} | |
static class ProcessSubmission implements ComponentAction<FormSection> { | |
public void execute(final FormSection component) { | |
component.processSubmission(); | |
} | |
@Override | |
public String toString() { | |
return "FormSection.ProcessSubmission"; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<t:form t:id="form" class="form-horizontal" autocomplete="off"> | |
<t:form.section t:id="commonSection"> | |
<t:form.fieldset title="message:section.common"> | |
<t:form.errors/> | |
<div class="control-group"> | |
<t:label for="first" class="control-label"/> | |
<div class="controls"> | |
<t:textfield t:id="first" value="firstName" validate="required,maxLength=50" | |
label="message:label.first-name"/> | |
</div> | |
</div> | |
<div class="control-group"> | |
<t:label for="last" class="control-label"/> | |
<div class="controls"> | |
<t:textfield t:id="last" value="lastName" validate="required,maxLength=50" | |
label="message:label.last-name"/> | |
</div> | |
</div> | |
<div class="control-group"> | |
<t:label for="about" class="control-label"/> | |
<div class="controls"> | |
<t:textarea t:id="about" value="about" validate="maxLength=255" | |
label="message:label.about"/> | |
</div> | |
</div> | |
</t:form.fieldset> | |
</t:form.section> | |
<t:form.section t:id="credentialsSection"> | |
<t:form.fieldset title="message:section.credentials"> | |
<t:form.errors/> | |
<div class="control-group"> | |
<t:label for="password" class="control-label"/> | |
<div class="controls"> | |
<t:passwordfield t:id="password" value="password" validate="required" | |
label="message:label.password"/> | |
</div> | |
</div> | |
<div class="control-group"> | |
<t:label for="confirmPassword" class="control-label"/> | |
<div class="controls"> | |
<t:passwordfield t:id="confirmPassword" value="confirmPassword" validate="required" | |
label="message:label.confirm-password"/> | |
</div> | |
</div> | |
</t:form.fieldset> | |
</t:form.section> | |
<div class="form-actions"> | |
<t:submit value="message:label.submit" class="btn btn-primary"/> | |
</div> | |
</t:form> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class FormSection { | |
private static final ComponentAction<FormSection> PROCESS_SUBMISSION = new ProcessSubmission(); | |
@Inject | |
private ComponentResources resources; | |
@Environmental | |
private ValidationTracker tracker; | |
@Environmental | |
private FormSupport formSupport; | |
@Environmental | |
private TrackableComponentEventCallback eventCallback; | |
@CleanupRender | |
void storeCleanupAction() { | |
formSupport.store(this, PROCESS_SUBMISSION); | |
} | |
private void processSubmission() { | |
// defer validation to be executed after all field values are populated | |
formSupport.defer(new Runnable() { | |
public void run() { | |
validateSection(); | |
} | |
}); | |
} | |
private void validateSection() { | |
try { | |
// trigger validate event | |
// it will record error if validation exception occurs | |
resources.triggerEvent(EventConstants.VALIDATE, null, eventCallback); | |
} catch (RuntimeException ex) { | |
final ValidationException ve = ExceptionUtils.findCause(ex, ValidationException.class); | |
if (ve != null) { | |
tracker.recordError(ve.getMessage()); | |
return; | |
} | |
throw ex; | |
} | |
} | |
static class ProcessSubmission implements ComponentAction<FormSection> { | |
public void execute(final FormSection component) { | |
component.processSubmission(); | |
} | |
@Override | |
public String toString() { | |
return "FormSection.ProcessSubmission"; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@OnEvent(value = EventConstants.VALIDATE, component = "credentialsSection") | |
void validateCredentials() throws ValidationException { | |
if (password != null && confirmPassword != null && !password.equals(confirmPassword)) { | |
throw new ValidationException(message("error.password-match")); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<t:form.section t:id="credentialsSection"> | |
<t:label for="password" class="control-label"/> | |
<t:passwordfield t:id="password" value="password" validate="required" | |
label="message:label.password"/> | |
<t:label for="confirmPassword" class="control-label"/> | |
<t:passwordfield t:id="confirmPassword" value="confirmPassword" validate="required" | |
label="message:label.confirm-password"/> | |
</t:form.section> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment