cancel
Showing results for 
Search instead for 
Did you mean: 

Want to Change the 'Modify categories' dialog

stevewickii
Champ in-the-making
Champ in-the-making
I want to change the "Modify categories" dialog, which you get to through the Document Details page.  In the "selected categories" panel, I want to list each category along with their parent categories like this:

Category1/Subcategory1
Category1/Subcategory2
Category1/Subcategory3
Category2/Subcategory1
Category2/Subcategory2
Category2/Subcategory3
etc…

I'm not really sure how to do this, and I don't see it posted in the forums.

I see in faces-config-navigation.xml that /jsp/dialog/document-details.jsp has the action "editCategories" configured to go to /jsp/dialog/edit-category.jsp.  Unfortunately, there is no file named /jsp/dialog/edit-category.jsp and there is no navigation-rule for /jsp/dialog/edit-category.jsp.

Does anyone know how this works and which files I need to modify?
2 REPLIES 2

stevewickii
Champ in-the-making
Champ in-the-making
Okay, I did some more digging and here's what I found.

I am using Alfresco 2.9.0B.

document-details.jsp actually contains an <a:actionLink> tag with action="dialog:editNodeCategories".  According to http://wiki.alfresco.com/wiki/Dialog_Framework this action maps to the dialog entry named editNodeCategories in web-client-config-dialogs.xml.  The editNodeCategories dialog is configured to use /jsp/categories/edit-node-categories.jsp for the content of the dialog, and the managed bean is defined in EditNodeCategoriesDialog.javaedit-node-categories.jsp contains an <r:multiValueSelector> tag, and the value attribute is bound to #{DialogManager.bean.categories} which is the EditNodeCategoriesDialog.getCategories() method.  The getCategories() method is implemented to return the value of the current Node's categories['b] property.  This may be a list of category NodeRefs, but I can't confirm.

I cannot find documentation for the multiValueSelector tag on the Alfresco WIKI or Forums, and I cannot figure out how to modify the presentation of this component.

Any suggestions would be appreciated.

I will continue to work toward a solution on my end.

Happy Programming.

stevewickii
Champ in-the-making
Champ in-the-making
I modified the Edit Node Categories Dialog, so that it behaves just like the Category Selector on the Advanced Search page.  You can add multiple categories without losing your place in the category hierarchy.  Keep in mind, that I am developing this enhancement for Alfresco 2.9.0B Build 683.

Implementation

Create source/com/nkics/alfrescox/web/bean/categories/EditNodeCategoriesDialogEx.java
package com.nkics.alfrescox.web.bean.categories;

import java.util.List;

import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.web.bean.categories.EditNodeCategoriesDialog;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class EditNodeCategoriesDialogEx extends EditNodeCategoriesDialog
{
   Log logger = LogFactory.getLog(getClass());
   
   List<NodeRef> addedCategories = null;
   
   public void setAddedCategories(List<NodeRef> categoryList)
   {
      logger.info("setAddedCategory("+categoryList+") invoked.");
      if(categoryList!=null && categoryList.size()>0)
         addedCategories = categoryList;
   }
   
   public List<NodeRef> getAddedCategories()
   {
      logger.info("getAddedCategory(): "+addedCategories+" invoked.");
      return addedCategories;
   }
}

Create source/com/nkics/alfrescox/web/ui/repo/component/UIMultiValueEditorEx.java
package com.nkics.alfrescox.web.ui.repo.component;

/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.

* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.

* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception.  You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.List;

import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;

import org.alfresco.web.app.Application;
import org.alfresco.web.ui.repo.RepoConstants;
import org.alfresco.web.ui.repo.component.UIMultiValueEditor;

/**
* This component wraps a standard component to give it multi value capabilities.
*
* A list of existing values are available, items can be removed from this list or new items added to the list. To add new items the component dynamically shows the child component this one wraps.
*
* Extended by Stephen Wick
*
* Nicholson Kovac has extended the UIMultiValueEditor to allow multiple selections from the AJAX component. The original UIMultiValueEditor.broadcast() method does not handle Collections correctly. This extension does. I had to pretty much copy the entire class, because all of the fields in UIMultiValueEditor are private and many do not have public accessors and setters.
*
* @author gavinc
*/
public class UIMultiValueEditorEx extends UIMultiValueEditor
{
   static final String MSG_SELECTED_ITEMS = "selected_items";
   static final String MSG_NO_SELECTED_ITEMS = "no_selected_items";
   static final String MSG_SELECT_ITEM = "select_an_item";

