package er.ajax; import java.net.MalformedURLException; 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.WORequest; import com.webobjects.appserver.WOResponse; import com.webobjects.appserver._private.WODynamicElementCreationException; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSForwardException; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSMutableDictionary; import er.extensions.appserver.ERXRequest; import er.extensions.appserver.ajax.ERXAjaxApplication; import er.extensions.components.ERXComponentUtilities; import er.extensions.foundation.ERXMutableURL; import er.extensions.foundation.ERXStringUtilities; /** * Updates a region on the screen by creating a request to an action, then returning a script that in turn creates an * Ajax.Updater for the area. If you do not provide an action binding, it will just update the specified area. * * @binding action the action to call when the link executes * @binding directActionName the direct action to call when link executes * @binding onLoading JavaScript function to evaluate when the request begins * @binding onComplete JavaScript function to evaluate when the request has finished. * @binding onSuccess JavaScript function to evaluate when the request was successful. * @binding onFailure JavaScript function to evaluate when the request has failed. * @binding onException JavaScript function to evaluate when the request had errors. * @binding evalScripts boolean defining if the container update is expected to be a script. * @binding ignoreActionResponse boolean defining if the action's response should be thrown away (useful when the same * action has both Ajax and plain links) * @binding onClickBefore if the given function returns true, the onClick is executed. This is to support confirm(..) * dialogs. * @binding onClick JS function, called after the click on the client * @binding onClickServer JS returned from the server after the update * @binding updateContainerID the id of the AjaxUpdateContainer to update after performing this action * @binding replaceID the ID of the div (or other html element) whose contents will be replaced with the results of this * action * @binding title title of the link * @binding style css style of the link * @binding class css class of the link * @binding id id of the link * @binding disabled boolean defining if the link renders the tag * @binding string string to get preprended to the contained elements * @binding function a custom function to call that takes a single parameter that is the action url * @binding elementName the element name to use (defaults to "a") * @binding functionName if set, the link becomes a javascript function * @binding button if true, this is rendered as a javascript button * @binding asynchronous boolean defining if the update request is sent asynchronously or synchronously, defaults to true * @binding accesskey hot key that should trigger the link (optional) * // PROTOTYPE EFFECTS * @binding effect synonym of afterEffect except it always applies to updateContainerID * @binding effectDuration the duration of the effect to apply before * // PROTOTYPE EFFECTS * @binding beforeEffect the Scriptaculous effect to apply onSuccess ("highlight", "slideIn", "blindDown", etc); * @binding beforeEffectID the ID of the container to apply the "before" effect to (blank = try nearest container, then * try updateContainerID) * @binding beforeEffectDuration the duration of the effect to apply before * // PROTOTYPE EFFECTS * @binding afterEffect the Scriptaculous effect to apply onSuccess ("highlight", "slideIn", "blindDown", etc); * @binding afterEffectID the ID of the container to apply the "after" effect to (blank = try nearest container, then * try updateContainerID) * @binding afterEffectDuration the duration of the effect to apply before * * // PROTOTYPE EFFECTS * @binding insertion JavaScript function to evaluate when the update takes place (or effect shortcuts like "Effect.blind", or "Effect.BlindUp") * @binding insertionDuration the duration of the before and after insertion animation (if using insertion) * @binding beforeInsertionDuration the duration of the before insertion animation (if using insertion) * @binding afterInsertionDuration the duration of the after insertion animation (if using insertion) */ public class AjaxUpdateLink extends AjaxDynamicElement { public AjaxUpdateLink(String name, NSDictionary<String, WOAssociation> associations, WOElement children) { super(name, associations, children); } public String onClick(WOContext context, boolean generateFunctionWrapper) { WOComponent component = context.component(); NSMutableDictionary options = createAjaxOptions(component); StringBuilder onClickBuffer = new StringBuilder(); String onClick = (String) valueForBinding("onClick", component); String onClickBefore = (String) valueForBinding("onClickBefore", component); String updateContainerID = AjaxUpdateContainer.updateContainerID(this, component); String functionName = (String) valueForBinding("functionName", component); String function = (String) valueForBinding("function", component); String replaceID = (String) valueForBinding("replaceID", component); // PROTOTYPE EFFECTS AjaxUpdateLink.addEffect(options, (String) valueForBinding("effect", component), updateContainerID, (String) valueForBinding("effectDuration", component)); String afterEffectID = (String) valueForBinding("afterEffectID", component); if (afterEffectID == null) { afterEffectID = AjaxUpdateContainer.currentUpdateContainerID(); if (afterEffectID == null) { afterEffectID = updateContainerID; } } // PROTOTYPE EFFECTS AjaxUpdateLink.addEffect(options, (String) valueForBinding("afterEffect", component), afterEffectID, (String) valueForBinding("afterEffectDuration", component)); // PROTOTYPE EFFECTS String beforeEffect = (String) valueForBinding("beforeEffect", component); WOAssociation directActionNameAssociation = (WOAssociation) associations().valueForKey("directActionName"); if (beforeEffect == null && updateContainerID != null && directActionNameAssociation == null && replaceID == null && function == null && onClick == null && onClickBefore == null) { NSDictionary nonDefaultOptions = AjaxUpdateContainer.removeDefaultOptions(options); onClickBuffer.append("AUL."); if (generateFunctionWrapper) { onClickBuffer.append("updateFunc"); } else { onClickBuffer.append("update"); } onClickBuffer.append("('"); onClickBuffer.append(updateContainerID); onClickBuffer.append("', "); AjaxOptions.appendToBuffer(nonDefaultOptions, onClickBuffer, context); onClickBuffer.append(", '"); onClickBuffer.append(context.contextID()); onClickBuffer.append('.'); onClickBuffer.append(context.elementID()); onClickBuffer.append('\''); // if (generateFunctionWrapper) { // onClickBuffer.append(", additionalParams"); // } onClickBuffer.append(')'); onClickBuffer.append(';'); } else { if (generateFunctionWrapper) { onClickBuffer.append("function(additionalParams) {"); } if (onClickBefore != null) { onClickBuffer.append("if ("); onClickBuffer.append(onClickBefore); onClickBuffer.append(") {"); } // PROTOTYPE EFFECTS if (beforeEffect != null) { onClickBuffer.append("new "); onClickBuffer.append(AjaxUpdateLink.fullEffectName(beforeEffect)); onClickBuffer.append("('"); String beforeEffectID = (String) valueForBinding("beforeEffectID", component); if (beforeEffectID == null) { beforeEffectID = AjaxUpdateContainer.currentUpdateContainerID(); if (beforeEffectID == null) { beforeEffectID = updateContainerID; } } onClickBuffer.append(beforeEffectID); onClickBuffer.append("', { "); String beforeEffectDuration = (String) valueForBinding("beforeEffectDuration", component); if (beforeEffectDuration != null) { onClickBuffer.append("duration: "); onClickBuffer.append(beforeEffectDuration); onClickBuffer.append(", "); } onClickBuffer.append("queue:'end', afterFinish: function() {"); } String actionUrl = null; if (directActionNameAssociation != null) { actionUrl = context._directActionURL((String) directActionNameAssociation.valueInComponent(component), ERXComponentUtilities.queryParametersInComponent(associations(), component), ERXRequest.isRequestSecure(context.request()), 0, false).replaceAll("&", "&"); } else { actionUrl = AjaxUtils.ajaxComponentActionUrl(context); } if (replaceID != null) { try { ERXMutableURL tempActionUrl = new ERXMutableURL(actionUrl); tempActionUrl.addQueryParameter(ERXAjaxApplication.KEY_REPLACED, "true"); actionUrl = tempActionUrl.toExternalForm(); } catch (MalformedURLException e) { throw NSForwardException._runtimeExceptionForThrowable(e); } } actionUrl = "'" + actionUrl + "'"; if (functionName != null) { actionUrl = actionUrl + ".addQueryParameters(additionalParams)"; } if (function != null) { onClickBuffer.append("return " + function + "(" + actionUrl + ")"); } else { // PROTOTYPE FUNCTIONS if (replaceID == null) { if (updateContainerID == null) { onClickBuffer.append("new Ajax.Request(" + actionUrl + ", "); AjaxOptions.appendToBuffer(options, onClickBuffer, context); onClickBuffer.append(')'); } else { onClickBuffer.append("new Ajax.Updater('" + updateContainerID + "', " + actionUrl + ", "); AjaxOptions.appendToBuffer(options, onClickBuffer, context); onClickBuffer.append(')'); } } else { onClickBuffer.append("new Ajax.Updater('" + replaceID + "', " + actionUrl + ", "); AjaxOptions.appendToBuffer(options, onClickBuffer, context); onClickBuffer.append(')'); } } if (onClick != null) { onClickBuffer.append(';'); onClickBuffer.append(onClick); } if (beforeEffect != null) { onClickBuffer.append("}});"); } if (onClickBefore != null) { onClickBuffer.append('}'); } if (generateFunctionWrapper) { onClickBuffer.append('}'); } } return onClickBuffer.toString(); } // PROTOTYPE EFFECTS public static void addEffect(NSMutableDictionary options, String effect, String updateContainerID, String duration) { if (effect != null) { if (options.objectForKey("onSuccess") != null) { throw new WODynamicElementCreationException("You cannot specify both an effect and a custom onSuccess function."); } if (updateContainerID == null) { throw new WODynamicElementCreationException("You cannot specify an effect without an updateContainerID."); } StringBuilder effectBuffer = new StringBuilder(); effectBuffer.append("function() { new " + AjaxUpdateLink.fullEffectName(effect) + "('" + updateContainerID + "', { queue:'end'"); if (duration != null) { effectBuffer.append(", duration: "); effectBuffer.append(duration); } effectBuffer.append("}) }"); options.setObjectForKey(effectBuffer.toString(), "onSuccess"); } } // PROTOTYPE EFFECTS public static String fullEffectName(String effectName) { String fullEffectName; if (effectName == null) { fullEffectName = null; } else if (effectName.indexOf('.') == -1) { fullEffectName = "Effect." + ERXStringUtilities.capitalize(effectName); } else { fullEffectName = effectName; } return fullEffectName; } // PROTOTYPE OPTIONS protected NSMutableDictionary createAjaxOptions(WOComponent component) { NSMutableArray<AjaxOption> ajaxOptionsArray = new NSMutableArray<>(); ajaxOptionsArray.addObject(new AjaxOption("onLoading", AjaxOption.SCRIPT)); ajaxOptionsArray.addObject(new AjaxOption("onComplete", AjaxOption.SCRIPT)); ajaxOptionsArray.addObject(new AjaxOption("onSuccess", AjaxOption.SCRIPT)); ajaxOptionsArray.addObject(new AjaxOption("onFailure", AjaxOption.SCRIPT)); ajaxOptionsArray.addObject(new AjaxOption("onException", AjaxOption.SCRIPT)); ajaxOptionsArray.addObject(new AjaxOption("evalScripts", AjaxOption.BOOLEAN)); ajaxOptionsArray.addObject(new AjaxOption("insertion", AjaxOption.SCRIPT)); ajaxOptionsArray.addObject(new AjaxOption("asynchronous", AjaxOption.BOOLEAN)); NSMutableDictionary<String, String> options = AjaxOption.createAjaxOptionsDictionary(ajaxOptionsArray, component, associations()); options.setObjectForKey("'get'", "method"); if (options.objectForKey("asynchronous") == null) { options.setObjectForKey("true", "asynchronous"); } if (options.objectForKey("evalScripts") == null) { options.setObjectForKey("true", "evalScripts"); } AjaxUpdateContainer.expandInsertionFromOptions(options, this, component); return options; } @Override public void appendToResponse(WOResponse response, WOContext context) { WOComponent component = context.component(); boolean disabled = booleanValueForBinding("disabled", false, component); Object stringValue = valueForBinding("string", component); String functionName = (String) valueForBinding("functionName", component); if (functionName == null) { String elementName; boolean button = booleanValueForBinding("button", false, component); if (button) { elementName = "input"; } else { elementName = (String) valueForBinding("elementName", "a", component); } boolean isATag = "a".equalsIgnoreCase(elementName); boolean renderTags = (!disabled || !isATag); if (renderTags) { response.appendContentString("<"); response.appendContentString(elementName); response.appendContentString(" "); if (button) { appendTagAttributeToResponse(response, "type", "button"); } if (isATag) { appendTagAttributeToResponse(response, "href", "javascript:void(0);"); } if (!disabled) { appendTagAttributeToResponse(response, "onclick", onClick(context, false)); } appendTagAttributeToResponse(response, "title", valueForBinding("title", component)); appendTagAttributeToResponse(response, "value", valueForBinding("value", component)); appendTagAttributeToResponse(response, "class", valueForBinding("class", component)); appendTagAttributeToResponse(response, "style", valueForBinding("style", component)); appendTagAttributeToResponse(response, "id", valueForBinding("id", component)); appendTagAttributeToResponse(response, "accesskey", valueForBinding("accesskey", component)); if (button) { if (stringValue != null) { appendTagAttributeToResponse(response, "value", stringValue); } if (disabled) { response.appendContentString(" disabled"); } } // appendTagAttributeToResponse(response, "onclick", // onClick(context)); response.appendContentString(">"); } if (stringValue != null && !button) { response.appendContentHTMLString(stringValue.toString()); } appendChildrenToResponse(response, context); if (renderTags) { response.appendContentString("</"); response.appendContentString(elementName); response.appendContentString(">"); } } else { AjaxUtils.appendScriptHeader(response); response.appendContentString(functionName); response.appendContentString(" = "); response.appendContentString(onClick(context, true)); AjaxUtils.appendScriptFooter(response); } super.appendToResponse(response, context); } @Override protected void addRequiredWebResources(WOResponse res, WOContext context) { addScriptResourceInHead(context, res, "prototype.js"); addScriptResourceInHead(context, res, "effects.js"); addScriptResourceInHead(context, res, "wonder.js"); } @Override public WOActionResults handleRequest(WORequest request, WOContext context) { WOComponent component = context.component(); boolean disabled = booleanValueForBinding("disabled", false, component); String updateContainerID = AjaxUpdateContainer.updateContainerID(this, component); AjaxUpdateContainer.setUpdateContainerID(request, updateContainerID); WOActionResults results = null; if (!disabled) { results = (WOActionResults) valueForBinding("action", component); } if (ERXAjaxApplication.isAjaxReplacement(request)) { AjaxUtils.setPageReplacementCacheKey(context, (String)valueForBinding("replaceID", component)); } else if (results == null || booleanValueForBinding("ignoreActionResponse", false, component)) { String script = (String) valueForBinding("onClickServer", component); if (script != null) { WOResponse response = AjaxUtils.createResponse(request, context); AjaxUtils.appendScriptHeaderIfNecessary(request, response); response.appendContentString(script); AjaxUtils.appendScriptFooterIfNecessary(request, response); results = response; } } else if (updateContainerID != null) { AjaxUtils.setPageReplacementCacheKey(context, updateContainerID); } return results; } }