package er.extensions.components;
import com.webobjects.appserver.WOActionResults;
import com.webobjects.appserver.WOApplication;
import com.webobjects.appserver.WOAssociation;
import com.webobjects.appserver.WOComponent;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WOElement;
import com.webobjects.appserver.WOPageNotFoundException;
import com.webobjects.appserver.WORequest;
import com.webobjects.appserver.WOResponse;
import com.webobjects.appserver._private.WOCGIFormValues;
import com.webobjects.appserver._private.WODynamicElementCreationException;
import com.webobjects.appserver._private.WOHTMLDynamicElement;
import com.webobjects.appserver._private.WONoContentElement;
import com.webobjects.appserver._private.WOStaticURLUtilities;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation._NSDictionaryUtilities;
import er.extensions.appserver.ERXSession;
import er.extensions.foundation.ERXProperties;
import er.extensions.foundation.ERXValueUtilities;
/**
* This dynamic element produces markup like
* <a><button>...</button></a>.
* This allows for consistent markup for all links and buttons that make
* use of this element. Outside of forms, it uses the combo like a link,
* populating the href attribute of the anchor element. Inside of forms,
* it defaults to using the combo as a button instead. Since IE is unable
* to correctly use the <button> element, the default behavior is
* to generate IE conditionals around the element and use simple <a>
* tags for links and <input> for buttons. Any unnamed bindings are
* passed through to the anchor element. (eg. title, onclick, etc.)
*
*
* @binding action Action method to invoke when this element is activated.
*
* @binding actionClass The name of the class in which the method
* designated in 'directActionName' can be found. Defaults to DirectAction.
*
* @binding class A class attribute to be bound to the anchor tag.
*
* @binding directActionName The name of the direct action method
* (minus the "Action" suffix) to invoke when this element is activated.
* Defaults to “default”.
*
* @binding disabled If evaluates to true, the button is in active.
*
* @binding escapeHTML If escapeHTML evaluates to true, the string rendered
* by the 'string' or 'value' binding is converted so that characters which
* would be interpreted as HTML control characters become their escaped
* equivalent (this is the default).
*
* @binding fragmentIdentifier Named location to display in the destination page.
*
* @binding href URL to direct the browser to when the button is clicked.
*
* @binding id An id attribute to be bound to the anchor tag.
*
* @binding name A name attribute to be bound to the button tag. This is ignored
* if 'actionClass' or 'directActionName' is bound. It will not appear if the
* button is disabled.
*
* @binding pageName Name of WebObjects page to display when the link is clicked.
*
* @binding queryDictionary Takes a dictionary that should be appended to the
* hyperlink’s URL after a question mark character. The dictionary must be
* correctly encoded and will be merged with any existing query dictionary
* for a particular session ID.
*
* @binding rel The rel attribute of the anchor tag. This binding has no effect
* when this element is used as a button. If the property
* er.extensions.ERXHyperlink.defaultNoFollow=true is used and 'action' is bound,
* then "nofollow" will automatically be appended.
*
* @binding secure Changes the URL prefix from http to https when WebObjects
* generates URLs for component actions and direct actions for this element.
* For this attribute to have any effect, you must provide bindings either for
* the 'action,' 'directAction,' 'actionClass,' or 'pageName' attribute (respecting
* the valid combinations). This binding has no effect when this element is
* used as a button.
*
* @binding string This binding provides the string value of the link when
* submit is false. If no string is provided, it falls back to the value
* binding. If no value is provided, no string is used. If the element has
* child elements, the string is ignored except when using IE conditionals.
*
* @binding submit This binding is ignored and the default is false if the
* this element is used outside of a form, 'pageName' is bound, or 'href' is bound.
* Otherwise, inside of a form, the default is true. Using buttons as links will
* not submit form values, but can be more efficient.
*
* @binding useIEConditionals When true, the element generates simple input or
* hyperlink elements using IE conditional comments, since IE is unable to
* handle button elements correctly. The default is true.
*
* @binding value This binding provides the title of the button when submit
* is true. If no value is provided, the value falls back to the string binding.
* If no string binding is provided, a default value of "Submit" is used. If the
* element has child elements, the value is ignored except when using IE conditionals.
*
* @author Ramsey Gurley
*
*/
public class ERXLinkButton extends WOHTMLDynamicElement {
/**
* Defines if the hyperlink adds a default <code>rel="nofollow"</code> if an action is bound.
*/
private static boolean defaultNoFollow = ERXProperties.booleanForKey("er.extensions.ERXHyperlink.defaultNoFollow");
/**
* Defines if the anchor adds a default <code>class="button"</code>
*/
private static boolean defaultButtonClass = ERXProperties.booleanForKeyWithDefault("er.r2d2w.components.misc.R2DLinkButton.defaultClass", true);
protected NSDictionary<String, WOAssociation> _otherQueryAssociations;
protected WOAssociation _action;
protected WOAssociation _actionClass;
protected WOAssociation _directActionName;
protected WOAssociation _disabled;
protected WOAssociation _escapeHTML;
protected WOAssociation _fragmentIdentifier;
protected WOAssociation _href;
protected WOAssociation _name;
protected WOAssociation _pageName;
protected WOAssociation _queryDictionary;
protected WOAssociation _rel;
protected WOAssociation _string;
protected WOAssociation _submit;
protected WOAssociation _useIEConditionals;
protected WOAssociation _value;
@SuppressWarnings("unchecked")
public ERXLinkButton(String aName, NSDictionary<String, WOAssociation> associations, WOElement template) {
super("a", associations, template);
_otherQueryAssociations = _NSDictionaryUtilities.extractObjectsForKeysWithPrefix(_associations, "?", true);
_otherQueryAssociations = _otherQueryAssociations == null || _otherQueryAssociations.count() <= 0 ? null : _otherQueryAssociations;
_action = _associations.removeObjectForKey("action");
_actionClass = _associations.removeObjectForKey("actionClass");
_directActionName = _associations.removeObjectForKey("directActionName");
_disabled = _associations.removeObjectForKey("disabled");
_escapeHTML = _associations.removeObjectForKey("escapeHTML");
_fragmentIdentifier = _associations.removeObjectForKey("fragmentIdentifier");
_href = _associations.removeObjectForKey("href");
_name = _associations.removeObjectForKey("name");
_pageName = _associations.removeObjectForKey("pageName");
_queryDictionary = _associations.removeObjectForKey("queryDictionary");
_rel = _associations.removeObjectForKey("rel");
_string = _associations.removeObjectForKey("string");
_submit = _associations.removeObjectForKey("submit");
_useIEConditionals = _associations.removeObjectForKey("useIEConditionals");
_value = _associations.removeObjectForKey("value");
if(_action == null && _href == null && _pageName == null && _directActionName == null && _actionClass == null) {
throw new WODynamicElementCreationException(new StringBuilder().append('<').append(getClass().getName()).append("> Missing required attribute: 'action' or 'href' or 'pageName' or 'directActionName' or 'actionClass'").toString());
}
if(_action != null && _href != null || _action != null && _pageName != null || _href != null && _pageName != null || _action != null && _directActionName != null || _href != null && _directActionName != null || _pageName != null && _directActionName != null || _action != null && _actionClass != null) {
throw new WODynamicElementCreationException(new StringBuilder().append('<').append(getClass().getName()).append("> At least two of these conflicting attributes are present: 'action', 'href', 'pageName', 'directActionName', 'actionClass'.").toString());
}
if(_action != null && _action.isValueConstant()) {
throw new WODynamicElementCreationException(new StringBuilder().append('<').append(getClass().getName()).append("> 'action' is a constant.").toString());
}
}
@Override
public void takeValuesFromRequest(WORequest request, WOContext context) {
//Do nothing
}
/**
* Overridden to perform the logging, propagating the action to sub-elements and returning the
* current page if an empty page is returned from super.
*/
@Override
public WOActionResults invokeAction(WORequest request, WOContext context) {
boolean submit = submitInContext(context);
WOActionResults result = submit?invokeButtonAction(request, context):invokeLinkAction(request, context);
if(!submit) {
if(result != null && (result instanceof WONoContentElement)) {
result = context.page();
}
if(result == null) {
String sender = context.senderID();
String element = context.elementID();
if(sender.startsWith(element) && !element.equals(sender)) {
result = invokeChildrenAction(request, context);
}
}
if (result != null && ERXSession.anySession() != null) {
ERXSession.anySession().setObjectForKey(toString(), "ERXActionLogging");
}
}
return result;
}
public WOActionResults invokeButtonAction(WORequest request, WOContext context) {
WOActionResults anActionResult = null;
WOComponent aComponent = context.component();
if(!isDisabledInContext(context) && context.wasFormSubmitted()) {
if(context.isMultipleSubmitForm()) {
if(request.formValueForKey(nameInContext(context)) != null) {
context.setActionInvoked(true);
if(_action != null) {
anActionResult = (WOActionResults)_action.valueInComponent(aComponent);
}
if(anActionResult == null) {
anActionResult = context.page();
}
}
} else {
context.setActionInvoked(true);
if(_action != null) {
anActionResult = (WOActionResults)_action.valueInComponent(aComponent);
}
if(anActionResult == null) {
anActionResult = context.page();
}
}
}
return anActionResult;
}
public WOActionResults invokeLinkAction(WORequest request, WOContext context) {
String nextPageName = null;
WOActionResults invokedElement = null;
WOComponent component = context.component();
if(context.elementID().equals(context.senderID())) {
if(_disabled == null || !_disabled.booleanValueInComponent(component)) {
if(_pageName != null) {
Object nextPageValue = _pageName.valueInComponent(component);
if(nextPageValue != null) {
nextPageName = nextPageValue.toString();
}
}
if(_action != null) {
invokedElement = (WOActionResults)_action.valueInComponent(component);
} else {
if(_pageName == null) {
throw new IllegalStateException(new StringBuilder().append('<').append(getClass().getName()).append("> : Missing page name.").toString());
}
if(nextPageName != null) {
invokedElement = WOApplication.application().pageWithName(nextPageName, context);
} else {
throw new WOPageNotFoundException(new StringBuilder().append('<').append(getClass().getName()).append("> : cannot find page.").toString());
}
}
} else {
invokedElement = new WONoContentElement();
}
if(invokedElement == null) {
invokedElement = context.page();
}
}
return invokedElement;
}
@Override
public void appendToResponse(WOResponse response, WOContext context) {
super.appendToResponse(response, context);
if(submitInContext(context)) {
if(useIEConditionalsInContext(context)) {
_appendIEOpenTagToResponse(response, context);
response._appendContentAsciiString("<input type=\"submit\"");
if(!isDisabledInContext(context)) {
_appendNameAttributeToResponse(response, context);
_appendValueAttributeToResponse(response, context);
} else {
_appendDisabledAttributeToResponse(response, context);
}
response._appendContentAsciiString("/>");
_appendIECloseTagToResponse(response, context);
}
if((_directActionName != null || _actionClass != null) && !isDisabledInContext(context)) {
response._appendContentAsciiString("<input type=\"hidden\" name=\"WOSubmitAction\"");
response._appendTagAttributeAndValue("value", _actionClassAndName(context), false);
response.appendContentString(" />");
}
}
}
@Override
public void appendChildrenToResponse(WOResponse response, WOContext context) {
_appendButtonOpenTagToResponse(response, context);
super.appendChildrenToResponse(response, context);
if(!hasChildrenElements()){
_appendChildStringToResponse(response, context);
}
_appendButtonCloseTagToResponse(response, context);
}
@Override
public void appendAttributesToResponse(WOResponse response, WOContext context) {
super.appendAttributesToResponse(response, context);
if(!submitInContext(context) && !isDisabledInContext(context)) {
_appendOpeningHrefToResponse(response, context);
if(_actionClass != null || _directActionName != null) {
_appendCGIActionURLToResponse(response, context, true);
} else if(_action != null || _pageName != null) {
_appendComponentActionURLToResponse(response, context, true);
} else if(_href != null) {
_appendStaticURLToResponse(response, context, true);
} else if(_fragmentIdentifier != null && fragmentIdentifierInContext(context).length() > 0) {
_appendQueryStringToResponse(response, context, "", true, true);
_appendFragmentToResponse(response, context);
}
_appendClosingHrefToResponse(response, context);
_appendRelToResponse(response, context);
}
}
@Override
protected void _appendOpenTagToResponse(WOResponse response, WOContext context) {
if(useIEConditionalsInContext(context) && submitInContext(context)) {
_appendNotIEOpenTagToResponse(response, context);
}
super._appendOpenTagToResponse(response, context);
if(useIEConditionalsInContext(context) && !submitInContext(context)) {
_appendIEStringToResponse(response, context);
_appendNotIEOpenTagToResponse(response, context);
}
}
@Override
protected void _appendCloseTagToResponse(WOResponse response, WOContext context) {
if(useIEConditionalsInContext(context) && !submitInContext(context)) {
_appendNotIECloseTagToResponse(response, context);
}
super._appendCloseTagToResponse(response, context);
if(useIEConditionalsInContext(context) && submitInContext(context)) {
_appendNotIECloseTagToResponse(response, context);
}
}
protected void _appendQueryStringToResponse(WOResponse response, WOContext context, String aRequestHandlerPath, boolean htmlEscapeURL, boolean defaultIncludeSessionID) {
NSDictionary<String, Object> aQueryDict = computeQueryDictionaryInContext(aRequestHandlerPath == null ? "" : aRequestHandlerPath, _queryDictionary, _otherQueryAssociations, defaultIncludeSessionID, context);
if(aQueryDict.count() > 0) {
String aQueryString = WOCGIFormValues.getInstance().encodeAsCGIFormValues(aQueryDict, htmlEscapeURL);
if(aQueryString.length() > 0) {
int questionMarkIndex = aRequestHandlerPath == null ? -1 : aRequestHandlerPath.indexOf("?");
if(questionMarkIndex > 0) {
response.appendContentString(htmlEscapeURL ? "&" : "&");
} else {
response.appendContentCharacter('?');
}
response.appendContentString(aQueryString);
}
}
}
protected void _appendFragmentToResponse(WOResponse response, WOContext context) {
String fragmentIdentifier = fragmentIdentifierInContext(context);
if(fragmentIdentifier.length() > 0) {
response.appendContentCharacter('#');
response.appendContentString(fragmentIdentifier);
}
}
protected void _appendCGIActionURLToResponse(WOResponse response, WOContext context, boolean htmlEscapeURL) {
String actionPath = computeActionStringInContext(_actionClass, _directActionName, context);
NSDictionary<String, Object> aQueryDict = computeQueryDictionaryInContext(actionPath, _queryDictionary, _otherQueryAssociations, true, context);
response.appendContentString(context._directActionURL(actionPath, aQueryDict, secureInContext(context), 0, htmlEscapeURL));
_appendFragmentToResponse(response, context);
}
protected void _appendComponentActionURLToResponse(WOResponse response, WOContext context, boolean escapeHTML) {
String actionURL = context.componentActionURL(WOApplication.application().componentRequestHandlerKey(), secureInContext(context));
response.appendContentString(actionURL);
_appendQueryStringToResponse(response, context, actionURL, escapeHTML, true);
_appendFragmentToResponse(response, context);
}
protected void _appendStaticURLToResponse(WOResponse response, WOContext context, boolean escapeHTML) {
String staticURL = hrefInContext(context);
if(WOStaticURLUtilities.isRelativeURL(staticURL) && !WOStaticURLUtilities.isFragmentURL(staticURL)) {
String resourceURL = context._urlForResourceNamed(staticURL, null, false);
if(resourceURL != null) {
response.appendContentString(resourceURL);
staticURL = resourceURL;
} else {
response.appendContentString(context.component().baseURL());
response.appendContentCharacter('/');
response.appendContentString(staticURL);
}
} else {
response.appendContentString(staticURL);
}
_appendQueryStringToResponse(response, context, staticURL, escapeHTML, false);
_appendFragmentToResponse(response, context);
}
protected boolean submitInContext(WOContext context) {
boolean isInForm = context.isInForm();
if(_pageName != null || _href != null || !isInForm) {
return false;
}
return (_submit == null)?
isInForm:
ERXValueUtilities.booleanValueWithDefault(_submit.valueInComponent(context.component()), isInForm);
}
protected boolean useIEConditionalsInContext(WOContext context) {
return (_useIEConditionals == null)?
true:
ERXValueUtilities.booleanValueWithDefault(_useIEConditionals.valueInComponent(context.component()), true);
}
private String _actionClassAndName(WOContext context) {
String anActionString = computeActionStringInContext(_actionClass, _directActionName, context);
return anActionString;
}
protected void _appendOpeningHrefToResponse(WOResponse response, WOContext context) {
response.appendContentCharacter(' ');
response.appendContentString("href");
response.appendContentCharacter('=');
response.appendContentCharacter('"');
String prefix = prefixInContext(context);
if(prefix.length() > 0) {
response.appendContentString(prefix);
}
}
protected void _appendClosingHrefToResponse(WOResponse response, WOContext context) {
String suffix = suffixInContext(context);
if(suffix.length() > 0) {
response.appendContentString(suffix);
}
response.appendContentCharacter('"');
}
protected void _appendButtonOpenTagToResponse(WOResponse response, WOContext context) {
response.appendContentString("<button");
_appendTypeAttributeToResponse(response, context);
if(submitInContext(context)) {
_appendNameAttributeToResponse(response, context);
_appendValueAttributeToResponse(response, context);
}
if(isDisabledInContext(context)) {
_appendDisabledAttributeToResponse(response, context);
}
response.appendContentString(">");
}
protected void _appendButtonCloseTagToResponse(WOResponse response, WOContext context) {
response.appendContentString("</button>");
}
protected void _appendIEOpenTagToResponse(WOResponse response, WOContext context) {
response.appendContentString("<!--[if IE]>");
}
protected void _appendIECloseTagToResponse(WOResponse response, WOContext context) {
response.appendContentString("<![endif]-->");
}
protected void _appendNotIEOpenTagToResponse(WOResponse response, WOContext context) {
response.appendContentString("<!--[if !(IE)]> <-->");
}
protected void _appendNotIECloseTagToResponse(WOResponse response, WOContext context) {
response.appendContentString("<!--> <![endif]-->");
}
protected void _appendTypeAttributeToResponse(WOResponse response, WOContext context) {
String type = submitInContext(context) ? "submit":"button";
_appendTagAttributeAndValueToResponse(response, "type", type, false);
}
protected void _appendNameAttributeToResponse(WOResponse response, WOContext context) {
_appendTagAttributeAndValueToResponse(response, "name", nameInContext(context), escapeHTMLInContext(context));
}
protected void _appendValueAttributeToResponse(WOResponse response, WOContext context) {
_appendTagAttributeAndValueToResponse(response, "value", valueInContext(context), escapeHTMLInContext(context));
}
protected void _appendDisabledAttributeToResponse(WOResponse response, WOContext context) {
_appendTagAttributeAndValueToResponse(response, "disabled", "disabled", false);
}
protected void _appendRelToResponse(WOResponse response, WOContext context) {
if(_rel != null) {
WOComponent component = context.component();
Object val = _rel.valueInComponent(component);
String value = (val == null)?null:val.toString();
if(defaultNoFollow && _action != null) {
if(value == null || value.length() == 0) {
value = "nofollow";
} else {
value = "nofollow " + value;
}
}
if(value != null) {
_appendTagAttributeAndValueToResponse(response, "rel", value, false);
}
}
}
@Override
public String classInContext(WOContext context) {
String value = super.classInContext(context);
if(defaultButtonClass) {
if(value == null || value.length() == 0) {
value = "button";
} else {
value = "button " + value;
}
}
return value;
}
protected void _appendIEStringToResponse(WOResponse response, WOContext context) {
Object value = childStringInContext(context);
_appendIEOpenTagToResponse(response, context);
if(escapeHTMLInContext(context)) {
response.appendContentHTMLString(value.toString());
} else {
response.appendContentString(value.toString());
}
_appendIECloseTagToResponse(response, context);
}
protected void _appendChildStringToResponse(WOResponse response, WOContext context) {
String childString = childStringInContext(context);
if(escapeHTMLInContext(context)) {
response.appendContentHTMLString(childString);
} else {
response.appendContentString(childString);
}
}
public String nameInContext(WOContext context) {
if(_actionClass != null || _directActionName != null) {
return _actionClassAndName(context);
}
if(_name != null) {
Object value = _name.valueInComponent(context.component());
if(value != null) {
return value.toString();
}
}
Object elementID = context.elementID();
if(elementID != null) {
return elementID.toString();
}
throw new IllegalStateException(new StringBuilder().append('<').append(getClass().getName()).append("> Cannot evaluate 'name' attribute, and context element ID is null.").toString());
}
protected String hrefInContext(WOContext context) {
Object value = _href == null ? null : _href.valueInComponent(context.component());
return value == null ? "" : value.toString();
}
protected String fragmentIdentifierInContext(WOContext context) {
Object value = _fragmentIdentifier == null ? null : _fragmentIdentifier.valueInComponent(context.component());
return value == null ? "" : value.toString();
}
protected boolean isDisabledInContext(WOContext context) {
boolean isDisabled = _disabled != null && _disabled.booleanValueInComponent(context.component()) || !isRenderedInContext(context);
return isDisabled;
}
protected String valueInContext(WOContext context) {
Object value = _value == null?null:_value.valueInComponent(context.component());
return value == null?null:value.toString();
}
/**
* Returns the child string for the button supplied by the string or value attribute.
* If submit is true, then the value binding is preferred. Otherwise, the string
* binding is preferred.
* @param context the current context
* @return the child string value
*/
protected String childStringInContext(WOContext context) {
Object value = null;
WOComponent component = context.component();
Object stringVal = _string == null?null:_string.valueInComponent(component);
Object valueVal = valueInContext(context);
if(submitInContext(context)) {
value = valueVal == null?stringVal == null?"Submit":stringVal:valueVal;
} else {
value = stringVal == null?valueVal == null?"":valueVal:stringVal;
}
return value.toString();
}
@Override
public String toString() {
return new StringBuilder().append('<').append(getClass().getName()).append(" action: ").append(_action).append(" actionClass: ").append(_actionClass).append(" href: ").append(_href).append(" value: ").append(_value).append(" queryDictionary: ").append(_queryDictionary).append(" otherQueryAssociations: ").append(_otherQueryAssociations).append(" pageName: ").append(_pageName).append(" fragmentIdentifier: ").append(_fragmentIdentifier).append(" disabled: ").append(_disabled).append(" secure: ").append(_secure).append('>').toString();
}
protected boolean defaultEscapeHTML() {
return true;
}
protected boolean escapeHTMLInContext(WOContext context) {
return _escapeHTML == null ?
defaultEscapeHTML():
ERXValueUtilities.booleanValueWithDefault(_escapeHTML.valueInComponent(context.component()),defaultEscapeHTML());
}
/**
* Overriding to prevent exceptions when actionClass or directActionName
* are bound, but resolve to null.
*/
@Override
protected String computeActionStringInContext(WOAssociation actionClass, WOAssociation directActionName, WOContext aContext) {
WOComponent aComponent = aContext.component();
Object anActionClassName = null;
Object anActionName = null;
Object anActionString = null;
if (actionClass != null) {
anActionClassName = actionClass.valueInComponent(aComponent);
if (!(anActionClassName == null || anActionClassName instanceof String)) {
throw new IllegalArgumentException(new StringBuilder().append('<').append(getClass().getName()).append("> Value for attribute named \"actionClass\" must be a string. Received ").append(anActionClassName).toString());
}
}
if (directActionName != null) {
anActionName = directActionName.valueInComponent(aComponent);
if (!(anActionName == null || anActionName instanceof String)) {
throw new IllegalArgumentException(new StringBuilder().append('<').append(getClass().getName()).append("> Value for attribute named \"directActionName\" must be a string. Received ").append(anActionName).toString());
}
}
if (anActionClassName != null && anActionName != null) {
if (anActionClassName.equals("DirectAction")) {
anActionString = anActionName;
} else {
anActionString = new StringBuilder().append(anActionClassName).append('/').append(anActionName).toString();
}
} else if (anActionClassName != null) {
anActionString = anActionClassName;
} else if (anActionName != null) {
anActionString = anActionName;
} else {
throw new IllegalStateException(new StringBuilder().append('<').append(getClass().getName()).append("> Both 'actionClass' and 'directActionName' are either absent or evaluated to null. Cannot generate dynamic url without an actionClass or directActionName.").toString());
}
return (String) anActionString;
}
}