   public final static String ACTION_SEPARATOR = ";";
   public final static int ACTION_NONE = -1;
   public final static int ACTION_REMOVE = 0;
   public final static int ACTION_SELECT = 1;
   public final static int ACTION_ADD = 2;

   Boolean addingNewItem = Boolean.FALSE;
   Boolean readOnly;
   Object lastItemAdded;
   String selectItemMsg;
   String selectedItemsMsg;
   String noSelectedItemsMsg;

   // ——————————————————————————
   // Component implementation

   /**
    * Default constructor
    */
   public UIMultiValueEditorEx()
   {
      setRendererType(RepoConstants.ALFRESCO_FACES_SELECTOR_RENDERER);
   }

   /**
    * @see javax.faces.component.UIComponent#getFamily()
    */
   public String getFamily()
   {
      return RepoConstants.ALFRESCO_FACES_MULTIVALUE_EDITOR;
   }

   /**
    * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object)
    */
   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.lastItemAdded = values[1];
      this.readOnly = (Boolean) values[2];
      this.addingNewItem = (Boolean) values[3];
      this.selectItemMsg = (String) values[4];
      this.selectedItemsMsg = (String) values[5];
      this.noSelectedItemsMsg = (String) values[6];
   }

   /**
    * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
    */
   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.lastItemAdded;
      values[2] = this.readOnly;
      values[3] = this.addingNewItem;
      values[4] = this.selectItemMsg;
      values[5] = this.selectedItemsMsg;
      values[6] = this.noSelectedItemsMsg;
      return (values);
   }

   /**
    * Returns the last item added by the user
    *
    * @return The last item added
    */
   public Object getLastItemAdded()
   {
      ValueBinding vb = getValueBinding("lastItemAdded");
      if (vb != null)
      {
         this.lastItemAdded = vb.getValue(getFacesContext());
      }

      return this.lastItemAdded;
   }

   /**
    * Sets the last item to be added by the user
    *
    * @param lastItemAdded
    *            The last item added
    */
   public void setLastItemAdded(Object lastItemAdded)
   {
      this.lastItemAdded = lastItemAdded;
   }

   /**
    * Returns the message to display for the selected items, if one hasn't been set it defaults to the message in the bundle under key 'selected_items'.
    *
    * @return The message
    */
   public String getSelectedItemsMsg()
   {
      ValueBinding vb = getValueBinding("selectedItemsMsg");
      if (vb != null)
      {
         this.selectedItemsMsg = (String) vb.getValue(getFacesContext());
      }

      if (this.selectedItemsMsg == null)
      {
         this.selectedItemsMsg = Application.getMessage(getFacesContext(), MSG_SELECTED_ITEMS);
      }

      return this.selectedItemsMsg;
   }

   /**
    * Sets the selected items message to display in the UI
    *
    * @param selectedItemsMsg
    *            The message
    */
   public void setSelectedItemsMsg(String selectedItemsMsg)
   {
      this.selectedItemsMsg = selectedItemsMsg;
   }

   /**
    * Returns the message to display when no items have been selected, if one hasn't been set it defaults to the message in the bundle under key 'no_selected_items'.
    *
    * @return The message
    */
   public String getNoSelectedItemsMsg()
   {
      ValueBinding vb = getValueBinding("noSelectedItemsMsg");
      if (vb != null)
      {
         this.noSelectedItemsMsg = (String) vb.getValue(getFacesContext());
      }

      if (this.noSelectedItemsMsg == null)
      {
         this.noSelectedItemsMsg = Application.getMessage(getFacesContext(), MSG_NO_SELECTED_ITEMS);
      }

      return this.noSelectedItemsMsg;
   }

   /**
    * Sets the no selected items message to display in the UI
    *
    * @param noSelectedItemsMsg
    *            The message
    */
   public void setNoSelectedItemsMsg(String noSelectedItemsMsg)
   {
      this.noSelectedItemsMsg = noSelectedItemsMsg;
   }

   /**
    * Returns the message to display for select an item, if one hasn't been set it defaults to the message in the bundle under key 'select_an_item'.
    *
    * @return The message
    */
   public String getSelectItemMsg()
   {
      ValueBinding vb = getValueBinding("selectItemMsg");
      if (vb != null)
      {
         this.selectItemMsg = (String) vb.getValue(getFacesContext());
      }

      if (this.selectItemMsg == null)
      {
         this.selectItemMsg = Application.getMessage(getFacesContext(), MSG_SELECT_ITEM);
      }

      return this.selectItemMsg;
   }

   /**
    * Sets the select an item message to display in the UI
    *
    * @param selectItemMsg
    *            The message
    */
   public void setSelectItemMsg(String selectItemMsg)
   {
      this.selectItemMsg = selectItemMsg;
   }

   /**
    * Determines whether the component is in read only mode
    *
    * @return true if the component is in read only mode
    */
   public boolean getReadOnly()
   {
      ValueBinding vb = getValueBinding("readOnly");
      if (vb != null)
      {
         this.readOnly = (Boolean) vb.getValue(getFacesContext());
      }

      if (this.readOnly == null)
      {
         this.readOnly = Boolean.FALSE;
      }

      return this.readOnly.booleanValue();
   }

   /**
    * Sets the read only mode for the component
    *
    * @param readOnly
    *            true to set read only mode
    */
   public void setReadOnly(boolean readOnly)
   {
      this.readOnly = Boolean.valueOf(readOnly);
   }

   /**
    * Determines whether the component is adding a new item
    *
    * @return true if we are adding a new item
    */
   public boolean getAddingNewItem()
   {
      return this.addingNewItem.booleanValue();
   }

   /**
    * @see javax.faces.component.UIComponent#broadcast(javax.faces.event.FacesEvent)
    */
   public void broadcast(FacesEvent event) throws AbortProcessingException
   {
      if (event instanceof MultiValueEditorEvent)
      {
         MultiValueEditorEvent assocEvent = (MultiValueEditorEvent) event;
         List items = (List) getValue();

         switch (assocEvent.Action)
         {
            case ACTION_SELECT:
            {
               this.addingNewItem = Boolean.TRUE;
               break;
            }
            case ACTION_ADD:
            {
               if (items == null)
               {
                  items = new ArrayList();
                  setSubmittedValue(items);
               }

               Object addedItem = null;

               if (getRendererType().equals(RepoConstants.ALFRESCO_FACES_FIELD_RENDERER))
               {
                  UIInput childComponent = (UIInput) this.getChildren().get(0);

                  // as the 'field' is being submitted in the same request we can go
                  // directly to the submitted value to find the entered value
                  addedItem = childComponent.getSubmittedValue();

                  if (childComponent.getRendererType() != null && childComponent.getRendererType().equals(RepoConstants.ALFRESCO_FACES_DATE_PICKER_RENDERER))
                  {
                     // the submitted value for the date is in it's raw form, convert to date
                     int[] parts = (int[]) addedItem;
                     Calendar date = new GregorianCalendar(parts[0], parts[1], parts[2], parts[3], parts[4]);
                     addedItem = date.getTime();
                  }

                  // conversely, we can erase the submitted value
                  childComponent.setSubmittedValue(null);
               }
               else
               {
                  addedItem = getLastItemAdded();

                  this.addingNewItem = Boolean.FALSE;

                  // get hold of the value binding for the lastItemAdded property
                  // and set it to null to show it's been added to the list
                  ValueBinding vb = getValueBinding("lastItemAdded");
                  if (vb != null)
                  {
                     vb.setValue(FacesContext.getCurrentInstance(), null);
                  }
               }

               if (addedItem != null)
               {
                  if (addedItem instanceof Collection)
                     items.addAll((Collection) addedItem);
                  else
                     items.add(addedItem);
               }

               break;
            }
            case ACTION_REMOVE:
            {
               items.remove(assocEvent.RemoveIndex);
               break;
            }
         }
      }
      else
      {
         super.broadcast(event);
      }
   }

   /**
    * @see javax.faces.component.UIComponent#getRendersChildren()
    */
   public boolean getRendersChildren()
   {
      if (getRendererType().equals(RepoConstants.ALFRESCO_FACES_FIELD_RENDERER))
      {
         // if we are using the field renderer always render the childre
         return false;
      }
      else
      {
         // only show the wrapped component when the add button has been clicked
         return !this.addingNewItem.booleanValue();
      }
   }
}

