package er.extensions.components; import com.webobjects.appserver.WOAssociation; import com.webobjects.appserver.WOComponent; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WOElement; import com.webobjects.appserver.WOResponse; import com.webobjects.appserver._private.WOConstantValueAssociation; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSMutableDictionary; import er.extensions.components._private.ERXSubmitButton; import er.extensions.foundation.ERXStringUtilities; /** * Extends ERXSubmitButton with self-configuring accessibility hot key for the * button. The default hot key is the first character. If that has been used by * a button that appeared earlier on the page, the next choice is a capital * character. If no non-conflicting capital characters are available, it selects * the first character that does not conflict with an earlier button. If you * want a different hot key, specify it in the accesskey binding. Note that if * this conflicts with the self configured choice for a button that appeared * earlier on the page that this conflict will not be resolved. You will have to * define an accesskey for the conflicting button as well. If you don't want a * hot key, bind accesskey to the empty string. * <p> * The default is to use the u (underline) element to identify the hot key in * the button text. Use the accesskeyElement binding to specify a different * element. The element can be decorated with other attributes, e.g. * * <pre><code> * accesskeyElement = "span style='text-decoration: underline;'"; * </code></pre> * * <p> * You can have this class replace WOSubmitButton via * ERXPatcher.setClassForName(ERXAccessibleSubmitButton.class, * "WOSubmitButton"); or use it explicitly by name in your WOD. It works best * (does the most work for you) if you use it with the value attribute. No self * configuring is done if there is any content between the open and close tags. * * @binding accesskey optional key for hot key, "" to disable hot key * @binding accesskeyElement optional element name and decoration to wrap hot * key character with in the button text * @binding value the button text * * @see ERXSubmitButton * * @author chill */ public class ERXAccessibleSubmitButton extends ERXSubmitButton { protected WOAssociation _accesskey; protected WOAssociation _accesskeyElement; public ERXAccessibleSubmitButton(String name, NSDictionary<String, WOAssociation> associations, WOElement template) { super(name, associations, template); _accesskey = _associations.removeObjectForKey("accesskey"); _accesskeyElement = _associations.removeObjectForKey("accesskeyElement"); if(_accesskeyElement == null) { _accesskeyElement = new WOConstantValueAssociation("u"); } } /** * @param component WOComponent to evaluate the associations in * @return the character to use for the button's accesskey attribute, or null if there will not be an accesskey */ protected String accesskey(WOComponent component) { String accessKey = null; // If accesskey is unbound or bound to null, determine it from the value binding if (_accesskey == null || _accesskey.valueInComponent(component) == null) { String value = (String)_value.valueInComponent(component); if ( ! ERXStringUtilities.stringIsNullOrEmpty(value)) { // Preference is for the first character in the button text // This CAN conflict with explicitly set hot keys accessKey = value.substring(0, 1); // If that character has been used, then try any capitalized characters in order if (hasUsedHotKey(component, accessKey)) { accessKey = null; for (int i = 1; accessKey == null && i < value.length(); i++) { if (Character.isUpperCase(value.charAt(i)) && ! hasUsedHotKey(component, value.substring(i, i+1))) { accessKey = value.substring(i, i+1); } } // If all the capitals have been used (or there aren't any more), then pick the first unused character for (int i = 1; accessKey == null && i < value.length(); i++) { if (! hasUsedHotKey(component, value.substring(i, i+1))) { accessKey = value.substring(i, i+1); } } } } } else { // Binding accesskey to "" indicates that there should not be a hot key accessKey = (String)_accesskey.valueInComponent(component); accessKey = "".equals(accessKey) ? null : accessKey; } return accessKey; } /** * @param component WOComponent to evaluate the associations in * @return element name and decoration to wrap hot key character with in the button text */ protected String accesskeyElement(WOComponent component) { return (String) _accesskeyElement.valueInComponent(component); } /** * @param component WOComponent to evaluate the associations in * @return <code>true</code> if accesskey should not be generated */ protected boolean isDisabled(WOComponent component) { return accesskey(component) == null; } /** * @param component WOComponent to evaluate the associations in * @return String from the value binding with the accesskey wrapped in accesskeyElement */ protected String styledValue(WOComponent component) { String value = (String)_value.valueInComponent(component); String accessKey = accesskey(component); if (accessKey == null) { return value; } String accesskeyElement = accesskeyElement(component); int index = value.indexOf(accessKey); if (index != -1) { StringBuilder sb = new StringBuilder(); sb.append(value.substring(0, index)); sb.append('<'); sb.append(accesskeyElement); sb.append('>'); sb.append(accessKey); sb.append("</"); int coIndex = accesskeyElement.indexOf(' '); sb.append(coIndex == -1 ? accesskeyElement : accesskeyElement.substring(0, coIndex)); sb.append('>'); sb.append(value.substring(index + 1)); value = sb.toString(); } return value; } /** * Records the accesskey for this button so other buttons on the page won't use it. * * @see er.extensions.components._private.ERXSubmitButton#appendToResponse(com.webobjects.appserver.WOResponse, com.webobjects.appserver.WOContext) */ @Override public void appendToResponse(WOResponse response, WOContext context) { super.appendToResponse(response, context); if (accesskey(context.component()) != null) { recordUsedHotKey(context.component(), accesskey(context.component())); } } /** * Adds the accesskey binding as we took it out of the associations dictionary * * @see er.extensions.components._private.ERXSubmitButton#appendAttributesToResponse(com.webobjects.appserver.WOResponse, com.webobjects.appserver.WOContext) */ @Override public void appendAttributesToResponse(WOResponse response, WOContext context) { super.appendAttributesToResponse(response, context); response._appendTagAttributeAndValue("accesskey", accesskey(context.component()), false); } /** * Adds styledValue between the open and close tags * * @see er.extensions.components._private.ERXSubmitButton#appendChildrenToResponse(com.webobjects.appserver.WOResponse, com.webobjects.appserver.WOContext) */ @Override public void appendChildrenToResponse(WOResponse response, WOContext context) { if(hasChildrenElements()) { super.appendChildrenToResponse(response, context); } else { response.appendContentString(styledValue(context.component())); } } /** * @param component WOComponent to evaluate the associations in * @param hotKey the character to check * @return <code>true</code> if hotKey has been used by another ERXAccessibleSubmitButton on this page */ protected boolean hasUsedHotKey(WOComponent component, String hotKey) { return usedHotKeys(component).containsObject(hotKey); } /** * Records that hotKey is being used by a ERXAccessibleSubmitButton on this page. * @param component WOComponent to evaluate the associations in * @param hotKey the character to record */ protected void recordUsedHotKey(WOComponent component, String hotKey) { usedHotKeys(component).addObject(hotKey); } /** * @param component WOComponent to evaluate the associations in * @return NSMutableArray containing the hotKeys being used by ERXAccessibleSubmitButtons on this page */ protected NSMutableArray usedHotKeys(WOComponent component) { WOResponse response = component.context().response(); NSDictionary userInfo = response.userInfo(); if (userInfo == null) { userInfo = new NSMutableDictionary(new NSMutableArray(), ERXAccessibleSubmitButton.class.getName()); response.setUserInfo(userInfo); } NSMutableArray usedHotKeys = (NSMutableArray)userInfo.objectForKey(ERXAccessibleSubmitButton.class.getName()); if (usedHotKeys == null) { userInfo = userInfo.mutableClone(); usedHotKeys = new NSMutableArray(); ((NSMutableDictionary)userInfo).setObjectForKey(usedHotKeys, ERXAccessibleSubmitButton.class.getName()); response.setUserInfo(userInfo); } return (NSMutableArray)userInfo.objectForKey(ERXAccessibleSubmitButton.class.getName()); } }