package er.ajax.mootools; 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.NSMutableArray; import com.webobjects.foundation.NSMutableDictionary; import er.ajax.AjaxDynamicElement; import er.ajax.AjaxOption; import er.ajax.AjaxOptions; import er.ajax.AjaxUpdateContainer; import er.ajax.AjaxUtils; import er.ajax.IAjaxElement; import er.extensions.appserver.ajax.ERXAjaxApplication; import er.extensions.components._private.ERXWOForm; import er.extensions.foundation.ERXProperties; import er.extensions.foundation.ERXStringUtilities; /** * AjaxSubmitButton behaves just like a WOSubmitButton except that it submits in the background with an Ajax.Request. * * @binding name the HTML name of this submit button (optional) * @binding value the HTML value of this submit button (optional) * @binding action the action to execute when this button is pressed * @binding id the HTML ID of this submit button * @binding class the HTML class of this submit button * @binding style the HTML style of this submit button * @binding title the HTML title of this submit button * @binding onClick arbitrary Javascript to execute when the client clicks the button * @binding onClickBefore if the given function returns true, the onClick is executed. This is to support confirm(..) dialogs. * @binding onClickServer if the action defined in the action binding returns null, the value of this binding will be returned as javascript from the server * @binding async boolean defining if the update request is sent asynchronously or synchronously, defaults to true * @binding accesskey hot key that should trigger the link (optional) * @binding onCancel Fired when a request has been cancelled. * @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 onComplete Fired when the Request is completed. * @binding onException Fired when setting a request header fails. * @binding onFailure Fired when the request failed (error status code). * @binding onRequest Fired when the Request is sent. * @binding onSuccess(responseTree, responseElements, responseHTML, responseJavaScript) Fired when the Request is completed successfully. * @binding evalScripts evaluate scripts on the result * @binding button if false, it will display a link * @binding formName if button is false, you must specify the name of the form to submit * @binding functionName if set, the link becomes a javascript function instead * @binding updateContainerID the id of the AjaxUpdateContainer to update after performing this action * @binding showUI if functionName is set, the UI defaults to hidden; showUI re-enables it * @binding formSerializer the name of the javascript function to call to serialize the form * @binding elementName the element name to use (defaults to "a") * @binding async boolean defining if the request is sent asynchronously or synchronously, defaults to true * @binding accesskey hot key that should trigger the button (optional) * @binding disabled if true, the button will be disabled (defaults to false) * @property er.ajax.formSerializer the default form serializer to use for all ajax submits */ public class MTAjaxSubmitButton extends AjaxDynamicElement { // MS: If you change this value, make sure to change it in ERXAjaxApplication public static final String KEY_AJAX_SUBMIT_BUTTON_NAME = "AJAX_SUBMIT_BUTTON_NAME"; // MS: If you change this value, make sure to change it in ERXAjaxApplication and in wonder.js public static final String KEY_PARTIAL_FORM_SENDER_ID = "_partialSenderID"; public MTAjaxSubmitButton(String name, NSDictionary<String, WOAssociation> associations, WOElement children) { super(name, associations, children); } public static boolean isAjaxSubmit(WORequest request) { return request.formValueForKey(KEY_AJAX_SUBMIT_BUTTON_NAME) != null; } public boolean disabledInComponent(WOComponent component) { return booleanValueForBinding("disabled", false, component); } public String nameInContext(WOContext context, WOComponent component) { return (String) valueForBinding("name", context.elementID(), component); } public NSMutableDictionary createAjaxOptions(WOComponent component) { NSMutableArray<AjaxOption> ajaxOptionsArray = new NSMutableArray<>(); ajaxOptionsArray.addObject(new AjaxOption("onCancel", AjaxOption.FUNCTION)); ajaxOptionsArray.addObject(new AjaxOption("onComplete", AjaxOption.FUNCTION)); ajaxOptionsArray.addObject(new AjaxOption("onException", AjaxOption.FUNCTION)); ajaxOptionsArray.addObject(new AjaxOption("onFailure", AjaxOption.FUNCTION)); ajaxOptionsArray.addObject(new AjaxOption("onRequest", AjaxOption.FUNCTION)); ajaxOptionsArray.addObject(new AjaxOption("onSuccess", AjaxOption.FUNCTION_2)); ajaxOptionsArray.addObject(new AjaxOption("evalScripts", AjaxOption.BOOLEAN)); ajaxOptionsArray.addObject(new AjaxOption("async", AjaxOption.BOOLEAN)); ajaxOptionsArray.addObject(new AjaxOption("useSpinner", AjaxOption.BOOLEAN)); ajaxOptionsArray.addObject(new AjaxOption("spinnerTarget", AjaxOption.STRING)); ajaxOptionsArray.addObject(new AjaxOption("spinnerOptions", AjaxOption.DICTIONARY)); String name = nameInContext(component.context(), component); NSMutableDictionary options = AjaxOption.createAjaxOptionsDictionary(ajaxOptionsArray, component, associations()); MTAjaxSubmitButton.fillInAjaxOptions(this, component, name, options); return options; } @Override public void addRequiredWebResources(WOResponse response, WOContext context) { MTAjaxUtils.addScriptResourceInHead(context, context.response(), "MooTools", MTAjaxUtils.MOOTOOLS_CORE_JS); MTAjaxUtils.addScriptResourceInHead(context, context.response(), "MooTools", MTAjaxUtils.MOOTOOLS_MORE_JS); Boolean useSpinner = (Boolean)valueForBinding("useSpinner", Boolean.FALSE, context.component()); if(useSpinner.booleanValue()) { Boolean useDefaultSpinnerClass = (Boolean)valueForBinding("defaultSpinnerClass", Boolean.TRUE, context.component()); if(useDefaultSpinnerClass.booleanValue()) { AjaxUtils.addStylesheetResourceInHead(context, context.response(), "MooTools", "scripts/plugins/spinner/spinner.css"); } } MTAjaxUtils.addScriptResourceInHead(context, context.response(), "MooTools", MTAjaxUtils.MOOTOOLS_WONDER_JS); } @Override @SuppressWarnings("rawtypes") public void appendToResponse(WOResponse response, WOContext context) { WOComponent component = context.component(); String functionName = (String)valueForBinding("functionName", null, component); String formName = (String)valueForBinding("formName", component); boolean showUI = (functionName == null || booleanValueForBinding("showUI", false, component)); boolean showButton = showUI && booleanValueForBinding("button", true, component); String formReference; if((!showButton || functionName != null) && formName == null) { formName = ERXWOForm.formName(context, null); if(formName == null) { throw new WODynamicElementCreationException("If button = false or functionName is not null, the containing form must have an explicit name."); } } if(formName == null) { formReference = "this.form"; } else { formReference = "document." + formName; } StringBuilder onClickBuffer = new StringBuilder(); // JavaScript function to be fired before submit is sent i.e. confirm(); String onClickBefore = (String)valueForBinding("onClickBefore", component); if(onClickBefore != null) { onClickBuffer.append("if(") .append(onClickBefore) .append(") {"); } String updateContainerID = (String)valueForBinding("updateContainerID", component); // Needs to be refactored. Same function as MoAjaxUpdateLink // Maybe create a helper class with a static function that takes the component as an argument? // Could add it to MoAjaxUpdateLink like addEffect. String beforeEffect = (String)valueForBinding("beforeEffect", component); if(beforeEffect != null) { String beforeEffectID = (String)valueForBinding("beforeEffectID", component); String beforeEffectDuration = (String) valueForBinding("beforeEffectDuration", component); String beforeEffectProperty = (String) valueForBinding("beforeEffectProperty", component); String beforeEffectStart = (String) valueForBinding("beforeEffectStart", component); if(beforeEffectID == null) { beforeEffectID = AjaxUpdateContainer.currentUpdateContainerID(); if (beforeEffectID == null) { beforeEffectID = updateContainerID; } } if(beforeEffect.equals("tween")) { if(beforeEffectDuration != null) { onClickBuffer.append("$('").append(beforeEffectID) .append("').set('tween', { duration: '") .append(beforeEffectDuration).append("', property: '" + beforeEffectProperty + "' });"); } else { onClickBuffer.append("$('").append(beforeEffectID).append("').set('tween', { property: '" + beforeEffectProperty + "' });"); } onClickBuffer.append("$('").append(beforeEffectID).append("').get('tween').start(") .append(beforeEffectStart).append(").chain(function() {"); } else if(beforeEffect.equals("morph")) { if(beforeEffectDuration != null) { onClickBuffer.append("$('").append(beforeEffectID).append("').set('morph', { duration: '").append(beforeEffectDuration).append("' });"); } onClickBuffer.append("$('").append(beforeEffectID).append("').get('morph').start('." + beforeEffectStart + "'").append(").chain(function() {"); } else if(beforeEffect.equals("slide")) { String mode = (String) valueForBinding("effectSlideMode", component); String transition = (String) valueForBinding("beforeEffectTransition", component); onClickBuffer.append("$('").append(beforeEffectID).append("').set('slide'"); if(beforeEffectDuration != null || mode != null) { onClickBuffer.append(", { "); if(beforeEffectDuration != null) { onClickBuffer.append("duration: '").append(beforeEffectDuration).append('\'').append(mode != null || transition != null ? "," : ""); } if(mode != null) { onClickBuffer.append("mode: '").append(mode).append('\'').append(transition != null ? "," : ""); } if(transition != null) { onClickBuffer.append("transition: ").append(transition); } onClickBuffer.append('}'); } onClickBuffer.append("); $('").append(beforeEffectID).append("').get('slide').slide").append(ERXStringUtilities.capitalize(beforeEffectProperty)).append("().chain(function() {"); } else if(beforeEffect.equals("highlight")) { if(beforeEffectDuration != null) { onClickBuffer.append("$('").append(beforeEffectID) .append("').set('tween', { duration: '").append(beforeEffectDuration).append("', property: 'background-color'});"); } else { onClickBuffer.append("$('").append(beforeEffectID) .append("').set('tween', { property: 'background-color' });"); } onClickBuffer.append("$('").append(updateContainerID).append("').get('tween').start('").append(beforeEffectProperty != null ? beforeEffectProperty : "#ffff88', '#ffffff") .append("').chain(function() { "); } } if(updateContainerID != null) { onClickBuffer.append("MTASB.update('").append(updateContainerID).append("', "); } else { onClickBuffer.append("MTASB.request("); } onClickBuffer.append(formReference); if(valueForBinding("functionName", component) != null) { onClickBuffer.append(", additionalParams"); } else { onClickBuffer.append(", null"); } onClickBuffer.append(','); NSMutableDictionary options = createAjaxOptions(component); String effect = (String) valueForBinding("effect", component); String afterEffect = (String) valueForBinding("afterEffect", component); if(effect != null) { String duration = (String) valueForBinding("effectDuration", component); String property = (String) valueForBinding("effectProperty", component); String start = (String) valueForBinding("effectStart", component); String mode = (String) valueForBinding("effectSlideMode", component); MTAjaxUpdateLink.addEffect(options, effect, updateContainerID, property, start, duration, mode); } else if(afterEffect != null) { String duration = (String) valueForBinding("afterEffectDuration", component); String property = (String) valueForBinding("afterEffectProperty", component); String start = (String) valueForBinding("afterEffectStart", component); String afterEffectID = (String) valueForBinding("afterEffectID", component); String mode = (String) valueForBinding("effectSlideMode", component); if(afterEffectID == null) { afterEffectID = AjaxUpdateContainer.currentUpdateContainerID() != null ? AjaxUpdateContainer.currentUpdateContainerID() : updateContainerID; } MTAjaxUpdateLink.addEffect(options, afterEffect, afterEffectID, property, start, duration, mode); } AjaxOptions.appendToBuffer(options, onClickBuffer, context); onClickBuffer.append(')'); String onClick = (String)valueForBinding("onClick", component); if(onClick != null) { onClickBuffer.append("; ").append(onClick); } if(beforeEffect != null) { onClickBuffer.append("}.bind(this));"); } if(onClickBefore != null) { onClickBuffer.append('}'); } if(functionName != null) { AjaxUtils.appendScriptHeader(response); response.appendContentString(functionName + " = function(additionalParams) { " + onClickBuffer + " }\n"); AjaxUtils.appendScriptFooter(response); } if(showUI) { boolean disabled = disabledInComponent(component); String elementName = (String)valueForBinding("elementName", "a", component); if(showButton) { response.appendContentString("<input "); appendTagAttributeToResponse(response, "type", "button"); String name = nameInContext(context, component); appendTagAttributeToResponse(response, "name", name); appendTagAttributeToResponse(response, "value", valueForBinding("value", component)); if(disabled) { appendTagAttributeToResponse(response, "disabled", "disabled"); } } else { boolean isATag = "a".equalsIgnoreCase(elementName); if(isATag) { response.appendContentString("<a href = \"javascript:void(0)\" "); } else { response.appendContentString("<" + elementName + " "); } } String classString = (String)valueForBinding("class", component); classString = (classString != null) ? classString + " m-a-s-b" : "m-a-s-b"; appendTagAttributeToResponse(response, "class", classString); appendTagAttributeToResponse(response, "style", valueForBinding("style", component)); appendTagAttributeToResponse(response, "id", valueForBinding("id", component)); if(functionName == null) { appendTagAttributeToResponse(response, "onclick", onClickBuffer.toString()); } else { appendTagAttributeToResponse(response, "onclick", functionName + "()"); } if(showButton) { response.appendContentString(" />"); } else { response.appendContentString(">"); if(hasChildrenElements()) { appendChildrenToResponse(response, context); } response.appendContentString("</" + elementName + ">"); } } super.appendToResponse(response, context); } @SuppressWarnings({ "rawtypes", "unchecked" }) public static void fillInAjaxOptions(IAjaxElement element, WOComponent component, String submitButtonName, NSMutableDictionary options) { String systemDefaultFormSerializer = "Form.serializeWithoutSubmits"; String defaultFormSerializer = ERXProperties.stringForKeyWithDefault("er.ajax.formSerializer", systemDefaultFormSerializer); String formSerializer = (String) element.valueForBinding("formSerializer", defaultFormSerializer, component); if(!defaultFormSerializer.equals(systemDefaultFormSerializer)) { options.setObjectForKey(formSerializer, "_fs"); } options.setObjectForKey("'" + submitButtonName + "'", "_asbn"); if("true".equals(options.objectForKey("async"))) { options.removeObjectForKey("async"); } if("true".equals(options.objectForKey("evalScripts"))) { options.removeObjectForKey("evalScripts"); } MTAjaxUpdateContainer.expandInsertionFromOptions(options, element, component); } @Override public WOActionResults invokeAction(WORequest worequest, WOContext wocontext) { WOActionResults result = null; WOComponent wocomponent = wocontext.component(); String nameInContext = nameInContext(wocontext, wocomponent); boolean shouldHandleRequest = (!disabledInComponent(wocomponent) && wocontext.wasFormSubmitted()) && ((wocontext.isMultipleSubmitForm() && nameInContext.equals(worequest.formValueForKey(KEY_AJAX_SUBMIT_BUTTON_NAME))) || !wocontext.isMultipleSubmitForm()); if (shouldHandleRequest) { String updateContainerID = MTAjaxUpdateContainer.updateContainerID(this, wocomponent); MTAjaxUpdateContainer.setUpdateContainerID(worequest, updateContainerID); wocontext.setActionInvoked(true); result = handleRequest(worequest, wocontext); ERXAjaxApplication.enableShouldNotStorePage(); } return result; } @Override public WOActionResults handleRequest(WORequest request, WOContext context) { WOComponent component = context.component(); WOActionResults result = (WOActionResults) valueForBinding("action", component); if (ERXAjaxApplication.isAjaxReplacement(request)) { AjaxUtils.setPageReplacementCacheKey(context, (String)valueForBinding("replaceID", component)); } else if (result == null || booleanValueForBinding("ignoreActionResponse", false, component)) { WOResponse response = AjaxUtils.createResponse(request, context); String onClickServer = (String) valueForBinding("onClickServer", component); if (onClickServer != null) { AjaxUtils.appendScriptHeaderIfNecessary(request, response); response.appendContentString(onClickServer); AjaxUtils.appendScriptFooterIfNecessary(request, response); } result = response; } else { String updateContainerID = MTAjaxUpdateContainer.updateContainerID(this, component); if (updateContainerID != null) { AjaxUtils.setPageReplacementCacheKey(context, updateContainerID); } } return result; } }