Create source/com/nkics/alfrescox/web/ui/repo/renderer/MultiValueSelectorRendererEx.java
package com.nkics.alfrescox.web.ui.repo.renderer;

import java.io.IOException;
import java.util.Date;
import java.util.List;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;

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.web.app.Application;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.ui.common.converter.XMLDateConverter;
import org.alfresco.web.ui.repo.RepoConstants;
import org.alfresco.web.ui.repo.component.UIMultiValueEditor;
import org.alfresco.web.ui.repo.renderer.MultiValueSelectorRenderer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class MultiValueSelectorRendererEx extends MultiValueSelectorRenderer
{
   Log logger = LogFactory.getLog(getClass());
   
   /**
    * Renders an existing item with a remove button
    *
    * Overridden to display Categories with all parents listed.
    *
    * @param context FacesContext
    * @param component The UIComponent
    * @param out Writer to write output to
    * @param nodeService The NodeService
    * @param index The index of the item
    * @param value The item's value
    * @throws IOException
    * @Override
    */
   protected void renderExistingItem(FacesContext context, UIComponent component, ResponseWriter out, NodeService nodeService, int index, Object value) throws IOException
   {
      out.write("<tr><td class='");
      if (this.highlightedRow)
      {
         out.write("selectedItemsRowAlt");
      }
      else
      {
         out.write("selectedItemsRow");
      }
      out.write("'>");

      if (value instanceof NodeRef)
      {
         QName nodeType = nodeService.getType((NodeRef)value);
         if(logger.isDebugEnabled())
            logger.debug("Node Type for "+value+" is "+nodeType.getLocalName());
         if(nodeType.getLocalName().equals("category"))
            out.write(getFullPath(nodeService, (NodeRef)value));
         else
            out.write(Repository.getNameForNode(nodeService, (NodeRef) value));
      }
      else if (value instanceof Date)
      {
         XMLDateConverter converter = (XMLDateConverter) context.getApplication().createConverter(RepoConstants.ALFRESCO_FACES_XMLDATE_CONVERTER);
         UIComponent childComponent = (UIComponent) component.getChildren().get(0);
         Boolean showTime = (Boolean) childComponent.getAttributes().get("showTime");
         if (showTime != null && showTime.booleanValue())
         {
            converter.setPattern(Application.getMessage(context, "date_time_pattern"));
         } else
         {
            converter.setPattern(Application.getMessage(context, "date_pattern"));
         }

         out.write(converter.getAsString(context, childComponent, value));
      }
      else if (value instanceof Boolean)
      {
         Converter converter = context.getApplication().createConverter(RepoConstants.ALFRESCO_FACES_BOOLEAN_CONVERTER);
         out.write(converter.getAsString(context, (UIComponent) component.getChildren().get(0), value));
      }
      else
      {
         out.write(value.toString());
      }

      out.write("  ");
      out.write("</td><td class='");
      if (this.highlightedRow)
      {
         out.write("selectedItemsRowAlt");
      }
      else
      {
         out.write("selectedItemsRow");
      }
      out.write("'><a href='#' title='");
      out.write(Application.getMessage(context, MSG_REMOVE));
      out.write("' onclick=\"");
      out.write(generateFormSubmit(context, component, UIMultiValueEditor.ACTION_REMOVE + UIMultiValueEditor.ACTION_SEPARATOR + index));
      out.write("\"><img src='");
      out.write(context.getExternalContext().getRequestContextPath());
      out.write("/images/icons/delete.gif' border='0' width='13' height='16'/></a>");

      this.highlightedRow = !this.highlightedRow;
   }
   
   public String getFullPath(NodeService nodeService, NodeRef ref)
   {
      if(logger.isDebugEnabled())
         logger.debug("getFullPath for "+ref);
      StringBuffer buffer = new StringBuffer(Repository.getNameForNode(nodeService, ref));
      List<ChildAssociationRef> parents = null;
      NodeRef parent = ref;
      while(parent!=null && (parents = nodeService.getParentAssocs(parent))!=null)
      {
         parent=null;
         // there shouldn't be more than one ChildAssociationRef
         for(ChildAssociationRef car : parents)
         {
            if(logger.isDebugEnabled())
               logger.debug("getFullPath - considering child association reference: "+car);
            String parentName = Repository.getNameForNode(nodeService, car.getParentRef());
            if(parentName.equalsIgnoreCase("general"))
               break; // break for loop.  parent is still null, which will break the while loop as well.
            parent = car.getParentRef(); // store parent for next loop
            buffer.insert(0,"/");
            buffer.insert(0,parentName);
         }
      }
      
      if(logger.isDebugEnabled())
         logger.debug("getFullPath returning path: "+buffer.toString());
      
      return buffer.toString();
   }
}

