diff --git a/config/alfresco/messages/webclient.properties b/config/alfresco/messages/webclient.properties index 3641f99700..bc714587d5 100644 --- a/config/alfresco/messages/webclient.properties +++ b/config/alfresco/messages/webclient.properties @@ -106,8 +106,7 @@ close_search=Close Search browse_spaces=Browse Spaces browse_content=Content Items location=Location -toggle_shelf=Hide or Show the Shelf -shelf=Shelf +toggle_shelf=Hide or Show the Sidebar actions=Actions view=View view_details=View Details @@ -373,6 +372,13 @@ start_discussion=Start Discussion discuss=View Discussions discussion_for={0} discussion +# Sidebar messages +navigator=Navigator +navigator_desc=Allows tree based navigation around the repository +shelf=Shelf +shelf_desc=Area that includes the clipboard, recent space and shortcuts +reset_navigator=Re-initialise the navigator + # Common Wizard messages steps=Steps summary=Summary diff --git a/config/alfresco/web-client-config-actions.xml b/config/alfresco/web-client-config-actions.xml index 853ede4c6f..dcce1f3818 100644 --- a/config/alfresco/web-client-config-actions.xml +++ b/config/alfresco/web-client-config-actions.xml @@ -562,6 +562,13 @@ #{RulesBean.ignoreInheritedRules} + + + reset_navigator + /images/icons/reset.gif + #{NavigatorPluginBean.reset} + + + + + + diff --git a/config/alfresco/web-client-config.xml b/config/alfresco/web-client-config.xml index 6d4a92244a..67a1835d17 100644 --- a/config/alfresco/web-client-config.xml +++ b/config/alfresco/web-client-config.xml @@ -17,6 +17,7 @@ + @@ -214,6 +215,20 @@ + + + + + + + + + navigator + + + diff --git a/source/java/org/alfresco/web/app/Application.java b/source/java/org/alfresco/web/app/Application.java index 0d3f7f25b3..e006353250 100644 --- a/source/java/org/alfresco/web/app/Application.java +++ b/source/java/org/alfresco/web/app/Application.java @@ -38,6 +38,7 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.web.app.servlet.AuthenticationHelper; import org.alfresco.web.app.servlet.FacesHelper; import org.alfresco.web.bean.ErrorBean; +import org.alfresco.web.bean.SidebarBean; import org.alfresco.web.bean.dashboard.DashboardManager; import org.alfresco.web.bean.dialog.DialogManager; import org.alfresco.web.bean.repository.User; @@ -203,6 +204,16 @@ public class Application return (DashboardManager)FacesHelper.getManagedBean(FacesContext.getCurrentInstance(), "DashboardManager"); } + /** + * Retrieves the SidebarBean + * + * @return SidebarBean bean + */ + public static SidebarBean getSidebarBean() + { + return (SidebarBean)FacesHelper.getManagedBean(FacesContext.getCurrentInstance(), "SidebarBean"); + } + /** * Retrieves the configured error page for the application * @@ -481,6 +492,7 @@ public class Application * @param context FacesContext for current user * @param code The ISO locale code to set */ + @SuppressWarnings("unchecked") public static void setLanguage(FacesContext context, String code) { Locale locale = parseLocale(code); @@ -633,6 +645,7 @@ public class Application * * @return ResourceBundle for this user */ + @SuppressWarnings("unchecked") public static ResourceBundle getBundle(FacesContext context) { // get the resource bundle for the current locale diff --git a/source/java/org/alfresco/web/app/context/IContextListener.java b/source/java/org/alfresco/web/app/context/IContextListener.java index effb091079..7b9d8d1c3a 100644 --- a/source/java/org/alfresco/web/app/context/IContextListener.java +++ b/source/java/org/alfresco/web/app/context/IContextListener.java @@ -21,7 +21,10 @@ package org.alfresco.web.app.context; *

* Beans supporting this interface should be register against the UIContextService. Then Beans * which wish to indicate that the UI should refresh itself i.e. dump all cached data and settings, - * call the UIContextService.notifyBeans() to inform all registered instances of the change. + * call the UIContextService.notifyBeans() to inform all registered instances of the change. + *

