/*==========================================================================*\
| $Id: ValidatingAction.java,v 1.2 2010/10/07 20:47:31 aallowat Exp $
|*-------------------------------------------------------------------------*|
| Copyright (C) 2009 Virginia Tech
|
| This file is part of Web-CAT.
|
| Web-CAT is free software; you can redistribute it and/or modify
| it under the terms of the GNU Affero General Public License as published
| by the Free Software Foundation; either version 3 of the License, or
| (at your option) any later version.
|
| Web-CAT is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU Affero General Public License
| along with Web-CAT; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/
package org.webcat.core;
import org.webcat.core.DualAction;
import org.json.JSONException;
import org.json.JSONObject;
import org.webcat.ui.WCButton;
import org.webcat.ui.WCForm;
import org.webcat.ui.generators.JavascriptGenerator;
import com.webobjects.appserver.WOActionResults;
import com.webobjects.appserver.WOAssociation;
import com.webobjects.appserver.WOComponent;
import com.webobjects.appserver.WOContext;
import com.webobjects.foundation.NSDictionary;
import er.extensions.appserver.ERXWOContext;
//-------------------------------------------------------------------------
/**
* <p>
* An action wrapper that provides for validation of fields in a form. To use
* this functionality, assign a {@link WCButton} with {@code remote=true} to
* an action on your component, and inside your action, do the following:
* </p>
* <pre>
* public WOActionResults myAction()
* {
* return new ValidatingAction(this)
* {
* protected WOActionResults performStandardAction()
* {
* // logic to be performed when validation was successful
* return pageWithName(SomeNewPage.class); // or null
* }
* };
* }
* </pre>
* <p>
* Clicking the button will invoke this action remotely, which will push the
* field value into their bindings, and then this class will run the validators
* to validate the values of those bindings. If validation failed, the response
* will be a Javascript fragment that will modify the page in order to display
* the errors, and the action will end. However, if validation succeeded, then
* the Javascript response will invoke a standard form submit again on the same
* button, which will result in the {@link #validationDidSucceed()} method
* being called so that the new values can be committed and a new page can be
* loaded.
* </p>
*
* @author Tony Allevato
* @version $Id: ValidatingAction.java,v 1.2 2010/10/07 20:47:31 aallowat Exp $
*/
public class ValidatingAction extends DualAction
{
//~ Constructors ..........................................................
// ----------------------------------------------------------
/**
* Initializes a new instance of the ValidatingAction class.
*
* @param component the component on which the action is being invoked
*/
public ValidatingAction(WOComponent component)
{
super(component);
// We have to retrieve this information at the time that this action is
// constructed inside the action method on the component, not later on
// when the action response is actually being generated, because at
// that time the context will not be the correct one.
WOContext currentContext = ERXWOContext.currentContext();
formName = WCForm.formName(currentContext, null);
formJsId = "form_" + formName;
formValidationResults = "validationResults_" + formName;
validators = WCForm.validators();
senderID = currentContext.senderID();
}
//~ Methods ...............................................................
// ----------------------------------------------------------
/**
* Runs the validators and manipulates the page layout to reflect any
* validation errors that may have occurred.
*
* @return a JavascriptGenerator that manipulates the page layout
*/
protected final WOActionResults performRemoteAction()
{
JavascriptGenerator page = new JavascriptGenerator();
JSONObject results = collectValidatorResults();
page.call(formValidationResults + ".setAll", results);
page.call(formJsId + ".validate");
if (results.length() == 0)
{
validationDidSucceed(page);
}
else
{
validationDidFail(page);
}
return page;
}
// ----------------------------------------------------------
/**
* Executes each validator method on the hosting component and collects the
* resulting messages.
*
* @return a JSONObject containing the validation results, where each key
* is the WebObjects element ID of a widget that failed validation, and
* the corresponding value is the message to be shown to the user
*/
private JSONObject collectValidatorResults()
{
JSONObject results = new JSONObject();
for (String elementID : validators.keySet())
{
WOAssociation validator =
validators.objectForKey(elementID);
String message = (String) validator.valueInComponent(component());
if (message != null)
{
try
{
results.put(elementID, message);
}
catch (JSONException e)
{
// Do nothing.
}
}
}
return results;
}
// ----------------------------------------------------------
/**
* Subclasses should override this in order to execute an action once, and
* if, validation succeeded. The default implementation simply returns
* null in order to reload the page. This method is not abstract because
* in some cases, you may override {@link #validationDidSucceed(JavascriptGenerator)}
* to take some action other than executing a component action.
*
* @return the results of the action
*/
@Override
protected WOActionResults performStandardAction()
{
return null;
}
// ----------------------------------------------------------
/**
* <p>
* Called if validation succeeded. The default behavior is to execute a
* Javascript statement that will cause the form to be submitted as a
* standard page-load action. Subclasses may override this if they wish to
* provide alternate behavior; for example, a validated form inside a
* dialog box may wish to close the dialog rather than reload the page.
* </p><p>
* If a subclass wants to add its own behavior but still retain the final
* form submit action, call <code>super.validationDidSucceed(page)</code>
* <b>after</b> adding your own behavior with the JavascriptGenerator.
* </p>
*
* @param page the JavascriptGenerator used to provide the client-side
* behavior
*/
protected void validationDidSucceed(JavascriptGenerator page)
{
// Generate a call that will re-submit the form as a standard
// page load request, which will result in validationDidSucceed()
// being called.
page.submit(formName, senderID);
}
// ----------------------------------------------------------
/**
* Called if validation failed. The default behavior, after flagging the
* invalid fields in the form, is to do nothing further; subclasses may
* override this if they wish to manipulate the page in any other way.
*
* @param page the JavascriptGenerator used to provide the client-side
* behavior
*/
protected void validationDidFail(JavascriptGenerator page)
{
// Do nothing.
}
//~ Static/instance variables .............................................
private NSDictionary<String, WOAssociation> validators;
private String formName;
private String formJsId;
private String formValidationResults;
private String senderID;
}