Create web/jsp/extension/categories/edit-node-categories.jsp
<%–
* Copyright (C) 2005-2007 Alfresco Software Limited.

* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.

* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.

* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception.  You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
–%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %>
<%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %>

<f:verbatim>
<table cellpadding="2" cellspacing="2" border="0" width="100%">
<tr>
<td colspan="2" class="paddingRow"></td></tr>
<tr>
<td></f:verbatim><h:outputText value="#{msg.categories}" /><f:verbatim>:</td>
<td width="98%">
</f:verbatim>
<r:multiValueSelector id="multi-category-selector"
value="#{DialogManager.bean.categories}"
lastItemAdded="#{DialogManager.bean.addedCategories}"
selectItemMsg="#{msg.select_category}"
selectedItemsMsg="#{msg.selected_categories}"
noSelectedItemsMsg="#{msg.no_selected_categories}"
styleClass="multiValueSelector">
<f:subview id="categorySelector">
<r:ajaxCategorySelector id="catSelector" styleClass="selector"
value="#{DialogManager.bean.addedCategories}"
label="#{msg.select_category_prompt}"
singleSelect="false" />
</f:subview>
</r:multiValueSelector>
<f:verbatim>
</td>
</tr>
<tr><td colspan="2" class="paddingRow"></td></tr>
</table>
</f:verbatim>

