MonoRail, Prototype validation and multiple submit buttons
Previously I have discussed how I added action level filters to allow me to drop a cancel button on the page and worry about how the cancel was handled in the controller. This worked fine and did exactly as what I needed until I added client-side validation to the pages, the cancel button (being a submit button) triggered the validation. To stop my cancel buttons triggering the validation I stopped the validator from automatically firing when the form was submitted by adding the following to the FormTag call:
$Form.FormTag( "%{action='processform', on_submit='false'}" )
To reconnect the validation and only validate the form if the button was not a cancel button, I used the following javascript that relies on the Prototype scripting library (I am using version 1.6.0.2).
/*
Allows form validation while enabling the use of a submit button to
cancel the page.
*/
// Register the validators once the form has loaded
Event.observe(window, 'load', RegisterValidationEventHandlers);
function RegisterValidationEventHandlers( event )
{
if (window.prototypeValidators)
{
validatorsHash = $H(window.prototypeValidators);
validatorsHash.values().each( function(value, index) {
Event.observe(value.form, 'submit', validateFormIfNotCancelButton.bindAsEventListener(this));
} );
}
}
// Check if the event originated from a "cancel" button, if not proceed with normal validation
function validateFormIfNotCancelButton( event )
{
var buttonClicked = document.activeElement || event.explicitOriginalTarget;
var eventElementName = Element.readAttribute(buttonClicked, 'name');
if(eventElementName != 'cancel')
{
var validator = prototypeValidators[event.element().id];
validator.onSubmit( event );
}
}
This first line registers an event listener on the window load event and executes the RegisterValidationEventHandlers function.
The RegisterValidationEventHandlers function checks for the existence of an associative array called prototypeValidators (automatically created by the PrototypeWebValidator) and if it is present, loops through attaching an event listener to the submit event for each form that has a validator. Note: for this to work, you will need to patch your PrototypeWebValidator as it currently creates an associative array from a normal array which is considered bad practice (see http://support.castleproject.org/browse/MR-440 for details).
The actual validation code is reasonably simple, retrieve the element that caused the event to fire, check it’s name and if it is not cancel, delegate the validation to the Prototype validator. The one interesting point to note is that there is no consistent mechanism to get the element that initiaited the submit, hence the var buttonClicked = document.activeElement || event.explicitOriginalTarget; which works in IE (document.activeElement) and FF (event.explicitOriginalTarget).
This script will work so long as you are using the default validation provider for MonoRail (prototype / dexagogo) and supports multiple forms without the need to add anything beyond suppressing the validation on submit behaviour via the additional property in the FormTag call.