package er.ajax;
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.foundation.NSDictionary;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import er.extensions.appserver.ERXWOContext;
import er.extensions.appserver.ajax.ERXAjaxApplication;
import er.extensions.foundation.ERXValueUtilities;
/**
* observeFieldID requires ERExtensions, specifically ERXWOForm
*
* @binding onRefreshComplete the script to execute at the end of refreshing the container
* @binding action the action to call when this updateContainer refreshes
*
* @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)
* @binding asynchronous set to false to force a synchronous refresh of the container. Defaults to true.
* @binding optional set to true if you want the container tags to be skipped if this is already in an update container (similar to ERXOptionalForm).
* If optional is true and there is a container, it's as if this AUC doesn't exist, and only its children will render to the page.
*
* @binding frequency the frequency (in seconds) of a periodic update
* @binding decay a multiplier (default is one) applied to the frequency if the response of the update is unchanged
* @binding stopped determines whether a periodic update container loads as stopped.
*/
public class AjaxUpdateContainer extends AjaxDynamicElement {
private static final String CURRENT_UPDATE_CONTAINER_ID_KEY = "er.ajax.AjaxUpdateContainer.currentID";
public AjaxUpdateContainer(String name, NSDictionary<String, WOAssociation> associations, WOElement children) {
super(name, associations, children);
}
/**
* Adds all required resources.
*/
@Override
protected void addRequiredWebResources(WOResponse response, WOContext context) {
addScriptResourceInHead(context, response, "prototype.js");
addScriptResourceInHead(context, response, "effects.js");
addScriptResourceInHead(context, response, "wonder.js");
}
protected boolean shouldRenderContainer(WOComponent component) {
boolean renderContainer = !booleanValueForBinding("optional", false, component) || AjaxUpdateContainer.currentUpdateContainerID() == null;
return renderContainer;
}
@Override
public void takeValuesFromRequest(WORequest request, WOContext context) {
if (shouldRenderContainer(context.component())) {
String previousUpdateContainerID = AjaxUpdateContainer.currentUpdateContainerID();
try {
AjaxUpdateContainer.setCurrentUpdateContainerID(_containerID(context));
super.takeValuesFromRequest(request, context);
}
finally {
AjaxUpdateContainer.setCurrentUpdateContainerID(previousUpdateContainerID);
}
}
else {
super.takeValuesFromRequest(request, context);
}
}
@Override
public WOActionResults invokeAction(WORequest request, WOContext context) {
WOActionResults results;
if (shouldRenderContainer(context.component())) {
String previousUpdateContainerID = AjaxUpdateContainer.currentUpdateContainerID();
try {
AjaxUpdateContainer.setCurrentUpdateContainerID(_containerID(context));
results = super.invokeAction(request, context);
}
finally {
AjaxUpdateContainer.setCurrentUpdateContainerID(previousUpdateContainerID);
}
}
else {
results = super.invokeAction(request, context);
}
return results;
}
public NSDictionary createAjaxOptions(WOComponent component) {
// PROTOTYPE OPTIONS
NSMutableArray<AjaxOption> ajaxOptionsArray = new NSMutableArray<>();
ajaxOptionsArray.addObject(new AjaxOption("frequency", AjaxOption.NUMBER));
ajaxOptionsArray.addObject(new AjaxOption("decay", AjaxOption.NUMBER));
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("insertion", AjaxOption.SCRIPT));
ajaxOptionsArray.addObject(new AjaxOption("evalScripts", Boolean.TRUE, AjaxOption.BOOLEAN));
ajaxOptionsArray.addObject(new AjaxOption("asynchronous", Boolean.TRUE, AjaxOption.BOOLEAN));
ajaxOptionsArray.addObject(new AjaxOption("method", "get", AjaxOption.STRING));
ajaxOptionsArray.addObject(new AjaxOption("evalScripts", Boolean.TRUE, AjaxOption.BOOLEAN));
ajaxOptionsArray.addObject(new AjaxOption("parameters", AjaxOption.STRING));
NSMutableDictionary<String, String> options = AjaxOption.createAjaxOptionsDictionary(ajaxOptionsArray, component, associations());
AjaxUpdateContainer.expandInsertionFromOptions(options, this, component);
return options;
}
public static void expandInsertionFromOptions(NSMutableDictionary<String, String> options, IAjaxElement element, WOComponent component) {
// PROTOTYPE EFFECTS
String insertionDuration = (String) element.valueForBinding("insertionDuration", component);
String beforeInsertionDuration = (String) element.valueForBinding("beforeInsertionDuration", component);
if (beforeInsertionDuration == null) {
beforeInsertionDuration = insertionDuration;
}
String afterInsertionDuration = (String) element.valueForBinding("afterInsertionDuration", component);
if (afterInsertionDuration == null) {
afterInsertionDuration = insertionDuration;
}
String insertion = options.objectForKey("insertion");
String expandedInsertion = AjaxUpdateContainer.expandInsertion(insertion, beforeInsertionDuration, afterInsertionDuration);
if (expandedInsertion != null) {
options.setObjectForKey(expandedInsertion, "insertion");
}
}
public static String expandInsertion(String originalInsertion, String beforeDuration, String afterDuration) {
// PROTOTYPE EFFECTS
String expandedInsertion = originalInsertion;
if (originalInsertion != null && originalInsertion.startsWith("Effect.")) {
String effectPairName = originalInsertion.substring("Effect.".length());
expandedInsertion = "AUC.insertionFunc('" + effectPairName + "', " + beforeDuration + "," + afterDuration + ")";
}
return expandedInsertion;
}
public static NSDictionary removeDefaultOptions(NSDictionary options) {
// PROTOTYPE OPTIONS
NSMutableDictionary mutableOptions = options.mutableClone();
if ("'get'".equals(mutableOptions.objectForKey("method"))) {
mutableOptions.removeObjectForKey("method");
}
if ("true".equals(mutableOptions.objectForKey("evalScripts"))) {
mutableOptions.removeObjectForKey("evalScripts");
}
if ("true".equals(mutableOptions.objectForKey("asynchronous"))) {
mutableOptions.removeObjectForKey("asynchronous");
}
return mutableOptions;
}
public NSMutableDictionary createObserveFieldOptions(WOComponent component) {
NSMutableArray ajaxOptionsArray = new NSMutableArray();
ajaxOptionsArray.addObject(new AjaxOption("observeFieldFrequency", AjaxOption.NUMBER));
NSMutableDictionary options = AjaxOption.createAjaxOptionsDictionary(ajaxOptionsArray, component, associations());
return options;
}
@Override
public void appendToResponse(WOResponse response, WOContext context) {
WOComponent component = context.component();
if (!shouldRenderContainer(component)) {
if (hasChildrenElements()) {
appendChildrenToResponse(response, context);
}
super.appendToResponse(response, context);
}
else {
String previousUpdateContainerID = AjaxUpdateContainer.currentUpdateContainerID();
try {
String elementName = (String) valueForBinding("elementName", "div", component);
String id = _containerID(context);
AjaxUpdateContainer.setCurrentUpdateContainerID(_containerID(context));
response.appendContentString("<" + elementName + " ");
appendTagAttributeToResponse(response, "id", id);
appendTagAttributeToResponse(response, "class", valueForBinding("class", component));
appendTagAttributeToResponse(response, "style", valueForBinding("style", component));
appendTagAttributeToResponse(response, "data-updateUrl", AjaxUtils.ajaxComponentActionUrl(context));
// appendTagAttributeToResponse(response, "woElementID", context.elementID());
response.appendContentString(">");
if (hasChildrenElements()) {
appendChildrenToResponse(response, context);
}
response.appendContentString("</" + elementName + ">");
super.appendToResponse(response, context);
NSDictionary options = createAjaxOptions(component);
Object frequency = valueForBinding("frequency", component);
String observeFieldID = (String) valueForBinding("observeFieldID", component);
boolean skipFunction = frequency == null && observeFieldID == null && booleanValueForBinding("skipFunction", false, component);
if (!skipFunction) {
AjaxUtils.appendScriptHeader(response);
if (frequency != null) {
// try to convert to a number to check whether it is 0
boolean isNotZero = true;
try {
float numberFrequency = ERXValueUtilities.floatValue(frequency);
if (numberFrequency == 0.0) {
// set this only to false if it can be converted to 0
isNotZero = false;
}
}
catch (RuntimeException e) {
throw new IllegalStateException("Error parsing float from value : <" + frequency + ">");
}
if (isNotZero) {
boolean canStop = false;
boolean stopped = false;
if (associations().objectForKey("stopped") != null) {
canStop = true;
stopped = booleanValueForBinding("stopped", false, component);
}
response.appendContentString("AUC.registerPeriodic('" + id + "'," + canStop + "," + stopped + ",");
AjaxOptions.appendToResponse(options, response, context);
response.appendContentString(");");
}
}
if (observeFieldID != null) {
boolean fullSubmit = booleanValueForBinding("fullSubmit", false, component);
AjaxObserveField.appendToResponse(response, context, this, observeFieldID, false, id, fullSubmit, createObserveFieldOptions(component));
}
response.appendContentString("AUC.register('" + id + "'");
NSDictionary nonDefaultOptions = AjaxUpdateContainer.removeDefaultOptions(options);
if (nonDefaultOptions.count() > 0) {
response.appendContentString(", ");
AjaxOptions.appendToResponse(nonDefaultOptions, response, context);
}
response.appendContentString(");");
AjaxUtils.appendScriptFooter(response);
}
}
finally {
AjaxUpdateContainer.setCurrentUpdateContainerID(previousUpdateContainerID);
}
}
}
@Override
public WOActionResults handleRequest(WORequest request, WOContext context) {
WOComponent component = context.component();
String id = _containerID(context);
if (associations().objectForKey("action") != null) {
@SuppressWarnings("unused")
WOActionResults results = (WOActionResults) valueForBinding("action", component);
// ignore results
}
WOResponse response = AjaxUtils.createResponse(request, context);
AjaxUtils.setPageReplacementCacheKey(context, id);
if (hasChildrenElements()) {
appendChildrenToResponse(response, context);
}
String onRefreshComplete = (String) valueForBinding("onRefreshComplete", component);
if (onRefreshComplete != null) {
AjaxUtils.appendScriptHeader(response);
response.appendContentString(onRefreshComplete);
AjaxUtils.appendScriptFooter(response);
}
if (AjaxModalDialog.isInDialog(context)) {
AjaxUtils.appendScriptHeader(response);
response.appendContentString("AMD.contentUpdated();");
AjaxUtils.appendScriptFooter(response);
}
return null;
}
@Override
protected String _containerID(WOContext context) {
String id = (String) valueForBinding("id", context.component());
if (id == null) {
id = ERXWOContext.safeIdentifierName(context, false);
}
return id;
}
public static String updateContainerID(WORequest request) {
return (String) ERXWOContext.contextDictionary().objectForKey(ERXAjaxApplication.KEY_UPDATE_CONTAINER_ID);
}
public static void setUpdateContainerID(WORequest request, String updateContainerID) {
if (updateContainerID != null) {
ERXWOContext.contextDictionary().setObjectForKey(updateContainerID, ERXAjaxApplication.KEY_UPDATE_CONTAINER_ID);
}
}
public static boolean hasUpdateContainerID(WORequest request) {
return AjaxUpdateContainer.updateContainerID(request) != null;
}
public static String currentUpdateContainerID() {
return (String) ERXWOContext.contextDictionary().objectForKey(AjaxUpdateContainer.CURRENT_UPDATE_CONTAINER_ID_KEY);
}
public static void setCurrentUpdateContainerID(String updateContainerID) {
if (updateContainerID == null) {
ERXWOContext.contextDictionary().removeObjectForKey(AjaxUpdateContainer.CURRENT_UPDATE_CONTAINER_ID_KEY);
}
else {
ERXWOContext.contextDictionary().setObjectForKey(updateContainerID, AjaxUpdateContainer.CURRENT_UPDATE_CONTAINER_ID_KEY);
}
}
public static String updateContainerID(AjaxDynamicElement element, WOComponent component) {
return AjaxUpdateContainer.updateContainerID(element, "updateContainerID", component);
}
public static String updateContainerID(AjaxDynamicElement element, String bindingName, WOComponent component) {
String updateContainerID = (String) element.valueForBinding("updateContainerID", component);
return AjaxUpdateContainer.updateContainerID(updateContainerID);
}
public static String updateContainerID(String updateContainerID) {
if ("_parent".equals(updateContainerID)) {
updateContainerID = AjaxUpdateContainer.currentUpdateContainerID();
}
return updateContainerID;
}
/**
* Creates or updates Ajax response so that the indicated AUC will get updated when the response is processed in the browser.
* Adds JavaScript like <code>AUC.update('SomeContainerID');</code>
*
* @param updateContainerID the HTML ID of the element implementing the AUC
* @param context WOContext for response
*/
public static void updateContainerWithID(String updateContainerID, WOContext context) {
String containerID = "'" + updateContainerID + "'";
AjaxUtils.javascriptResponse("AUC.update(" + containerID + ");", context);
}
/**
* Creates or updates Ajax response so that the indicated AUC will get updated when the response is processed in the browser.
* If the container element does not exist, does nothing.
* Adds JavaScript like <code>if ( $('SomeContainerID') != null ) AUC.update('SomeContainerID');</code>
*
* @param updateContainerID the HTML ID of the element implementing the AUC
* @param context WOContext for response
*/
public static void safeUpdateContainerWithID(String updateContainerID, WOContext context) {
String containerID = "'" + updateContainerID + "'";
AjaxUtils.javascriptResponse("if ( $(" + containerID + ") != null ) AUC.update(" + containerID + ");", context);
}
}