package er.extensions.appserver.navigation; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.webobjects.appserver.WOComponent; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WORedirect; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSKeyValueCodingAdditions; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSMutableDictionary; import er.extensions.appserver.ERXDirectAction; import er.extensions.components.ERXStatelessComponent; import er.extensions.foundation.ERXArrayUtilities; import er.extensions.foundation.ERXProperties; import er.extensions.foundation.ERXValueUtilities; import er.extensions.localization.ERXLocalizer; /** * This is a menu item component that represents a single item in the tree of navigation menu items. * It's an updated ERXNavigationMenuItem component that should simplify common usage. Namely, it now recurses through * the tree of navigation items, creating nested, unordered lists of navigation items. Just as importantly, with a very * few exceptions,it forgoes declaring element style as possible, leaving positioning and styling to be defined in the * user's stylesheet. * * Please read "Documentation/Navigation.html" to find out how to use the navigation components. * * @author Travis Cripps */ /* Note that I've purposely not extended the old class, hoping to deprecate or replace it with this one at a later date. */ public class ERXModernNavigationMenuItem extends ERXStatelessComponent { /** * Do I need to update serialVersionUID? * See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the * <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a> */ private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(ERXNavigationMenuItem.class); protected ERXNavigationItem _navigationItem; protected ERXNavigationState _navigationState; protected boolean _linkDirectlyToDirectActions = true; protected Boolean _isDisabled; protected Boolean _meetsDisplayConditions; protected Boolean _isSelected; protected Boolean _hasActivity; protected WOComponent _redirect; public ERXNavigationItem aChildItem; // used in WORepetition private static final String EMPTY_STRING = ""; protected static final String STYLE_CLASS_SELECTED = "selected"; protected static final String STYLE_CLASS_DISABLED = "disabled"; protected static final String STYLE_CLASS_SUB = "sub"; public static final String SHOULD_DISPLAY_DISABLED_MENU_ITEMS = "ERXModernNavigationMenuItem.shouldDisplayDisabledMenuItems"; public ERXModernNavigationMenuItem(WOContext context) { super(context); } public String navigationItemID() { if (navigationItem().uniqueID() != null) { return navigationItem().uniqueID(); } return null; } @Override public void reset() { _navigationItem = null; _navigationState = null; _meetsDisplayConditions = null; _hasActivity = null; _isDisabled = null; _isSelected = null; super.reset(); } public String navigationItemWidth() { if (navigationItem().width() > 0) { return "" + navigationItem().width(); } return null; } public ERXNavigationState navigationState() { if (_navigationState == null) { return ERXNavigationManager.manager().navigationStateForSession(session()); } return _navigationState; } /** * AK This is only an experiment: when calling up a DA, we use a component action and redirect to the actual DA * @return a WORedirect to the direct action URL. */ public WOComponent directActionRedirect() { WOComponent page = pageWithName("WORedirect"); String url = context().directActionURLForActionNamed(navigationItem().directActionName(), navigationItem().queryBindings()); ((WORedirect)page).setUrl(url); return page; } public String contextComponentActionURL() { // If the navigation should be disabled return null if (navigationState().isDisabled() || !meetsDisplayConditions()) { return null; } // If the user specified an action or pageName, return the source URL if ((navigationItem().action() != null) || (navigationItem().pageName() != null)) { // Return the URL to the action or page placed in the context by invokeAction return context().componentActionURL(); } if (navigationItem().directActionName() != null) { if(_linkDirectlyToDirectActions) { NSMutableDictionary bindings = navigationItem().queryBindings().mutableClone(); bindings.setObjectForKey(context().contextID(), "__cid"); return context().directActionURLForActionNamed(navigationItem().directActionName(), bindings); } return context().componentActionURL(); } // If the user specified some javascript, put that into the HREF and return it if (canGetValueForBinding("javascriptFunction")) { // Make sure there are no extra quotations marks - replace them with apostrophes String theFunction = (String)valueForBinding("javascriptFunction"); return StringUtils.replace(theFunction, "\"", "'"); } return null; } /** * Determines whether the menu item is selected, or in the path of the current navigation state. * @return true if the menu item is selected */ public WOComponent menuItemSelected() { WOComponent anActionResult = null; if ((navigationItem().action() != null) && (!navigationItem().action().equals(EMPTY_STRING))) { anActionResult = (WOComponent)valueForKeyPath(navigationItem().action()); // it would be nice to have the navigation state to be associated with the // ERXNavigationItem during loadNavigationMenu(). But, with the current model // this menu system allows the same ERXNavigationItem to be a child of more than one parent items. // So we have to loop onItemClick until we hit the root node to // get the navigationState. --santoash // Note: The parent() on an item is only set when you ask for a children of a particular item. // @see ERXNavigationItem.childItemsInContext() NSMutableArray state = new NSMutableArray(); ERXNavigationItem currentNavItem = navigationItem(); do { state.addObject(currentNavItem.name()); currentNavItem = currentNavItem.parent(); } while (!currentNavItem.isRootNode()); ERXNavigationManager.manager().navigationStateForSession(session()).setState(ERXArrayUtilities.reverse(state)); } else if ((navigationItem().pageName() != null) && (!navigationItem().pageName().equals(EMPTY_STRING))) { anActionResult = pageWithName(navigationItem().pageName()); } else if ((navigationItem().directActionName() != null) && (!navigationItem().directActionName().equals(EMPTY_STRING))) { // FIXME: Need to support directAction classes if(_linkDirectlyToDirectActions) { ERXDirectAction da = new ERXDirectAction(context().request()); anActionResult = (WOComponent)(da.performActionNamed(navigationItem().directActionName())); } else { anActionResult = (WOComponent)valueForKeyPath("directActionRedirect"); } } return anActionResult; } /** * Decides whether the item gets displayed at all. * This is done by evaluating the boolean value of a "conditions" array in the definition file. * eg: conditions = ("session.user.canEditThisStuff", "session.user.isEditor") * will display the item only if the user can edit this stuff *and* is an editor. * @return true if the display conditions are met */ public boolean meetsDisplayConditions() { if (_meetsDisplayConditions == null) { if(navigationItem() != null) { _meetsDisplayConditions = navigationItem().meetsDisplayConditionsInComponent(this) ? Boolean.TRUE : Boolean.FALSE; } else { _meetsDisplayConditions = Boolean.FALSE; } } return _meetsDisplayConditions.booleanValue(); } /** * Determines if the item should be displayed in the UI, based upon the disabled status. You may disable display of * items that do not meet their display conditions or are explicitly disabled. * @return true if the item should be displayed */ public boolean shouldDisplay() { boolean result = true; if (isDisabled()) { // Must explicitly disable display with a property. result = ERXProperties.booleanForKeyWithDefault(SHOULD_DISPLAY_DISABLED_MENU_ITEMS, true); } return result; } /** * Gets the {@link ERXNavigationItem} that provides the backing store for the properties of this menu item. * @return the navigation item */ public ERXNavigationItem navigationItem() { if (_navigationItem == null) { _navigationItem = (ERXNavigationItem)valueForBinding("navigationItem"); if(_navigationItem == null) { String name = (String)valueForBinding("navigationItemName"); if(name != null) { _navigationItem = ERXNavigationManager.manager().navigationItemForName(name); } else { log.warn("Navigation unset: {}", name); _navigationItem = ERXNavigationManager.manager().newNavigationItem(new NSDictionary(name, "name")); } } } return _navigationItem; } public boolean isDisabled() { if (_isDisabled == null) { _isDisabled = navigationState().isDisabled() || !meetsDisplayConditions() ? Boolean.TRUE : Boolean.FALSE; } return _isDisabled.booleanValue(); } public boolean isSelected() { if (_isSelected == null) { NSArray navigationState = navigationState().state(); _isSelected=!isDisabled() && navigationState != null && navigationState.containsObject(navigationItem().name()) ? Boolean.TRUE : Boolean.FALSE; } return _isSelected.booleanValue(); } public String itemStyleClass() { NSMutableArray styleClasses = new NSMutableArray(); String result = EMPTY_STRING; // Check to see if the item is disabled. if (isDisabled()) { styleClasses.addObject(STYLE_CLASS_DISABLED); } else { // Check to see if this is one of the "active" locations in the navigation state. if (navigationState().state().containsObject(navigationItem().name())) { styleClasses.addObject(STYLE_CLASS_SELECTED); } if (children().count() > 0) { styleClasses.addObject(STYLE_CLASS_SUB); } } if (styleClasses.count() > 0) { result += styleClasses.componentsJoinedByString(" "); } return result; } public Object resolveValue(String key) { if (key != null && key.startsWith("^")) { return valueForKeyPath(key.substring(1)); } return key; } public boolean hasActivity() { if (_hasActivity == null) _hasActivity = ERXValueUtilities.booleanValue(resolveValue(navigationItem().hasActivity())) ? Boolean.TRUE : Boolean.FALSE; return _hasActivity.booleanValue(); } public boolean hasActivityAndIsEnabled(){ return hasActivity() && !isDisabled(); } public String displayName() { String name = (String) resolveValue(navigationItem().displayName()); if(name != null) { if(ERXProperties.booleanForKey("er.extensions.ERXNavigationManager.localizeDisplayKeys")) { String localizerKey = "Nav." + name; String localizedValue = ERXLocalizer.currentLocalizer().localizedStringForKey(localizerKey); if(localizedValue == null) { localizedValue = ERXLocalizer.currentLocalizer().localizedStringForKey(name); if(localizedValue != null) { log.info("Found old-style entry: {}->{}", localizerKey, localizedValue); ERXLocalizer.currentLocalizer().takeValueForKey(localizedValue, localizerKey); name = localizedValue; } } else { name = localizedValue; } } } return name; } public NSArray children() { return navigationItem().childItemsInContext(this); } public NSKeyValueCodingAdditions navigationContext() { return (NSKeyValueCodingAdditions)valueForBinding("navigationContext"); } }