+ * Registered beans will also be informed of changes in location, for example when the current + * space changes or when the user has changed area i.e. from company home to my home. * * @author Kevin Roast */ @@ -32,4 +35,16 @@ public interface IContextListener * all UI Beans should refresh dump all cached data and settings. */ public void contextUpdated(); + + /** + * Method called by UIContextService.spaceChanged() to inform all registered beans that + * the current space has changed. + */ + public void spaceChanged(); + + /** + * Method called by UIContextService.areaChanged() to inform all registered beans that + * the user has changed area i.e. from company home to my home. + */ + public void areaChanged(); } diff --git a/source/java/org/alfresco/web/app/context/UIContextService.java b/source/java/org/alfresco/web/app/context/UIContextService.java index 4335462df6..9ffe220187 100644 --- a/source/java/org/alfresco/web/app/context/UIContextService.java +++ b/source/java/org/alfresco/web/app/context/UIContextService.java @@ -24,7 +24,10 @@ import javax.faces.context.FacesContext; /** * Beans supporting the IContextListener interface are registered against this class. Then Beans * which wish to indicate that the UI should refresh itself i.e. dump all cached data and settings, - * call the UIContextService.notifyBeans() to inform all registered instances of the change. + * call the UIContextService.notifyBeans() to inform all registered instances of the change. + *

+ * Registered beans will also be informed of changes in location, for example when the current + * space changes or when the user has changed area i.e. from company home to my home. * * @author Kevin Roast */ @@ -42,6 +45,7 @@ public final class UIContextService * * @return UIContextService for this Thread */ + @SuppressWarnings("unchecked") public static UIContextService getInstance(FacesContext fc) { Map session = fc.getExternalContext().getSessionMap(); @@ -97,6 +101,29 @@ public final class UIContextService } } + /** + * Call to notify all register beans that the current space has changed and they should + * refresh themselves as appropriate. + */ + public void spaceChanged() + { + for (IContextListener listener: this.registeredBeans.values()) + { + listener.spaceChanged(); + } + } + + /** + * Call to notify all register beans that the area i.e. my home, has changed and they should + * refresh themselves as appropriate. + */ + public void areaChanged() + { + for (IContextListener listener: this.registeredBeans.values()) + { + listener.areaChanged(); + } + } /** key for the UI context service in the session */ private final static String CONTEXT_KEY = "__uiContextService"; diff --git a/source/java/org/alfresco/web/app/servlet/ajax/InvokeCommand.java b/source/java/org/alfresco/web/app/servlet/ajax/InvokeCommand.java index 9810c33f37..b5da969d0a 100644 --- a/source/java/org/alfresco/web/app/servlet/ajax/InvokeCommand.java +++ b/source/java/org/alfresco/web/app/servlet/ajax/InvokeCommand.java @@ -52,9 +52,9 @@ import org.alfresco.web.bean.repository.Repository; public class InvokeCommand extends BaseAjaxCommand { public void execute(final FacesContext facesContext, - final String expression, - final HttpServletRequest request, - final HttpServletResponse response) + final String expression, + final HttpServletRequest request, + final HttpServletResponse response) throws ServletException, IOException { // setup the JSF response writer. @@ -64,6 +64,8 @@ public class InvokeCommand extends BaseAjaxCommand // therefore, for now we will always return a content type of text/xml. // In the future we may use annotations on the method to be called to specify what content // type should be used for the response. + // NOTE: JSF only seems to support XML and HTML content types by default so this will + // also need to be addressed if other content types need to be returned i.e. JSON. OutputStream os = response.getOutputStream(); UIViewRoot viewRoot = facesContext.getViewRoot(); @@ -76,7 +78,7 @@ public class InvokeCommand extends BaseAjaxCommand facesContext.setResponseWriter(writer); // must be text/xml otherwise IE doesn't parse the response properly into responseXML response.setContentType(MimetypeMap.MIMETYPE_XML); - + // create the JSF binding expression String bindingExpr = makeBindingExpression(expression); @@ -107,16 +109,19 @@ public class InvokeCommand extends BaseAjaxCommand { // rollback the transaction try { if (tx != null) { tx.rollback(); } } catch (Exception ex) { } - if (err instanceof EvaluationException) - { + + if (err instanceof EvaluationException) + { final Throwable cause = ((EvaluationException)err).getCause(); - if (cause != null) - err = cause; - } - logger.error(err); + if (cause != null) + { + err = cause; + } + } + + logger.error(err); throw new AlfrescoRuntimeException("Failed to execute method " + expression + - ": " + err.getMessage(), - err); + ": " + err.getMessage(), err); } // force the output back to the client diff --git a/source/java/org/alfresco/web/bean/BrowseBean.java b/source/java/org/alfresco/web/bean/BrowseBean.java index f81011a164..74b26c1b7c 100644 --- a/source/java/org/alfresco/web/bean/BrowseBean.java +++ b/source/java/org/alfresco/web/bean/BrowseBean.java @@ -25,7 +25,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import javax.faces.application.FacesMessage; import javax.faces.context.FacesContext; import javax.faces.event.ActionEvent; import javax.transaction.UserTransaction; @@ -52,7 +51,6 @@ import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; -import org.alfresco.web.app.AlfrescoNavigationHandler; import org.alfresco.web.app.Application; import org.alfresco.web.app.context.IContextListener; import org.alfresco.web.app.context.UIContextService; @@ -485,10 +483,25 @@ public class BrowseBean implements IContextListener invalidateComponents(); } + /** + * @see org.alfresco.web.app.context.IContextListener#areaChanged() + */ + public void areaChanged() + { + // nothing to do + } + + /** + * @see org.alfresco.web.app.context.IContextListener#spaceChanged() + */ + public void spaceChanged() + { + // nothing to do + } // ------------------------------------------------------------------------------ // NodeEventListener listeners - + /** * Add a listener to those called by the BrowseBean when nodes are created */ @@ -1547,6 +1560,9 @@ public class BrowseBean implements IContextListener // set up the dispatch context for the navigation handler this.navigator.setupDispatchContext(new Node(ref)); + // inform any listeners that the current space has changed + UIContextService.getInstance(FacesContext.getCurrentInstance()).spaceChanged(); + navigateBrowseScreen(); } @@ -1652,6 +1668,9 @@ public class BrowseBean implements IContextListener // setup the dispatch context navigator.setupDispatchContext(new Node(this.nodeRef)); + // inform any listeners that the current space has changed + UIContextService.getInstance(FacesContext.getCurrentInstance()).spaceChanged(); + // return to browse page if required return (isViewCurrent() ? null : "browse"); } diff --git a/source/java/org/alfresco/web/bean/CategoriesBean.java b/source/java/org/alfresco/web/bean/CategoriesBean.java index 599410fd2e..7a44de6f92 100644 --- a/source/java/org/alfresco/web/bean/CategoriesBean.java +++ b/source/java/org/alfresco/web/bean/CategoriesBean.java @@ -658,6 +658,21 @@ public class CategoriesBean implements IContextListener this.categoriesRichList.setValue(null); } + /** + * @see org.alfresco.web.app.context.IContextListener#areaChanged() + */ + public void areaChanged() + { + // nothing to do + } + + /** + * @see org.alfresco.web.app.context.IContextListener#spaceChanged() + */ + public void spaceChanged() + { + // nothing to do + } // ------------------------------------------------------------------------------ // Inner classes diff --git a/source/java/org/alfresco/web/bean/GroupsBean.java b/source/java/org/alfresco/web/bean/GroupsBean.java index 5ee6f4a2ef..9fa53f8f35 100644 --- a/source/java/org/alfresco/web/bean/GroupsBean.java +++ b/source/java/org/alfresco/web/bean/GroupsBean.java @@ -913,6 +913,21 @@ public class GroupsBean implements IContextListener this.usersRichList.setValue(null); } + /** + * @see org.alfresco.web.app.context.IContextListener#areaChanged() + */ + public void areaChanged() + { + // nothing to do + } + + /** + * @see org.alfresco.web.app.context.IContextListener#spaceChanged() + */ + public void spaceChanged() + { + // nothing to do + } // ------------------------------------------------------------------------------ // Inner classes diff --git a/source/java/org/alfresco/web/bean/LoginBean.java b/source/java/org/alfresco/web/bean/LoginBean.java index 406f26b039..83969e8f87 100644 --- a/source/java/org/alfresco/web/bean/LoginBean.java +++ b/source/java/org/alfresco/web/bean/LoginBean.java @@ -39,6 +39,7 @@ import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.web.app.Application; import org.alfresco.web.app.servlet.AuthenticationHelper; +import org.alfresco.web.app.servlet.FacesHelper; import org.alfresco.web.bean.repository.Repository; import org.alfresco.web.bean.repository.User; import org.alfresco.web.config.LanguagesConfigElement; @@ -336,8 +337,16 @@ public class LoginBean } else { - // special case to handle jump to My Alfresco page initially + // setup the current location with the NavigationBean String location = this.preferences.getStartLocation(); + NavigationBean navBean = (NavigationBean)FacesHelper.getManagedBean( + fc, "NavigationBean"); + if (navBean != null) + { + navBean.setToolbarLocation(location); + } + + // special case to handle jump to My Alfresco page initially if (NavigationBean.LOCATION_MYALFRESCO.equals(location)) { return "myalfresco"; diff --git a/source/java/org/alfresco/web/bean/NavigationBean.java b/source/java/org/alfresco/web/bean/NavigationBean.java index 9fcd318a13..5f85d6f6dd 100644 --- a/source/java/org/alfresco/web/bean/NavigationBean.java +++ b/source/java/org/alfresco/web/bean/NavigationBean.java @@ -44,6 +44,7 @@ import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.web.app.Application; import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.app.servlet.FacesHelper; import org.alfresco.web.bean.repository.Node; import org.alfresco.web.bean.repository.Repository; import org.alfresco.web.bean.repository.User; @@ -193,6 +194,24 @@ public class NavigationBean */ public String getToolbarLocation() { + if (this.toolbarLocation == null) + { + // if the toolbar location has not been set yet, try and get the + // default via the user preferences object + UserPreferencesBean prefsBean = (UserPreferencesBean)FacesHelper.getManagedBean( + FacesContext.getCurrentInstance(), "UserPreferencesBean"); + if (prefsBean != null) + { + this.toolbarLocation = prefsBean.getStartLocation(); + } + + // if we still haven't found a location default to my home + if (this.toolbarLocation == null) + { + this.toolbarLocation = LOCATION_HOME; + } + } + return this.toolbarLocation; } @@ -201,7 +220,7 @@ public class NavigationBean */ public void setToolbarLocation(String location) { - processToolbarLocation(location, true); + this.toolbarLocation = location; } /** @@ -211,7 +230,8 @@ public class NavigationBean * @param location Toolbar location constant * @param navigate True to perform navigation, false otherwise */ - private void processToolbarLocation(String location, boolean navigate) + @SuppressWarnings("serial") + public void processToolbarLocation(String location, boolean navigate) { this.toolbarLocation = location; @@ -224,6 +244,9 @@ public class NavigationBean setLocation(elements); setCurrentNodeId(companyHome.getId()); + // inform registered beans that the current area has changed + UIContextService.getInstance(FacesContext.getCurrentInstance()).areaChanged(); + // we need to force a navigation to refresh the browse screen breadcrumb if (navigate) { @@ -240,6 +263,9 @@ public class NavigationBean setLocation(elements); setCurrentNodeId(homeSpaceRef.getId()); + // inform registered beans that the current area has changed + UIContextService.getInstance(FacesContext.getCurrentInstance()).areaChanged(); + // we need to force a navigation to refresh the browse screen breadcrumb if (navigate) { @@ -254,6 +280,9 @@ public class NavigationBean setLocation(elements); setCurrentNodeId(guestHome.getId()); + // inform registered beans that the current area has changed + UIContextService.getInstance(FacesContext.getCurrentInstance()).areaChanged(); + // we need to force a navigation to refresh the browse screen breadcrumb if (navigate) { @@ -274,6 +303,7 @@ public class NavigationBean List elements = new ArrayList(1); elements.add(new IBreadcrumbHandler() { + @SuppressWarnings("unchecked") public String navigationOutcome(UIBreadcrumb breadcrumb) { setLocation( (List)breadcrumb.getValue() ); @@ -287,6 +317,9 @@ public class NavigationBean }); setLocation(elements); + // inform registered beans that the current area has changed + UIContextService.getInstance(FacesContext.getCurrentInstance()).areaChanged(); + // we need to force a navigation to refresh the browse screen breadcrumb if (navigate) { @@ -673,7 +706,7 @@ public class NavigationBean { UIModeList locationList = (UIModeList)event.getComponent(); String location = locationList.getValue().toString(); - setToolbarLocation(location); + processToolbarLocation(location, true); } catch (InvalidNodeRefException refErr) { @@ -864,7 +897,7 @@ public class NavigationBean private Node companyHomeNode = null; /** Current toolbar location */ - private String toolbarLocation = LOCATION_HOME; + private String toolbarLocation = null; /** Search context object we are currently using or null for no search */ private SearchContext searchContext; diff --git a/source/java/org/alfresco/web/bean/RecentSpacesBean.java b/source/java/org/alfresco/web/bean/RecentSpacesBean.java index ddf1f931d6..b8ffa027b6 100644 --- a/source/java/org/alfresco/web/bean/RecentSpacesBean.java +++ b/source/java/org/alfresco/web/bean/RecentSpacesBean.java @@ -175,6 +175,25 @@ public class RecentSpacesBean implements IContextListener this.recentSpaces.add(0, node); } + /** + * @see org.alfresco.web.app.context.IContextListener#areaChanged() + */ + public void areaChanged() + { + // nothing to do + } + + /** + * @see org.alfresco.web.app.context.IContextListener#spaceChanged() + */ + public void spaceChanged() + { + // nothing to do + } + + // ------------------------------------------------------------------------------ + // Helper methods + /** * @return the max number of recent spaces to show, retrieved from client config */ diff --git a/source/java/org/alfresco/web/bean/SidebarBean.java b/source/java/org/alfresco/web/bean/SidebarBean.java new file mode 100644 index 0000000000..021de76d62 --- /dev/null +++ b/source/java/org/alfresco/web/bean/SidebarBean.java @@ -0,0 +1,169 @@ +package org.alfresco.web.bean; + +import java.util.ArrayList; +import java.util.List; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; + +import org.alfresco.config.Config; +import org.alfresco.web.app.Application; +import org.alfresco.web.config.SidebarConfigElement; +import org.alfresco.web.config.SidebarConfigElement.SidebarPluginConfig; +import org.alfresco.web.ui.common.component.UIListItem; +import org.alfresco.web.ui.common.component.UIModeList; + +/** + * Managed bean used by the sidebar component to manage it's state. + * + * @author gavinc + */ +public class SidebarBean +{ + protected String activePlugin; + protected List plugins; + protected SidebarConfigElement sidebarConfig; + + /** + * Default constructor + */ + public SidebarBean() + { + // get the sidebar config object + this.sidebarConfig = getSidebarConfig(FacesContext.getCurrentInstance()); + + // make sure we found the config + if (this.sidebarConfig == null) + { + throw new IllegalStateException("Failed to find configuration for the sidebar"); + } + + // build the list of plugins available and check we have at least one + List items = this.getPlugins(); + if (items.size() == 0) + { + throw new IllegalStateException("Failed to find configuration for any sidebar plugins, at least one must be defined!"); + } + + // determine the default plugin + this.activePlugin = this.sidebarConfig.getDefaultPlugin(); + if (this.activePlugin == null) + { + this.activePlugin = (String)items.get(0).getValue(); + } + } + + // ------------------------------------------------------------------------------ + // Event handlers + + public void pluginChanged(ActionEvent event) + { + UIModeList pluginList = (UIModeList)event.getComponent(); + + // get the selected plugin + this.activePlugin = pluginList.getValue().toString(); + } + + // ------------------------------------------------------------------------------ + // Bean Getters and Setters + + /** + * Returns a list of configured plugins + * + * @return List of UIListItem's representing the plugins available + */ + public List getPlugins() + { + if (this.plugins == null) + { + FacesContext context = FacesContext.getCurrentInstance(); + this.plugins = new ArrayList(); + + // create a list entry for each configured plugin + for (String pluginId : this.sidebarConfig.getPlugins().keySet()) + { + SidebarPluginConfig plugin = this.sidebarConfig.getPlugin(pluginId); + + // resolve the label for the plugin + String label = plugin.getlabelId(); + if (label != null) + { + label = Application.getMessage(context, label); + } + if (label == null) + { + label = plugin.getlabel(); + } + if (label == null) + { + label = plugin.getId(); + } + + // resolve the description (tooltip for the plugin) + String tooltip = plugin.getDescriptionId(); + if (tooltip != null) + { + tooltip = Application.getMessage(context, tooltip); + } + if (tooltip == null) + { + tooltip = plugin.getDescription(); + } + + UIListItem item = new UIListItem(); + item.setValue(plugin.getId()); + item.setLabel(label); + if (tooltip != null) + { + item.setTooltip(tooltip); + } + + this.plugins.add(item); + } + } + + return this.plugins; + } + + /** + * Returns the id of the currently active plugin + * + * @return Id of the current plugin + */ + public String getActivePlugin() + { + return activePlugin; + } + + /** + * Returns the path of the JSP to use for the current plugin + * + * @return JSP to use for the current plugin + */ + public String getActivePluginPage() + { + return this.sidebarConfig.getPlugin(this.activePlugin).getPage(); + } + + // ------------------------------------------------------------------------------ + // Helper methods + + /** + * Returns the SidebarConfigElement for the application + * + * @param context Faces context + * @return The SidebarConfigElement object or null if it's not found + */ + public static SidebarConfigElement getSidebarConfig(FacesContext context) + { + SidebarConfigElement config = null; + + Config cfg = Application.getConfigService(context).getConfig("Sidebar"); + if (cfg != null) + { + config = (SidebarConfigElement)cfg.getConfigElement(SidebarConfigElement.CONFIG_ELEMENT_ID); + } + + return config; + } +} diff --git a/source/java/org/alfresco/web/bean/TrashcanBean.java b/source/java/org/alfresco/web/bean/TrashcanBean.java index eba6e8ee2f..e8748b585f 100644 --- a/source/java/org/alfresco/web/bean/TrashcanBean.java +++ b/source/java/org/alfresco/web/bean/TrashcanBean.java @@ -1255,4 +1255,20 @@ public class TrashcanBean implements IContextListener } this.showItems = false; } + + /** + * @see org.alfresco.web.app.context.IContextListener#areaChanged() + */ + public void areaChanged() + { + // nothing to do + } + + /** + * @see org.alfresco.web.app.context.IContextListener#spaceChanged() + */ + public void spaceChanged() + { + // nothing to do + } } diff --git a/source/java/org/alfresco/web/bean/ajax/NavigatorPluginBean.java b/source/java/org/alfresco/web/bean/ajax/NavigatorPluginBean.java new file mode 100644 index 0000000000..16ef63176d --- /dev/null +++ b/source/java/org/alfresco/web/bean/ajax/NavigatorPluginBean.java @@ -0,0 +1,610 @@ +package org.alfresco.web.bean.ajax; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; + +import org.alfresco.model.ApplicationModel; +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.IContextListener; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.app.servlet.FacesHelper; +import org.alfresco.web.bean.BrowseBean; +import org.alfresco.web.bean.NavigationBean; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.ui.repo.component.UITree.TreeNode; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Bean used by the navigator component to manage the tree data. + * + * @author gavinc + */ +public class NavigatorPluginBean implements IContextListener +{ + public static final String BEAN_NAME = "NavigatorPluginBean"; + + protected List companyHomeRootNodes; + protected List myHomeRootNodes; + protected List guestHomeRootNodes; + protected Map companyHomeNodes; + protected Map myHomeNodes; + protected Map guestHomeNodes; + protected NodeRef previouslySelectedNode; + + private NodeService nodeService; + private DictionaryService dictionaryService; + + private static final Log logger = LogFactory.getLog(NavigatorPluginBean.class); + + public NavigatorPluginBean() + { + UIContextService.getInstance(FacesContext.getCurrentInstance()).registerBean(this); + } + + // ------------------------------------------------------------------------------ + // AJAX handler methods + + /** + * Retrieves the child folders for the noderef given in the + * 'noderef' parameter and caches the nodes against the area in + * the 'area' parameter. + */ + public void retrieveChildren() throws IOException + { + FacesContext context = FacesContext.getCurrentInstance(); + ResponseWriter out = context.getResponseWriter(); + + Map params = context.getExternalContext().getRequestParameterMap(); + String nodeRefStr = (String)params.get("nodeRef"); + String area = (String)params.get("area"); + + if (logger.isDebugEnabled()) + logger.debug("retrieveChildren: area = " + area + ", nodeRef = " + nodeRefStr); + + // work out which list to cache the nodes in + Map currentNodes = getNodesMapForArea(area); + + if (nodeRefStr != null && currentNodes != null) + { + // get the given node's details + NodeRef parentNodeRef = new NodeRef(nodeRefStr); + TreeNode parentNode = currentNodes.get(parentNodeRef.toString()); + parentNode.setExpanded(true); + + if (logger.isDebugEnabled()) + logger.debug("retrieving children for noderef: " + parentNodeRef); + + // remove any existing children as the latest ones will be added below + parentNode.removeChildren(); + + // get all the child folder objects for the parent + List childRefs = this.nodeService.getChildAssocs(parentNodeRef, + ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); + + StringBuilder xml = new StringBuilder(""); + for (ChildAssociationRef ref: childRefs) + { + NodeRef nodeRef = ref.getChildRef(); + + if (isAddableChild(nodeRef)) + { + // build the XML representation of the child node + TreeNode childNode = createTreeNode(nodeRef); + parentNode.addChild(childNode); + currentNodes.put(childNode.getNodeRef(), childNode); + xml.append(childNode.toXML()); + } + } + xml.append(""); + + // send the generated XML back to the tree + out.write(xml.toString()); + + if (logger.isDebugEnabled()) + logger.debug("returning XML: " + xml.toString()); + } + } + + /** + * Sets the state of the node given in the 'nodeRef' parameter to collapsed + */ + public void nodeCollapsed() throws IOException + { + FacesContext context = FacesContext.getCurrentInstance(); + ResponseWriter out = context.getResponseWriter(); + Map params = context.getExternalContext().getRequestParameterMap(); + String nodeRefStr = (String)params.get("nodeRef"); + String area = (String)params.get("area"); + + if (logger.isDebugEnabled()) + logger.debug("nodeCollapsed: area = " + area + ", nodeRef = " + nodeRefStr); + + // work out which list to cache the nodes in + Map currentNodes = getNodesMapForArea(area); + + if (nodeRefStr != null && currentNodes != null) + { + TreeNode treeNode = currentNodes.get(nodeRefStr); + if (treeNode != null) + { + treeNode.setExpanded(false); + + // we need to return something for the client to be happy! + out.write(""); + + if (logger.isDebugEnabled()) + logger.debug("Set node " + treeNode + " to collapsed state"); + } + } + } + + // ------------------------------------------------------------------------------ + // IContextListener implementation + + /** + * @see org.alfresco.web.app.context.IContextListener#contextUpdated() + */ + public void contextUpdated() + { + // nothing to do + } + + /** + * @see org.alfresco.web.app.context.IContextListener#areaChanged() + */ + public void areaChanged() + { + // nothing to do + + // NOTE: The code below is WIP for synchronizing the tree with + // the main navigation area of the application. + + /* + this.resetSelectedNode(); + */ + } + + /** + * @see org.alfresco.web.app.context.IContextListener#spaceChanged() + */ + public void spaceChanged() + { + // nothing to do + + // NOTE: The code below is WIP for synchronizing the tree with + // the main navigation area of the application. + + /* + NavigationBean navBean = getNavigationBean(); + if (navBean != null) + { + // get the current area and the new parent + String area = navBean.getToolbarLocation(); + Node parent = navBean.getDispatchContextNode(); + + if (parent != null) + { + if (logger.isDebugEnabled()) + logger.debug("Space changed, parent node is now: " + parent.getNodeRef().toString()); + + // select the new parent node + selectNode(parent.getNodeRef(), area); + + // get the nodes for the current area + BrowseBean browseBean = getBrowseBean(); + Map currentNodes = getNodesMapForArea(area); + + if (browseBean != null && currentNodes != null) + { + // find the parent node in the cache + TreeNode parentNode = currentNodes.get(parent.getNodeRef().toString()); + if (parentNode != null) + { + // reset the previously selected node + resetSelectedNode(); + + // set the parent to expanded and selected + parentNode.setExpanded(true); + selectNode(parent.getNodeRef(), area); + + for (Node child : browseBean.getNodes()) + { + NodeRef nodeRef = child.getNodeRef(); + + // check the child is applicable for the tree and is not already + // in the cache + if (isAddableChild(nodeRef) && + currentNodes.containsKey(nodeRef.toString()) == false) + { + // create the child tree node and add to the cache + TreeNode childNode = createTreeNode(nodeRef); + parentNode.addChild(childNode); + currentNodes.put(childNode.getNodeRef(), childNode); + } + } + } + } + } + } + */ + } + + // ------------------------------------------------------------------------------ + // Bean getters and setters + + /** + * Returns the root nodes for the company home panel. + *

+ * As the user expands and collapses nodes in the client this + * cache will be updated with the appropriate nodes and states. + *

+ * + * @return List of root nodes for the company home panel + */ + public List getCompanyHomeRootNodes() + { + if (this.companyHomeRootNodes == null) + { + this.companyHomeRootNodes = new ArrayList(); + this.companyHomeNodes = new HashMap(); + + // query for the child nodes of company home + NodeRef root = new NodeRef(Repository.getStoreRef(), + Application.getCompanyRootId()); + List childRefs = this.nodeService.getChildAssocs(root, + ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); + + for (ChildAssociationRef ref: childRefs) + { + NodeRef child = ref.getChildRef(); + + if (isAddableChild(child)) + { + TreeNode node = createTreeNode(child); + this.companyHomeRootNodes.add(node); + this.companyHomeNodes.put(node.getNodeRef(), node); + } + } + } + + return this.companyHomeRootNodes; + } + + /** + * Returns the root nodes for the my home panel. + *

+ * As the user expands and collapses nodes in the client this + * cache will be updated with the appropriate nodes and states. + *

+ * + * @return List of root nodes for the my home panel + */ + public List getMyHomeRootNodes() + { + if (this.myHomeRootNodes == null) + { + this.myHomeRootNodes = new ArrayList(); + this.myHomeNodes = new HashMap(); + + // query for the child nodes of the user's home + NodeRef root = new NodeRef(Repository.getStoreRef(), + Application.getCurrentUser(FacesContext.getCurrentInstance()).getHomeSpaceId()); + List childRefs = this.nodeService.getChildAssocs(root, + ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); + + for (ChildAssociationRef ref: childRefs) + { + NodeRef child = ref.getChildRef(); + + if (isAddableChild(child)) + { + TreeNode node = createTreeNode(child); + this.myHomeRootNodes.add(node); + this.myHomeNodes.put(node.getNodeRef(), node); + } + } + } + + return this.myHomeRootNodes; + } + + /** + * Returns the root nodes for the guest home panel. + *

+ * As the user expands and collapses nodes in the client this + * cache will be updated with the appropriate nodes and states. + *

+ * + * @return List of root nodes for the guest home panel + */ + public List getGuestHomeRootNodes() + { + if (this.guestHomeRootNodes == null) + { + this.guestHomeRootNodes = new ArrayList(); + this.guestHomeNodes = new HashMap(); + + // query for the child nodes of the guest home space + NavigationBean navBean = getNavigationBean(); + if (navBean != null) + { + NodeRef root = navBean.getGuestHomeNode().getNodeRef(); + List childRefs = this.nodeService.getChildAssocs(root, + ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); + + for (ChildAssociationRef ref: childRefs) + { + NodeRef child = ref.getChildRef(); + + if (isAddableChild(child)) + { + TreeNode node = createTreeNode(child); + this.guestHomeRootNodes.add(node); + this.guestHomeNodes.put(node.getNodeRef(), node); + } + } + } + } + + return this.guestHomeRootNodes; + } + + /** + * @param nodeService The NodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param dictionaryService The DictionaryService to set. + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + // ------------------------------------------------------------------------------ + // Helper methods + + /** + * Sets the currently selected node in the tree + * + * @param selectedNode The node that has been selected + */ + public void selectNode(NodeRef selectedNode, String area) + { + // if there is a currently selected node, get hold of + // it (from any of the areas) and reset to unselected + if (this.previouslySelectedNode != null) + { + if (NavigationBean.LOCATION_COMPANY.equals(area) && + this.companyHomeNodes != null) + { + TreeNode node = this.companyHomeNodes.get(this.previouslySelectedNode.toString()); + if (node != null) + { + node.setSelected(false); + } + } + else if (NavigationBean.LOCATION_HOME.equals(area) && + this.myHomeNodes != null) + { + TreeNode node = this.myHomeNodes.get(this.previouslySelectedNode.toString()); + if (node != null) + { + node.setSelected(false); + } + } + else if (NavigationBean.LOCATION_GUEST.equals(area) && + this.guestHomeNodes != null) + { + TreeNode node = this.guestHomeNodes.get(this.previouslySelectedNode.toString()); + if (node != null) + { + node.setSelected(false); + } + } + } + + // find the node just selected and set its state to selected + if (selectedNode != null) + { + if (NavigationBean.LOCATION_COMPANY.equals(area) && + this.companyHomeNodes != null) + { + TreeNode node = this.companyHomeNodes.get(selectedNode.toString()); + if (node != null) + { + node.setSelected(true); + } + } + else if (NavigationBean.LOCATION_HOME.equals(area) && + this.myHomeNodes != null) + { + TreeNode node = this.myHomeNodes.get(selectedNode.toString()); + if (node != null) + { + node.setSelected(true); + } + } + else if (NavigationBean.LOCATION_GUEST.equals(area) && + this.guestHomeNodes != null) + { + TreeNode node = this.guestHomeNodes.get(selectedNode.toString()); + if (node != null) + { + node.setSelected(true); + } + } + } + + this.previouslySelectedNode = selectedNode; + + if (logger.isDebugEnabled()) + logger.debug("Selected node: " + selectedNode); + } + + /** + * Resets the selected node + */ + public void resetSelectedNode() + { + if (this.previouslySelectedNode != null) + { + if (this.companyHomeNodes != null) + { + TreeNode node = this.companyHomeNodes.get(this.previouslySelectedNode.toString()); + if (node != null) + { + node.setSelected(false); + } + } + if (this.myHomeNodes != null) + { + TreeNode node = this.myHomeNodes.get(this.previouslySelectedNode.toString()); + if (node != null) + { + node.setSelected(false); + } + } + if (this.guestHomeNodes != null) + { + TreeNode node = this.guestHomeNodes.get(this.previouslySelectedNode.toString()); + if (node != null) + { + node.setSelected(false); + } + } + + if (logger.isDebugEnabled()) + logger.debug("Reset selected node: " + this.previouslySelectedNode); + } + } + + /** + * Resets all the caches held by the bean. + */ + public void reset() + { + this.companyHomeNodes = null; + this.companyHomeRootNodes = null; + this.myHomeNodes = null; + this.myHomeRootNodes = null; + this.guestHomeNodes = null; + this.guestHomeRootNodes = null; + + resetSelectedNode(); + } + + /** + * Determines whether the given NodeRef can be added to the tree as + * a child for example, if it's a folder. + * + * @param nodeRef The NodeRef to check + * @return true if the node should be added to the tree + */ + protected boolean isAddableChild(NodeRef nodeRef) + { + boolean addable = false; + + if (this.nodeService.exists(nodeRef)) + { + // find it's type so we can see if it's a node we are interested in + QName type = this.nodeService.getType(nodeRef); + + // make sure the type is defined in the data dictionary + TypeDefinition typeDef = this.dictionaryService.getType(type); + + if (typeDef != null) + { + // look for folder node types + if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_FOLDER) == true && + this.dictionaryService.isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER) == false) + { + addable = true; + } + } + } + + return addable; + } + + /** + * Creates a TreeNode object from the given NodeRef + * + * @param nodeRef The NodeRef to create the TreeNode from + */ + protected TreeNode createTreeNode(NodeRef nodeRef) + { + TreeNode node = new TreeNode(nodeRef.toString(), + Repository.getNameForNode(this.nodeService, nodeRef), + (String)this.nodeService.getProperty(nodeRef, ApplicationModel.PROP_ICON)); + + return node; + } + + /** + * Retrieves the instance of the NavigationBean being used by the application + * + * @return NavigationBean instance + */ + protected NavigationBean getNavigationBean() + { + return (NavigationBean)FacesHelper.getManagedBean( + FacesContext.getCurrentInstance(), "NavigationBean"); + } + + /** + * Retrieves the instance of the BrowseBean being used by the application + * + * @return BrowseBean instance + */ + protected BrowseBean getBrowseBean() + { + return (BrowseBean)FacesHelper.getManagedBean( + FacesContext.getCurrentInstance(), "BrowseBean"); + } + + /** + * Returns the map of tree nodes for the given area + * + * @param area The area to retrieve the map for + * @return The map of nodes + */ + protected Map getNodesMapForArea(String area) + { + Map nodes = null; + + if (NavigationBean.LOCATION_COMPANY.equals(area)) + { + nodes = this.companyHomeNodes; + } + else if (NavigationBean.LOCATION_HOME.equals(area)) + { + nodes = this.myHomeNodes; + } + else if (NavigationBean.LOCATION_GUEST.equals(area)) + { + nodes = this.guestHomeNodes; + } + + return nodes; + } +} diff --git a/source/java/org/alfresco/web/bean/forums/ForumsBean.java b/source/java/org/alfresco/web/bean/forums/ForumsBean.java index 81d016fd86..b0a7220490 100644 --- a/source/java/org/alfresco/web/bean/forums/ForumsBean.java +++ b/source/java/org/alfresco/web/bean/forums/ForumsBean.java @@ -649,6 +649,21 @@ public class ForumsBean implements IContextListener this.posts = null; } + /** + * @see org.alfresco.web.app.context.IContextListener#areaChanged() + */ + public void areaChanged() + { + // nothing to do + } + + /** + * @see org.alfresco.web.app.context.IContextListener#spaceChanged() + */ + public void spaceChanged() + { + // nothing to do + } // ------------------------------------------------------------------------------ // Navigation action event handlers diff --git a/source/java/org/alfresco/web/bean/rules/RulesBean.java b/source/java/org/alfresco/web/bean/rules/RulesBean.java index 2a45f58c61..61bf880e7f 100644 --- a/source/java/org/alfresco/web/bean/rules/RulesBean.java +++ b/source/java/org/alfresco/web/bean/rules/RulesBean.java @@ -388,6 +388,21 @@ public class RulesBean implements IContextListener } } + /** + * @see org.alfresco.web.app.context.IContextListener#areaChanged() + */ + public void areaChanged() + { + // nothing to do + } + + /** + * @see org.alfresco.web.app.context.IContextListener#spaceChanged() + */ + public void spaceChanged() + { + // nothing to do + } /** * Inner class to wrap the Rule objects so we can expose a flag to indicate whether diff --git a/source/java/org/alfresco/web/bean/users/EmailSpaceUsersDialog.java b/source/java/org/alfresco/web/bean/users/EmailSpaceUsersDialog.java index 1bf167d1ae..6ce362fa78 100644 --- a/source/java/org/alfresco/web/bean/users/EmailSpaceUsersDialog.java +++ b/source/java/org/alfresco/web/bean/users/EmailSpaceUsersDialog.java @@ -190,10 +190,25 @@ public class EmailSpaceUsersDialog extends BaseDialogBean implements IContextLis this.userGroupLookup = new HashMap(); } + /** + * @see org.alfresco.web.app.context.IContextListener#areaChanged() + */ + public void areaChanged() + { + // nothing to do + } + + /** + * @see org.alfresco.web.app.context.IContextListener#spaceChanged() + */ + public void spaceChanged() + { + // nothing to do + } // ------------------------------------------------------------------------------ // Bean Getters and Setters - + /** * @param permissionService The PermissionService to set */ diff --git a/source/java/org/alfresco/web/bean/users/UserMembersBean.java b/source/java/org/alfresco/web/bean/users/UserMembersBean.java index 039b8d4d74..f6e2cfe5bd 100644 --- a/source/java/org/alfresco/web/bean/users/UserMembersBean.java +++ b/source/java/org/alfresco/web/bean/users/UserMembersBean.java @@ -424,6 +424,21 @@ public abstract class UserMembersBean implements IContextListener } } + /** + * @see org.alfresco.web.app.context.IContextListener#areaChanged() + */ + public void areaChanged() + { + // nothing to do + } + + /** + * @see org.alfresco.web.app.context.IContextListener#spaceChanged() + */ + public void spaceChanged() + { + // nothing to do + } // ------------------------------------------------------------------------------ // Action event handlers diff --git a/source/java/org/alfresco/web/bean/users/UsersBean.java b/source/java/org/alfresco/web/bean/users/UsersBean.java index 5653d090f0..40134f4ddb 100644 --- a/source/java/org/alfresco/web/bean/users/UsersBean.java +++ b/source/java/org/alfresco/web/bean/users/UsersBean.java @@ -544,4 +544,20 @@ public class UsersBean implements IContextListener this.users = null; } } + + /** + * @see org.alfresco.web.app.context.IContextListener#areaChanged() + */ + public void areaChanged() + { + // nothing to do + } + + /** + * @see org.alfresco.web.app.context.IContextListener#spaceChanged() + */ + public void spaceChanged() + { + // nothing to do + } } diff --git a/source/java/org/alfresco/web/bean/wcm/AVMBrowseBean.java b/source/java/org/alfresco/web/bean/wcm/AVMBrowseBean.java index ad15b9311d..46ac770d09 100644 --- a/source/java/org/alfresco/web/bean/wcm/AVMBrowseBean.java +++ b/source/java/org/alfresco/web/bean/wcm/AVMBrowseBean.java @@ -1184,6 +1184,21 @@ public class AVMBrowseBean implements IContextListener this.folders = null; } + /** + * @see org.alfresco.web.app.context.IContextListener#areaChanged() + */ + public void areaChanged() + { + // nothing to do + } + + /** + * @see org.alfresco.web.app.context.IContextListener#spaceChanged() + */ + public void spaceChanged() + { + // nothing to do + } // ------------------------------------------------------------------------------ // Inner classes diff --git a/source/java/org/alfresco/web/config/SidebarConfigElement.java b/source/java/org/alfresco/web/config/SidebarConfigElement.java new file mode 100644 index 0000000000..fc031bff69 --- /dev/null +++ b/source/java/org/alfresco/web/config/SidebarConfigElement.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigException; +import org.alfresco.config.element.ConfigElementAdapter; +import org.alfresco.util.ParameterCheck; + +/** + * Custom config element that represents the config data for the sidebar + * + * @author gavinc + */ +public class SidebarConfigElement extends ConfigElementAdapter +{ + public static final String CONFIG_ELEMENT_ID = "sidebar"; + + private String defaultPlugin; + private Map plugins = new LinkedHashMap(8, 10f); + + /** + * Default constructor + */ + public SidebarConfigElement() + { + super(CONFIG_ELEMENT_ID); + } + + /** + * Constructor + * + * @param name Name of the element this config element represents + */ + public SidebarConfigElement(String name) + { + super(name); + } + + /** + * @see org.alfresco.config.ConfigElement#getChildren() + */ + public List getChildren() + { + throw new ConfigException("Reading the sidebar config via the generic interfaces is not supported"); + } + + /** + * @see org.alfresco.config.ConfigElement#combine(org.alfresco.config.ConfigElement) + */ + public ConfigElement combine(ConfigElement configElement) + { + SidebarConfigElement newElement = (SidebarConfigElement)configElement; + SidebarConfigElement combinedElement = new SidebarConfigElement(); + + // add all the plugins from this element + for (SidebarPluginConfig plugin : this.getPlugins().values()) + { + combinedElement.addPlugin(plugin); + } + + // add all the plugins from the given element + for (SidebarPluginConfig plugin : newElement.getPlugins().values()) + { + combinedElement.addPlugin(plugin); + } + + // work out the default plugin + String newDefaultPlugin = newElement.getDefaultPlugin(); + if (newDefaultPlugin != null) + { + combinedElement.setDefaultPlugin(newDefaultPlugin); + } + else + { + combinedElement.setDefaultPlugin(this.getDefaultPlugin()); + } + + return combinedElement; + } + + /** + * Returns the named plugin + * + * @param id The id of the plugin to retrieve + * @return The SidebarPluginConfig object for the requested plugin or null if it doesn't exist + */ + public SidebarPluginConfig getPlugin(String id) + { + return this.plugins.get(id); + } + + /** + * @return Returns a map of the plugins. A linked hash map is used internally to + * preserve ordering. + */ + public Map getPlugins() + { + return this.plugins; + } + + /** + * @return The id of the default plugin, null if there isn't a default defined + */ + public String getDefaultPlugin() + { + return this.defaultPlugin; + } + + /** + * Sets the plugin to use as the default + * + * @param defaultPlugin Id of the default plugin + */ + public void setDefaultPlugin(String defaultPlugin) + { + this.defaultPlugin = defaultPlugin; + } + + /** + * Adds a plugin + * + * @param pluginConfig A pre-configured plugin config object + */ + /*package*/ void addPlugin(SidebarPluginConfig pluginConfig) + { + this.plugins.put(pluginConfig.getId(), pluginConfig); + } + + /** + * Inner class representing the configuration of a sidebar plugin + * + * @author gavinc + */ + public static class SidebarPluginConfig + { + protected String id; + protected String page; + protected String actionsConfigId; + protected String icon; + protected String label; + protected String labelId; + protected String description; + protected String descriptionId; + + public SidebarPluginConfig(String id, String page, + String label, String labelId, + String description, String descriptionId, + String actionsConfigId, String icon) + { + // check the mandatory parameters are present + ParameterCheck.mandatoryString("id", id); + ParameterCheck.mandatoryString("page", page); + + this.id = id; + this.page = page; + this.icon = icon; + this.label = label; + this.labelId = labelId; + this.description = description; + this.descriptionId = descriptionId; + this.actionsConfigId = actionsConfigId; + } + + public String getId() + { + return this.id; + } + + public String getPage() + { + return this.page; + } + + public String getIcon() + { + return this.icon; + } + + public String getlabel() + { + return this.label; + } + + public String getlabelId() + { + return this.labelId; + } + + public String getDescription() + { + return this.description; + } + + public String getDescriptionId() + { + return this.descriptionId; + } + + public String getActionsConfigId() + { + return this.actionsConfigId; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + StringBuilder buffer = new StringBuilder(super.toString()); + buffer.append(" (id=").append(this.id); + buffer.append(" page=").append(this.page); + buffer.append(" icon=").append(this.icon); + buffer.append(" label=").append(this.label); + buffer.append(" labelId=").append(this.labelId); + buffer.append(" description=").append(this.description); + buffer.append(" descriptionId=").append(this.descriptionId); + buffer.append(" actions-config-id=").append(this.actionsConfigId).append(")"); + return buffer.toString(); + } + } +} diff --git a/source/java/org/alfresco/web/config/SidebarElementReader.java b/source/java/org/alfresco/web/config/SidebarElementReader.java new file mode 100644 index 0000000000..834f791b55 --- /dev/null +++ b/source/java/org/alfresco/web/config/SidebarElementReader.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import java.util.Iterator; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigException; +import org.alfresco.config.xml.elementreader.ConfigElementReader; +import org.dom4j.Element; + +/** + * Custom element reader to parse config for the sidebar + * + * @author gavinc + */ +public class SidebarElementReader implements ConfigElementReader +{ + public static final String ELEMENT_SIDEBAR = "sidebar"; + public static final String ELEMENT_PLUGINS = "plugins"; + public static final String ELEMENT_PLUGIN = "plugin"; + public static final String ELEMENT_DEFAULT_PLUGIN = "default-plugin"; + public static final String ATTR_ID = "id"; + public static final String ATTR_LABEL = "label"; + public static final String ATTR_LABEL_ID = "label-id"; + public static final String ATTR_DESCRIPTION = "description"; + public static final String ATTR_DESCRIPTION_ID = "description-id"; + public static final String ATTR_PAGE = "page"; + public static final String ATTR_ICON = "icon"; + public static final String ATTR_ACTIONS_CONFIG_ID = "actions-config-id"; + + /** + * @see org.alfresco.config.xml.elementreader.ConfigElementReader#parse(org.dom4j.Element) + */ + @SuppressWarnings("unchecked") + public ConfigElement parse(Element element) + { + SidebarConfigElement configElement = null; + + if (element != null) + { + String elementName = element.getName(); + if (elementName.equals(ELEMENT_SIDEBAR) == false) + { + throw new ConfigException("SidebarElementReader can only parse " + + ELEMENT_SIDEBAR + "elements, the element passed was '" + + elementName + "'"); + } + + configElement = new SidebarConfigElement(); + + // go through the plugins that make up the sidebar + Element pluginsElem = element.element(ELEMENT_PLUGINS); + if (pluginsElem != null) + { + Iterator plugins = pluginsElem.elementIterator(ELEMENT_PLUGIN); + while (plugins.hasNext()) + { + Element plugin = plugins.next(); + + String id = plugin.attributeValue(ATTR_ID); + String page = plugin.attributeValue(ATTR_PAGE); + String label = plugin.attributeValue(ATTR_LABEL); + String labelId = plugin.attributeValue(ATTR_LABEL_ID); + String description = plugin.attributeValue(ATTR_DESCRIPTION); + String descriptionId = plugin.attributeValue(ATTR_DESCRIPTION_ID); + String actionsConfigId = plugin.attributeValue(ATTR_ACTIONS_CONFIG_ID); + String icon = plugin.attributeValue(ATTR_ICON); + + SidebarConfigElement.SidebarPluginConfig cfg = + new SidebarConfigElement.SidebarPluginConfig(id, page, + label, labelId, description, descriptionId, + actionsConfigId, icon); + + configElement.addPlugin(cfg); + } + } + + // see if a default plugin is specified + Element defaultPlugin = element.element(ELEMENT_DEFAULT_PLUGIN); + if (defaultPlugin != null) + { + configElement.setDefaultPlugin(defaultPlugin.getTextTrim()); + } + } + + return configElement; + } +} diff --git a/source/java/org/alfresco/web/ui/common/Utils.java b/source/java/org/alfresco/web/ui/common/Utils.java index 5d2f6f5b4d..adbbf039fe 100644 --- a/source/java/org/alfresco/web/ui/common/Utils.java +++ b/source/java/org/alfresco/web/ui/common/Utils.java @@ -90,6 +90,7 @@ public final class Utils private static final String DEFAULT_FILE_IMAGE32 = IMAGE_PREFIX32 + "_default" + IMAGE_POSTFIX; private static final String AJAX_SCRIPTS_WRITTEN = "_alfAjaxScriptsWritten"; + private static final String YAHOO_SCRIPTS_WRITTEN = "_alfYahooScriptsWritten"; private static final Map s_fileExtensionMap = new HashMap(89, 1.0f); @@ -412,7 +413,7 @@ public final class Utils */ public static String generateFormSubmit(FacesContext context, UIComponent component, String fieldId, String fieldValue) { - return generateFormSubmit(context, component, fieldId, fieldValue, null); + return generateFormSubmit(context, component, fieldId, fieldValue, false, null); } /** @@ -429,7 +430,31 @@ public final class Utils * * @return JavaScript event code */ - public static String generateFormSubmit(FacesContext context, UIComponent component, String fieldId, String fieldValue, Map params) + public static String generateFormSubmit(FacesContext context, UIComponent component, String fieldId, + String fieldValue, Map params) + { + return generateFormSubmit(context, component, fieldId, fieldValue, false, params); + } + + /** + * Generate the JavaScript to submit set the specified hidden Form field to the + * supplied value and submit the parent Form. + * + * NOTE: the supplied hidden field name is added to the Form Renderer map for output. + * + * @param context FacesContext + * @param component UIComponent to generate JavaScript for + * @param fieldId Hidden field id to set value for + * @param fieldValue Hidden field value to set hidden field too on submit + * @param valueIsParam Determines whether the fieldValue parameter should be treated + * as a parameter in the generated JavaScript, false will treat + * the value i.e. surround it with single quotes + * @param params Optional map of param name/values to output + * + * @return JavaScript event code + */ + public static String generateFormSubmit(FacesContext context, UIComponent component, String fieldId, + String fieldValue, boolean valueIsParam, Map params) { UIForm form = Utils.getParentForm(context, component); if (form == null) @@ -446,9 +471,17 @@ public final class Utils buf.append("'"); buf.append("]['"); buf.append(fieldId); - buf.append("'].value='"); + buf.append("'].value="); + if (valueIsParam == false) + { + buf.append("'"); + } buf.append(fieldValue); - buf.append("';"); + if (valueIsParam == false) + { + buf.append("'"); + } + buf.append(";"); if (params != null) { @@ -474,9 +507,12 @@ public final class Utils buf.append("'"); buf.append(formClientId); buf.append("'"); - buf.append("].submit()"); + buf.append("].submit();"); - buf.append(";return false;"); + if (valueIsParam == false) + { + buf.append("return false;"); + } // weak, but this seems to be the way Sun RI do it... //FormRenderer.addNeededHiddenField(context, fieldId); @@ -1244,14 +1280,14 @@ public final class Utils } /** - * Writes the script tags for including AJAX support, ensuring they + * Writes the script tags for including dojo support, ensuring they * only get written once per page render. * * @param context Faces context * @param out The response writer */ @SuppressWarnings("unchecked") - public static void writeAjaxScripts(FacesContext context, ResponseWriter out) + public static void writeDojoScripts(FacesContext context, ResponseWriter out) throws IOException { Object present = context.getExternalContext().getRequestMap().get(AJAX_SCRIPTS_WRITTEN); @@ -1259,6 +1295,10 @@ public final class Utils if (present == null) { // write out the scripts +// out.write("\n"); + out.write("\n\n"); @@ -1275,4 +1315,57 @@ public final class Utils context.getExternalContext().getRequestMap().put(AJAX_SCRIPTS_WRITTEN, Boolean.TRUE); } } + + /** + * Writes the scripts tags for using the Yahoo UI toolkit, ensuring they + * only get written once per page render. + *

+ * A comma separated list of scripts can also be passed to determine + * which components are to be used, again these are only written once per page. + * + * @param context Faces context + * @param out The response writer + * @param scripts Comma separated list of scripts to include, if null the + * base yahoo.js script only is included. + */ + @SuppressWarnings("unchecked") + public static void writeYahooScripts(FacesContext context, ResponseWriter out, + String scripts) throws IOException + { + Object present = context.getExternalContext().getRequestMap().get(YAHOO_SCRIPTS_WRITTEN); + + if (present == null) + { + // TODO: use the scripts parameter to determine which scripts to output + // also add an ajax debug flag to the config and output relevant file + + // base yahoo file + out.write("\n\n"); + + // io handling (AJAX) + out.write("\n\n"); + + // event handling + out.write("\n\n"); + + // common alfresco util methods + out.write("\n"); + + // write out a global variable to hold the webapp context path + out.write("\n"); + + // add marker to request + context.getExternalContext().getRequestMap().put(YAHOO_SCRIPTS_WRITTEN, Boolean.TRUE); + } + } } diff --git a/source/java/org/alfresco/web/ui/common/renderer/ModeListRenderer.java b/source/java/org/alfresco/web/ui/common/renderer/ModeListRenderer.java index 54cfaaa4d9..55678e7a3c 100644 --- a/source/java/org/alfresco/web/ui/common/renderer/ModeListRenderer.java +++ b/source/java/org/alfresco/web/ui/common/renderer/ModeListRenderer.java @@ -156,7 +156,28 @@ public class ModeListRenderer extends BaseRenderer for (Iterator i=list.getChildren().iterator(); i.hasNext(); /**/) { UIComponent child = (UIComponent)i.next(); - if (child instanceof UIListItem && child.isRendered() == true) + if (child instanceof UIListItems) + { + // get the value of the list items component and iterate + // through it's collection + Object listItems = ((UIListItems)child).getValue(); + if (listItems instanceof Collection) + { + Iterator iter = ((Collection)listItems).iterator(); + while (iter.hasNext()) + { + UIListItem item = (UIListItem)iter.next(); + + // if selected render as the label + if (item.getValue().equals(list.getValue()) == true) + { + label = item.getLabel(); + break; + } + } + } + } + else if (child instanceof UIListItem && child.isRendered() == true) { // found a valid UIListItem child to render UIListItem item = (UIListItem)child; @@ -179,7 +200,7 @@ public class ModeListRenderer extends BaseRenderer outputAttribute(out, attrs.get("labelStyleClass"), "class"); out.write('>'); out.write(Utils.encode(label)); - out.write(""); + out.write(" "); } // output image diff --git a/source/java/org/alfresco/web/ui/repo/component/UINavigator.java b/source/java/org/alfresco/web/ui/repo/component/UINavigator.java new file mode 100644 index 0000000000..afbb920ef1 --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/component/UINavigator.java @@ -0,0 +1,387 @@ +package org.alfresco.web.ui.repo.component; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.el.ValueBinding; +import javax.faces.event.AbortProcessingException; +import javax.faces.event.ActionEvent; +import javax.faces.event.FacesEvent; + +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.servlet.FacesHelper; +import org.alfresco.web.bean.BrowseBean; +import org.alfresco.web.bean.NavigationBean; +import org.alfresco.web.bean.ajax.NavigatorPluginBean; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.SelfRenderingComponent; +import org.alfresco.web.ui.repo.component.UITree.TreeNode; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Navigator component that consists of 4 panels representing + * the main areas of the repository i.e. company home, my home, + * guest home and my alfresco. + *

+ * Each panel (apart from my alfresco) uses the tree component + * to allow navigation around that area of the repository. + *

+ * + * @author gavinc + */ +public class UINavigator extends SelfRenderingComponent +{ + public static final String COMPONENT_TYPE = "org.alfresco.faces.Navigator"; + + protected String activeArea; + + private static final Log logger = LogFactory.getLog(UINavigator.class); + private static final String NAVIGATION_BEAN = "NavigationBean"; + private static final String BROWSE_BEAN = "BrowseBean"; + private static final String AJAX_URL_START = "/ajax/invoke/" + NavigatorPluginBean.BEAN_NAME; + private static final String PANEL_ACTION = "panel:"; + private static final int PANEL_SELECTED = 1; + private static final int NODE_SELECTED = 2; + + // ------------------------------------------------------------------------------ + // Component Impl + + @Override + public String getFamily() + { + return COMPONENT_TYPE; + } + + @Override + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.activeArea = (String)values[1]; + } + + @Override + public Object saveState(FacesContext context) + { + Object values[] = new Object[2]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.activeArea; + return values; + } + + /** + * @see javax.faces.component.UIComponentBase#decode(javax.faces.context.FacesContext) + */ + public void decode(FacesContext context) + { + Map requestMap = context.getExternalContext().getRequestParameterMap(); + String fieldId = getClientId(context); + String value = (String)requestMap.get(fieldId); + + if (value != null && value.length() != 0) + { + if (logger.isDebugEnabled()) + logger.debug("Received post back: " + value); + + // work out whether a panel or a node was selected + int mode = NODE_SELECTED; + String item = value; + if (value.startsWith(PANEL_ACTION)) + { + mode = PANEL_SELECTED; + item = value.substring(PANEL_ACTION.length()); + } + + // queue an event to be handled later + NavigatorEvent event = new NavigatorEvent(this, mode, item); + this.queueEvent(event); + } + } + + /** + * @see javax.faces.component.UIInput#broadcast(javax.faces.event.FacesEvent) + */ + public void broadcast(FacesEvent event) throws AbortProcessingException + { + if (event instanceof NavigatorEvent) + { + FacesContext context = FacesContext.getCurrentInstance(); + NavigatorEvent navEvent = (NavigatorEvent)event; + + // node or panel selected? + switch (navEvent.getMode()) + { + case PANEL_SELECTED: + { + String panelSelected = navEvent.getItem(); + + // a panel was selected, setup the context to make the panel + // the focus + NavigationBean nb = (NavigationBean)FacesHelper.getManagedBean( + context, NAVIGATION_BEAN); + if (nb != null) + { + try + { + if (logger.isDebugEnabled()) + logger.debug("Selecting panel: " + panelSelected); + + nb.processToolbarLocation(panelSelected, true); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NOHOME), + Application.getCurrentUser(context).getHomeSpaceId()), refErr ); + } + catch (Exception err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), + err.getMessage()), err); + } + } + + break; + } + case NODE_SELECTED: + { + // a node was clicked in the tree + NodeRef nodeClicked = new NodeRef(navEvent.getItem()); + + // setup the context to make the node the current node + BrowseBean bb = (BrowseBean)FacesHelper.getManagedBean( + context, BROWSE_BEAN); + if (bb != null) + { + if (logger.isDebugEnabled()) + logger.debug("Selected node: " + nodeClicked); + + bb.clickSpace(nodeClicked); + } + + break; + } + } + } + else + { + super.broadcast(event); + } + } + + @Override + @SuppressWarnings("unchecked") + public void encodeBegin(FacesContext context) throws IOException + { + if (!isRendered()) return; + + // TODO: pull width and height from user preferences and/or the main config, + // if present override below using the style attribute + + ResponseWriter out = context.getResponseWriter(); + NavigationBean navBean = (NavigationBean)FacesHelper.getManagedBean( + context, NAVIGATION_BEAN); + NavigatorPluginBean navPluginBean = (NavigatorPluginBean)FacesHelper.getManagedBean( + context, NavigatorPluginBean.BEAN_NAME); + + List rootNodesForArea = null; + String area = this.getActiveArea(); + String areaTitle = null; + boolean treePanel = true; + if (NavigationBean.LOCATION_COMPANY.equals(area)) + { + rootNodesForArea = navPluginBean.getCompanyHomeRootNodes(); + areaTitle = Application.getMessage(context, "company_home"); + } + else if (NavigationBean.LOCATION_HOME.equals(area)) + { + rootNodesForArea = navPluginBean.getMyHomeRootNodes(); + areaTitle = Application.getMessage(context, "my_home"); + } + else if (NavigationBean.LOCATION_GUEST.equals(area)) + { + rootNodesForArea = navPluginBean.getGuestHomeRootNodes(); + areaTitle = Application.getMessage(context, "guest_home"); + } + else + { + treePanel = false; + areaTitle = Application.getMessage(context, "my_alfresco"); + } + + // generate the active panel title + out.write("
"); + out.write("
"); + out.write(areaTitle); + out.write("
"); + + // generate the javascript method to capture the tree node click events + if (treePanel) + { + out.write("\n\n"); + + // generate the active panel containing the tree + out.write("
"); + UITree tree = (UITree)context.getApplication().createComponent( + UITree.COMPONENT_TYPE); + tree.setId("tree"); + tree.setRootNodes(rootNodesForArea); + tree.setRetrieveChildrenUrl(AJAX_URL_START + ".retrieveChildren?area=" + area); + tree.setNodeCollapsedUrl(AJAX_URL_START + ".nodeCollapsed?area=" + area); + tree.setNodeSelectedCallback("treeNodeSelected"); + tree.setNodeCollapsedCallback("informOfCollapse"); + Utils.encodeRecursive(context, tree); + out.write("
"); + } + + // generate the closed panel title areas + if (NavigationBean.LOCATION_COMPANY.equals(area) == false && + navBean.getCompanyHomeVisible()) + { + out.write(""); + } + + if (NavigationBean.LOCATION_HOME.equals(area) == false) + { + out.write(""); + } + + if (NavigationBean.LOCATION_GUEST.equals(area) == false && + navBean.getIsGuest() == false && navBean.getGuestHomeVisible()) + { + out.write(""); + } + + if (NavigationBean.LOCATION_MYALFRESCO.equals(area) == false) + { + out.write(""); + } + + out.write("
"); + } + + @Override + public void encodeChildren(FacesContext context) throws IOException + { + if (!isRendered()) return; + + for (Iterator i=this.getChildren().iterator(); i.hasNext(); /**/) + { + UIComponent child = (UIComponent)i.next(); + Utils.encodeRecursive(context, child); + } + } + + @Override + public boolean getRendersChildren() + { + return true; + } + + // ------------------------------------------------------------------------------ + // Strongly typed component property accessors + + /** + * Returns the active area the navigator component is showing + * + * @return The active area + */ + public String getActiveArea() + { + ValueBinding vb = getValueBinding("activeArea"); + if (vb != null) + { + this.activeArea = (String)vb.getValue(getFacesContext()); + } + + if (this.activeArea == null) + { + this.activeArea = NavigationBean.LOCATION_HOME; + } + + return this.activeArea; + } + + /** + * Sets the active area for the navigator panel + * + * @param activeArea + */ + public void setActiveArea(String activeArea) + { + this.activeArea = activeArea; + } + + // ------------------------------------------------------------------------------ + // Helper methods + + /** + * Class representing the clicking of a tree node. + */ + @SuppressWarnings("serial") + public static class NavigatorEvent extends ActionEvent + { + private int mode; + private String item; + + public NavigatorEvent(UIComponent component, int mode, String item) + { + super(component); + + this.mode = mode; + this.item = item; + } + + public String getItem() + { + return item; + } + + public int getMode() + { + return mode; + } + } +} diff --git a/source/java/org/alfresco/web/ui/repo/component/UINodeInfo.java b/source/java/org/alfresco/web/ui/repo/component/UINodeInfo.java index e894e5bf19..7e516a071f 100644 --- a/source/java/org/alfresco/web/ui/repo/component/UINodeInfo.java +++ b/source/java/org/alfresco/web/ui/repo/component/UINodeInfo.java @@ -65,7 +65,7 @@ public class UINodeInfo extends SelfRenderingComponent // output the scripts required by the component (checks are // made to make sure the scripts are only written once) - Utils.writeAjaxScripts(context, out); + Utils.writeDojoScripts(context, out); // write out the JavaScript specific to the NodeInfo component, // again, make sure it's only done once diff --git a/source/java/org/alfresco/web/ui/repo/component/UISidebar.java b/source/java/org/alfresco/web/ui/repo/component/UISidebar.java new file mode 100644 index 0000000000..705f7b8060 --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/component/UISidebar.java @@ -0,0 +1,223 @@ +package org.alfresco.web.ui.repo.component; + +import java.io.IOException; + +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.el.MethodBinding; +import javax.faces.el.ValueBinding; +import javax.faces.event.ActionEvent; + +import org.alfresco.web.bean.SidebarBean; +import org.alfresco.web.config.SidebarConfigElement; +import org.alfresco.web.config.SidebarConfigElement.SidebarPluginConfig; +import org.alfresco.web.ui.common.PanelGenerator; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.SelfRenderingComponent; +import org.alfresco.web.ui.common.component.UIListItems; +import org.alfresco.web.ui.common.component.UIModeList; + +/** + * Component that represents the sidebar. + *

+ * A sidebar consists of multiple plugins, of which only + * one is active at one time. All registered plugins are + * displayed in a drop down allowing the user to + * change the active plugin. An action group can also be + * associated with a plugin, which get rendered in the + * sidebar header. + *

+ * + * @author gavinc + */ +public class UISidebar extends SelfRenderingComponent +{ + public static final String COMPONENT_TYPE = "org.alfresco.faces.Sidebar"; + + protected String activePlugin; + + @Override + public String getFamily() + { + return COMPONENT_TYPE; + } + + @Override + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.activePlugin = (String)values[1]; + } + + @Override + public Object saveState(FacesContext context) + { + Object values[] = new Object[8]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.activePlugin; + return values; + } + + @SuppressWarnings("unchecked") + @Override + public void encodeBegin(FacesContext context) throws IOException + { + if (!isRendered()) return; + + ResponseWriter out = context.getResponseWriter(); + + out.write("
"); + + // render the start of the header panel + PanelGenerator.generatePanelStart(out, + context.getExternalContext().getRequestContextPath(), + "blue", "#D3E6FE"); + + // generate the required child components if not present + if (this.getChildCount() == 1) + { + // create the mode list component + UIModeList modeList = (UIModeList)context.getApplication(). + createComponent("org.alfresco.faces.ModeList"); + modeList.setId("sidebarPluginList"); + modeList.setValue(this.getActivePlugin()); + modeList.setIconColumnWidth(2); + modeList.setMenu(true); + modeList.setMenuImage("/images/icons/menu.gif"); + modeList.getAttributes().put("itemSpacing", 4); + modeList.getAttributes().put("styleClass", "moreActionsMenu"); + modeList.getAttributes().put("selectedStyleClass", "statusListHighlight"); + MethodBinding listener = context.getApplication().createMethodBinding( + "#{SidebarBean.pluginChanged}", new Class[] {ActionEvent.class}); + modeList.setActionListener(listener); + + // create the child list items component + UIListItems items = (UIListItems)context.getApplication(). + createComponent("org.alfresco.faces.ListItems"); + ValueBinding binding = context.getApplication().createValueBinding( + "#{SidebarBean.plugins}"); + items.setValueBinding("value", binding); + + // add the list items to the mode list component + modeList.getChildren().add(items); + + // create the actions component + UIActions actions = (UIActions)context.getApplication(). + createComponent("org.alfresco.faces.Actions"); + actions.setId("sidebarActions"); + actions.setShowLink(false); + // TODO: we need to setup the context for the actions component + // but when the app first starts there is no context yet, + // also the tree will not update the context as it is + // navigated so what do we use? we may have to only support + // global non-context actions + String actionsGroupId = null; + SidebarConfigElement config = SidebarBean.getSidebarConfig(context); + if (config != null) + { + SidebarPluginConfig plugin = config.getPlugin(getActivePlugin()); + if (plugin != null) + { + actionsGroupId = plugin.getActionsConfigId(); + } + } + actions.setValue(actionsGroupId); + + // add components to the sidebar + this.getChildren().add(0, modeList); + this.getChildren().add(1, actions); + } + } + + @Override + public void encodeChildren(FacesContext context) throws IOException + { + if (!isRendered()) return; + + // there should be 3 children, the modelist, the actions + // and the plugin, get them individually and render + + if (getChildren().size() == 3) + { + ResponseWriter out = context.getResponseWriter(); + + out.write("
"); + + // render the list + UIModeList modeList = (UIModeList)getChildren().get(0); + Utils.encodeRecursive(context, modeList); + + out.write(""); + + // render the actions + UIActions actions = (UIActions)getChildren().get(1); + Utils.encodeRecursive(context, actions); + + out.write("
"); + + // render the end of the header panel + PanelGenerator.generateTitledPanelMiddle(out, + context.getExternalContext().getRequestContextPath(), + "blue", "white", "white"); + + // render the plugin + UIComponent plugin = (UIComponent)getChildren().get(2); + Utils.encodeRecursive(context, plugin); + } + } + + @Override + public void encodeEnd(FacesContext context) throws IOException + { + if (!isRendered()) return; + + // render the end of the panel + ResponseWriter out = context.getResponseWriter(); + PanelGenerator.generatePanelEnd(out, + context.getExternalContext().getRequestContextPath(), + "white"); + out.write("
"); + } + + @Override + public boolean getRendersChildren() + { + return true; + } + + // ------------------------------------------------------------------------------ + // Strongly typed component property accessors + + /** + * Returns the id of the plugin that is currently active + * + * @return The currently active plugin + */ + public String getActivePlugin() + { + ValueBinding vb = getValueBinding("activePlugin"); + if (vb != null) + { + this.activePlugin = (String)vb.getValue(getFacesContext()); + } + + return this.activePlugin; + } + + /** + * Sets the active plugin the sidebar should show + * + * @param activePlugin Id of the plugin to make active + */ + public void setActivePlugin(String activePlugin) + { + this.activePlugin = activePlugin; + } +} + + + diff --git a/source/java/org/alfresco/web/ui/repo/component/UITree.java b/source/java/org/alfresco/web/ui/repo/component/UITree.java new file mode 100644 index 0000000000..0980543cdd --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/component/UITree.java @@ -0,0 +1,384 @@ +package org.alfresco.web.ui.repo.component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.faces.component.UIComponentBase; +import javax.faces.context.FacesContext; +import javax.faces.el.ValueBinding; + +/** + * JSF component that renders an AJAX based tree for browsing the + * repository. + * + * @author gavinc + */ +public class UITree extends UIComponentBase +{ + public static final String COMPONENT_TYPE = "org.alfresco.faces.Tree"; + public static final String DEFAULT_RENDERER = "org.alfresco.faces.Yahoo"; + + protected List rootNodes = null; + protected String retrieveChildrenUrl; + protected String nodeCollapsedUrl; + protected String nodeExpandedCallback; + protected String nodeCollapsedCallback; + protected String nodeSelectedCallback; + + // ------------------------------------------------------------------------------ + // Component Impl + + public UITree() + { + setRendererType(DEFAULT_RENDERER); + } + + @Override + public String getFamily() + { + return COMPONENT_TYPE; + } + + @Override + @SuppressWarnings("unchecked") + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.rootNodes = (List)values[1]; + this.retrieveChildrenUrl = (String)values[2]; + this.nodeCollapsedUrl = (String)values[3]; + this.nodeExpandedCallback = (String)values[4]; + this.nodeCollapsedCallback = (String)values[5]; + this.nodeSelectedCallback = (String)values[6]; + } + + @Override + public Object saveState(FacesContext context) + { + Object values[] = new Object[7]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.rootNodes; + values[2] = this.retrieveChildrenUrl; + values[3] = this.nodeCollapsedUrl; + values[4] = this.nodeExpandedCallback; + values[5] = this.nodeCollapsedCallback; + values[6] = this.nodeSelectedCallback; + return values; + } + + // ------------------------------------------------------------------------------ + // Strongly typed component property accessors + + /** + * Get the root nodes for the tree + * + * @return the list of nodes representing the root nodes of the tree + */ + @SuppressWarnings("unchecked") + public List getRootNodes() + { + ValueBinding vb = getValueBinding("rootNodes"); + if (vb != null) + { + this.rootNodes = (List)vb.getValue(getFacesContext()); + } + + return this.rootNodes; + } + + /** + * Set the root nodes for the tree to show + * + * @param rootNodes The list of node for the tree + */ + public void setRootNodes(List rootNodes) + { + this.rootNodes = rootNodes; + } + + /** + * Returns the Javascript function name to be used for node collapsed event + * + * @return Javascript function name to be used for node collapsed event + */ + public String getNodeCollapsedCallback() + { + ValueBinding vb = getValueBinding("nodeCollapsedCallback"); + if (vb != null) + { + this.nodeCollapsedCallback = (String)vb.getValue(getFacesContext()); + } + + return this.nodeCollapsedCallback; + } + + /** + * Sets the name of the Javascript function to use for the node collapsed event + * + * @param nodeCollapsedCallback The Javascript function to use for the node collapsed event + */ + public void setNodeCollapsedCallback(String nodeCollapsedCallback) + { + this.nodeCollapsedCallback = nodeCollapsedCallback; + } + + /** + * Returns the Javascript function name to be used for node expanded event + * + * @return Javascript function name to be used for node expanded event + */ + public String getNodeExpandedCallback() + { + ValueBinding vb = getValueBinding("nodeExpandedCallback"); + if (vb != null) + { + this.nodeExpandedCallback = (String)vb.getValue(getFacesContext()); + } + + return this.nodeExpandedCallback; + } + + /** + * Sets the name of the Javascript function to use for the expanded event + * + * @param nodeCollapsedCallback The Javascript function to use for the expanded event + */ + public void setNodeExpandedCallback(String nodeExpandedCallback) + { + this.nodeExpandedCallback = nodeExpandedCallback; + } + + /** + * Returns the Javascript function name to be used for node selected event + * + * @return Javascript function name to be used for node selected event + */ + public String getNodeSelectedCallback() + { + ValueBinding vb = getValueBinding("nodeSelectedCallback"); + if (vb != null) + { + this.nodeSelectedCallback = (String)vb.getValue(getFacesContext()); + } + + return this.nodeSelectedCallback; + } + + /** + * Sets the name of the Javascript function to use for the node selected event + * + * @param nodeCollapsedCallback The Javascript function to use for the node selected event + */ + public void setNodeSelectedCallback(String nodeSelectedCallback) + { + this.nodeSelectedCallback = nodeSelectedCallback; + } + + /** + * Returns the URL to use for the AJAX call to retrieve the child nodea + * + * @return AJAX URL to get children + */ + public String getRetrieveChildrenUrl() + { + ValueBinding vb = getValueBinding("retrieveChildrenUrl"); + if (vb != null) + { + this.retrieveChildrenUrl = (String)vb.getValue(getFacesContext()); + } + + return this.retrieveChildrenUrl; + } + + /** + * Sets the AJAX URL to use to retrive child nodes + * + * @param retrieveChildrenUrl The AJAX URL to use + */ + public void setRetrieveChildrenUrl(String retrieveChildrenUrl) + { + this.retrieveChildrenUrl = retrieveChildrenUrl; + } + + /** + * Returns the URL to use for the AJAX call to inform the server + * that a node has been collapsed + * + * @return AJAX URL to inform of node collapse + */ + public String getNodeCollapsedUrl() + { + ValueBinding vb = getValueBinding("nodeCollapsedUrl"); + if (vb != null) + { + this.nodeCollapsedUrl = (String)vb.getValue(getFacesContext()); + } + + return this.nodeCollapsedUrl; + } + + /** + * Sets the AJAX URL to use to inform the server that a node + * has been collapsed + * + * @param nodeCollapsedUrl The AJAX URL to use + */ + public void setNodeCollapsedUrl(String nodeCollapsedUrl) + { + this.nodeCollapsedUrl = nodeCollapsedUrl; + } + + /** + * Inner class representing a node in the tree + * + * @author gavinc + */ + public static class TreeNode + { + private String nodeRef; + private String name; + private String icon; + private boolean leafNode = false; + private boolean expanded = false; + private boolean selected = false; + private TreeNode parent; + private List children = new ArrayList(); + + /** + * Default constructor + * + * @param nodeRef The NodeRef of the item the node is representing + * @param name The name for the tree label + * @param icon The icon for the node + */ + public TreeNode(String nodeRef, String name, String icon) + { + this.nodeRef = nodeRef; + this.name = name; + this.icon = icon; + } + + public String getIcon() + { + return this.icon; + } + + public void setIcon(String icon) + { + this.icon = icon; + } + + public boolean isLeafNode() + { + return this.leafNode; + } + + public void setLeafNode(boolean leafNode) + { + this.leafNode = leafNode; + } + + public boolean isExpanded() + { + return this.expanded; + } + + public void setExpanded(boolean expanded) + { + this.expanded = expanded; + } + + public boolean isSelected() + { + return this.selected; + } + + public void setSelected(boolean selected) + { + this.selected = selected; + } + + public String getName() + { + return this.name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getNodeRef() + { + return this.nodeRef; + } + + public void setNodeRef(String nodeRef) + { + this.nodeRef = nodeRef; + } + + public TreeNode getParent() + { + return this.parent; + } + + public void setParent(TreeNode parent) + { + this.parent = parent; + } + + public List getChildren() + { + return this.children; + } + + public void addChild(TreeNode child) + { + child.setParent(this); + this.children.add(child); + } + + public void removeChildren() + { + this.children = new ArrayList(); + } + + public String toString() + { + StringBuilder buffer = new StringBuilder(super.toString()); + buffer.append(" (nodeRef=").append(this.nodeRef); + buffer.append(", name=").append(this.name); + buffer.append(", icon=").append(this.icon); + buffer.append(", expanded=").append(this.expanded); + buffer.append(", selected=").append(this.selected); + if (this.parent != null) + { + buffer.append(", parent=").append(this.parent.getNodeRef()); + } + else + { + buffer.append(", parent=null"); + } + buffer.append(", leafNode=").append(this.leafNode).append(")"); + return buffer.toString(); + } + + public String toXML() + { + StringBuilder xml = new StringBuilder(); + xml.append(""); + return xml.toString(); + } + } +} diff --git a/source/java/org/alfresco/web/ui/repo/renderer/YahooTreeRenderer.java b/source/java/org/alfresco/web/ui/repo/renderer/YahooTreeRenderer.java new file mode 100644 index 0000000000..c2eccaf165 --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/renderer/YahooTreeRenderer.java @@ -0,0 +1,188 @@ +package org.alfresco.web.ui.repo.renderer; + +import java.io.IOException; +import java.util.List; + +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; + +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.renderer.BaseRenderer; +import org.alfresco.web.ui.repo.component.UITree; +import org.alfresco.web.ui.repo.component.UITree.TreeNode; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Renderer for the UITree component that outputs the necessary + * JavaScript to use the Yahoo UI toolkit tree control. + * + * @author gavinc + */ +public class YahooTreeRenderer extends BaseRenderer +{ + protected int nodeCounter; + + protected final static String TREE_SCRIPTS_WRITTEN = "_alfTreeScripts"; + + private static final Log logger = LogFactory.getLog(YahooTreeRenderer.class); + + @SuppressWarnings("unchecked") + @Override + public void encodeBegin(FacesContext context, UIComponent component) throws IOException + { + // get the root nodes + UITree tree = (UITree)component; + List rootNodes = tree.getRootNodes(); + + if (rootNodes != null && rootNodes.size() > 0) + { + ResponseWriter out = context.getResponseWriter(); + String treeContainerId = component.getClientId(context) + "Container"; + + // output the scripts required by the component (checks are + // made to make sure the scripts are only written once) + Utils.writeYahooScripts(context, out, null); + + // write out the JavaScript specific to the Tree component, + // again, make sure it's only done once + Object present = context.getExternalContext().getRequestMap(). + get(TREE_SCRIPTS_WRITTEN); + if (present == null) + { + out.write(""); + + out.write("\n"); + + out.write("\n"); + + context.getExternalContext().getRequestMap().put( + TREE_SCRIPTS_WRITTEN, Boolean.TRUE); + } + + // output the div container for the tree + out.write("
\n"); + + // generate the startup + out.write("\n"); + } + else if (logger.isDebugEnabled()) + { + logger.debug("There weren't any nodes to render"); + } + } + + /** + * Generates the JavaScript required to create the branch of a tree from + * the given node. + * + * @param node The node to generate + * @param out Response writer + * @param parentVarName Name of the parent variable, null if the node has no parent + */ + protected void generateNode(TreeNode node, ResponseWriter out, String parentVarName) + throws IOException + { + String currentVarName = getNextVarName(); + + // generate the Javascript to create the given node using the + // appropriate parent variable + out.write(" var "); + out.write(currentVarName); + out.write(" = createYahooTreeNode("); + + if (node.getParent() == null) + { + out.write("root"); + } + else + { + out.write(parentVarName); + } + + out.write(", \""); + out.write(node.getNodeRef()); + out.write("\", \""); + out.write(node.getName()); + out.write("\", \""); + out.write(node.getIcon()); + out.write("\", "); + out.write(Boolean.toString(node.isExpanded())); + out.write(", "); + out.write(Boolean.toString(node.isSelected())); + out.write(");\n"); + + // iterate through the child nodes and generate them + if (node.isExpanded() && node.getChildren().size() > 0) + { + for (TreeNode child : node.getChildren()) + { + generateNode(child, out, currentVarName); + } + } + } + + protected String getNextVarName() + { + this.nodeCounter++; + return "n" + Integer.toString(this.nodeCounter); + } +} diff --git a/source/java/org/alfresco/web/ui/repo/tag/NavigatorTag.java b/source/java/org/alfresco/web/ui/repo/tag/NavigatorTag.java new file mode 100644 index 0000000000..487bfb9ee4 --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/tag/NavigatorTag.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.repo.tag; + +import javax.faces.component.UIComponent; + +import org.alfresco.web.ui.common.tag.HtmlComponentTag; + +/** + * Tag class for using the navigator component on a JSP page. + * + * @author gavinc + */ +public class NavigatorTag extends HtmlComponentTag +{ + private String activeArea; + + /** + * @see javax.faces.webapp.UIComponentTag#getComponentType() + */ + public String getComponentType() + { + return "org.alfresco.faces.Navigator"; + } + + /** + * @see javax.faces.webapp.UIComponentTag#getRendererType() + */ + public String getRendererType() + { + return null; + } + + /** + * @see javax.faces.webapp.UIComponentTag#setProperties(javax.faces.component.UIComponent) + */ + protected void setProperties(UIComponent component) + { + super.setProperties(component); + + setStringBindingProperty(component, "activeArea", this.activeArea); + } + + /** + * @see org.alfresco.web.ui.common.tag.HtmlComponentTag#release() + */ + public void release() + { + super.release(); + + this.activeArea = null; + } + + /** + * Sets the active area of the navigator + * + * @param activeArea The active area + */ + public void setActiveArea(String activeArea) + { + this.activeArea = activeArea; + } +} diff --git a/source/java/org/alfresco/web/ui/repo/tag/SidebarTag.java b/source/java/org/alfresco/web/ui/repo/tag/SidebarTag.java new file mode 100644 index 0000000000..951387af67 --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/tag/SidebarTag.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.repo.tag; + +import javax.faces.component.UIComponent; + +import org.alfresco.web.ui.common.tag.HtmlComponentTag; +import org.alfresco.web.ui.repo.component.UISidebar; + +/** + * Tag class for using the sidebar component on a JSP page. + * + * @author gavinc + */ +public class SidebarTag extends HtmlComponentTag +{ + private String activePlugin; + + /** + * @see javax.faces.webapp.UIComponentTag#getComponentType() + */ + public String getComponentType() + { + return UISidebar.COMPONENT_TYPE; + } + + /** + * @see javax.faces.webapp.UIComponentTag#getRendererType() + */ + public String getRendererType() + { + return null; + } + + /** + * @see javax.faces.webapp.UIComponentTag#setProperties(javax.faces.component.UIComponent) + */ + protected void setProperties(UIComponent component) + { + super.setProperties(component); + + setStringBindingProperty(component, "activePlugin", this.activePlugin); + } + + /** + * @see org.alfresco.web.ui.common.tag.HtmlComponentTag#release() + */ + public void release() + { + super.release(); + + this.activePlugin = null; + } + + /** + * Sets the activePlugin attribute + * + * @param activePlugin + */ + public void setActivePlugin(String activePlugin) + { + this.activePlugin = activePlugin; + } +} diff --git a/source/java/org/alfresco/web/ui/repo/tag/YahooTreeTag.java b/source/java/org/alfresco/web/ui/repo/tag/YahooTreeTag.java new file mode 100644 index 0000000000..424e6289a8 --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/tag/YahooTreeTag.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.repo.tag; + +import javax.faces.component.UIComponent; + +import org.alfresco.web.ui.common.tag.HtmlComponentTag; + +/** + * Tag class for using the Yahoo tree component on a JSP page. + * + * @author gavinc + */ +public class YahooTreeTag extends HtmlComponentTag +{ + private String rootNodes; + private String retrieveChildrenUrl; + private String nodeCollapsedUrl; + private String nodeExpandedCallback; + private String nodeCollapsedCallback; + private String nodeSelectedCallback; + + /** + * @see javax.faces.webapp.UIComponentTag#getComponentType() + */ + public String getComponentType() + { + return "org.alfresco.faces.Tree"; + } + + /** + * @see javax.faces.webapp.UIComponentTag#getRendererType() + */ + public String getRendererType() + { + return "org.alfresco.faces.Yahoo"; + } + + /** + * @see javax.faces.webapp.UIComponentTag#setProperties(javax.faces.component.UIComponent) + */ + protected void setProperties(UIComponent component) + { + super.setProperties(component); + + setStringBindingProperty(component, "rootNodes", this.rootNodes); + setStringBindingProperty(component, "retrieveChildrenUrl", this.retrieveChildrenUrl); + setStringBindingProperty(component, "nodeCollapsedUrl", this.nodeCollapsedUrl); + setStringBindingProperty(component, "nodeExpandedCallback", this.nodeExpandedCallback); + setStringBindingProperty(component, "nodeCollapsedCallback", this.nodeCollapsedCallback); + setStringBindingProperty(component, "nodeSelectedCallback", this.nodeSelectedCallback); + } + + /** + * @see org.alfresco.web.ui.common.tag.HtmlComponentTag#release() + */ + public void release() + { + super.release(); + + this.rootNodes = null; + this.retrieveChildrenUrl = null; + this.nodeCollapsedUrl = null; + this.nodeExpandedCallback = null; + this.nodeCollapsedCallback = null; + this.nodeSelectedCallback = null; + } + + /** + * Set the root nodes for the tree + * + * @param rootNodes + */ + public void setRootNodes(String rootNodes) + { + this.rootNodes = rootNodes; + } + + /** + * Set the name of the Javascript function to handle the node collapsed event + * + * @param nodeCollapsedCallback + */ + public void setNodeCollapsedCallback(String nodeCollapsedCallback) + { + this.nodeCollapsedCallback = nodeCollapsedCallback; + } + + /** + * Set the name of the Javascript function to handle the node expanded event + * + * @param nodeExpandedCallback + */ + public void setNodeExpandedCallback(String nodeExpandedCallback) + { + this.nodeExpandedCallback = nodeExpandedCallback; + } + + /** + * Set the name of the Javascript function to handle the node selected event + * + * @param nodeSelectedCallback + */ + public void setNodeSelectedCallback(String nodeSelectedCallback) + { + this.nodeSelectedCallback = nodeSelectedCallback; + } + + /** + * Set the URL to use to retrieve child nodes + * + * @param retrieveChildrenUrl + */ + public void setRetrieveChildrenUrl(String retrieveChildrenUrl) + { + this.retrieveChildrenUrl = retrieveChildrenUrl; + } + + /** + * Set the URL to use to inform the server that a node has been collapsed + * + * @param nodeCollapsedUrl + */ + public void setNodeCollapsedUrl(String nodeCollapsedUrl) + { + this.nodeCollapsedUrl = nodeCollapsedUrl; + } +} diff --git a/source/web/WEB-INF/faces-config-beans.xml b/source/web/WEB-INF/faces-config-beans.xml index 762f8ca573..e64876eeb4 100644 --- a/source/web/WEB-INF/faces-config-beans.xml +++ b/source/web/WEB-INF/faces-config-beans.xml @@ -2029,6 +2029,15 @@ org.alfresco.web.bean.UserPreferencesBean session + + + + The bean that backs up the Sidebar component + + SidebarBean + org.alfresco.web.bean.SidebarBean + session + @@ -2968,6 +2977,23 @@ + + + Bean that returns manages the tree data for the navigator component + + NavigatorPluginBean + org.alfresco.web.bean.ajax.NavigatorPluginBean + session + + nodeService + #{NodeService} + + + dictionaryService + #{DictionaryService} + + + Bean that returns information on a node diff --git a/source/web/WEB-INF/faces-config-repo.xml b/source/web/WEB-INF/faces-config-repo.xml index a798a656ee..ec9a223624 100644 --- a/source/web/WEB-INF/faces-config-repo.xml +++ b/source/web/WEB-INF/faces-config-repo.xml @@ -13,8 +13,8 @@ org.alfresco.faces.Property org.alfresco.web.ui.repo.component.property.UIProperty - - + + org.alfresco.faces.Separator org.alfresco.web.ui.repo.component.property.UISeparator @@ -158,7 +158,22 @@ org.alfresco.faces.NodeWorkflowInfo org.alfresco.web.ui.repo.component.UINodeWorkflowInfo - + + + org.alfresco.faces.Sidebar + org.alfresco.web.ui.repo.component.UISidebar + + + + org.alfresco.faces.Tree + org.alfresco.web.ui.repo.component.UITree + + + + org.alfresco.faces.Navigator + org.alfresco.web.ui.repo.component.UINavigator + + org.alfresco.faces.PermissionEvaluator @@ -195,8 +210,8 @@ org.alfresco.faces.PropertyRenderer org.alfresco.web.ui.repo.renderer.property.PropertyRenderer - - + + org.alfresco.faces.Separator org.alfresco.faces.SeparatorRenderer org.alfresco.web.ui.repo.renderer.property.SeparatorRenderer @@ -225,6 +240,12 @@ org.alfresco.faces.Field org.alfresco.web.ui.repo.renderer.MultiValueFieldRenderer + + + org.alfresco.faces.Tree + org.alfresco.faces.Yahoo + org.alfresco.web.ui.repo.renderer.YahooTreeRenderer + diff --git a/source/web/WEB-INF/repo.tld b/source/web/WEB-INF/repo.tld index 101e1f9a36..372ff18981 100644 --- a/source/web/WEB-INF/repo.tld +++ b/source/web/WEB-INF/repo.tld @@ -1755,4 +1755,113 @@ + + sidebar + org.alfresco.web.ui.repo.tag.SidebarTag + JSP + + + Allows the sidebar component to be used on a JSP page. + + + + id + false + true + + + + activePlugin + false + true + + + + rendered + false + true + + + + + navigator + org.alfresco.web.ui.repo.tag.NavigatorTag + JSP + + + Allows the navigator component to be used on a JSP page. + + + + id + false + true + + + + rendered + false + true + + + + activeArea + false + true + + + + + yahooTree + org.alfresco.web.ui.repo.tag.YahooTreeTag + JSP + + + The tree tag allows the yahoo based tree component to be + added to a JSP page. + + + + id + false + true + + + + rootNodes + false + true + + + + retrieveChildrenUrl + false + true + + + + nodeCollapsedUrl + false + true + + + + nodeExpandedCallback + false + true + + + + nodeCollapsedCallback + false + true + + + + nodeSelectedCallback + false + true + + + diff --git a/source/web/css/main.css b/source/web/css/main.css index f0e10fb33f..f2228ebe6b 100644 --- a/source/web/css/main.css +++ b/source/web/css/main.css @@ -11,16 +11,16 @@ p table.moduletable td { - font-family: Tahoma, Arial, Helvetica, sans-serif; - font-size: 11px; - font-weight: normal; + font-family: Tahoma, Arial, Helvetica, sans-serif; + font-size: 11px; + font-weight: normal; } td,tr,p,div { color: #003366; - font-family: Tahoma, Arial, Helvetica, sans-serif; - font-size: 11px; + font-family: Tahoma, Arial, Helvetica, sans-serif; + font-size: 11px; } th @@ -40,68 +40,68 @@ p.status a:link, a:visited { - font-size: 11px; - color: #003366; - text-decoration: none; - font-family: Tahoma, Arial, Helvetica, sans-serif; - font-weight: normal; + font-size: 11px; + color: #003366; + text-decoration: none; + font-family: Tahoma, Arial, Helvetica, sans-serif; + font-weight: normal; } a:hover { - color: #4272B4; - text-decoration: underline; - font-weight: normal; + color: #4272B4; + text-decoration: underline; + font-weight: normal; } a.path:link, a.path:visited { - font-size: 14px; - color: #003366; - text-decoration: none; - font-weight: normal; + font-size: 14px; + color: #003366; + text-decoration: none; + font-weight: normal; } a.path:hover { font-size: 14px; - color: #4272B4; - text-decoration: underline; - font-weight: normal; + color: #4272B4; + text-decoration: underline; + font-weight: normal; } a.header:link, a.header:visited { - font-weight: bold; + font-weight: bold; } a.header:hover { - font-weight: bold; + font-weight: bold; } a.title:link, a.title:visited { font-size: 12px; - font-weight: bold; + font-weight: bold; } a.title:hover { font-size: 12px; - font-weight: bold; + font-weight: bold; } a.moreactions:link, a.moreactions:visited { vertical-align: 30%; - font-size: 10px; + font-size: 10px; } a.moreactions:hover { vertical-align: 30%; - font-size: 10px; + font-size: 10px; } a.small:link, a.small:visited @@ -336,7 +336,7 @@ input,textarea,select .dialogButtonSpacing { - height: 5px; + height: 5px; } .topToolbar @@ -434,7 +434,7 @@ a.topToolbarLinkHighlight, a.topToolbarLinkHighlight:link, a.topToolbarLinkHighl .wizardButtonSpacing { - height: 5px; + height: 5px; } .propertiesLabel @@ -486,12 +486,12 @@ a.topToolbarLinkHighlight, a.topToolbarLinkHighlight:link, a.topToolbarLinkHighl .alignTop { - vertical-align: top; + vertical-align: top; } .alignMiddle { - vertical-align: middle; + vertical-align: middle; } .tableThirdWidth @@ -514,10 +514,10 @@ a.topToolbarLinkHighlight, a.topToolbarLinkHighlight:link, a.topToolbarLinkHighl .summaryPopupPanel { - background-color: #e9f0f4; - border: 1px solid #999999; - padding: 4px; - -moz-border-radius: 4px; + background-color: #e9f0f4; + border: 1px solid #999999; + padding: 4px; + -moz-border-radius: 4px; } .userGroupPickerList @@ -531,9 +531,9 @@ a.topToolbarLinkHighlight, a.topToolbarLinkHighlight:link, a.topToolbarLinkHighl .workflowSelection { - border: 1px solid #676767; - background-color: #efefef; - padding: 6px 12px 12px 6px; + border: 1px solid #676767; + background-color: #efefef; + padding: 6px 12px 12px 6px; } .workflowSummary @@ -549,15 +549,15 @@ a.topToolbarLinkHighlight, a.topToolbarLinkHighlight:link, a.topToolbarLinkHighl .nodeWorkflowInfoTitle { - font-weight: bold; - color: #003366; - padding-top: 3px; - padding-bottom: 3px; + font-weight: bold; + color: #003366; + padding-top: 3px; + padding-bottom: 3px; } .nodeWorkflowInfoText { - padding-bottom: 8px; + padding-bottom: 8px; } .selectListTable @@ -589,3 +589,43 @@ a.topToolbarLinkHighlight, a.topToolbarLinkHighlight:link, a.topToolbarLinkHighl border-style: solid; border-color: #999999; } + +#navigator +{ + border: 1px solid #aaaaaa; +} + +.navigatorPanelTitle +{ + background-color: #efefef; + color: #003366; + padding: 5px; + border-top: 1px solid #aaaaaa; +} + +.navigatorPanelTitle a +{ + font-weight: bold; +} + +.navigatorPanelTitle a:hover +{ + color: #4272b4; + font-weight: bold; +} + +.navigatorPanelTitleSelected +{ + width: 220px; + font-weight: bold; + background-color: #7b7b7b; + color: white; + padding: 5px; +} + +.navigatorPanelBody +{ + height: 330px; + width: 220px; + overflow: auto; +} \ No newline at end of file diff --git a/source/web/css/yahoo-tree.css b/source/web/css/yahoo-tree.css new file mode 100644 index 0000000000..0250eededb --- /dev/null +++ b/source/web/css/yahoo-tree.css @@ -0,0 +1,134 @@ +/* first or middle sibling, no children */ +.ygtvtn +{ + width:16px; height:18px; + cursor:pointer ; + background: url(../images/icons/arrow_closed.gif) 0 0 no-repeat; +} + +/* first or middle sibling, collapsable */ +.ygtvtm +{ + width:16px; height:18px; + cursor:pointer ; + background: url(../images/icons/arrow_open.gif) 0 0 no-repeat; +} + +/* first or middle sibling, collapsable, hover */ +.ygtvtmh +{ + width:16px; height:18px; + cursor:pointer ; + background: url(../images/icons/arrow_open.gif) 0 0 no-repeat; +} + +/* first or middle sibling, expandable */ +.ygtvtp +{ + width:16px; height:18px; + cursor:pointer ; + background: url(../images/icons/arrow_closed.gif) 0 0 no-repeat; +} + +/* first or middle sibling, expandable, hover */ +.ygtvtph +{ + width:16px; height:18px; + cursor:pointer ; + background: url(../images/icons/arrow_closed.gif) 0 0 no-repeat; +} + +/* last sibling, no children */ +.ygtvln +{ + width:16px; height:18px; + cursor:pointer ; + background: url(../images/icons/arrow_closed.gif) 0 0 no-repeat; +} + +/* Last sibling, collapsable */ +.ygtvlm +{ + width:16px; height:18px; + cursor:pointer ; + background: url(../images/icons/arrow_open.gif) 0 0 no-repeat; +} + +/* Last sibling, collapsable, hover */ +.ygtvlmh +{ + width:16px; height:18px; + cursor:pointer ; + background: url(../images/icons/arrow_open.gif) 0 0 no-repeat; +} + +/* Last sibling, expandable */ +.ygtvlp +{ + width:16px; height:18px; + cursor:pointer ; + background: url(../images/icons/arrow_closed.gif) 0 0 no-repeat; +} + +/* Last sibling, expandable, hover */ +.ygtvlph +{ + width:16px; height:18px; cursor:pointer ; + background: url(../images/icons/arrow_closed.gif) 0 0 no-repeat; +} + +/* Loading icon */ +.ygtvloading +{ + width:16px; height:18px; + background: url(../scripts/ajax/yahoo/treeview/assets/loading.gif) 0 0 no-repeat; +} + +/* the style for the empty cells that are used for rendering the depth +* of the node */ +.ygtvdepthcell +{ + width:16px; height:18px; + background: url() 0 0 no-repeat; +} + +.ygtvblankdepthcell { width:16px; height:22px; } + +/* the style of the div around each node */ +.ygtvitem { } + +/* the style of the div around each node's collection of children */ +.ygtvchildren { } +* html .ygtvchildren { height:2%; } + +/* the style of the text label in ygTextNode */ +.ygtvlabel, .ygtvlabel:link, .ygtvlabel:visited, .ygtvlabel:hover +{ + margin-left:2px; + text-decoration: none; + background-color: white; +} + +.ygtvspacer +{ + height: 10px; + width: 10px; + margin: 2px; +} + +.ygtvhtml +{ + cursor: pointer; +} + +.alflabel +{ + white-space: nowrap; +} + +.alflabelselected +{ + background-color: #d3e6fe; +} + + diff --git a/source/web/images/icons/arrow_closed.gif b/source/web/images/icons/arrow_closed.gif new file mode 100644 index 0000000000..655a4d6a08 Binary files /dev/null and b/source/web/images/icons/arrow_closed.gif differ diff --git a/source/web/images/icons/arrow_open.gif b/source/web/images/icons/arrow_open.gif new file mode 100644 index 0000000000..85853aa6cf Binary files /dev/null and b/source/web/images/icons/arrow_open.gif differ diff --git a/source/web/images/icons/reset.gif b/source/web/images/icons/reset.gif new file mode 100644 index 0000000000..c5102fee84 Binary files /dev/null and b/source/web/images/icons/reset.gif differ diff --git a/source/web/jsp/dashboards/dashlets/getting-started.jsp b/source/web/jsp/dashboards/dashlets/getting-started.jsp index c8b4eee5fc..385d891a80 100644 --- a/source/web/jsp/dashboards/dashlets/getting-started.jsp +++ b/source/web/jsp/dashboards/dashlets/getting-started.jsp @@ -24,31 +24,31 @@ <% PanelGenerator.generatePanelStart(out, request.getContextPath(), "yellow", "#ffffcc"); %> - - + + - + - +
- + - +
- + - +
@@ -56,21 +56,21 @@
- + - + - +
- + - + @@ -82,9 +82,9 @@ - + - + @@ -96,9 +96,9 @@ - + - + diff --git a/source/web/jsp/parts/shelf.jsp b/source/web/jsp/parts/shelf.jsp index c01324edd6..33c98f2eb4 100644 --- a/source/web/jsp/parts/shelf.jsp +++ b/source/web/jsp/parts/shelf.jsp @@ -14,60 +14,19 @@ language governing permissions and limitations under the License. --%> -<%-- Shelf area --%> - - - - - - - - - - - - - - - - - - -
-
-
- - <%-- Shelf component --%> - <%-- IMPORTANT NOTE: All inner components must be given an explicit ID! --%> - <%-- This is because they are wrapped in a Panel component --%> - - - - - - <%-- NOTE: this component is exanded=true as default so the RecentSpaces managed Bean is - instantied early - otherwise it will not be seen until this shelf component is - first expanded. There is no config setting to do this in JSF by default --%> - - - - - - - - - <%-- TBD - - - - - - - - --%> - - -
- -
+ +<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> +<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> +<%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %> +<%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %> + +<%@ page import="org.alfresco.web.app.Application" %> + +<%-- Sidebar --%> + + + + + + + \ No newline at end of file diff --git a/source/web/scripts/ajax/common.js b/source/web/scripts/ajax/common.js index 0675817a56..8c56c4136d 100644 --- a/source/web/scripts/ajax/common.js +++ b/source/web/scripts/ajax/common.js @@ -4,7 +4,7 @@ // /** - * Default handler for errors + * Default handler for errors when using the dojo toolkit */ function handleErrorDojo(type, errObj) { @@ -19,4 +19,22 @@ function handleErrorDojo(type, errObj) // TODO: Show a nicer error page, an alert will do for now! alert(msg); +} + +/** + * Default handler for errors when using the yahoo toolkit + */ +function handleErrorYahoo(msg) +{ + // TODO: Show a nicer error page, an alert will do for now! + alert(msg); +} + +/** + * Calculates and returns the context path for the current page + */ +function getContextPath() +{ + var w = window.location; + alert(w.pathname); } \ No newline at end of file diff --git a/source/web/scripts/ajax/yahoo-tree.js b/source/web/scripts/ajax/yahoo-tree.js new file mode 100644 index 0000000000..6f552265f4 --- /dev/null +++ b/source/web/scripts/ajax/yahoo-tree.js @@ -0,0 +1,158 @@ +var loadDataUrl = null; +var collapseUrl = null; +var nodeSelectedHandler = null; + +/** + * Sets the URL to use to retrive child nodes + */ +function setLoadDataUrl(url) +{ + loadDataUrl = url; +} + +/** + * Sets the URL to inform the server that a node collapsed + */ +function setCollapseUrl(url) +{ + collapseUrl = url; +} + +/** + * Sets the name of the handler function to use for the node selected event + */ +function setNodeSelectedHandler(handler) +{ + nodeSelectedHandler = handler; +} + +/** + * Callback method used by the tree to load the given node's children + */ +function loadDataForNode(node, onCompleteCallback) +{ + if (loadDataUrl == null) + { + alert("AJAX URL has not been set for retrieving child nodes, call setLoadDataUrl()!"); + return; + } + + var nodeRef = node.data.nodeRef; + + // TODO: add method to add param to url + var transaction = YAHOO.util.Connect.asyncRequest('GET', WEBAPP_CONTEXT + loadDataUrl + "&nodeRef=" + nodeRef, + { + success: function(o) + { + var parentNode = o.argument[0]; + var data = o.responseXML.documentElement; + + // parse the child data to create the child nodes + parseChildData(parentNode, data); + + // execute the callback method + o.argument[1](); + }, + failure: function(o) + { + handleErrorYahoo("Error: Failed to retrieve children for node: " + o.argument[0].data.nodeRef); + + // execute the callback method + o.argument[1](); + }, + argument: [node, onCompleteCallback] + } + , null); +} + +/** + * Parses the given data returned from the server into the required child nodes + */ +function parseChildData(parentNode, data) +{ + if (data != undefined && data != null) + { + var nodes = data.getElementsByTagName("node"); + + for (var i = 0; i < nodes.length; i++) + { + var node = nodes[i]; + var nodeRef = node.getAttribute("ref"); + var name = node.getAttribute("name"); + var icon = node.getAttribute("icon"); + + // create the new node + createYahooTreeNode(parentNode, nodeRef, name, icon, false, false); + } + } + else + { + alert("No data returned from server!"); + } +} + +/** + * Generates an HTML tree node and adds it to the given parent node + */ +function createYahooTreeNode(parentNode, nodeRef, name, icon, expanded, selected) +{ + var nodeHtml = "
" + name + "
"; + + return new YAHOO.widget.HTMLNode({ html: nodeHtml, nodeRef: nodeRef, icon: icon }, parentNode, expanded, 1); +} + +/** + * Callback used to inform the server that the given node was collapsed in the UI + */ +function informOfCollapse(node) +{ + if (collapseUrl == null) + { + alert("AJAX URL has not been set for collapsing nodes, call setCollapseUrl()!"); + return; + } + + var nodeRef = node.data.nodeRef; + + // remove the children from the node so when it's expanded again it re-queries the server + node.childrenRendered = false; + node.dynamicLoadComplete = false; + while (node.children.length) + { + tree.removeNode(node.children[0], false); + } + + // TODO: add method to add param to url + var transaction = YAHOO.util.Connect.asyncRequest('GET', WEBAPP_CONTEXT + collapseUrl + "&nodeRef=" + nodeRef, + { + success: function(o) + { + // nothing to do on the client + }, + failure: function(o) + { + handleErrorYahoo("Error: Failed to collapse node: " + o.argument[0].data.nodeRef); + }, + argument: [node] + } + , null); +} + +