package er.ajax; import java.util.Enumeration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.webobjects.appserver.WOActionResults; import com.webobjects.appserver.WOApplication; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WORequest; import com.webobjects.appserver.WOResponse; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSMutableDictionary; import er.extensions.appserver.ERXResponse; import er.extensions.appserver.ERXWOContext; import er.extensions.appserver.ajax.ERXAjaxApplication; import er.extensions.appserver.ajax.ERXAjaxApplication.ERXAjaxResponseDelegate; /** * AjaxResponse provides support for performing an AjaxUpdate in the same response * as an ajax action. * * @author mschrag */ public class AjaxResponse extends ERXResponse { private static final Logger log = LoggerFactory.getLogger(Ajax.class); public static final String AJAX_UPDATE_PASS = "_ajaxUpdatePass"; private static NSMutableArray _responseAppenders; /** * Add a response appender to the list of response appender. At the end of * every AjaxResponse, the AjaxResponseAppenders are given an opportunity to * tag along. For instance, if you have an area at the top of your pages that * show errors or notifications, you may want all of your ajax responses to have * a chance to trigger an update of this area, so you could register an * {@link er.ajax.AjaxResponseAppender} that renders a javascript block that calls * MyNotificationsUpdate() only if there are notifications to be shown. Without * response appenders, you would have to include a check in all of your * components to do this. * * @param responseAppender the appender to add */ public static void addAjaxResponseAppender(AjaxResponseAppender responseAppender) { if (_responseAppenders == null) { _responseAppenders = new NSMutableArray(); } _responseAppenders.addObject(responseAppender); } private WORequest _request; private WOContext _context; public AjaxResponse(WORequest request, WOContext context) { super(context); _request = request; _context = context; } @Override public WOResponse generateResponse() { if (AjaxUpdateContainer.hasUpdateContainerID(_request)) { String originalSenderID = _context.senderID(); _context._setSenderID(""); try { CharSequence originalContent = _content; _content = new StringBuilder(); NSMutableDictionary userInfo = ERXWOContext.contextDictionary(); userInfo.setObjectForKey(Boolean.TRUE, AjaxResponse.AJAX_UPDATE_PASS); WOActionResults woactionresults = WOApplication.application().invokeAction(_request, _context); _content.append(originalContent); if (_responseAppenders != null) { Enumeration responseAppendersEnum = _responseAppenders.objectEnumerator(); while (responseAppendersEnum.hasMoreElements()) { AjaxResponseAppender responseAppender = (AjaxResponseAppender) responseAppendersEnum.nextElement(); responseAppender.appendToResponse(this, _context); } } if (_contentLength() == 0) { setStatus(HTTP_STATUS_INTERNAL_ERROR); log.warn("You performed an Ajax update, but no response was generated. A common cause of this is that you spelled your updateContainerID wrong. You specified a container ID '" + AjaxUpdateContainer.updateContainerID(_request) + "'."); } } finally { _context._setSenderID(originalSenderID); } } return this; } public static boolean isAjaxUpdatePass(WORequest request) { return ERXWOContext.contextDictionary().valueForKey(AjaxResponse.AJAX_UPDATE_PASS) != null; } /** * If you click on, for instance, an AjaxInPlace, that sends a request to the server that * you want to be in edit mode. The server flips its state so it is now in edit mode, but * if you click a second time before the response comes back, the state has changed, so * your click doesn't actually get delivered to an Ajax component. If there was no * recipient of your click, it means that there also is no update container specified * to be updated and your response was not turned into an AjaxResponse. This means that * the second click causes the contents to be replaced with a blank. * * AjaxResponseDelegate looks for the confluence of all of these events and uses its * fallback of pulling the update container ID out of the query parameters (generated on * the Javascript side) and forces an AjaxResponse process to occur. * * @author mschrag */ public static class AjaxResponseDelegate implements ERXAjaxResponseDelegate { public WOActionResults handleNullActionResults(WORequest request, WOResponse response, WOContext context) { WOActionResults finalActionResults = response; // If it's not an AjaxResponse, it's suspect ... It means it did not // go through an Ajax update pass, so it might be messed up if (!(response instanceof AjaxResponse)) { // This check is pretty much trickery. It turns out that when the problem // that we're looking for happens, you end up with a null content length, and // it seems to be the fastest way to determine that we're in this state. String contentLength = response.headerForKey("content-length"); if (contentLength == null) { // So updateContainerID never got set by the triggered action, so let's // pull it out of the query parameters. This isn't ideal, but it's // better than sending you back a big blank component to replace whatever // it is you double-clicked on. String updateContainerID = request.stringFormValueForKey(ERXAjaxApplication.KEY_UPDATE_CONTAINER_ID); if (updateContainerID != null) { // .. .and let's make an AjaxResponse so we get our update container // to be evaluated AjaxUpdateContainer.setUpdateContainerID(request, updateContainerID); finalActionResults = AjaxUtils.createResponse(request, context); } } } return finalActionResults; } } /** * Convenience method that calls <code>AjaxUtils.appendScriptHeaderIfNecessary</code> with this request. * @see er.ajax.AjaxUtils#appendScriptHeaderIfNecessary(WORequest, WOResponse) */ public void appendScriptHeaderIfNecessary() { AjaxUtils.appendScriptHeaderIfNecessary(_request, this); } /** * Convenience method that calls <code>AjaxUtils.appendScriptFooterIfNecessary</code> with this request. * @see er.ajax.AjaxUtils#appendScriptFooterIfNecessary(WORequest, WOResponse) */ public void appendScriptFooterIfNecessary() { AjaxUtils.appendScriptFooterIfNecessary(_request, this); } /** * Convenience method that calls <code>AjaxUtils.updateDomElement</code> with this request. * * @param id * ID of the DOM element to update * @param value * The new value * @param numberFormat * optional number format to format the value with * @param dateFormat * optional date format to format the value with * @param valueWhenEmpty * string to use when value is null * * @see er.ajax.AjaxUtils#updateDomElement(WOResponse, String, Object, String, String, String) */ public void updateDomElement(String id, Object value, String numberFormat, String dateFormat, String valueWhenEmpty) { AjaxUtils.updateDomElement(this, id, value, numberFormat, dateFormat, valueWhenEmpty); } /** * Convenience method that calls <code>updateDomElement</code> with no formatters and no valueWhenEmpty string. * * @param id * ID of the DOM element to update * @param value * The new value */ public void updateDomElement(String id, Object value) { updateDomElement(id, value, null, null, null); } }