Add the following to web/WEB-INF/faces-config-custom.xml
   <render-kit>
      <renderer>
         <component-family>
            org.alfresco.faces.MultiValueEditor
         </component-family>
         <renderer-type>org.alfresco.faces.Selector</renderer-type>
         <renderer-class>
            com.nkics.alfrescox.web.ui.repo.renderer.MultiValueSelectorRendererEx
         </renderer-class>
      </renderer>
   </render-kit>

   <managed-bean>
      <description>
         The bean for the Edit Node Categories screen.
      </description>
      <managed-bean-name>EditNodeCategoriesDialog</managed-bean-name>
      <managed-bean-class>
         com.nkics.alfrescox.web.bean.categories.EditNodeCategoriesDialogEx
      </managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope>
      <managed-property>
         <property-name>nodeService</property-name>
         <value>#{NodeService}</value>
      </managed-property>
      <managed-property>
         <property-name>dictionaryService</property-name>
         <value>#{DictionaryService}</value>
      </managed-property>
   </managed-bean>

   <component>
      <component-type>
         org.alfresco.faces.MultiValueEditor
      </component-type>
      <component-class>
         com.nkics.alfrescox.web.ui.repo.component.UIMultiValueEditorEx
      </component-class>
   </component>

Add this XML to source/alfresco/extension/web-client-config-custom.xml
   <config>
      <dialogs>
         <dialog name="editNodeCategories" page="/jsp/extension/categories/edit-node-categories.jsp"
            managed-bean="EditNodeCategoriesDialog" icon="/images/icons/edit_large.gif" />
      </dialogs>
   </config>

Compile the classes under the source folder.
Create a custom.jar containing:
    alfresco/extension/web-client-config-custom.xml
    com/nkics/alfrescox/web/bean/categories/EditNodeCategoriesDialogEx.class
    com/nkics/alfrescox/web/ui/repo/component/UIMultiValueEditorEx.class
    com/nkics/alfrescox/web/ui/repo/renderer/MultiValueSelectorRendererEx.class
Integrate the custom.jar and other files into the alfresco.war like this:
    WEB-INF/lib/custom.jar
    WEB-INF/faces-config-custom.xml
    jsp/extension/categories/edit-node-categories.jsp
Deploy the integrated alfresco.war, and you're done!

Please let me know if you like this solution.  Also, let me know if you have any problems implementing this solution as it is documented here, and I will do my best to fix this documentation as quickly as I can.

Cheers!   Smiley Happy