/*==========================================================================*\ | $Id: DojoActionFormElement.java,v 1.4 2011/03/23 15:07:45 aallowat Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2006-2008 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.ui._base; import org.apache.log4j.Logger; import org.webcat.ui.WCForm; import org.webcat.ui.util.DojoRemoteHelper; import org.webcat.ui.util.JSHash; import com.webobjects.appserver.WOActionResults; import com.webobjects.appserver.WOAssociation; import com.webobjects.appserver.WOComponent; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WOElement; import com.webobjects.appserver.WOMessage; import com.webobjects.appserver.WORequest; import com.webobjects.appserver.WOResponse; import com.webobjects.foundation.NSDictionary; import er.ajax.AjaxUtils; import er.extensions.appserver.ERXResponseRewriter; import er.extensions.appserver.ERXWOContext; import er.extensions.components.ERXComponentUtilities; import er.extensions.components._private.ERXWOForm; //------------------------------------------------------------------------ /** * <p> * The base class from which all form widgets that can execute an action * (WCButton, WCMenuItem, and so forth) inherit. (Note that WCLink implements * this functionality separately since it is not a form element.) This class * provides a common set of bindings for executing server-side actions, both in * the form of ordinary page-load submits and via an Ajax interface. * </p><p> * Important note: Some uses of these elements (such as remote actions) will * involve the generation of an <tt>onClick</tt> event handler for the element. * If you wish to include your own <tt>onClick</tt> handling as well, use a * script with <tt>type="dojo/connect"</tt> rather than <tt>dojo/method</tt> to * ensure that both handlers are called correctly. In all cases, user-supplied * <tt>onClick</tt> handlers will be executed <i>before</i> the action handler. * </p> * * <h2>Bindings</h2> * <table> * <tr> * <td>{@code action}</td> * <td>The action method to invoke when this element is activated.</td> * </tr> * <tr> * <td>{@code directActionName}</td> * <td>The name of the direct action method (minus the "Action" suffix) to * invoke when this element is activated. Defaults to "default".</td> * </tr> * <tr> * <td>{@code actionClass}</td> * <td>The name of the class in which the method designated in * <tt>directActionName</tt> can be found. Defaults to DirectAction.</td> * </tr> * <tr> * <td>{@code remote}</td> * <td>If <tt>false</tt> or unspecified, the action is a traditional * synchronous action. If <tt>true</tt>, the action is executed via an Ajax * request. This defaults to false unless any of the other "remote.*" bindings * are specified, in which case this is assumed to be true. Therefore, it is * not necessary to explicitly use this binding unless you want an Ajax request * that does not use any of the other "remote.*" bindings.</td> * </tr> * <tr> * <td>{@code remote.responseType}</td> * <td>A string indicating how the action response should be treated, and in * which format it will be passed to the callback functions. Choices are "text" * (the default), where the response will be sent to the callback as a string; * "javascript", where the response is JavaScript code that will be executed * before the callback is called; "json", "json-comment-optional", * "json-comment-filtered", where the response is a JSON string that will be * evaluated and passed to the callback as an object; and "xml", where the * response is XML text that is parsed and passed to the callback as a Document * DOM object.</td> * </tr> * <tr> * <td>{@code remote.synchronous}</td> * <td>A boolean value indicating that the request should be synchronous * instead of asynchronous. Use this with caution as a long-running server-side * action will cause the browser to appear hung. * </td> * </tr> * </table> * * <h2>Events</h2> * <table> * <tr> * <td>{@code onRemoteLoad(response, ioArgs)}</td> * <td>Called upon a successful HTTP response code. The argument "response" is * the response returned from the action (see <tt>remote.responseType</tt>), * and "ioArgs" is defined as in dojo.xhr. <b>This function should return the * response for proper callback chaining.</b></td> * </tr> * <tr> * <td>{@code onRemoteError(response, ioArgs)}</td> * <td>Called upon an unsuccessful HTTP response code. The argument "response" * is the response returned from the action (see <tt>remote.responseType</tt>), * and "ioArgs" is defined as in dojo.xhr. <b>This function should return the * response for proper callback chaining.</b></td> * </tr> * <tr> * <td>{@code onRemoteEnd(response, ioArgs)}</td> * <td>Called upon the end of the request, regardless of the HTTP response code. * This handler will be called <b>after</b> <tt>onRemoteLoad</tt> and * <tt>onRemoteError</tt> if either of those is also specified. The argument * "response" is the response returned from the action (see * <tt>remote.responseType</tt>), and "ioArgs" is defined as in dojo.xhr. * <b>This function should return the response for proper callback chaining. * </b></td> * </tr> * </table> * * @author Tony Allevato * @version $Id: DojoActionFormElement.java,v 1.4 2011/03/23 15:07:45 aallowat Exp $ */ public abstract class DojoActionFormElement extends DojoFormElement { //~ Constructor ........................................................... // ---------------------------------------------------------- public DojoActionFormElement(String name, NSDictionary<String, WOAssociation> someAssociations, WOElement template) { super(name, someAssociations, template); _action = _associations.removeObjectForKey("action"); _actionClass = _associations.removeObjectForKey("actionClass"); _directActionName = _associations.removeObjectForKey("directActionName"); _remoteHelper = new DojoRemoteHelper(_associations); } //~ Methods ............................................................... // ---------------------------------------------------------- private String _actionClassAndName(WOContext context) { return computeActionStringInContext(_actionClass, _directActionName, context); } // ---------------------------------------------------------- protected boolean hasActionInContext(WOContext context) { WOComponent component = context.component(); boolean hasAction = (_action != null); boolean hasActionClass = (_actionClass != null && _actionClass.valueInComponent(component) != null); boolean hasDirectActionName = (_directActionName != null && _directActionName.valueInComponent(component) != null); return hasAction || hasActionClass || hasDirectActionName; } // ---------------------------------------------------------- @Override public boolean hasContent() { return true; } // ---------------------------------------------------------- /** * <p> * If the action element is implemented as a button or a hyperlink, then * that element itself suffices to perform a standard action. If the * element is represented by, for example, a div where the onclick * attribute is the only method of interaction, then that element's class * should override this and return true to use a "fake full submit" that * dynamically creates a button and uses it to submit the form. * </p><p> * Important: Make sure to use WCForm (or, at the very least, ERXWOForm) so * that the auto-detection of the containing form works correctly. * </p> * * @return true if the element needs a fake form submit to simulate a * page-load submit; false if the element itself can perform the submit */ protected boolean usesFakeFullSubmit() { return false; } // ---------------------------------------------------------- @Override public void appendToResponse(WOResponse response, WOContext context) { super.appendToResponse(response, context); if (_directActionName != null || _actionClass != null) { response._appendContentAsciiString( "<input type=\"hidden\" name=\"WOSubmitAction\""); response._appendTagAttributeAndValue("value", _actionClassAndName(context), false); response.appendContentString(" />"); } } // ---------------------------------------------------------- protected void appendNameAttributeToResponse(WOResponse response, WOContext context) { if(_directActionName != null || _actionClass != null) { response._appendTagAttributeAndValue("name", _actionClassAndName(context), false); } else { super.appendNameAttributeToResponse(response, context); } } // ---------------------------------------------------------- protected void appendOnClickScriptToResponse(WOResponse response, WOContext context) { if (_remoteHelper.isRemoteInContext(context)) { response.appendContentString("<script type=\"dojo/connect\" " + "event=\"onClick\" args=\"evt\">\n"); appendXhrGetToResponse(response, context); response.appendContentString("\n</script>\n"); } else if (usesFakeFullSubmit()) { response.appendContentString("<script type=\"dojo/connect\" " + "event=\"onClick\" args=\"evt\">\n"); response.appendContentString(WCForm.scriptToPerformFullSubmit( context, nameInContext(context))); response.appendContentString("\n</script>\n"); } } // ---------------------------------------------------------- @Override public void appendChildrenToResponse(WOResponse response, WOContext context) { super.appendChildrenToResponse(response, context); if (dojoType() != null && hasActionInContext(context)) { appendOnClickScriptToResponse(response, context); } } // ---------------------------------------------------------- @Override public void appendAttributesToResponse(WOResponse response, WOContext context) { super.appendAttributesToResponse(response, context); if (_remoteHelper.isRemoteInContext(context) && dojoType() == null && hasActionInContext(context)) { WOResponse xhrResponse = new WOResponse(); appendXhrGetToResponse(xhrResponse, context); xhrResponse.appendContentString(" return false;"); response._appendTagAttributeAndValue("onclick", xhrResponse.contentString(), true); } } // ---------------------------------------------------------- protected String shadowButtonIdInContext(WOContext context) { return "__dae_shadow_button__" + ERXWOContext.safeIdentifierName(context, false); } // ---------------------------------------------------------- @SuppressWarnings("unchecked") protected void appendXhrGetToResponse(WOResponse response, WOContext context) { WOComponent component = context.component(); JSHash requestOptions = new JSHash(); if (_directActionName != null) { // FIXME may need work, untested String actionUrl = context.directActionURLForActionNamed( (String) _directActionName.valueInComponent(component), ERXComponentUtilities.queryParametersInComponent( _associations, component)).replaceAll("&", "&"); requestOptions.put("url", actionUrl); } // If we're inside a form, include the name of the element as the // sender. This is unnecessary for buttons, but required for other // types of action elements, like WCMenuItem. String formName = ERXWOForm.formName(context, null); if (formName != null) { requestOptions.put("sender", nameInContext(context)); } response.appendContentString(_remoteHelper.remoteSubmitCall( "this", requestOptions, context)); } // ---------------------------------------------------------- public WOActionResults invokeAction(WORequest request, WOContext context) { if (AjaxUtils.isAjaxRequest(request) && AjaxUtils.shouldHandleRequest(request, context, null)) { return invokeRemoteAction(request, context); } else { return invokeStandardAction(request, context); } } // ---------------------------------------------------------- protected WOActionResults invokeRemoteAction(WORequest request, WOContext context) { WOActionResults result = null; WOComponent component = context.component(); AjaxUtils.createResponse(request, context); AjaxUtils.mutableUserInfo(request); context.setActionInvoked(true); if (_action != null) { result = (WOActionResults) _action.valueInComponent(component); } AjaxUtils.updateMutableUserInfoWithAjaxInfo(context); if (result == context.page()) { log.warn("An Ajax request attempted to return the page, which " + "is almost certainly an error."); result = null; } if (result == null) { result = AjaxUtils.createResponse(request, context); } return result; } // ---------------------------------------------------------- protected WOActionResults invokeStandardAction(WORequest request, WOContext context) { WOActionResults actionResult = null; WOComponent component = context.component(); if(!isDisabledInContext(context) && context.wasFormSubmitted()) { if(context.isMultipleSubmitForm()) { if(request.formValueForKey(nameInContext(context)) != null) { context.setActionInvoked(true); if(_action != null) { actionResult = (WOActionResults) _action.valueInComponent(component); } if(actionResult == null) { actionResult = context.page(); } } } else { context.setActionInvoked(true); if(_action != null) { actionResult = (WOActionResults) _action.valueInComponent(component); } if(actionResult == null) { actionResult = context.page(); } } } return actionResult; } //~ Static/instance variables ............................................. protected WOAssociation _action; protected WOAssociation _actionClass; protected WOAssociation _directActionName; protected DojoRemoteHelper _remoteHelper; private static final Logger log = Logger.getLogger(DojoActionFormElement.class); }