Moving to root below branch label

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2005 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2005-12-08 07:13:07 +00:00
commit d051d1153c
920 changed files with 98871 additions and 0 deletions

View File

@@ -0,0 +1,800 @@
/*
* 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.component;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
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 javax.transaction.UserTransaction;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.ui.common.Utils;
import org.alfresco.web.ui.common.WebResources;
/**
* Abstract component to allow the selection of a hierarchical item
*
* @author gavinc
*/
public abstract class AbstractItemSelector extends UIInput
{
private static final String MSG_GO_UP = "go_up";
private static final String MSG_OK = "ok";
private static final String MSG_CANCEL = "cancel";
private final static String OK_BUTTON = "_ok";
protected final static String OPTION = "_option";
protected final static int MODE_BEFORE_SELECTION = 0;
protected final static int MODE_INITIAL_SELECTION = 1;
protected final static int MODE_DRILLDOWN_SELECTION = 2;
protected final static int MODE_CONFIRM_SELECTION = 3;
protected final static int MODE_CANCEL_SELECTION = 4;
/** label to be displayed before a space is selected */
protected String label = null;
/** cellspacing between options */
protected Integer spacing = null;
/** what mode the component is in */
protected int mode = MODE_BEFORE_SELECTION;
/** currently browsing node id */
protected String navigationId = null;
/** id of the initially selected item, if value is not set */
protected String initialSelectionId = null;
/** Flag to show whether the component is disabled */
protected Boolean disabled;
// ------------------------------------------------------------------------------
// Component Impl
/**
* Default constructor
*/
public AbstractItemSelector()
{
// set the default renderer
setRendererType(null);
}
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public abstract String getFamily();
/**
* Retrieves the default label to show if none has been defined and nothing has been selected
*
* @return Default label
*/
public abstract String getDefaultLabel();
/**
* Retrieves the id of the parent node of the current navigation node
*
* @param context The Faces context
* @return Id of the parent node or null if the parent is the root
*/
public abstract String getParentNodeId(FacesContext context);
/**
* Returns a collection of child associations for the current navigation node
*
* @param context The Faces context
* @return The children
*/
public abstract Collection<ChildAssociationRef> getChildrenForNode(FacesContext context);
/**
* Returns a collection of child associations of the root
*
* @param context The Faces context
* @return The root options
*/
public abstract Collection<ChildAssociationRef> getRootChildren(FacesContext context);
/**
* @return The icon image to display next to the item links, or null for no icon
*/
public abstract String getItemIcon();
/**
* @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.label = (String)values[1];
this.spacing = (Integer)values[2];
this.mode = ((Integer)values[3]).intValue();
this.navigationId = (String)values[4];
this.initialSelectionId = (String)values[5];
this.disabled = (Boolean)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.label;
values[2] = this.spacing;
values[3] = this.mode;
values[4] = this.navigationId;
values[5] = this.initialSelectionId;
values[6] = this.disabled;
return (values);
}
/**
* @see javax.faces.component.UIComponentBase#decode(javax.faces.context.FacesContext)
*/
public void decode(FacesContext context)
{
Map requestMap = context.getExternalContext().getRequestParameterMap();
String fieldId = getHiddenFieldName();
String value = (String)requestMap.get(fieldId);
int mode = this.mode;
if (value != null && value.length() != 0)
{
// break up the submitted value into it's parts
// first part is the mode the component is in
// followed by the id of the selection if we are drilling down
String id = null;
int sepIndex = value.indexOf(NamingContainer.SEPARATOR_CHAR);
if (sepIndex != -1)
{
mode = Integer.parseInt(value.substring(0, sepIndex));
if (value.length() > sepIndex + 1)
{
id = value.substring(sepIndex + 1);
}
}
else
{
mode = Integer.parseInt(value);
}
// raise an event so we can pick the changed values up later
ItemSelectorEvent event = new ItemSelectorEvent(this, mode, id);
this.queueEvent(event);
}
if (mode == MODE_CONFIRM_SELECTION)
{
// only bother to check the selection if the mode is set to MODE_AFTER_SELECTION
// see if a selection has been submitted
String selection = (String)requestMap.get(getClientId(context) + OPTION);
if (selection != null && selection.length() != 0)
{
((EditableValueHolder)this).setSubmittedValue(new NodeRef(Repository.getStoreRef(), selection));
}
}
}
/**
* @see javax.faces.component.UIInput#broadcast(javax.faces.event.FacesEvent)
*/
public void broadcast(FacesEvent event) throws AbortProcessingException
{
if (event instanceof ItemSelectorEvent)
{
ItemSelectorEvent spaceEvent = (ItemSelectorEvent)event;
this.mode = spaceEvent.Mode;
this.navigationId = spaceEvent.Id;
}
else
{
super.broadcast(event);
}
}
/**
* @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext)
*/
public void encodeBegin(FacesContext context) throws IOException
{
if (isRendered() == false)
{
return;
}
if (isDisabled())
{
// render a read-only view of the selected category (if any)
ResponseWriter out = context.getResponseWriter();
// see if there is a current value for the category
NodeRef nodeRef = (NodeRef)getSubmittedValue();
if (nodeRef == null)
{
Object val = getValue();
if (val instanceof NodeRef)
{
nodeRef = (NodeRef)val;
}
else if (val instanceof String && ((String)val).length() != 0)
{
nodeRef = new NodeRef((String)val);
}
}
// if there is a value show it's name
if (nodeRef != null)
{
NodeService service = Repository.getServiceRegistry(context).getNodeService();
out.write(Repository.getNameForNode(service, nodeRef));
}
}
else
{
// render an editable control for selecting categories
String clientId = getClientId(context);
StringBuilder buf = new StringBuilder(512);
Map attrs = this.getAttributes();
boolean showValueInHiddenField = false;
NodeRef value = null;
String image = null;
if (getItemIcon() != null)
{
image = "<span style='padding-right:4px'>" + Utils.buildImageTag(context, getItemIcon(), null, "absmiddle") + "</span>";
}
switch (this.mode)
{
case MODE_BEFORE_SELECTION:
case MODE_CONFIRM_SELECTION:
case MODE_CANCEL_SELECTION:
{
UserTransaction tx = null;
try
{
tx = Repository.getUserTransaction(context, true);
tx.begin();
NodeRef submittedValue = (NodeRef)getSubmittedValue();
if (submittedValue != null)
{
value = submittedValue;
}
else
{
Object val = getValue();
if (val instanceof NodeRef)
{
value = (NodeRef)val;
}
else if (val instanceof String && ((String)val).length() != 0)
{
value = new NodeRef((String)val);
}
}
// show just the initial or current selection link
String label;
if (value == null)
{
label = getLabel();
// if the label is still null get the default from the message bundle
if (label == null)
{
label = getDefaultLabel();
}
}
else
{
label = Repository.getNameForNode(getNodeService(context), value);
showValueInHiddenField = true;
}
// output surrounding span for style purposes
buf.append("<span");
if (attrs.get("style") != null)
{
buf.append(" style=\"")
.append(attrs.get("style"))
.append('"');
}
if (attrs.get("styleClass") != null)
{
buf.append(" class=")
.append(attrs.get("styleClass"));
}
buf.append(">");
// rendering as initial selection mode means the sibilings of the selected
// item are shown instead of the children on first click in.
int theMode = MODE_INITIAL_SELECTION;
// if we have an initial selection and no value set the initial one up
if (value == null && this.getInitialSelection() != null)
{
value = new NodeRef(Repository.getStoreRef(), this.getInitialSelection());
}
// field value is whether we are picking and the current or parent Id value
String fieldValue;
if (value != null)
{
fieldValue = encodeFieldValues(theMode, value.getId());
}
else
{
fieldValue = encodeFieldValues(theMode, null);
}
buf.append("<a href='#' onclick=\"");
buf.append(Utils.generateFormSubmit(context, this, getHiddenFieldName(), fieldValue));
buf.append('"');
if (attrs.get("nodeStyle") != null)
{
buf.append(" style=\"")
.append(attrs.get("nodeStyle"))
.append('"');
}
if (attrs.get("nodeStyleClass") != null)
{
buf.append(" class=")
.append(attrs.get("nodeStyleClass"));
}
buf.append(">")
.append(label)
.append("</a></span>");
tx.commit();
}
catch (Throwable err)
{
try { if (tx != null) {tx.rollback();} } catch (Exception tex) {}
Utils.addErrorMessage(err.getMessage(), err);
}
break;
}
case MODE_DRILLDOWN_SELECTION:
case MODE_INITIAL_SELECTION:
{
// show the picker list
// get the children of the node ref to show
NodeService service = getNodeService(context);
UserTransaction tx = null;
try
{
tx = Repository.getUserTransaction(context, true);
tx.begin();
buf.append("<table border=0 cellspacing=1 cellpadding=1");
if (attrs.get("style") != null)
{
buf.append(" style=\"")
.append(attrs.get("style"))
.append('"');
}
if (attrs.get("styleClass") != null)
{
buf.append(" class=")
.append(attrs.get("styleClass"));
}
buf.append(">");
// if we are setting up the initial selection we need to get the
// parent id of the initial selection so the user can actually see
// the item when the list is rendered
if (this.mode == MODE_INITIAL_SELECTION)
{
this.navigationId = getParentNodeId(context);
}
// render "Go Up" link
if (this.navigationId != null)
{
// get the id of the parent node of the current navigation node,
// null indicates we are at the root level
String id = getParentNodeId(context);
buf.append("<tr><td></td><td>");
String upImage = Utils.buildImageTag(context, WebResources.IMAGE_GO_UP, null, "absmiddle");
// render a link to the parent node
renderNodeLink(context, id, Application.getMessage(context, MSG_GO_UP), upImage, buf);
buf.append("</td></tr>");
}
String okButtonId = clientId + OK_BUTTON;
boolean okButtonEnabled = false;
// display the children of the specified navigation node ID
if (this.navigationId != null)
{
// get a list of children for the current navigation node
Collection<ChildAssociationRef> childRefs = getChildrenForNode(context);
for (ChildAssociationRef childRef : childRefs)
{
// render each child found
String childId = childRef.getChildRef().getId();
buf.append("<tr><td><input type='radio' name='")
.append(clientId).append(OPTION).append("' value='")
.append(childId).append("'");
if (childId.equals(this.initialSelectionId))
{
buf.append(" checked");
// if any radio buttons are checked, the OK button must start enabled
okButtonEnabled = true;
// now remove the initial selection as we only need it the first time
this.initialSelectionId = null;
}
buf.append(" onclick=\"javascript:document.getElementById('")
.append(okButtonId)
.append("').disabled=false;\"");
buf.append("/></td><td>");
// get the name for the child and output as link
NodeRef childNodeRef = new NodeRef(Repository.getStoreRef(), childId);
String name = Repository.getNameForNode(service, childNodeRef);
renderNodeLink(context, childId, name, image, buf);
buf.append("</td></tr>");
}
}
else
{
// no node set - special case so show the root items
Collection<ChildAssociationRef> childRefs = getRootChildren(context);
for (ChildAssociationRef childRef : childRefs)
{
// render each root category found
String childId = childRef.getChildRef().getId();
buf.append("<tr><td><input type='radio' name='")
.append(clientId).append(OPTION).append("' value='")
.append(childId).append("'");
if (childId.equals(this.initialSelectionId))
{
buf.append(" checked");
// if any radio buttons are checked, the OK button must start enabled
okButtonEnabled = true;
// now remove the initial selection as we only need it the first time
this.initialSelectionId = null;
}
buf.append(" onclick=\"javascript:document.getElementById('")
.append(okButtonId)
.append("').disabled=false;\"");
buf.append("/></td><td>");
// get the name for the child (rather than association name)
NodeRef childNodeRef = new NodeRef(Repository.getStoreRef(), childId);
String name = Repository.getNameForNode(service, childNodeRef);
renderNodeLink(context, childId, name, image, buf);
buf.append("</td></tr>");
}
}
// render OK button
String fieldValue = encodeFieldValues(MODE_CONFIRM_SELECTION, null);
buf.append("<tr style='padding-top:4px'><td></td><td align=center>")
.append("<input type='button' ")
.append(okButtonEnabled == false ? "disabled" : "")
.append(" onclick=\"")
.append(Utils.generateFormSubmit(context, this, getHiddenFieldName(), fieldValue))
.append("\" value='")
.append(Application.getMessage(context, MSG_OK))
.append("' id='")
.append(okButtonId)
.append("'>&nbsp;");
// render Cancel button
fieldValue = encodeFieldValues(MODE_CANCEL_SELECTION, null);
buf.append("<input type='button' onclick=\"")
.append(Utils.generateFormSubmit(context, this, getHiddenFieldName(), fieldValue))
.append("\" value='")
.append(Application.getMessage(context, MSG_CANCEL))
.append("'></td></tr>");
buf.append("</table>");
tx.commit();
}
catch (Throwable err)
{
try { if (tx != null) {tx.rollback();} } catch (Exception tex) {}
throw new RuntimeException(err);
}
break;
}
}
// output a hidden field containing the currently selected NodeRef so that JavaScript
// can be used to check the state of the component
buf.append("<input type='hidden' name='");
buf.append(clientId);
buf.append("_selected' id='");
buf.append(clientId);
buf.append("_selected' value='");
if (showValueInHiddenField)
{
buf.append(value);
}
buf.append("'/>");
context.getResponseWriter().write(buf.toString());
}
}
// ------------------------------------------------------------------------------
// Strongly typed component property accessors
/**
* @return Returns the label.
*/
public String getLabel()
{
ValueBinding vb = getValueBinding("label");
if (vb != null)
{
this.label = (String)vb.getValue(getFacesContext());
}
return this.label;
}
/**
* @param label The label to set.
*/
public void setLabel(String label)
{
this.label = label;
}
/**
* @return Returns the cell spacing value between space options. Default is 2.
*/
public Integer getSpacing()
{
ValueBinding vb = getValueBinding("spacing");
if (vb != null)
{
this.spacing = (Integer)vb.getValue(getFacesContext());
}
if (this.spacing != null)
{
return this.spacing.intValue();
}
else
{
// return default
return 2;
}
}
/**
* @param spacing The spacing to set.
*/
public void setSpacing(Integer spacing)
{
this.spacing = spacing;
}
/**
* @return Returns the initial selecttion.
*/
public String getInitialSelection()
{
ValueBinding vb = getValueBinding("initialSelection");
if (vb != null)
{
this.initialSelectionId = (String)vb.getValue(getFacesContext());
}
return this.initialSelectionId;
}
/**
* @param initialSelection The initial selection to set.
*/
public void setInitialSelection(String initialSelection)
{
this.initialSelectionId = initialSelection;
}
/**
* Determines whether the component should be rendered in a disabled state
*
* @return Returns whether the component is disabled
*/
public boolean isDisabled()
{
if (this.disabled == null)
{
ValueBinding vb = getValueBinding("disabled");
if (vb != null)
{
this.disabled = (Boolean)vb.getValue(getFacesContext());
}
}
if (this.disabled == null)
{
this.disabled = Boolean.FALSE;
}
return this.disabled;
}
/**
* Determines whether the component should be rendered in a disabled state
*
* @param disabled true to disable the component
*/
public void setDisabled(boolean disabled)
{
this.disabled = disabled;
}
// ------------------------------------------------------------------------------
// Protected helpers
/**
* We use a unique hidden field name based on our client Id.
* This is on the assumption that there won't be many selectors on screen at once!
* Also means we have less values to decode on submit.
*
* @return hidden field name
*/
protected String getHiddenFieldName()
{
return this.getClientId(getFacesContext());
}
protected String encodeFieldValues(int mode, String id)
{
if (id != null)
{
return Integer.toString(mode) + NamingContainer.SEPARATOR_CHAR + id;
}
else
{
return Integer.toString(mode);
}
}
/**
* Render a node descendant as a clickable link
*
* @param context FacesContext
* @param childRef The ChildAssocRef of the child to render an HTML link for
*
* @return HTML for a descendant link
*/
protected String renderNodeLink(FacesContext context, String id, String name, String prefix, StringBuilder buf)
{
buf.append("<a href='#' onclick=\"");
String fieldValue = encodeFieldValues(MODE_DRILLDOWN_SELECTION, id);
buf.append(Utils.generateFormSubmit(context, this, getHiddenFieldName(), fieldValue));
buf.append('"');
Map attrs = this.getAttributes();
if (attrs.get("nodeStyle") != null)
{
buf.append(" style=\"")
.append(attrs.get("nodeStyle"))
.append('"');
}
if (attrs.get("nodeStyleClass") != null)
{
buf.append(" class=")
.append(attrs.get("nodeStyleClass"));
}
buf.append('>');
if (prefix != null)
{
buf.append(prefix);
}
buf.append(Utils.encode(name));
buf.append("</a>");
return buf.toString();
}
/**
* Use Spring JSF integration to return the Node Service bean instance
*
* @param context FacesContext
*
* @return Node Service bean instance or throws exception if not found
*/
protected static NodeService getNodeService(FacesContext context)
{
NodeService service = Repository.getServiceRegistry(context).getNodeService();
if (service == null)
{
throw new IllegalStateException("Unable to obtain NodeService bean reference.");
}
return service;
}
/**
* Use Spring JSF integration to return the Dictionary Service bean instance
*
* @param context FacesContext
*
* @return Dictionary Service bean instance or throws exception if not found
*/
protected static DictionaryService getDictionaryService(FacesContext context)
{
DictionaryService service = Repository.getServiceRegistry(context).getDictionaryService();
if (service == null)
{
throw new IllegalStateException("Unable to obtain DictionaryService bean reference.");
}
return service;
}
/**
* Class representing the clicking of a breadcrumb element.
*/
public static class ItemSelectorEvent extends ActionEvent
{
public ItemSelectorEvent(UIComponent component, int mode, String id)
{
super(component);
Mode = mode;
Id = id;
}
public int Mode;
private String Id;
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.component;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.web.ui.common.component.IBreadcrumbHandler;
/**
* @author Kevin Roast
*/
public interface IRepoBreadcrumbHandler extends IBreadcrumbHandler
{
/**
* Return a NodeRef relevant to this breadcrumb element, if any
*
* @return a NodeRef if relevant to the breadcrumb element, null otherwise
*/
public NodeRef getNodeRef();
}

View File

@@ -0,0 +1,137 @@
/*
* 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.component;
import java.util.Collection;
import javax.faces.context.FacesContext;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.search.CategoryService;
import org.alfresco.service.cmr.search.CategoryService.Depth;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.ui.repo.WebResources;
/**
* Component to allow the selection of a category
*
* @author gavinc
*/
public class UICategorySelector extends AbstractItemSelector
{
// ------------------------------------------------------------------------------
// Component Impl
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.CategorySelector";
}
/**
*
* @see org.alfresco.web.ui.repo.component.AbstractItemSelector#getDefaultLabel()
*/
public String getDefaultLabel()
{
return Application.getMessage(FacesContext.getCurrentInstance(), "select_category_prompt");
}
/**
* Use Spring JSF integration to return the category service bean instance
*
* @param context FacesContext
*
* @return category service bean instance or throws runtime exception if not found
*/
private static CategoryService getCategoryService(FacesContext context)
{
CategoryService service = Repository.getServiceRegistry(context).getCategoryService();
if (service == null)
{
throw new IllegalStateException("Unable to obtain CategoryService bean reference.");
}
return service;
}
/**
* Returns the parent id of the current category, or null if the parent has the category root type
*
* @see org.alfresco.web.ui.repo.component.AbstractItemSelector#getParentNodeId(javax.faces.context.FacesContext)
*/
public String getParentNodeId(FacesContext context)
{
String id = null;
if (this.navigationId != null)
{
ChildAssociationRef parentRef = getNodeService(context).getPrimaryParent(
new NodeRef(Repository.getStoreRef(), this.navigationId));
Node parentNode = new Node(parentRef.getParentRef());
DictionaryService dd = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getDictionaryService();
if (dd.isSubClass(parentNode.getType(), ContentModel.TYPE_CATEGORYROOT) == false)
{
id = parentRef.getParentRef().getId();
}
}
return id;
}
/**
* Returns the child categories of the current navigation node
*
* @see org.alfresco.web.ui.repo.component.AbstractItemSelector#getChildrenForNode(javax.faces.context.FacesContext)
*/
public Collection<ChildAssociationRef> getChildrenForNode(FacesContext context)
{
NodeRef nodeRef = new NodeRef(Repository.getStoreRef(), this.navigationId);
Collection<ChildAssociationRef> childRefs = getCategoryService(context).getChildren(nodeRef,
CategoryService.Mode.SUB_CATEGORIES, CategoryService.Depth.IMMEDIATE);
return childRefs;
}
/**
* Returns the root categories
*
* @see org.alfresco.web.ui.repo.component.AbstractItemSelector#getRootChildren(javax.faces.context.FacesContext)
*/
public Collection<ChildAssociationRef> getRootChildren(FacesContext context)
{
return getCategoryService(context).getCategories(Repository.getStoreRef(), ContentModel.ASPECT_GEN_CLASSIFIABLE, Depth.IMMEDIATE);
}
/**
* @see org.alfresco.web.ui.repo.component.AbstractItemSelector#getItemIcon()
*/
public String getItemIcon()
{
return WebResources.IMAGE_CATEGORY;
}
}

View File

@@ -0,0 +1,405 @@
/*
* 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.component;
import java.io.IOException;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.el.ValueBinding;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.lock.LockService;
import org.alfresco.service.cmr.lock.LockStatus;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.web.app.Application;
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.WebResources;
/**
* @author Kevin Roast
*/
public class UILockIcon extends SelfRenderingComponent
{
private static final String MSG_LOCKED_YOU = "locked_you";
private static final String MSG_LOCKED_USER = "locked_user";
// ------------------------------------------------------------------------------
// Component implementation
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.LockIcon";
}
/**
* @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.lockImage = (String)values[1];
this.lockOwnerImage = (String)values[2];
this.align = (String)values[3];
this.width = ((Integer)values[4]).intValue();
this.height = ((Integer)values[5]).intValue();
this.value = 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.lockImage;
values[2] = this.lockOwnerImage;
values[3] = this.align;
values[4] = this.width;
values[5] = this.height;
values[6] = this.value;
return (values);
}
/**
* @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext)
*/
public void encodeBegin(FacesContext context) throws IOException
{
if (isRendered() == false)
{
return;
}
ResponseWriter out = context.getResponseWriter();
// get the value and see if the image is locked
NodeService nodeService = getNodeService(context);
boolean locked = false;
boolean lockedOwner = false;
Object val = getValue();
NodeRef ref = null;
if (val instanceof NodeRef)
{
ref = (NodeRef)val;
if (nodeService.exists(ref) && nodeService.hasAspect(ref, ContentModel.ASPECT_LOCKABLE) == true)
{
LockStatus lockStatus = getLockService(context).getLockStatus(ref);
locked = (lockStatus == LockStatus.LOCKED || lockStatus == LockStatus.LOCK_OWNER);
lockedOwner = (lockStatus == LockStatus.LOCK_OWNER);
}
}
String msg = null;
if (locked == true)
{
out.write("&nbsp;<img");
outputAttribute(out, getAttributes().get("style"), "style");
outputAttribute(out, getAttributes().get("styleClass"), "class");
outputAttribute(out, getAlign(), "align");
outputAttribute(out, getWidth(), "width");
outputAttribute(out, getHeight(), "height");
out.write("src=\"");
out.write(context.getExternalContext().getRequestContextPath());
String lockImage = getLockImage();
if (lockedOwner == true && getLockOwnerImage() != null)
{
lockImage = getLockOwnerImage();
}
out.write(lockImage);
out.write("\" border=0");
if (lockedOwner == true)
{
msg = Application.getMessage(context, MSG_LOCKED_YOU);
if (getLockedOwnerTooltip() != null)
{
msg = getLockedOwnerTooltip();
}
}
else
{
String lockingUser = (String)nodeService.getProperty(ref, ContentModel.PROP_LOCK_OWNER);
msg = Application.getMessage(context, MSG_LOCKED_USER);
if (getLockedUserTooltip() != null)
{
msg = getLockedUserTooltip();
}
StringBuilder buf = new StringBuilder(32);
msg = buf.append(msg).append(" '")
.append(lockingUser)
.append("'").toString();
}
msg = Utils.encode(msg);
out.write(" alt=\"");
out.write(msg);
out.write("\" title=\"");
out.write(msg);
out.write("\">");
}
}
/**
* Use Spring JSF integration to return the Node Service bean instance
*
* @param context FacesContext
*
* @return Node Service bean instance or throws exception if not found
*/
private static NodeService getNodeService(FacesContext context)
{
NodeService service = Repository.getServiceRegistry(context).getNodeService();
if (service == null)
{
throw new IllegalStateException("Unable to obtain NodeService bean reference.");
}
return service;
}
/**
* Use Spring JSF integration to return the Lock Service bean instance
*
* @param context FacesContext
*
* @return Lock Service bean instance or throws exception if not found
*/
private static LockService getLockService(FacesContext context)
{
LockService service = Repository.getServiceRegistry(context).getLockService();
if (service == null)
{
throw new IllegalStateException("Unable to obtain LockService bean reference.");
}
return service;
}
// ------------------------------------------------------------------------------
// Strongly typed component property accessors
/**
* @return the image to display as the lock icon. A default is provided if none is set.
*/
public String getLockImage()
{
ValueBinding vb = getValueBinding("lockImage");
if (vb != null)
{
this.lockImage = (String)vb.getValue(getFacesContext());
}
return this.lockImage;
}
/**
* @param lockImage the image to display as the lock icon. A default is provided if none is set.
*/
public void setLockImage(String lockImage)
{
this.lockImage = lockImage;
}
/**
* @return Returns the image to display if the owner has the lock.
*/
public String getLockOwnerImage()
{
ValueBinding vb = getValueBinding("lockOwnerImage");
if (vb != null)
{
this.lockOwnerImage = (String)vb.getValue(getFacesContext());
}
return this.lockOwnerImage;
}
/**
* @param lockOwnerImage the image to display if the owner has the lock.
*/
public void setLockOwnerImage(String lockOwnerImage)
{
this.lockOwnerImage = lockOwnerImage;
}
/**
* @return Returns the image alignment value.
*/
public String getAlign()
{
ValueBinding vb = getValueBinding("align");
if (vb != null)
{
this.align = (String)vb.getValue(getFacesContext());
}
return this.align;
}
/**
* @param align The image alignment value to set.
*/
public void setAlign(String align)
{
this.align = align;
}
/**
* @return Returns the icon height.
*/
public int getHeight()
{
ValueBinding vb = getValueBinding("height");
if (vb != null)
{
Integer value = (Integer)vb.getValue(getFacesContext());
if (value != null)
{
this.height = value.intValue();
}
}
return this.height;
}
/**
* @param height The icon height to set.
*/
public void setHeight(int height)
{
this.height = height;
}
/**
* @return Returns the icon width.
*/
public int getWidth()
{
ValueBinding vb = getValueBinding("width");
if (vb != null)
{
Integer value = (Integer)vb.getValue(getFacesContext());
if (value != null)
{
this.width = value.intValue();
}
}
return this.width;
}
/**
* @param width The iconwidth to set.
*/
public void setWidth(int width)
{
this.width = width;
}
/**
* @return Returns the lockedOwnerTooltip.
*/
public String getLockedOwnerTooltip()
{
ValueBinding vb = getValueBinding("lockedOwnerTooltip");
if (vb != null)
{
this.lockedOwnerTooltip = (String)vb.getValue(getFacesContext());
}
return this.lockedOwnerTooltip;
}
/**
* @param lockedOwnerTooltip The lockedOwnerTooltip to set.
*/
public void setLockedOwnerTooltip(String lockedOwnerTooltip)
{
this.lockedOwnerTooltip = lockedOwnerTooltip;
}
/**
* @return Returns the lockedUserTooltip.
*/
public String getLockedUserTooltip()
{
ValueBinding vb = getValueBinding("lockedUserTooltip");
if (vb != null)
{
this.lockedUserTooltip = (String)vb.getValue(getFacesContext());
}
return this.lockedUserTooltip;
}
/**
* @param lockedUserTooltip The lockedUserTooltip to set.
*/
public void setLockedUserTooltip(String lockedUserTooltip)
{
this.lockedUserTooltip = lockedUserTooltip;
}
/**
* @return Returns the value (Node or NodeRef)
*/
public Object getValue()
{
ValueBinding vb = getValueBinding("value");
if (vb != null)
{
this.value = vb.getValue(getFacesContext());
}
return this.value;
}
/**
* @param value The Node or NodeRef value to set.
*/
public void setValue(Object value)
{
this.value = value;
}
private String lockImage = WebResources.IMAGE_LOCK;
private String lockOwnerImage = WebResources.IMAGE_LOCK_OWNER;
private String align = null;
private int width = 16;
private int height = 16;
private String lockedOwnerTooltip = null;
private String lockedUserTooltip = null;
private Object value = null;
}

View File

@@ -0,0 +1,361 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the GNU Lesser General Public License as
* published by the Free Software Foundation; either version
* 2.1 of the License, or (at your option) any later version.
* You may obtain a copy of the License at
*
* http://www.gnu.org/licenses/lgpl.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.component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.FacesEvent;
import org.alfresco.web.app.Application;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* 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.
*
* @author gavinc
*/
public class UIMultiValueEditor extends UIInput
{
private static final Log logger = LogFactory.getLog(UIMultiValueEditor.class);
private static final String MSG_SELECTED_ITEMS = "selected_items";
private static final String MSG_NO_SELECTED_ITEMS = "no_selected_items";
private 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;
private Boolean addingNewItem = Boolean.FALSE;
private Boolean readOnly;
private Object lastItemAdded;
private String selectItemMsg;
private String selectedItemsMsg;
private String noSelectedItemsMsg;
// ------------------------------------------------------------------------------
// Component implementation
/**
* Default constructor
*/
public UIMultiValueEditor()
{
setRendererType("org.alfresco.faces.List");
}
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.MultiValueEditor";
}
/**
* @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 selectedItemsMsg 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);
}
items.add(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);
}
break;
}
case ACTION_REMOVE:
{
items.remove(assocEvent.RemoveIndex);
break;
}
}
}
else
{
super.broadcast(event);
}
}
/**
* @see javax.faces.component.UIComponent#getRendersChildren()
*/
public boolean getRendersChildren()
{
// only show the wrapped component when the add button has been clicked
return !this.addingNewItem.booleanValue();
}
// ------------------------------------------------------------------------------
// Inner classes
/**
* Class representing an action relevant to the ChildAssociationEditor component.
*/
public static class MultiValueEditorEvent extends ActionEvent
{
public int Action;
public int RemoveIndex;
public MultiValueEditorEvent(UIComponent component, int action, int removeIndex)
{
super(component);
this.Action = action;
this.RemoveIndex = removeIndex;
}
}
}

View File

@@ -0,0 +1,176 @@
/*
* 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.component;
import javax.faces.component.UICommand;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.ActionEvent;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* @author Kevin Roast
*/
public class UINodeDescendants extends UICommand
{
// ------------------------------------------------------------------------------
// Construction
/**
* Default constructor
*/
public UINodeDescendants()
{
setRendererType("org.alfresco.faces.NodeDescendantsLinkRenderer");
}
// ------------------------------------------------------------------------------
// Component implementation
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.NodeDescendants";
}
/**
* @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.maxChildren = (Integer)values[1];
this.showEllipses = (Boolean)values[2];
}
/**
* @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
*/
public Object saveState(FacesContext context)
{
Object values[] = new Object[3];
// standard component attributes are saved by the super class
values[0] = super.saveState(context);
values[1] = this.maxChildren;
values[2] = this.showEllipses;
return (values);
}
// ------------------------------------------------------------------------------
// Strongly typed component property accessors
/**
* @return the maximum number of child descendants to be displayed, default maximum is 3.
*/
public int getMaxChildren()
{
ValueBinding vb = getValueBinding("maxChildren");
if (vb != null)
{
this.maxChildren = (Integer)vb.getValue(getFacesContext());
}
if (this.maxChildren != null)
{
return this.maxChildren.intValue();
}
else
{
// return default
return 3;
}
}
/**
* @param value The maximum allowed before the no more links are shown
*/
public void setMaxChildren(int value)
{
if (value > 0 && value <= 256)
{
this.maxChildren = Integer.valueOf(value);
}
}
/**
* @return whether to show ellipses "..." if more descendants than the maxChildren value are found
*/
public boolean getShowEllipses()
{
ValueBinding vb = getValueBinding("showEllipses");
if (vb != null)
{
this.showEllipses = (Boolean)vb.getValue(getFacesContext());
}
if (this.showEllipses != null)
{
return this.showEllipses.booleanValue();
}
else
{
// return default
return true;
}
}
/**
* @param showLink True to show ellipses "..." if more descendants than maxChildren are found
*/
public void setShowEllipses(boolean showEllipses)
{
this.showEllipses = Boolean.valueOf(showEllipses);
}
// ------------------------------------------------------------------------------
// Inner classes
/**
* Class representing the clicking of a node descendant element.
*/
public static class NodeSelectedEvent extends ActionEvent
{
public NodeSelectedEvent(UIComponent component, NodeRef nodeRef, boolean isParent)
{
super(component);
this.NodeReference = nodeRef;
this.IsParent = isParent;
}
public NodeRef NodeReference;
public boolean IsParent;
}
// ------------------------------------------------------------------------------
// Private data
/** maximum number of child descendants to display */
private Integer maxChildren = null;
/** whether to show ellipses if more descendants than the maxChildren are found */
private Boolean showEllipses = null;
}

View File

@@ -0,0 +1,70 @@
/*
* 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.component;
import javax.faces.component.UICommand;
import javax.faces.component.UIComponent;
import javax.faces.event.ActionEvent;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* @author Kevin Roast
*/
public class UINodePath extends UICommand
{
// ------------------------------------------------------------------------------
// Construction
/**
* Default constructor
*/
public UINodePath()
{
setRendererType("org.alfresco.faces.NodePathLinkRenderer");
}
// ------------------------------------------------------------------------------
// Component implementation
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.NodePath";
}
// ------------------------------------------------------------------------------
// Inner classes
/**
* Class representing the clicking of a part of the path element.
*/
public static class PathElementEvent extends ActionEvent
{
public PathElementEvent(UIComponent component, NodeRef nodeRef)
{
super(component);
this.NodeReference = nodeRef;
}
public NodeRef NodeReference;
}
}

View File

@@ -0,0 +1,332 @@
/*
* 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.component;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.component.UIOutput;
import javax.faces.component.UIPanel;
import javax.faces.component.UISelectBoolean;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.el.ValueBinding;
import org.alfresco.config.ConfigService;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryException;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.namespace.QName;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.config.ClientConfigElement;
import org.alfresco.web.config.ClientConfigElement.CustomProperty;
import org.alfresco.web.ui.common.ComponentConstants;
import org.alfresco.web.ui.common.Utils;
import org.alfresco.web.ui.common.component.SelfRenderingComponent;
import org.alfresco.web.ui.repo.RepoConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.jsf.FacesContextUtils;
/**
* @author Kevin Roast
*/
public class UISearchCustomProperties extends SelfRenderingComponent implements NamingContainer
{
public static final String PREFIX_DATE_TO = "to_";
public static final String PREFIX_DATE_FROM = "from_";
private static final String MSG_TO = "to";
private static final String MSG_FROM = "from";
private static Log logger = LogFactory.getLog(UISearchCustomProperties.class);
// ------------------------------------------------------------------------------
// Component implementation
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.AdvancedSearch";
}
/**
* @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext)
*/
public void encodeBegin(FacesContext context) throws IOException
{
if (isRendered() == false)
{
return;
}
ResponseWriter out = context.getResponseWriter();
if (getChildCount() == 0)
{
createComponentsFromConfig(context);
}
// encode the components in a 2 column table
out.write("<table cellspacing=2 cellpadding=2 border=0");
outputAttribute(out, getAttributes().get("styleClass"), "class");
outputAttribute(out, getAttributes().get("style"), "style");
out.write('>');
List<UIComponent> children = getChildren();
int colCounter = 0;
for (int i=0; i<children.size(); i++)
{
UIComponent component = children.get(i);
if (component instanceof UIPanel)
{
out.write("<tr><td colspan=2>");
Utils.encodeRecursive(context, component);
out.write("</td></tr>");
colCounter += 2;
}
else
{
if ((colCounter & 1) == 0)
{
out.write("<tr>");
}
out.write("<td>");
Utils.encodeRecursive(context, component);
out.write("</td>");
if ((colCounter & 1) == 1)
{
out.write("</tr>");
}
colCounter++;
}
}
out.write("</table>");
}
/**
* Build the components from the Advanced Search config entries
*
* @param context FacesContext
*/
private void createComponentsFromConfig(FacesContext context)
{
DictionaryService dd = Repository.getServiceRegistry(context).getDictionaryService();
ConfigService configService = Application.getConfigService(context);
ClientConfigElement clientConfig = (ClientConfigElement)configService.getGlobalConfig().getConfigElement(
ClientConfigElement.CONFIG_ELEMENT_ID);
// create an appropriate component for each custom property
// using the DataDictionary to look-up labels and value types
String beanBinding = (String)getAttributes().get("bean") + '.' + (String)getAttributes().get("var");
List<CustomProperty> props = clientConfig.getCustomProperties();
if (props != null)
{
for (CustomProperty property : props)
{
try
{
// try to find the Property definition for the specified Type or Aspect
PropertyDefinition propDef = null;
if (property.Type != null)
{
QName type = Repository.resolveToQName(property.Type);
TypeDefinition typeDef = dd.getType(type);
propDef = typeDef.getProperties().get(Repository.resolveToQName(property.Property));
}
else if (property.Aspect != null)
{
QName aspect = Repository.resolveToQName(property.Aspect);
AspectDefinition aspectDef = dd.getAspect(aspect);
propDef = aspectDef.getProperties().get(Repository.resolveToQName(property.Property));
}
// if we found a def, then we can build components to represent it
if (propDef != null)
{
// TODO: add display label I18N message support to configelement and here
String label = propDef.getTitle() != null ? propDef.getTitle() : propDef.getName().getLocalName();
// special handling for Date and DateTime
DataTypeDefinition dataTypeDef = propDef.getDataType();
if (DataTypeDefinition.DATE.equals(dataTypeDef.getName()) || DataTypeDefinition.DATETIME.equals(dataTypeDef.getName()))
{
getChildren().add( generateControl(context, propDef, label, beanBinding) );
}
else
{
getChildren().add( generateLabel(context, label) );
getChildren().add( generateControl(context, propDef, null, beanBinding) );
}
}
}
catch (DictionaryException ddErr)
{
logger.warn("Error building custom properties for Advanced Search: " + ddErr.getMessage());
}
}
}
}
/**
* Generates a JSF OutputText component/renderer
*
* @param context JSF context
* @param displayLabel The display label text
* @param parent The parent component for the label
*
* @return UIComponent
*/
private UIComponent generateLabel(FacesContext context, String displayLabel)
{
UIOutput label = (UIOutput)context.getApplication().createComponent(ComponentConstants.JAVAX_FACES_OUTPUT);
label.setId(context.getViewRoot().createUniqueId());
label.setRendererType(ComponentConstants.JAVAX_FACES_TEXT);
label.setValue(displayLabel + ": ");
return label;
}
/**
* Generates an appropriate control for the given property
*
* @param context JSF context
* @param propDef The definition of the property to create the control for
* @param displayLabel Display label for the component
* @param beanBinding Combined name of the value bound bean and variable used for value binding expression
*
* @return UIComponent
*/
private UIComponent generateControl(FacesContext context, PropertyDefinition propDef, String displayLabel, String beanBinding)
{
UIComponent control = null;
DataTypeDefinition dataTypeDef = propDef.getDataType();
QName typeName = dataTypeDef.getName();
javax.faces.application.Application facesApp = context.getApplication();
// create default value binding to a Map of values with a defined name
ValueBinding vb = facesApp.createValueBinding(
"#{" + beanBinding + "[\"" + propDef.getName().toString() + "\"]}");
// generate the appropriate input field
if (typeName.equals(DataTypeDefinition.BOOLEAN))
{
control = (UISelectBoolean)facesApp.createComponent(ComponentConstants.JAVAX_FACES_SELECT_BOOLEAN);
control.setRendererType(ComponentConstants.JAVAX_FACES_CHECKBOX);
control.setValueBinding("value", vb);
}
else if (typeName.equals(DataTypeDefinition.CATEGORY))
{
control = (UICategorySelector)facesApp.createComponent(RepoConstants.ALFRESCO_FACES_CATEGORY_SELECTOR);
control.setValueBinding("value", vb);
}
else if (typeName.equals(DataTypeDefinition.DATETIME) || typeName.equals(DataTypeDefinition.DATE))
{
Boolean showTime = Boolean.valueOf(typeName.equals(DataTypeDefinition.DATETIME));
// Need to output component for From and To date selectors and labels
// also neeed checkbox for enable/disable state - requires an outer wrapper component
control = (UIPanel)facesApp.createComponent(ComponentConstants.JAVAX_FACES_PANEL);
control.setRendererType(ComponentConstants.JAVAX_FACES_GRID);
control.getAttributes().put("columns", Integer.valueOf(2));
// enabled state checkbox
UIInput checkbox = (UIInput)facesApp.createComponent(ComponentConstants.JAVAX_FACES_SELECT_BOOLEAN);
checkbox.setRendererType(ComponentConstants.JAVAX_FACES_CHECKBOX);
checkbox.setId(context.getViewRoot().createUniqueId());
ValueBinding vbCheckbox = facesApp.createValueBinding(
"#{" + beanBinding + "[\"" + propDef.getName().toString() + "\"]}");
checkbox.setValueBinding("value", vbCheckbox);
control.getChildren().add(checkbox);
// main display label
UIOutput label = (UIOutput)context.getApplication().createComponent(ComponentConstants.JAVAX_FACES_OUTPUT);
label.setId(context.getViewRoot().createUniqueId());
label.setRendererType(ComponentConstants.JAVAX_FACES_TEXT);
label.setValue(displayLabel + ":");
control.getChildren().add(label);
// from date label
UIOutput labelFromDate = (UIOutput)context.getApplication().createComponent(ComponentConstants.JAVAX_FACES_OUTPUT);
labelFromDate.setId(context.getViewRoot().createUniqueId());
labelFromDate.setRendererType(ComponentConstants.JAVAX_FACES_TEXT);
labelFromDate.setValue(Application.getMessage(context, MSG_FROM));
control.getChildren().add(labelFromDate);
// from date control
UIInput inputFromDate = (UIInput)facesApp.createComponent(ComponentConstants.JAVAX_FACES_INPUT);
inputFromDate.setId(context.getViewRoot().createUniqueId());
inputFromDate.setRendererType(RepoConstants.ALFRESCO_FACES_DATE_PICKER_RENDERER);
inputFromDate.getAttributes().put("startYear", new Integer(1970));
inputFromDate.getAttributes().put("yearCount", new Integer(50));
inputFromDate.getAttributes().put("showTime", showTime);
ValueBinding vbFromDate = facesApp.createValueBinding(
"#{" + beanBinding + "[\"" + PREFIX_DATE_FROM + propDef.getName().toString() + "\"]}");
inputFromDate.setValueBinding("value", vbFromDate);
control.getChildren().add(inputFromDate);
// to date label
UIOutput labelToDate = (UIOutput)context.getApplication().createComponent(ComponentConstants.JAVAX_FACES_OUTPUT);
labelToDate.setId(context.getViewRoot().createUniqueId());
labelToDate.setRendererType(ComponentConstants.JAVAX_FACES_TEXT);
labelToDate.setValue(Application.getMessage(context, MSG_TO));
control.getChildren().add(labelToDate);
// to date control
UIInput inputToDate = (UIInput)facesApp.createComponent(ComponentConstants.JAVAX_FACES_INPUT);
inputToDate.setId(context.getViewRoot().createUniqueId());
inputToDate.setRendererType(RepoConstants.ALFRESCO_FACES_DATE_PICKER_RENDERER);
inputToDate.getAttributes().put("startYear", new Integer(1970));
inputToDate.getAttributes().put("yearCount", new Integer(50));
inputToDate.getAttributes().put("showTime", showTime);
ValueBinding vbToDate = facesApp.createValueBinding(
"#{" + beanBinding + "[\"" + PREFIX_DATE_TO + propDef.getName().toString() + "\"]}");
inputToDate.setValueBinding("value", vbToDate);
control.getChildren().add(inputToDate);
}
else if (typeName.equals(DataTypeDefinition.NODE_REF))
{
control = (UISpaceSelector)facesApp.createComponent(RepoConstants.ALFRESCO_FACES_SPACE_SELECTOR);
control.setValueBinding("value", vb);
}
else
{
// any other type is represented as an input text field
control = (UIInput)facesApp.createComponent(ComponentConstants.JAVAX_FACES_INPUT);
control.setRendererType(ComponentConstants.JAVAX_FACES_TEXT);
control.getAttributes().put("size", "28");
control.getAttributes().put("maxlength", "1024");
control.setValueBinding("value", vb);
}
// set up the common aspects of the control
control.setId(context.getViewRoot().createUniqueId());
return control;
}
}

View File

@@ -0,0 +1,343 @@
/*
* 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.component;
import java.io.IOException;
import java.util.Map;
import java.util.ResourceBundle;
import javax.faces.component.NamingContainer;
import javax.faces.component.UICommand;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.FacesEvent;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.SearchContext;
import org.alfresco.web.ui.common.Utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @author Kevin Roast
*/
public class UISimpleSearch extends UICommand
{
// ------------------------------------------------------------------------------
// Component implementation
/**
* Default Constructor
*/
public UISimpleSearch()
{
// specifically set the renderer type to null to indicate to the framework
// that this component renders itself - there is no abstract renderer class
setRendererType(null);
}
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.SimpleSearch";
}
/**
* @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.search = (SearchContext)values[1];
}
/**
* @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
*/
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.search;
return (values);
}
/**
* @see javax.faces.component.UIComponentBase#decode(javax.faces.context.FacesContext)
*/
public void decode(FacesContext context)
{
Map requestMap = context.getExternalContext().getRequestParameterMap();
String fieldId = Utils.getActionHiddenFieldName(context, this);
String value = (String)requestMap.get(fieldId);
// we are clicked if the hidden field contained our client id
if (value != null)
{
if (value.equals(this.getClientId(context)))
{
String searchText = (String)requestMap.get(getClientId(context));
if (searchText.length() != 0)
{
if (logger.isDebugEnabled())
logger.debug("Search text submitted: " + searchText);
int option = -1;
String optionFieldName = getClientId(context) + NamingContainer.SEPARATOR_CHAR + OPTION_PARAM;
String optionStr = (String)requestMap.get(optionFieldName);
if (optionStr.length() != 0)
{
option = Integer.parseInt(optionStr);
}
if (logger.isDebugEnabled())
logger.debug("Search option submitted: " + option);
// queue event so system can perform a search and update the component
SearchEvent event = new SearchEvent(this, searchText, option);
this.queueEvent(event);
}
}
else if (value.equals(ADVSEARCH_PARAM))
{
// found advanced search navigation action
// TODO: TEMP: set this outcome from a component attribute!
AdvancedSearchEvent event = new AdvancedSearchEvent(this, "advSearch");
this.queueEvent(event);
}
}
}
/**
* @see javax.faces.component.UICommand#broadcast(javax.faces.event.FacesEvent)
*/
public void broadcast(FacesEvent event) throws AbortProcessingException
{
if (event instanceof SearchEvent)
{
// update the component parameters from the search event details
SearchEvent searchEvent = (SearchEvent)event;
// construct the Search Context object
SearchContext context = new SearchContext();
context.setText(searchEvent.SearchText);
context.setMode(searchEvent.SearchMode);
this.search = context;
super.broadcast(event);
}
else if (event instanceof AdvancedSearchEvent)
{
// special case to navigate to the advanced search screen
AdvancedSearchEvent searchEvent = (AdvancedSearchEvent)event;
FacesContext fc = getFacesContext();
fc.getApplication().getNavigationHandler().handleNavigation(fc, null, searchEvent.Outcome);
// NOTE: we don't call super() here so that our nav outcome is the one that occurs!
}
}
/**
* @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext)
*/
public void encodeBegin(FacesContext context) throws IOException
{
if (isRendered() == false)
{
return;
}
ResponseWriter out = context.getResponseWriter();
ResourceBundle bundle = (ResourceBundle)Application.getBundle(context);
// script for dynamic simple search menu drop-down options
out.write("<script>");
out.write("function _noenter(event) {" +
"if (event && event.keyCode == 13) {" +
" _searchSubmit();return false; }" +
"else {" +
" return true; } }");
out.write("function _searchSubmit() {");
out.write(Utils.generateFormSubmit(context, this, Utils.getActionHiddenFieldName(context, this), getClientId(context)));
out.write("}");
out.write("</script>");
// outer table containing search drop-down icon, text box and search Go image button
out.write("<table cellspacing=4 cellpadding=0>");
out.write("<tr><td style='padding-top:2px'>");
String searchImage = Utils.buildImageTag(context, "/images/icons/search_icon.gif", 15, 15,
bundle.getString(MSG_GO), "_searchSubmit();");
out.write(Utils.buildImageTag(context, "/images/icons/search_controls.gif", 27, 13,
bundle.getString(MSG_OPTIONS), "javascript:_toggleMenu(event, '_alfsearch');"));
// dynamic DIV area containing search options
out.write("<br><div id='_alfsearch' style='position:absolute;display:none'>");
out.write("<table border=0 bgcolor='#eeeeee' style='border-top:thin solid #FFFFFF;border-left:thin solid #FFFFFF;border-right:thin solid #444444;border-bottom:thin solid #444444;' cellspacing=4 cellpadding=0>");
// output each option - setting the current one to CHECKED
String optionFieldName = getClientId(context) + NamingContainer.SEPARATOR_CHAR + OPTION_PARAM;
String radioOption = "<tr><td class='userInputForm'><input type='radio' name='" + optionFieldName + "'";
out.write(radioOption);
out.write(" VALUE='0'");
int searchMode = getSearchMode();
if (searchMode == 0) out.write(" CHECKED");
out.write("><nobr>" + bundle.getString(MSG_ALL_ITEMS) + "</nobr></td></tr>");
out.write(radioOption);
out.write(" VALUE='1'");
if (searchMode == 1) out.write(" CHECKED");
out.write("><nobr>" + bundle.getString(MSG_FILE_NAMES_CONTENTS) + "</nobr></td></tr>");
out.write(radioOption);
out.write(" VALUE='2'");
if (searchMode == 2) out.write(" CHECKED");
out.write("><nobr>" + bundle.getString(MSG_FILE_NAMES_ONLY) + "</nobr></td></tr>");
out.write(radioOption);
out.write(" VALUE='3'");
if (searchMode == 3) out.write(" CHECKED");
out.write("><nobr>" + bundle.getString(MSG_SPACE_NAMES_ONLY) + "</nobr></td></tr>");
// row with table containing advanced search link and Search Go button
out.write("<tr><td><table width=100%><tr><td>");
// generate a link that will cause an action event to navigate to the advanced search screen
out.write("<a class='small' href='#' onclick=\"");
out.write(Utils.generateFormSubmit(context, this, Utils.getActionHiddenFieldName(context, this), ADVSEARCH_PARAM));
out.write("\">");
out.write(bundle.getString(MSG_ADVANCED_SEARCH));
out.write("</a>");
out.write("</td><td align=right>");
out.write(searchImage);
out.write("</td></tr></table></td></tr>");
out.write("</table></div>");
// input text box
out.write("</td><td>");
out.write("<input name='");
out.write(getClientId(context));
// TODO: style and class from component properties!
out.write("' onkeypress=\"return _noenter(event)\"");
out.write(" type='text' maxlength='1024' style='width:130px;padding-top:3px;font-size:10px' value=\"");
// output previous search text stored in this component!
out.write(Utils.replace(getLastSearch(), "\"", "&quot;"));
out.write("\">");
// search Go image button
out.write("</td><td>");
out.write(searchImage);
// end outer table
out.write("</td></tr></table>");
}
/**
* Return the current Search Context
*/
public SearchContext getSearchContext()
{
return this.search;
}
/**
* @return The last set search text value
*/
public String getLastSearch()
{
if (search != null)
{
return this.search.getText();
}
else
{
return "";
}
}
/**
* @return The current search mode (see constants)
*/
public int getSearchMode()
{
if (search != null)
{
return this.search.getMode();
}
else
{
return SearchContext.SEARCH_ALL;
}
}
// ------------------------------------------------------------------------------
// Private data
private static Log logger = LogFactory.getLog(UISimpleSearch.class);
/** I18N message Ids */
private static final String MSG_ADVANCED_SEARCH = "advanced_search";
private static final String MSG_OPTIONS = "options";
private static final String MSG_GO = "go";
private static final String MSG_SPACE_NAMES_ONLY = "space_names";
private static final String MSG_FILE_NAMES_ONLY = "file_names";
private static final String MSG_FILE_NAMES_CONTENTS = "file_names_contents";
private static final String MSG_ALL_ITEMS = "all_items";
private static final String OPTION_PARAM = "_option";
private static final String ADVSEARCH_PARAM = "_advsearch";
/** last search context used */
private SearchContext search = null;
// ------------------------------------------------------------------------------
// Inner classes
/**
* Class representing a search execution from the UISimpleSearch component.
*/
public static class SearchEvent extends ActionEvent
{
private static final long serialVersionUID = 3918135612344774322L;
public SearchEvent(UIComponent component, String text, int mode)
{
super(component);
SearchText = text;
SearchMode = mode;
}
public String SearchText;
public int SearchMode;
}
public static class AdvancedSearchEvent extends ActionEvent
{
public AdvancedSearchEvent(UIComponent component, String outcome)
{
super(component);
Outcome = outcome;
}
public String Outcome;
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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.component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.faces.context.FacesContext;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.ui.repo.WebResources;
/**
* @author Kevin Roast
*/
public class UISpaceSelector extends AbstractItemSelector
{
// ------------------------------------------------------------------------------
// Component Impl
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.SpaceSelector";
}
/**
*
* @see org.alfresco.web.ui.repo.component.AbstractItemSelector#getDefaultLabel()
*/
public String getDefaultLabel()
{
return Application.getMessage(FacesContext.getCurrentInstance(), "select_space_prompt");
}
/**
* Returns the parent id of the current space or null if the parent space is the company home space
*
* @see org.alfresco.web.ui.repo.component.AbstractItemSelector#getParentNodeId(javax.faces.context.FacesContext)
*/
public String getParentNodeId(FacesContext context)
{
String id = null;
if (this.navigationId != null && this.navigationId.equals(Application.getCompanyRootId()) == false)
{
ChildAssociationRef parentRef = getNodeService(context).getPrimaryParent(
new NodeRef(Repository.getStoreRef(), this.navigationId));
id = parentRef.getParentRef().getId();
}
return id;
}
/**
* Returns the child spaces of the current space
*
* @see org.alfresco.web.ui.repo.component.AbstractItemSelector#getChildrenForNode(javax.faces.context.FacesContext)
*/
public Collection<ChildAssociationRef> getChildrenForNode(FacesContext context)
{
NodeRef nodeRef = new NodeRef(Repository.getStoreRef(), this.navigationId);
List<ChildAssociationRef> allKids = getNodeService(context).getChildAssocs(nodeRef);
DictionaryService dd = getDictionaryService(context);
NodeService service = getNodeService(context);
// filter out those children that are not spaces
List<ChildAssociationRef> spaceKids = new ArrayList<ChildAssociationRef>();
for (ChildAssociationRef ref : allKids)
{
if (dd.isSubClass(service.getType(ref.getChildRef()), ContentModel.TYPE_FOLDER) &&
dd.isSubClass(service.getType(ref.getChildRef()), ContentModel.TYPE_SYSTEM_FOLDER) == false)
{
spaceKids.add(ref);
}
}
return spaceKids;
}
/**
* Returns the current users home space
*
* @see org.alfresco.web.ui.repo.component.AbstractItemSelector#getRootChildren(javax.faces.context.FacesContext)
*/
public Collection<ChildAssociationRef> getRootChildren(FacesContext context)
{
// get the root space from the current user
//String rootId = Application.getCurrentUser(context).getHomeSpaceId();
NodeRef rootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId());
// get a child association reference back to the real repository root to satisfy
// the generic API we have in the abstract super class
ChildAssociationRef childRefFromRealRoot = getNodeService(context).getPrimaryParent(rootRef);
List<ChildAssociationRef> roots = new ArrayList<ChildAssociationRef>(1);
roots.add(childRefFromRealRoot);
return roots;
}
/**
* @see org.alfresco.web.ui.repo.component.AbstractItemSelector#getItemIcon()
*/
public String getItemIcon()
{
return WebResources.IMAGE_SPACE;
}
}

View File

@@ -0,0 +1,214 @@
/*
* 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.component.evaluator;
import java.util.StringTokenizer;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.ui.common.component.evaluator.BaseEvaluator;
/**
* Evaulator for returning whether a Node is Allowed/Denied a list of permissions
*
* @author Kevin Roast
*/
public class PermissionEvaluator extends BaseEvaluator
{
/**
* Evaluate against the component attributes. Return true to allow the inner
* components to render, false to hide them during rendering.
*
* IN: Value - either a Node (preferred) or NodeRef to test
* Allow - Permission(s) (comma separated) to test against
* Deny - Permission(s) (comma separated) to test against
*
* @return true to allow rendering of child components, false otherwise
*/
public boolean evaluate()
{
boolean result = false;
// TODO: implement Deny permissions checking (as required...)
try
{
Object obj = getValue();
if (obj instanceof Node)
{
// used the cached permissions checks in the Node instance
// this means multiple calls to evaluators don't need to keep calling services
// and permissions on a Node shouldn't realistically change over the life of an instance
String[] allow = getAllowPermissions();
if (allow.length != 0)
{
result = true;
for (int i=0; i<allow.length; i++)
{
result = result & ((Node)obj).hasPermission(allow[i]);
}
}
}
else if (obj instanceof NodeRef)
{
// perform the check for permissions here against NodeRef using service
PermissionService service = Repository.getServiceRegistry(getFacesContext()).getPermissionService();
String[] allow = getAllowPermissions();
if (allow.length != 0)
{
result = true;
for (int i=0; i<allow.length; i++)
{
result = result & (AccessStatus.ALLOWED == service.hasPermission(((NodeRef)obj), allow[i]));
}
}
}
}
catch (Exception err)
{
// return default value on error
s_logger.debug("Error during PermissionEvaluator evaluation: " + err.getMessage());
}
return result;
}
/**
* @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.allow = (String)values[1];
this.deny = (String)values[2];
}
/**
* @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
*/
public Object saveState(FacesContext context)
{
Object values[] = new Object[3];
// standard component attributes are saved by the super class
values[0] = super.saveState(context);
values[1] = this.allow;
values[2] = this.deny;
return (values);
}
/**
* @return the array of Allow permissions
*/
private String[] getAllowPermissions()
{
String[] allowPermissions;
String allow = getAllow();
if (allow != null)
{
if (allow.indexOf(',') == -1)
{
// simple case - one permission
allowPermissions = new String[1];
allowPermissions[0] = allow;
}
else
{
// complex case - multiple permissions
StringTokenizer t = new StringTokenizer(allow, ",");
allowPermissions = new String[t.countTokens()];
for (int i=0; i<allowPermissions.length; i++)
{
allowPermissions[i] = t.nextToken();
}
}
}
else
{
allowPermissions = new String[0];
}
return allowPermissions;
}
/**
* Get the allow permissions to match value against
*
* @return the allow permissions to match value against
*/
public String getAllow()
{
ValueBinding vb = getValueBinding("allow");
if (vb != null)
{
this.allow = (String)vb.getValue(getFacesContext());
}
return this.allow;
}
/**
* Set the allow permissions to match value against
*
* @param allow allow permissions to match value against
*/
public void setAllow(String allow)
{
this.allow = allow;
}
/**
* Get the deny permissions to match value against
*
* @return the deny permissions to match value against
*/
public String getDeny()
{
ValueBinding vb = getValueBinding("deny");
if (vb != null)
{
this.deny = (String)vb.getValue(getFacesContext());
}
return this.deny;
}
/**
* Set the deny permissions to match value against
*
* @param deny deny permissions to match value against
*/
public void setDeny(String deny)
{
this.deny = deny;
}
/** the deny permissions to match value against */
private String deny = null;
/** the allow permissions to match value against */
private String allow = null;
}

View File

@@ -0,0 +1,924 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the GNU Lesser General Public License as
* published by the Free Software Foundation; either version
* 2.1 of the License, or (at your option) any later version.
* You may obtain a copy of the License at
*
* http://www.gnu.org/licenses/lgpl.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.component.property;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
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.dictionary.AssociationDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.repository.DataDictionary;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.ui.common.Utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.jsf.FacesContextUtils;
/**
* Base class for all association editor components
*
* @author gavinc
*/
public abstract class BaseAssociationEditor extends UIInput
{
private final static Log logger = LogFactory.getLog(BaseAssociationEditor.class);
private final static String ACTION_SEPARATOR = ";";
private final static int ACTION_NONE = -1;
private final static int ACTION_REMOVE = 0;
private final static int ACTION_SELECT = 1;
private final static int ACTION_ADD = 2;
private final static int ACTION_CHANGE = 3;
private final static int ACTION_CANCEL = 4;
private final static int ACTION_SEARCH = 5;
private final static int ACTION_SET = 6;
private final static String MSG_ERROR_ASSOC = "error_association";
private final static String FIELD_CONTAINS = "_contains";
private final static String FIELD_AVAILABLE = "_available";
/** I18N message strings */
private final static String MSG_ADD_TO_LIST_BUTTON = "add_to_list_button";
private final static String MSG_SELECT_BUTTON = "select_button";
private static final String MSG_NO_SELECTED_ITEMS = "no_selected_items";
private final static String MSG_SEARCH_SELECT_ITEMS = "search_select_items";
private final static String MSG_SEARCH_SELECT_ITEM = "search_select_item";
private final static String MSG_SELECTED_ITEMS = "selected_items";
private final static String MSG_REMOVE = "remove";
private final static String MSG_ADD = "add";
private final static String MSG_OK = "ok";
private final static String MSG_CANCEL = "cancel";
private final static String MSG_SEARCH = "search";
private final static String MSG_CHANGE = "change";
protected String associationName;
protected String availableOptionsSize;
protected String selectItemMsg;
protected String selectItemsMsg;
protected String selectedItemsMsg;
protected String noSelectedItemsMsg;
protected Boolean disabled;
protected boolean showAvailable = false;
/** Map of the original associations keyed by the id of the child */
protected Map<String, Object> originalAssocs;
protected Map<String, Object> added;
protected Map<String, Object> removed;
/** List containing the currently available options */
protected List<NodeRef> availableOptions;
protected String changingAssociation;
protected boolean highlightedRow;
// ------------------------------------------------------------------------------
// Component implementation
/**
* Default constructor
*/
public BaseAssociationEditor()
{
setRendererType(null);
}
/**
* @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object)
*/
@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.associationName = (String)values[1];
this.originalAssocs = (Map<String, Object>)values[2];
this.availableOptions = (List<NodeRef>)values[3];
this.availableOptionsSize = (String)values[4];
this.selectItemMsg = (String)values[5];
this.selectItemsMsg = (String)values[6];
this.selectedItemsMsg = (String)values[7];
this.changingAssociation = (String)values[8];
this.disabled = (Boolean)values[9];
}
/**
* @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
*/
public Object saveState(FacesContext context)
{
Object values[] = new Object[10];
// standard component attributes are saved by the super class
values[0] = super.saveState(context);
values[1] = this.associationName;
values[2] = this.originalAssocs;
values[3] = this.availableOptions;
values[4] = this.availableOptionsSize;
values[5] = this.selectItemMsg;
values[6] = this.selectItemsMsg;
values[7] = this.selectedItemsMsg;
values[8] = this.changingAssociation;
values[9] = this.disabled;
// NOTE: we don't save the state of the added and removed maps as these
// need to be rebuilt everytime
return (values);
}
/**
* @see javax.faces.component.UIComponent#decode(javax.faces.context.FacesContext)
*/
public void decode(FacesContext context)
{
Map requestMap = context.getExternalContext().getRequestParameterMap();
Map valuesMap = context.getExternalContext().getRequestParameterValuesMap();
String fieldId = getHiddenFieldName();
String value = (String)requestMap.get(fieldId);
int action = ACTION_NONE;
String removeId = null;
if (value != null && value.length() != 0)
{
// break up the action into it's parts
int sepIdx = value.indexOf(ACTION_SEPARATOR);
if (sepIdx != -1)
{
action = Integer.parseInt(value.substring(0, sepIdx));
removeId = value.substring(sepIdx+1);
}
else
{
action = Integer.parseInt(value);
}
}
// gather the current state and queue an event
String[] addedItems = (String[])valuesMap.get(fieldId + FIELD_AVAILABLE);
String contains = (String)requestMap.get(fieldId + FIELD_CONTAINS);
AssocEditorEvent event = new AssocEditorEvent(this, action, addedItems, removeId, contains);
queueEvent(event);
super.decode(context);
}
/**
* @see javax.faces.component.UIComponent#broadcast(javax.faces.event.FacesEvent)
*/
public void broadcast(FacesEvent event) throws AbortProcessingException
{
if (event instanceof AssocEditorEvent)
{
AssocEditorEvent assocEvent = (AssocEditorEvent)event;
Node node = (Node)getValue();
switch (assocEvent.Action)
{
case ACTION_SEARCH:
{
this.showAvailable = true;
this.availableOptions = new ArrayList<NodeRef>();
getAvailableOptions(FacesContext.getCurrentInstance(), assocEvent.Contains);
break;
}
case ACTION_SELECT:
{
this.showAvailable = true;
break;
}
case ACTION_ADD:
{
addTarget(node, assocEvent.ToAdd);
break;
}
case ACTION_REMOVE:
{
removeTarget(node, assocEvent.RemoveId);
break;
}
case ACTION_CHANGE:
{
this.changingAssociation = assocEvent.RemoveId;
this.showAvailable = true;
break;
}
case ACTION_CANCEL:
{
this.showAvailable = false;
break;
}
case ACTION_SET:
{
if (assocEvent.ToAdd != null && assocEvent.ToAdd.length > 0)
{
removeTarget(node, this.changingAssociation);
addTarget(node, assocEvent.ToAdd);
}
break;
}
}
}
else
{
super.broadcast(event);
}
}
/**
* @see javax.faces.component.UIComponent#encodeBegin(javax.faces.context.FacesContext)
*/
public void encodeBegin(FacesContext context) throws IOException
{
if (isRendered() == false)
{
return;
}
// reset the highlighted row flag
this.highlightedRow = false;
ResponseWriter out = context.getResponseWriter();
String clientId = getClientId(context);
// get the child associations currently on the node and any that have been added
NodeService nodeService = Repository.getServiceRegistry(context).getNodeService();
// show the editable association component
AssociationDefinition assocDef = getAssociationDefinition(context);
if (assocDef == null)
{
logger.warn("Failed to find association definition for association '" + associationName + "'");
// add an error message as the property is not defined in the data dictionary
String msg = MessageFormat.format(Application.getMessage(context, MSG_ERROR_ASSOC), new Object[] {this.associationName});
Utils.addErrorMessage(msg);
}
else
{
String targetType = assocDef.getTargetClass().getName().toString();
boolean allowMany = assocDef.isTargetMany();
populateAssocationMaps((Node)getValue());
if (isDisabled())
{
// show the current list of associations in a read-only form
renderReadOnlyAssociations(context, out, nodeService);
}
else
{
// start outer table
out.write("<table border='0' cellspacing='4' cellpadding='0' class='selector'>");
if (allowMany)
{
out.write("<tr><td colspan='2'>1.&nbsp;");
out.write(getSelectItemsMsg());
out.write("</td></tr>");
// show the search field
renderSearchField(context, out);
// show available options for this association
renderAvailableOptions(context, out, nodeService, targetType, allowMany);
// add the Add to List button
out.write("<tr><td colspan='2'>2.&nbsp;<input type='submit' value='");
out.write(Application.getMessage(context, MSG_ADD_TO_LIST_BUTTON));
out.write("' onclick=\"");
out.write(generateFormSubmit(context, Integer.toString(ACTION_ADD)));
out.write("\"/>");
// add some padding
out.write("<tr><td height='6'></td></tr>");
out.write("<tr><td colspan='2'>");
out.write(getSelectedItemsMsg());
out.write("</td></tr>");
// show all the current associations
out.write("<tr><td colspan='2'><table cellspacing='0' cellpadding='2' border='0' class='selectedItems'>");
out.write("<tr><td colspan='2' class='selectedItemsHeader'>");
out.write(Application.getMessage(context, "name"));
out.write("</td></tr>");
renderExistingAssociations(context, out, nodeService, allowMany);
out.write("</table></td></tr>");
}
else
{
if (this.showAvailable)
{
out.write("<tr><td colspan='2'>1.&nbsp;");
out.write(getSelectItemMsg());
out.write("</td></tr>");
// show the search field
renderSearchField(context, out);
// show available options for this association
renderAvailableOptions(context, out, nodeService, targetType, allowMany);
// add the ok and cancel buttons
out.write("<tr><td colspan='2' align='right'><input type='submit' value='");
out.write(Application.getMessage(context, MSG_OK));
out.write("' onclick=\"");
out.write(generateFormSubmit(context, Integer.toString(ACTION_SET)));
out.write("\"/>&nbsp;&nbsp;<input type='submit' value='");
out.write(Application.getMessage(context, MSG_CANCEL));
out.write("' onclick=\"");
out.write(generateFormSubmit(context, Integer.toString(ACTION_CANCEL)));
out.write("\"/></td></tr>");
}
else
{
// show the select button if required
if ((allowMany == false && this.originalAssocs.size() == 0 && this.added.size() == 0) ||
(allowMany == false && this.originalAssocs.size() == 1 && this.removed.size() == 1 && this.added.size() == 0) )
{
out.write("<tr><td><input type='submit' value='");
out.write(Application.getMessage(context, MSG_SELECT_BUTTON));
out.write("' onclick=\"");
out.write(generateFormSubmit(context, Integer.toString(ACTION_SELECT)));
out.write("\"/></td></tr>");
}
else
{
// show the current association
renderExistingAssociations(context, out, nodeService, allowMany);
}
}
}
if (logger.isDebugEnabled())
{
logger.debug("number original = " + this.originalAssocs.size());
logger.debug("number added = " + this.added.size());
logger.debug("number removed = " + this.removed.size());
}
// close table
out.write("</table>");
}
}
}
/**
* Returns the name of the association this component is editing
*
* @return Association name
*/
public String getAssociationName()
{
ValueBinding vb = getValueBinding("associationName");
if (vb != null)
{
this.associationName = (String)vb.getValue(getFacesContext());
}
return this.associationName;
}
/**
* Sets the name of the association this component will edit
*
* @param associationName Name of the association to edit
*/
public void setAssociationName(String associationName)
{
this.associationName = associationName;
}
/**
* Determines whether the component should be rendered in a disabled state
*
* @return Returns whether the component is disabled
*/
public boolean isDisabled()
{
if (this.disabled == null)
{
ValueBinding vb = getValueBinding("disabled");
if (vb != null)
{
this.disabled = (Boolean)vb.getValue(getFacesContext());
}
}
if (this.disabled == null)
{
this.disabled = Boolean.FALSE;
}
return this.disabled;
}
/**
* Determines whether the component should be rendered in a disabled state
*
* @param disabled true to disable the component
*/
public void setDisabled(boolean disabled)
{
this.disabled = disabled;
}
/**
* Returns the size of the select control when multiple items
* can be selected
*
* @return The size of the select control
*/
public String getAvailableOptionsSize()
{
if (this.availableOptionsSize == null)
{
this.availableOptionsSize = "4";
}
return this.availableOptionsSize;
}
/**
* Sets the size of the select control used when multiple items can
* be selected
*
* @param availableOptionsSize The size
*/
public void setAvailableOptionsSize(String availableOptionsSize)
{
this.availableOptionsSize = availableOptionsSize;
}
/**
* 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 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 for select an item, if one hasn't been
* set it defaults to the message in the bundle under key 'search_select_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_SEARCH_SELECT_ITEM);
}
return this.selectItemMsg;
}
/**
* Sets the select an item message to display in the UI
*
* @param selectedItemMsg The message
*/
public void setSelectItemMsg(String selectItemMsg)
{
this.selectItemMsg = selectItemMsg;
}
/**
* Returns the message to display for select items, if one hasn't been
* set it defaults to the message in the bundle under key 'search_select_items'.
*
* @return The message
*/
public String getSelectItemsMsg()
{
ValueBinding vb = getValueBinding("selectItemsMsg");
if (vb != null)
{
this.selectItemsMsg = (String)vb.getValue(getFacesContext());
}
if (this.selectItemsMsg == null)
{
this.selectItemsMsg = Application.getMessage(getFacesContext(), MSG_SEARCH_SELECT_ITEMS);
}
return this.selectItemsMsg;
}
/**
* Sets the select items message to display in the UI
*
* @param selectedItemsMsg The message
*/
public void setSelectItemsMsg(String selectItemsMsg)
{
this.selectItemsMsg = selectItemsMsg;
}
/**
* Populates all the internal Maps with the appropriate association reference objects
*
* @param node The Node we are dealing with
*/
protected abstract void populateAssocationMaps(Node node);
/**
* Renders the existing associations in a read-only form
*
* @param context FacesContext
* @param out ResponseWriter
* @param nodeService The NodeService
* @throws IOException
*/
protected abstract void renderReadOnlyAssociations(FacesContext context, ResponseWriter out,
NodeService nodeService) throws IOException;
/**
* Renders the existing associations in an editable form
*
* @param context FacesContext
* @param out ResponseWriter
* @param nodeService The NodeService
* @param allowMany Whether multiple associations are allowed
* @throws IOException
*/
protected abstract void renderExistingAssociations(FacesContext context, ResponseWriter out,
NodeService nodeService, boolean allowMany) throws IOException;
/**
* Updates the component and node state to reflect an association being removed
*
* @param node The node we are dealing with
* @param targetId The id of the child to remove
*/
protected abstract void removeTarget(Node node, String targetId);
/**
* Updates the component and node state to reflect an association being added
*
* @param node The node we are dealing with
* @param childId The id of the child to add
*/
protected abstract void addTarget(Node node, String[] toAdd);
/**
* Renders an existing association with the appropriate options
*
* @param context FacesContext
* @param out Writer to write output to
* @param nodeService The NodeService
* @param targetRef The node at the end of the association being rendered
* @param allowMany Whether the current association allows multiple children
* @throws IOException
*/
protected void renderExistingAssociation(FacesContext context, ResponseWriter out, NodeService nodeService,
NodeRef targetRef, boolean allowMany) throws IOException
{
out.write("<tr><td class='");
if (this.highlightedRow)
{
out.write("selectedItemsRowAlt");
}
else
{
out.write("selectedItemsRow");
}
out.write("'>");
out.write(Repository.getDisplayPath(nodeService.getPath(targetRef)));
out.write("/");
out.write(Repository.getNameForNode(nodeService, targetRef));
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, ACTION_REMOVE + ACTION_SEPARATOR + targetRef.getId()));
out.write("\"><img src='");
out.write(context.getExternalContext().getRequestContextPath());
out.write("/images/icons/delete.gif' border='0' width='13' height='16'/></a>");
if (allowMany == false)
{
out.write("&nbsp;<a href='#' title='");
out.write(Application.getMessage(context, MSG_CHANGE));
out.write("' onclick=\"");
out.write(generateFormSubmit(context, ACTION_CHANGE + ACTION_SEPARATOR + targetRef.getId()));
out.write("\"><img src='");
out.write(context.getExternalContext().getRequestContextPath());
out.write("/images/icons/edit_icon.gif' border='0' width='12' height='16'/></a>");
}
out.write("</td></tr>");
this.highlightedRow = !this.highlightedRow;
}
/**
* Renders the search fields
*
* @param context Faces Context
* @param out The Response Writer
* @throws IOException
*/
protected void renderSearchField(FacesContext context, ResponseWriter out) throws IOException
{
// TODO: externalise the max and size attributes
out.write("<tr><td colspan='2'><input type='text' maxlength='1024' size='32' name='");
out.write(getClientId(context) + FIELD_CONTAINS);
out.write("'/>&nbsp;&nbsp;<input type='submit' value='");
out.write(Application.getMessage(context, MSG_SEARCH));
out.write("' onclick=\"");
out.write(generateFormSubmit(context, Integer.toString(ACTION_SEARCH)));
out.write("\"/></td></tr>");
}
/**
* Renders the <None> message
*
* @param context Faces Context
* @param out Response Writer
* @throws IOException
*/
protected void renderNone(FacesContext context, ResponseWriter out) throws IOException
{
out.write("<tr><td class='selectedItemsRow'>");
out.write(getNoSelectedItemsMsg());
out.write("</td></tr>");
}
/**
* Renders the list of available options for a new association
*
* @param context FacesContext
* @param out Writer to write output to
* @param nodeService The NodeService
* @param targetType The type of the child at the end of the association
* @param allowMany Whether the current association allows multiple children
* @throws IOException
*/
protected void renderAvailableOptions(FacesContext context, ResponseWriter out, NodeService nodeService,
String targetType, boolean allowMany) throws IOException
{
boolean itemsPresent = (this.availableOptions != null && this.availableOptions.size() > 0);
out.write("<tr><td colspan='2'><select ");
if (itemsPresent == false)
{
// rather than having a very slim select box set the width if there are no results
out.write("style='width:240px;' ");
}
out.write("name='");
out.write(getClientId(context) + FIELD_AVAILABLE);
out.write("' size='");
if (allowMany)
{
out.write(getAvailableOptionsSize());
out.write("' multiple");
}
else
{
out.write("1'");
}
out.write(">");
if (itemsPresent)
{
Node currentNode = (Node)getValue();
for (NodeRef item : this.availableOptions)
{
// NOTE: only show the items that are not already associated to and don't show the current node
if ((this.originalAssocs.containsKey(item.getId()) == false && this.added.containsKey(item.getId()) == false &&
item.getId().equals(currentNode.getId()) == false) ||
this.removed.containsKey(item.getId()))
{
out.write("<option value='");
out.write(item.getId());
out.write("'>");
out.write(Repository.getDisplayPath(nodeService.getPath(item)));
out.write("/");
out.write(Repository.getNameForNode(nodeService, item));
out.write("</option>");
}
}
}
out.write("</select></td></tr>");
}
/**
* Retrieves the AssociationDefinition for the association we are representing
*
* @param context Faces Context
* @return The AssociationDefinition for the association, null if a definition does not exist
*/
protected AssociationDefinition getAssociationDefinition(FacesContext context)
{
// get some metadata about the association from the data dictionary
DataDictionary dd = (DataDictionary)FacesContextUtils.getRequiredWebApplicationContext(
context).getBean(Application.BEAN_DATA_DICTIONARY);
return dd.getAssociationDefinition((Node)getValue(), this.associationName);
}
/**
* Retrieves the available options for the current association
*
* @param context Faces Context
* @param contains The contains part of the query
*/
protected void getAvailableOptions(FacesContext context, String contains)
{
AssociationDefinition assocDef = getAssociationDefinition(context);
if (assocDef != null)
{
// find and show all the available options for the current association
StringBuilder query = new StringBuilder("+TYPE:\"");
query.append(assocDef.getTargetClass().getName().toString());
query.append("\"");
if (contains != null && contains.length() > 0)
{
String safeContains = Utils.remove(contains.trim(), "\"");
String nameAttr = Repository.escapeQName(QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "name"));
query.append(" AND +@");
query.append(nameAttr);
query.append(":*" + safeContains + "*");
}
if (logger.isDebugEnabled())
logger.debug("Query: " + query.toString());
ResultSet results = null;
try
{
results = Repository.getServiceRegistry(context).getSearchService().query(
Repository.getStoreRef(), SearchService.LANGUAGE_LUCENE, query.toString());
this.availableOptions = results.getNodeRefs();
}
finally
{
if (results != null)
{
results.close();
}
}
if (logger.isDebugEnabled())
logger.debug("Found " + this.availableOptions.size() + " available options");
}
}
/**
* We use a hidden field per picker instance on the page.
*
* @return hidden field name
*/
private String getHiddenFieldName()
{
return getClientId(getFacesContext());
}
/**
* Generate FORM submit JavaScript for the specified action
*
* @param context FacesContext
* @param action Action string
*
* @return FORM submit JavaScript
*/
private String generateFormSubmit(FacesContext context, String action)
{
return Utils.generateFormSubmit(context, this, getHiddenFieldName(), action);
}
// ------------------------------------------------------------------------------
// Inner classes
/**
* Class representing an action relevant to the AssociationEditor component.
*/
public static class AssocEditorEvent extends ActionEvent
{
private static final long serialVersionUID = 7346758616063937703L;
public int Action;
public String[] ToAdd;
public String RemoveId;
public String Contains;
public AssocEditorEvent(UIComponent component, int action, String[] toAdd, String removeId, String contains)
{
super(component);
this.Action = action;
this.ToAdd = toAdd;
this.RemoveId = removeId;
this.Contains = contains;
}
}
}

View File

@@ -0,0 +1,247 @@
/*
* 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.component.property;
import java.io.IOException;
import java.text.MessageFormat;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIOutput;
import javax.faces.component.UIPanel;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.repository.DataDictionary;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.ui.common.Utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.jsf.FacesContextUtils;
/**
* Abstract base class for all items that can appear in a property sheet component
*
* @author gavinc
*/
public abstract class PropertySheetItem extends UIPanel implements NamingContainer
{
private static Log logger = LogFactory.getLog(PropertySheetItem.class);
protected String name;
protected String displayLabel;
protected String converter;
protected Boolean readOnly;
/**
* @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext)
*/
public void encodeBegin(FacesContext context) throws IOException
{
// get the variable being used from the parent
UIComponent parent = this.getParent();
if ((parent instanceof UIPropertySheet) == false)
{
throw new IllegalStateException(getIncorrectParentMsg());
}
// only build the components if there are currently no children
int howManyKids = getChildren().size();
if (howManyKids == 0)
{
UIPropertySheet propSheet = (UIPropertySheet)parent;
generateItem(context, propSheet.getNode(), propSheet.getVar());
}
super.encodeBegin(context);
}
/**
* @return Returns the display label
*/
public String getDisplayLabel()
{
if (this.displayLabel == null)
{
ValueBinding vb = getValueBinding("displayLabel");
if (vb != null)
{
this.displayLabel = (String)vb.getValue(getFacesContext());
}
}
return this.displayLabel;
}
/**
* @param displayLabel Sets the display label
*/
public void setDisplayLabel(String displayLabel)
{
this.displayLabel = displayLabel;
}
/**
* @return Returns the name
*/
public String getName()
{
if (this.name == null)
{
ValueBinding vb = getValueBinding("name");
if (vb != null)
{
this.name = (String)vb.getValue(getFacesContext());
}
}
return this.name;
}
/**
* @param name Sets the name
*/
public void setName(String name)
{
this.name = name;
}
/**
* @return Returns the converter
*/
public String getConverter()
{
if (this.converter == null)
{
ValueBinding vb = getValueBinding("converter");
if (vb != null)
{
this.converter = (String)vb.getValue(getFacesContext());
}
}
return this.converter;
}
/**
* @param converter Sets the converter
*/
public void setConverter(String converter)
{
this.converter = converter;
}
/**
* @return Returns whether the property is read only
*/
public boolean isReadOnly()
{
if (this.readOnly == null)
{
ValueBinding vb = getValueBinding("readOnly");
if (vb != null)
{
this.readOnly = (Boolean)vb.getValue(getFacesContext());
}
}
if (this.readOnly == null)
{
this.readOnly = Boolean.FALSE;
}
return this.readOnly;
}
/**
* @param readOnly Sets the read only flag for the component
*/
public void setReadOnly(boolean readOnly)
{
this.readOnly = readOnly;
}
/**
* @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.name = (String)values[1];
this.displayLabel = (String)values[2];
this.readOnly = (Boolean)values[3];
this.converter = (String)values[4];
}
/**
* @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
*/
public Object saveState(FacesContext context)
{
Object values[] = new Object[5];
// standard component attributes are saved by the super class
values[0] = super.saveState(context);
values[1] = this.name;
values[2] = this.displayLabel;
values[3] = this.readOnly;
values[4] = this.converter;
return (values);
}
/**
* Generates the label and control for the item
*
* @param context FacesContext
* @param node The Node we are displaying the property sheet for
* @param var The variable name used to store the Node in the session
*/
protected abstract void generateItem(FacesContext context, Node node, String var)
throws IOException;
/**
* Returns the message to use in the exception that is thrown if the component
* is not nested inside a PropertySheet component
*
* @return The message
*/
protected abstract String getIncorrectParentMsg();
/**
* Generates a JSF OutputText component/renderer
*
* @param context JSF context
* @param displayLabel The display label text
* @param parent The parent component for the label
*/
protected void generateLabel(FacesContext context, String displayLabel)
{
UIOutput label = (UIOutput)context.getApplication().
createComponent("javax.faces.Output");
label.setId(context.getViewRoot().createUniqueId());
label.setRendererType("javax.faces.Text");
label.setValue(displayLabel + ": ");
this.getChildren().add(label);
if (logger.isDebugEnabled())
logger.debug("Created label " + label.getClientId(context) +
" for '" + displayLabel + "' and added it to component " + this);
}
}

View File

@@ -0,0 +1,158 @@
/*
* 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.component.property;
import java.io.IOException;
import java.text.MessageFormat;
import javax.faces.FacesException;
import javax.faces.component.UIOutput;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.el.ValueBinding;
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.repository.DataDictionary;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.ui.common.Utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.jsf.FacesContextUtils;
/**
* Component to represent an individual association within a property sheet
*
* @author gavinc
*/
public class UIAssociation extends PropertySheetItem
{
private static final String MSG_ERROR_ASSOC = "error_association";
private static final String MSG_ERROR_NOT_ASSOC = "error_not_association";
private static Log logger = LogFactory.getLog(UIAssociation.class);
/**
* Default constructor
*/
public UIAssociation()
{
// set the default renderer
setRendererType("org.alfresco.faces.AssociationRenderer");
}
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.Association";
}
/**
* @see org.alfresco.web.ui.repo.component.property.PropertySheetItem#getIncorrectParentMsg()
*/
protected String getIncorrectParentMsg()
{
return "The association component must be nested within a property sheet component";
}
/**
* @see org.alfresco.web.ui.repo.component.property.PropertySheetItem#generateItem(javax.faces.context.FacesContext, org.alfresco.web.bean.repository.Node, java.lang.String)
*/
protected void generateItem(FacesContext context, Node node, String var) throws IOException
{
String associationName = (String)getName();
// get details of the association
DataDictionary dd = (DataDictionary)FacesContextUtils.getRequiredWebApplicationContext(
context).getBean(Application.BEAN_DATA_DICTIONARY);
AssociationDefinition assocDef = dd.getAssociationDefinition(node, associationName);
if (assocDef == null)
{
logger.warn("Failed to find association definition for association '" + associationName + "'");
// add an error message as the property is not defined in the data dictionary and
// not in the node's set of properties
String msg = MessageFormat.format(Application.getMessage(context, MSG_ERROR_ASSOC), new Object[] {associationName});
Utils.addErrorMessage(msg);
}
else
{
// we've found the association definition but we also need to check
// that the association is a parent child one
if (assocDef.isChild())
{
String msg = MessageFormat.format(Application.getMessage(context, MSG_ERROR_NOT_ASSOC), new Object[] {associationName});
Utils.addErrorMessage(msg);
}
else
{
String displayLabel = (String)getDisplayLabel();
if (displayLabel == null)
{
// try and get the repository assigned label
displayLabel = assocDef.getTitle();
// if the label is still null default to the local name of the property
if (displayLabel == null)
{
displayLabel = assocDef.getName().getLocalName();
}
}
// generate the label and type specific control
generateLabel(context, displayLabel);
generateControl(context, assocDef, var);
}
}
}
/**
* Generates an appropriate control for the given property
*
* @param context JSF context
* @param propDef The definition of the association to create the control for
* @param varName Name of the variable the node is stored in the session as
* (used for value binding expression)
* @param parent The parent component for the control
*/
private void generateControl(FacesContext context, AssociationDefinition assocDef,
String varName)
{
UIPropertySheet propSheet = (UIPropertySheet)this.getParent();
ValueBinding vb = context.getApplication().createValueBinding("#{" + varName + "}");
UIAssociationEditor control = (UIAssociationEditor)context.
getApplication().createComponent("org.alfresco.faces.AssociationEditor");
control.setAssociationName(assocDef.getName().toString());
// set up the common aspects of the control
control.setId(context.getViewRoot().createUniqueId());
control.setValueBinding("value", vb);
// disable the component if necessary
if (propSheet.getMode().equalsIgnoreCase(UIPropertySheet.VIEW_MODE) || isReadOnly() || assocDef.isProtected())
{
control.setDisabled(true);
}
// add the control itself
this.getChildren().add(control);
}
}

View File

@@ -0,0 +1,242 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the GNU Lesser General Public License as
* published by the Free Software Foundation; either version
* 2.1 of the License, or (at your option) any later version.
* You may obtain a copy of the License at
*
* http://www.gnu.org/licenses/lgpl.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.component.property;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.bean.repository.Repository;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Component that allows associations to be edited
* i.e. new associations to be added, existing ones to be
* removed whilst following the rules in the data dictionary
*
* @author gavinc
*/
public class UIAssociationEditor extends BaseAssociationEditor
{
private static final Log logger = LogFactory.getLog(UIAssociationEditor.class);
// ------------------------------------------------------------------------------
// Component implementation
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.AssociationEditor";
}
/**
* @see org.alfresco.web.ui.repo.component.property.BaseAssociationEditor#populateAssocationMaps(org.alfresco.web.bean.repository.Node)
*/
protected void populateAssocationMaps(Node node)
{
// we need to remember the original set of associations (if there are any)
// and place them in a map keyed by the id of the child node
if (this.originalAssocs == null)
{
this.originalAssocs = new HashMap<String, Object>();
List assocs = (List)node.getAssociations().get(this.associationName);
if (assocs != null)
{
Iterator iter = assocs.iterator();
while (iter.hasNext())
{
AssociationRef assoc = (AssociationRef)iter.next();
// add the association to the map
this.originalAssocs.put(assoc.getTargetRef().getId(), assoc);
}
}
}
// get the map of added associations for this node and association type
this.added = (Map)node.getAddedAssociations().get(this.associationName);
if (added == null)
{
// if there aren't any added associations for 'associationName' create a map and add it
added = new HashMap<String, Object>();
node.getAddedAssociations().put(this.associationName, (Map)added);
}
// get the map of removed associations for this node and association type
this.removed = (Map)node.getRemovedAssociations().get(this.associationName);
if (removed == null)
{
// if there aren't any added associations for 'associationName' create a map and add it
removed = new HashMap<String, Object>();
node.getRemovedAssociations().put(this.associationName, (Map)removed);
}
}
/**
* @see org.alfresco.web.ui.repo.component.property.BaseAssociationEditor#renderExistingAssociations(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, org.alfresco.service.cmr.repository.NodeService, boolean)
*/
protected void renderExistingAssociations(FacesContext context, ResponseWriter out,
NodeService nodeService, boolean allowManyChildren) throws IOException
{
boolean itemsRendered = false;
// show the associations from the original list if they are not in the removed list
Iterator iter = this.originalAssocs.values().iterator();
while (iter.hasNext())
{
AssociationRef assoc = (AssociationRef)iter.next();
if (removed.containsKey(assoc.getTargetRef().getId()) == false)
{
renderExistingAssociation(context, out, nodeService, assoc.getTargetRef(), allowManyChildren);
itemsRendered = true;
}
}
// also show any associations added in this session
iter = this.added.values().iterator();
while (iter.hasNext())
{
AssociationRef assoc = (AssociationRef)iter.next();
renderExistingAssociation(context, out, nodeService, assoc.getTargetRef(), allowManyChildren);
itemsRendered = true;
}
// show the none selected message if no items were rendered
if (itemsRendered == false && allowManyChildren == true)
{
renderNone(context, out);
}
}
/**
* @see org.alfresco.web.ui.repo.component.property.BaseAssociationEditor#renderReadOnlyAssociations(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, org.alfresco.service.cmr.repository.NodeService)
*/
protected void renderReadOnlyAssociations(FacesContext context, ResponseWriter out, NodeService nodeService) throws IOException
{
if (this.originalAssocs.size() > 0)
{
out.write("<table cellspacing='0' cellpadding='2' border='0'>");
Iterator iter = this.originalAssocs.values().iterator();
while (iter.hasNext())
{
out.write("<tr><td>");
AssociationRef assoc = (AssociationRef)iter.next();
out.write(Repository.getDisplayPath(nodeService.getPath(assoc.getTargetRef())));
out.write("/");
out.write(Repository.getNameForNode(nodeService, assoc.getTargetRef()));
out.write("</td></tr>");
}
out.write("</table>");
}
}
/**
* Updates the component and node state to reflect an association being removed
*
* @param node The node we are dealing with
* @param targetId The id of the child to remove
*/
protected void removeTarget(Node node, String targetId)
{
if (node != null && targetId != null)
{
QName assocQName = Repository.resolveToQName(this.associationName);
AssociationRef newAssoc = new AssociationRef(node.getNodeRef(), assocQName, new NodeRef(Repository.getStoreRef(), targetId));
// update the node so it knows to remove the association, but only if the association
// was one of the original ones
if (this.originalAssocs.containsKey(targetId))
{
Map<String, AssociationRef> removed = node.getRemovedAssociations().get(this.associationName);
removed.put(targetId, newAssoc);
if (logger.isDebugEnabled())
logger.debug("Added association to " + targetId + " to the removed list");
}
// if this association was previously added in this session it will still be
// in the added list so remove it if it is
Map<String, AssociationRef> added = node.getAddedAssociations().get(this.associationName);
if (added.containsKey(targetId))
{
added.remove(targetId);
if (logger.isDebugEnabled())
logger.debug("Removed association to " + targetId + " from the added list");
}
}
}
/**
* Updates the component and node state to reflect an association being added
*
* @param node The node we are dealing with
* @param childId The id of the child to add
*/
protected void addTarget(Node node, String[] toAdd)
{
if (node != null && toAdd != null && toAdd.length > 0)
{
for (int x = 0; x < toAdd.length; x++)
{
String targetId = toAdd[x];
// update the node so it knows to add the association (if it wasn't there originally)
if (this.originalAssocs.containsKey(targetId) == false)
{
QName assocQName = Repository.resolveToQName(this.associationName);
AssociationRef newAssoc = new AssociationRef(node.getNodeRef(), assocQName,
new NodeRef(Repository.getStoreRef(), targetId));
Map<String, AssociationRef> added = node.getAddedAssociations().get(this.associationName);
added.put(targetId, newAssoc);
if (logger.isDebugEnabled())
logger.debug("Added association to " + targetId + " to the added list");
}
// if the association was previously removed and has now been re-added it
// will still be in the "to be removed" list so remove it if it is
Map<String, AssociationRef> removed = node.getRemovedAssociations().get(this.associationName);
if (removed.containsKey(targetId))
{
removed.remove(targetId);
if (logger.isDebugEnabled())
logger.debug("Removed association to " + targetId + " from the removed list");
}
}
}
}
}

View File

@@ -0,0 +1,157 @@
/*
* 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.component.property;
import java.io.IOException;
import java.text.MessageFormat;
import javax.faces.FacesException;
import javax.faces.component.UIOutput;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.el.ValueBinding;
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.repository.DataDictionary;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.ui.common.Utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.jsf.FacesContextUtils;
/**
* Component to represent an individual child association within a property sheet
*
* @author gavinc
*/
public class UIChildAssociation extends PropertySheetItem
{
private static final String MSG_ERROR_CHILD_ASSOC = "error_child_association";
private static final String MSG_ERROR_NOT_CHILD_ASSOC = "error_not_child_association";
private static Log logger = LogFactory.getLog(UIChildAssociation.class);
/**
* Default constructor
*/
public UIChildAssociation()
{
// set the default renderer
setRendererType("org.alfresco.faces.ChildAssociationRenderer");
}
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.ChildAssociation";
}
/**
* @see org.alfresco.web.ui.repo.component.property.PropertySheetItem#getIncorrectParentMsg()
*/
protected String getIncorrectParentMsg()
{
return "The childAssociation component must be nested within a property sheet component";
}
/**
* @see org.alfresco.web.ui.repo.component.property.PropertySheetItem#generateItem(javax.faces.context.FacesContext, org.alfresco.web.bean.repository.Node, java.lang.String)
*/
protected void generateItem(FacesContext context, Node node, String var) throws IOException
{
String associationName = (String)getName();
// get details of the association
DataDictionary dd = (DataDictionary)FacesContextUtils.getRequiredWebApplicationContext(
context).getBean(Application.BEAN_DATA_DICTIONARY);
AssociationDefinition assocDef = dd.getAssociationDefinition(node, associationName);
if (assocDef == null)
{
logger.warn("Failed to find child association definition for association '" + associationName + "'");
// add an error message as the property is not defined in the data dictionary
String msg = MessageFormat.format(Application.getMessage(context, MSG_ERROR_CHILD_ASSOC), new Object[] {associationName});
Utils.addErrorMessage(msg);
}
else
{
// we've found the association definition but we also need to check
// that the association is a parent child one
if (assocDef.isChild() == false)
{
String msg = MessageFormat.format(Application.getMessage(context, MSG_ERROR_NOT_CHILD_ASSOC), new Object[] {associationName});
Utils.addErrorMessage(msg);
}
else
{
String displayLabel = (String)getDisplayLabel();
if (displayLabel == null)
{
// try and get the repository assigned label
displayLabel = assocDef.getTitle();
// if the label is still null default to the local name of the property
if (displayLabel == null)
{
displayLabel = assocDef.getName().getLocalName();
}
}
// generate the label and type specific control
generateLabel(context, displayLabel);
generateControl(context, assocDef, var);
}
}
}
/**
* Generates an appropriate control for the given property
*
* @param context JSF context
* @param propDef The definition of the association to create the control for
* @param varName Name of the variable the node is stored in the session as
* (used for value binding expression)
* @param parent The parent component for the control
*/
private void generateControl(FacesContext context, AssociationDefinition assocDef,
String varName)
{
UIPropertySheet propSheet = (UIPropertySheet)this.getParent();
ValueBinding vb = context.getApplication().createValueBinding("#{" + varName + "}");
UIChildAssociationEditor control = (UIChildAssociationEditor)context.
getApplication().createComponent("org.alfresco.faces.ChildAssociationEditor");
control.setAssociationName(assocDef.getName().toString());
// set up the common aspects of the control
control.setId(context.getViewRoot().createUniqueId());
control.setValueBinding("value", vb);
// disable the component if necessary
if (propSheet.getMode().equalsIgnoreCase(UIPropertySheet.VIEW_MODE) || isReadOnly() || assocDef.isProtected())
{
control.setDisabled(true);
}
// add the control itself
this.getChildren().add(control);
}
}

View File

@@ -0,0 +1,237 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the GNU Lesser General Public License as
* published by the Free Software Foundation; either version
* 2.1 of the License, or (at your option) any later version.
* You may obtain a copy of the License at
*
* http://www.gnu.org/licenses/lgpl.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.component.property;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
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.bean.repository.Node;
import org.alfresco.web.bean.repository.Repository;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Component that allows child associations to be edited
* i.e. new associations to be added, existing ones to be
* removed whilst following the rules in the data dictionary
*
* @author gavinc
*/
public class UIChildAssociationEditor extends BaseAssociationEditor
{
private static final Log logger = LogFactory.getLog(UIChildAssociationEditor.class);
// ------------------------------------------------------------------------------
// Component implementation
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.ChildAssociationEditor";
}
/**
* @see org.alfresco.web.ui.repo.component.property.BaseAssociationEditor#populateAssocationMaps(org.alfresco.web.bean.repository.Node)
*/
protected void populateAssocationMaps(Node node)
{
// we need to remember the original set of associations (if there are any)
// and place them in a map keyed by the id of the child node
if (this.originalAssocs == null)
{
this.originalAssocs = new HashMap<String, Object>();
List assocs = (List)node.getChildAssociations().get(this.associationName);
if (assocs != null)
{
Iterator iter = assocs.iterator();
while (iter.hasNext())
{
ChildAssociationRef assoc = (ChildAssociationRef)iter.next();
// add the association to the map
this.originalAssocs.put(assoc.getChildRef().getId(), assoc);
}
}
}
// get the map of added associations for this node and association type
this.added = (Map)node.getAddedChildAssociations().get(this.associationName);
if (added == null)
{
// if there aren't any added associations for 'associationName' create a map and add it
added = new HashMap<String, Object>();
node.getAddedChildAssociations().put(this.associationName, (Map)added);
}
// get the map of removed associations for this node and association type
this.removed = (Map)node.getRemovedChildAssociations().get(this.associationName);
if (removed == null)
{
// if there aren't any added associations for 'associationName' create a map and add it
removed = new HashMap<String, Object>();
node.getRemovedChildAssociations().put(this.associationName, (Map)removed);
}
}
/**
* @see org.alfresco.web.ui.repo.component.property.BaseAssociationEditor#renderExistingAssociations(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, org.alfresco.service.cmr.repository.NodeService, boolean)
*/
protected void renderExistingAssociations(FacesContext context, ResponseWriter out,
NodeService nodeService, boolean allowMany) throws IOException
{
boolean itemsRendered = false;
// show the associations from the original list if they are not in the removed list
Iterator iter = this.originalAssocs.values().iterator();
while (iter.hasNext())
{
ChildAssociationRef assoc = (ChildAssociationRef)iter.next();
if (removed.containsKey(assoc.getChildRef().getId()) == false)
{
renderExistingAssociation(context, out, nodeService, assoc.getChildRef(), allowMany);
itemsRendered = true;
}
}
// also show any associations added in this session
iter = this.added.values().iterator();
while (iter.hasNext())
{
ChildAssociationRef assoc = (ChildAssociationRef)iter.next();
renderExistingAssociation(context, out, nodeService, assoc.getChildRef(), allowMany);
itemsRendered = true;
}
// show the none selected message if no items were rendered
if (itemsRendered == false && allowMany == true)
{
renderNone(context, out);
}
}
/**
* @see org.alfresco.web.ui.repo.component.property.BaseAssociationEditor#renderReadOnlyAssociations(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, org.alfresco.service.cmr.repository.NodeService)
*/
protected void renderReadOnlyAssociations(FacesContext context, ResponseWriter out, NodeService nodeService) throws IOException
{
if (this.originalAssocs.size() > 0)
{
out.write("<table cellspacing='0' cellpadding='2' border='0'>");
Iterator iter = this.originalAssocs.values().iterator();
while (iter.hasNext())
{
out.write("<tr><td>");
ChildAssociationRef assoc = (ChildAssociationRef)iter.next();
out.write(Repository.getDisplayPath(nodeService.getPath(assoc.getChildRef())));
out.write("/");
out.write(Repository.getNameForNode(nodeService, assoc.getChildRef()));
out.write("</tr></td>");
}
out.write("</table>");
}
}
/**
* @see org.alfresco.web.ui.repo.component.property.BaseAssociationEditor#removeTarget(org.alfresco.web.bean.repository.Node, java.lang.String)
*/
protected void removeTarget(Node node, String childId)
{
if (node != null && childId != null)
{
QName assocQName = Repository.resolveToQName(this.associationName);
ChildAssociationRef childAssoc = new ChildAssociationRef(assocQName,
node.getNodeRef(), assocQName, new NodeRef(Repository.getStoreRef(), childId));
// update the node so it knows to remove the association, but only if the association
// was one of the original ones
if (this.originalAssocs.containsKey(childId))
{
Map<String, ChildAssociationRef> removed = node.getRemovedChildAssociations().get(this.associationName);
removed.put(childId, childAssoc);
if (logger.isDebugEnabled())
logger.debug("Added association to " + childId + " to the removed list");
}
// if this association was previously added in this session it will still be
// in the added list so remove it if it is
Map<String, ChildAssociationRef> added = node.getAddedChildAssociations().get(this.associationName);
if (added.containsKey(childId))
{
added.remove(childId);
if (logger.isDebugEnabled())
logger.debug("Removed association to " + childId + " from the added list");
}
}
}
/**
* @see org.alfresco.web.ui.repo.component.property.BaseAssociationEditor#addTarget(org.alfresco.web.bean.repository.Node, java.lang.String[])
*/
protected void addTarget(Node node, String[] toAdd)
{
if (node != null && toAdd != null && toAdd.length > 0)
{
for (int x = 0; x < toAdd.length; x++)
{
String childId = toAdd[x];
// update the node so it knows to add the association
if (this.originalAssocs.containsKey(childId) == false)
{
QName assocQName = Repository.resolveToQName(this.associationName);
ChildAssociationRef childAssoc = new ChildAssociationRef(assocQName,
node.getNodeRef(), assocQName, new NodeRef(Repository.getStoreRef(), childId));
Map<String, ChildAssociationRef> added = node.getAddedChildAssociations().get(this.associationName);
added.put(childId, childAssoc);
if (logger.isDebugEnabled())
logger.debug("Added association to " + childId + " to the added list");
}
// if the association was previously removed and has now been re-added it
// will still be in the "to be removed" list so remove it if it is
Map<String, ChildAssociationRef> removed = node.getRemovedChildAssociations().get(this.associationName);
if (removed.containsKey(childId))
{
removed.remove(childId);
if (logger.isDebugEnabled())
logger.debug("Removed association to " + childId + " from the removed list");
}
}
}
}
}

View File

@@ -0,0 +1,367 @@
/*
* 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.component.property;
import java.io.IOException;
import java.text.MessageFormat;
import javax.faces.FacesException;
import javax.faces.component.UIInput;
import javax.faces.component.UIOutput;
import javax.faces.component.UISelectBoolean;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.el.ValueBinding;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.namespace.QName;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.repository.DataDictionary;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.ui.common.ComponentConstants;
import org.alfresco.web.ui.common.Utils;
import org.alfresco.web.ui.common.converter.XMLDateConverter;
import org.alfresco.web.ui.repo.RepoConstants;
import org.alfresco.web.ui.repo.component.UICategorySelector;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.jsf.FacesContextUtils;
/**
* Component to represent an individual property within a property sheet
*
* @author gavinc
*/
public class UIProperty extends PropertySheetItem
{
private static final String MSG_ERROR_PROPERTY = "error_property";
private static final String MSG_DATE_TIME = "date_time_pattern";
private static final String MSG_DATE = "date_pattern";
private static Log logger = LogFactory.getLog(UIProperty.class);
/**
* Default constructor
*/
public UIProperty()
{
// set the default renderer
setRendererType("org.alfresco.faces.PropertyRenderer");
}
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.Property";
}
/**
* @see org.alfresco.web.ui.repo.component.property.PropertySheetItem#getIncorrectParentMsg()
*/
protected String getIncorrectParentMsg()
{
return "The property component must be nested within a property sheet component";
}
/**
* @see org.alfresco.web.ui.repo.component.property.PropertySheetItem#generateItem(javax.faces.context.FacesContext, org.alfresco.web.bean.repository.Node, java.lang.String)
*/
protected void generateItem(FacesContext context, Node node, String var) throws IOException
{
String propertyName = (String)getName();
DataDictionary dd = (DataDictionary)FacesContextUtils.getRequiredWebApplicationContext(
context).getBean(Application.BEAN_DATA_DICTIONARY);
PropertyDefinition propDef = dd.getPropertyDefinition(node, propertyName);
if (propDef == null)
{
// there is no definition for the node, so it may have been added to
// the node as an additional property, so look for it in the node itself
if (node.hasProperty(propertyName))
{
String displayLabel = (String)getDisplayLabel();
if (displayLabel == null)
{
displayLabel = propertyName;
}
// generate the label and generic control
generateLabel(context, displayLabel);
generateControl(context, propertyName, var);
}
else
{
if (logger.isDebugEnabled())
logger.debug("Failed to find property definition for property '" + propertyName + "'");
// add an error message as the property is not defined in the data dictionary and
// not in the node's set of properties
String msg = MessageFormat.format(Application.getMessage(context, MSG_ERROR_PROPERTY), new Object[] {propertyName});
Utils.addErrorMessage(msg);
if (logger.isDebugEnabled())
logger.debug("Added global error message: " + msg);
}
}
else
{
String displayLabel = (String)getDisplayLabel();
if (displayLabel == null)
{
// try and get the repository assigned label
displayLabel = propDef.getTitle();
// if the label is still null default to the local name of the property
if (displayLabel == null)
{
displayLabel = propDef.getName().getLocalName();
}
}
// generate the label and type specific control
generateLabel(context, displayLabel);
generateControl(context, propDef, var);
}
}
/**
* Generates an appropriate control for the given property
*
* @param context JSF context
* @param propDef The definition of the property to create the control for
* @param varName Name of the variable the node is stored in the session as
* (used for value binding expression)
* @param parent The parent component for the control
*/
private void generateControl(FacesContext context, PropertyDefinition propDef,
String varName)
{
UIOutput control = null;
ValueBinding vb = context.getApplication().
createValueBinding("#{" + varName + ".properties[\"" +
propDef.getName().toString() + "\"]}");
UIPropertySheet propSheet = (UIPropertySheet)this.getParent();
DataTypeDefinition dataTypeDef = propDef.getDataType();
QName typeName = dataTypeDef.getName();
if (propSheet.getMode().equalsIgnoreCase(UIPropertySheet.VIEW_MODE))
{
// if we are in view mode simply output the text to the screen unless the type
// of the property is a category
if (typeName.equals(DataTypeDefinition.CATEGORY))
{
control = (UICategorySelector)context.getApplication().
createComponent(RepoConstants.ALFRESCO_FACES_CATEGORY_SELECTOR);
((UICategorySelector)control).setDisabled(true);
}
else
{
control = (UIOutput)context.getApplication().createComponent(ComponentConstants.JAVAX_FACES_OUTPUT);
control.setRendererType(ComponentConstants.JAVAX_FACES_TEXT);
}
// if it is a date or datetime property add the converter
if (typeName.equals(DataTypeDefinition.DATETIME) )
{
XMLDateConverter conv = (XMLDateConverter)context.getApplication().
createConverter(RepoConstants.ALFRESCO_FACES_XMLDATA_CONVERTER);
conv.setType("both");
conv.setPattern(Application.getMessage(context, MSG_DATE_TIME));
control.setConverter(conv);
}
else if (typeName.equals(DataTypeDefinition.DATE))
{
XMLDateConverter conv = (XMLDateConverter)context.getApplication().
createConverter(RepoConstants.ALFRESCO_FACES_XMLDATA_CONVERTER);
conv.setType("date");
conv.setPattern(Application.getMessage(context, MSG_DATE));
control.setConverter(conv);
}
}
else
{
// generate the appropriate input field
if (typeName.equals(DataTypeDefinition.BOOLEAN))
{
control = (UISelectBoolean)context.getApplication().
createComponent(ComponentConstants.JAVAX_FACES_SELECT_BOOLEAN);
control.setRendererType(ComponentConstants.JAVAX_FACES_CHECKBOX);
}
else if (typeName.equals(DataTypeDefinition.CATEGORY))
{
control = (UICategorySelector)context.getApplication().
createComponent(RepoConstants.ALFRESCO_FACES_CATEGORY_SELECTOR);
}
else if (typeName.equals(DataTypeDefinition.DATETIME))
{
control = (UIInput)context.getApplication().
createComponent(ComponentConstants.JAVAX_FACES_INPUT);
control.setRendererType(RepoConstants.ALFRESCO_FACES_DATE_PICKER_RENDERER);
control.getAttributes().put("startYear", new Integer(1970));
control.getAttributes().put("yearCount", new Integer(50));
control.getAttributes().put("showTime", Boolean.valueOf(true));
control.getAttributes().put("style", "margin-right: 7px;");
}
else if (typeName.equals(DataTypeDefinition.DATE))
{
control = (UIInput)context.getApplication().
createComponent(ComponentConstants.JAVAX_FACES_INPUT);
control.setRendererType(RepoConstants.ALFRESCO_FACES_DATE_PICKER_RENDERER);
control.getAttributes().put("startYear", new Integer(1970));
control.getAttributes().put("yearCount", new Integer(50));
control.getAttributes().put("style", "margin-right: 7px;");
}
else
{
// any other type is represented as an input text field
control = (UIInput)context.getApplication().
createComponent(ComponentConstants.JAVAX_FACES_INPUT);
control.setRendererType(ComponentConstants.JAVAX_FACES_TEXT);
control.getAttributes().put("size", "35");
control.getAttributes().put("maxlength", "1024");
}
// if we are trying to edit a NodeRef or Path property type set it to read-only as
// these are internal properties that shouldn't be edited.
if (typeName.equals(DataTypeDefinition.NODE_REF) || typeName.equals(DataTypeDefinition.PATH))
{
logger.warn("Setting property " + propDef.getName().toString() + " to read-only as it can not be edited");
control.getAttributes().put("disabled", Boolean.TRUE);
}
// set control to disabled state if set to read only or if the
// property definition says it is protected
if (isReadOnly() || propDef.isProtected())
{
control.getAttributes().put("disabled", Boolean.TRUE);
}
// add a validator if the field is required
// if (propDef.isMandatory())
// {
// control.setRequired(true);
// LengthValidator val = (LengthValidator)context.getApplication().
// createValidator("javax.faces.Length");
// val.setMinimum(1);
// control.addValidator(val);
// }
}
// set up the common aspects of the control
control.setId(context.getViewRoot().createUniqueId());
control.setValueBinding("value", vb);
// if a converter has been specified we need to instantiate it
// and apply it to the control
if (getConverter() != null)
{
// catch null pointer exception to workaround bug in myfaces
try
{
Converter conv = context.getApplication().createConverter(getConverter());
control.setConverter(conv);
}
catch (FacesException fe)
{
logger.warn("Converter " + getConverter() + " could not be applied");
}
}
// add the control itself
this.getChildren().add(control);
if (logger.isDebugEnabled())
logger.debug("Created control " + control + "(" +
control.getClientId(context) +
") for '" + propDef.getName().toString() +
"' and added it to component " + this);
}
/**
* Generates an appropriate control for the given property name
*
* @param context JSF context
* @param propName The name of the property to create a control for
* @param varName Name of the variable the node is stored in the session as
* (used for value binding expression)
* @param parent The parent component for the control
*/
private void generateControl(FacesContext context, String propName,
String varName)
{
ValueBinding vb = context.getApplication().
createValueBinding("#{" + varName + ".properties[\"" +
propName + "\"]}");
UIOutput control = null;
UIPropertySheet propSheet = (UIPropertySheet)this.getParent();
if (propSheet.getMode().equalsIgnoreCase(UIPropertySheet.VIEW_MODE))
{
// if we are in view mode simply output the text to the screen
control = (UIOutput)context.getApplication().createComponent(ComponentConstants.JAVAX_FACES_OUTPUT);
control.setRendererType(ComponentConstants.JAVAX_FACES_TEXT);
}
else
{
// as we don't know the type of the property we can only output a text field
control = (UIInput)context.getApplication().createComponent(ComponentConstants.JAVAX_FACES_INPUT);
control.setRendererType(ComponentConstants.JAVAX_FACES_TEXT);
control.getAttributes().put("size", "35");
control.getAttributes().put("maxlength", "1024");
// set control to disabled state if set to read only
if (isReadOnly())
{
control.getAttributes().put("disabled", Boolean.TRUE);
}
}
// set the common attributes
control.setId(context.getViewRoot().createUniqueId());
control.setValueBinding("value", vb);
// if a converter has been specified we need to instantiate it
// and apply it to the control
if (getConverter() != null)
{
try
{
Converter conv = context.getApplication().createConverter(getConverter());
control.setConverter(conv);
}
catch (FacesException fe)
{
logger.warn("Converter " + getConverter() + " could not be applied");
}
}
// add the control itself
this.getChildren().add(control);
if (logger.isDebugEnabled())
logger.debug("Created control " + control + "(" +
control.getClientId(context) +
") for '" + propName +
"' and added it to component " + this);
}
}

View File

@@ -0,0 +1,549 @@
/*
* 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.component.property;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIPanel;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import org.alfresco.config.Config;
import org.alfresco.config.ConfigLookupContext;
import org.alfresco.config.ConfigService;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.config.PropertySheetConfigElement;
import org.alfresco.web.config.PropertySheetConfigElement.AssociationConfig;
import org.alfresco.web.config.PropertySheetConfigElement.ChildAssociationConfig;
import org.alfresco.web.config.PropertySheetConfigElement.ItemConfig;
import org.alfresco.web.config.PropertySheetConfigElement.PropertyConfig;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.jsf.FacesContextUtils;
/**
* Component that represents the properties of a Node
*
* @author gavinc
*/
public class UIPropertySheet extends UIPanel implements NamingContainer
{
public static final String VIEW_MODE = "view";
public static final String EDIT_MODE = "edit";
private static Log logger = LogFactory.getLog(UIPropertySheet.class);
private static String DEFAULT_VAR_NAME = "node";
private String variable;
private NodeRef nodeRef;
private Node node;
private Boolean readOnly;
private String mode;
private String configArea;
/**
* Default constructor
*/
public UIPropertySheet()
{
// set the default renderer for a property sheet
setRendererType("javax.faces.Grid");
}
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "javax.faces.Panel";
}
/**
* @see javax.faces.component.UIComponent#encodeBegin(javax.faces.context.FacesContext)
*/
public void encodeBegin(FacesContext context) throws IOException
{
String var = null;
int howManyKids = getChildren().size();
Boolean externalConfig = (Boolean)getAttributes().get("externalConfig");
// generate a variable name to use if necessary
if (this.variable == null)
{
this.variable = DEFAULT_VAR_NAME;
}
// force retrieval of node info
Node node = getNode();
if (howManyKids == 0)
{
if (externalConfig != null && externalConfig.booleanValue())
{
// configure the component using the config service
if (logger.isDebugEnabled())
logger.debug("Configuring property sheet using ConfigService");
// get the properties to display
ConfigService configSvc = (ConfigService)FacesContextUtils.getRequiredWebApplicationContext(
context).getBean(Application.BEAN_CONFIG_SERVICE);
Config configProps = null;
if (getConfigArea() == null)
{
configProps = configSvc.getConfig(node);
}
else
{
// only look within the given area
configProps = configSvc.getConfig(node, new ConfigLookupContext(getConfigArea()));
}
PropertySheetConfigElement itemsToDisplay = (PropertySheetConfigElement)configProps.
getConfigElement("property-sheet");
if (itemsToDisplay != null)
{
List<ItemConfig> itemsToRender = itemsToDisplay.getItemsToShow();
if (logger.isDebugEnabled())
logger.debug("Items to render: " + itemsToDisplay.getItemNamesToShow());
createComponentsFromConfig(context, itemsToRender);
}
else
{
if (logger.isDebugEnabled())
logger.debug("There are no items to render!");
}
}
else
{
// show all the properties for the current node
if (logger.isDebugEnabled())
logger.debug("Configuring property sheet using node's current state");
createComponentsFromNode(context, node);
}
}
// put the node in the session if it is not there already
Map sessionMap = getFacesContext().getExternalContext().getSessionMap();
sessionMap.put(this.variable, node);
if (logger.isDebugEnabled())
logger.debug("Put node into session with key '" + this.variable + "': " + node);
super.encodeBegin(context);
}
/**
* @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.nodeRef = (NodeRef)values[1];
this.node = (Node)values[2];
this.variable = (String)values[3];
this.readOnly = (Boolean)values[4];
this.mode = (String)values[5];
this.configArea = (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.nodeRef;
values[2] = this.node;
values[3] = this.variable;
values[4] = this.readOnly;
values[5] = this.mode;
values[6] = this.configArea;
return (values);
}
/**
* @return Returns the node
*/
public Node getNode()
{
Node node = null;
if (this.node == null)
{
// use the value to get hold of the actual object
Object value = getAttributes().get("value");
if (value == null)
{
ValueBinding vb = getValueBinding("value");
if (vb != null)
{
value = vb.getValue(getFacesContext());
}
}
// TODO: for now we presume the object is a Node, but we need to support id's too
if (value instanceof Node)
{
node = (Node)value;
}
}
else
{
node = this.node;
}
return node;
}
/**
* @param node The node
*/
public void setNode(Node node)
{
this.node = node;
}
/**
* @return Returns the variable.
*/
public String getVar()
{
return this.variable;
}
/**
* @param variable The variable to set.
*/
public void setVar(String variable)
{
this.variable = variable;
}
/**
* @return Returns whether the property sheet is read only
*/
public boolean isReadOnly()
{
if (this.readOnly == null)
{
ValueBinding vb = getValueBinding("readOnly");
if (vb != null)
{
this.readOnly = (Boolean)vb.getValue(getFacesContext());
}
}
if (this.readOnly == null)
{
this.readOnly = Boolean.FALSE;
}
return this.readOnly;
}
/**
* @param readOnly Sets the read only flag for the property sheet
*/
public void setReadOnly(boolean readOnly)
{
this.readOnly = Boolean.valueOf(readOnly);
}
/**
* @return Returns the mode
*/
public String getMode()
{
if (this.mode == null)
{
ValueBinding vb = getValueBinding("mode");
if (vb != null)
{
this.mode = (String)vb.getValue(getFacesContext());
}
}
if (this.mode == null)
{
mode = EDIT_MODE;
}
return mode;
}
/**
* @param mode Sets the mode
*/
public void setMode(String mode)
{
this.mode = mode;
}
/**
* @return Returns the config area to use
*/
public String getConfigArea()
{
if (this.configArea == null)
{
ValueBinding vb = getValueBinding("configArea");
if (vb != null)
{
this.configArea = (String)vb.getValue(getFacesContext());
}
}
return configArea;
}
/**
* @param configArea Sets the config area to use
*/
public void setConfigArea(String configArea)
{
this.configArea = configArea;
}
/**
* Creates all the property components required to display the properties held by the node.
*
* @param context JSF context
* @param node The Node to show all the properties for
* @throws IOException
*/
private void createComponentsFromNode(FacesContext context, Node node)
throws IOException
{
// add all the properties of the node to the UI
Map<String, Object> props = node.getProperties();
for (String propertyName : props.keySet())
{
// create the property component
UIProperty propComp = (UIProperty)context.getApplication().createComponent("org.alfresco.faces.Property");
propComp.setId(context.getViewRoot().createUniqueId());
propComp.setName(propertyName);
// if this property sheet is set as read only, set all properties to read only
if (isReadOnly())
{
propComp.setReadOnly(true);
}
// NOTE: we don't know what the display label is so don't set it
this.getChildren().add(propComp);
if (logger.isDebugEnabled())
logger.debug("Created property component " + propComp + "(" +
propComp.getClientId(context) +
") for '" + propertyName +
"' and added it to property sheet " + this);
}
// add all the associations of the node to the UI
Map associations = node.getAssociations();
Iterator iter = associations.keySet().iterator();
while (iter.hasNext())
{
String assocName = (String)iter.next();
UIAssociation assocComp = (UIAssociation)context.getApplication().createComponent("org.alfresco.faces.Association");
assocComp.setId(context.getViewRoot().createUniqueId());
assocComp.setName(assocName);
// if this property sheet is set as read only, set all properties to read only
if (isReadOnly())
{
assocComp.setReadOnly(true);
}
// NOTE: we don't know what the display label is so don't set it
this.getChildren().add(assocComp);
if (logger.isDebugEnabled())
logger.debug("Created association component " + assocComp + "(" +
assocComp.getClientId(context) +
") for '" + assocName +
"' and added it to property sheet " + this);
}
// add all the child associations of the node to the UI
Map childAssociations = node.getChildAssociations();
iter = childAssociations.keySet().iterator();
while (iter.hasNext())
{
String assocName = (String)iter.next();
UIChildAssociation childAssocComp = (UIChildAssociation)context.getApplication().createComponent(
"org.alfresco.faces.ChildAssociation");
childAssocComp.setId(context.getViewRoot().createUniqueId());
childAssocComp.setName(assocName);
// if this property sheet is set as read only, set all properties to read only
if (isReadOnly())
{
childAssocComp.setReadOnly(true);
}
// NOTE: we don't know what the display label is so don't set it
this.getChildren().add(childAssocComp);
if (logger.isDebugEnabled())
logger.debug("Created child association component " + childAssocComp + "(" +
childAssocComp.getClientId(context) +
") for '" + assocName +
"' and added it to property sheet " + this);
}
}
/**
* Creates all the property components required to display the properties specified
* in an external config file.
*
* @param context JSF context
* @param properties List of properties to render (driven from configuration)
* @throws IOException
*/
private void createComponentsFromConfig(FacesContext context, List<ItemConfig> items)
throws IOException
{
// **********************************
// TODO: Make a common base class for the UIxxx components so we can
// reduce the code below...
// **********************************
for (ItemConfig item : items)
{
// create the appropriate component
if (item instanceof PropertyConfig)
{
UIProperty propComp = (UIProperty)context.getApplication().createComponent("org.alfresco.faces.Property");
propComp.setId(context.getViewRoot().createUniqueId());
propComp.setName(item.getName());
propComp.setConverter(item.getConverter());
String displayLabel = item.getDisplayLabel();
if (item.getDisplayLabelId() != null)
{
String label = Application.getMessage(context, item.getDisplayLabelId());
if (label != null)
{
displayLabel = label;
}
}
propComp.setDisplayLabel(displayLabel);
// if this property sheet is set as read only or the config says the property
// should be read only set it as such
if (isReadOnly() || item.isReadOnly())
{
propComp.setReadOnly(true);
}
this.getChildren().add(propComp);
if (logger.isDebugEnabled())
logger.debug("Created property component " + propComp + "(" +
propComp.getClientId(context) +
") for '" + item.getName() +
"' and added it to property sheet " + this);
}
else if (item instanceof AssociationConfig)
{
UIAssociation assocComp = (UIAssociation)context.getApplication().createComponent("org.alfresco.faces.Association");
assocComp.setId(context.getViewRoot().createUniqueId());
assocComp.setName(item.getName());
assocComp.setConverter(item.getConverter());
String displayLabel = item.getDisplayLabel();
if (item.getDisplayLabelId() != null)
{
String label = Application.getMessage(context, item.getDisplayLabelId());
if (label != null)
{
displayLabel = label;
}
}
assocComp.setDisplayLabel(displayLabel);
// if this property sheet is set as read only or the config says the property
// should be read only set it as such
if (isReadOnly() || item.isReadOnly())
{
assocComp.setReadOnly(true);
}
this.getChildren().add(assocComp);
if (logger.isDebugEnabled())
logger.debug("Created association component " + assocComp + "(" +
assocComp.getClientId(context) +
") for '" + item.getName() +
"' and added it to property sheet " + this);
}
else if (item instanceof ChildAssociationConfig)
{
UIChildAssociation assocComp = (UIChildAssociation)context.getApplication().createComponent("org.alfresco.faces.ChildAssociation");
assocComp.setId(context.getViewRoot().createUniqueId());
assocComp.setName(item.getName());
assocComp.setConverter(item.getConverter());
String displayLabel = item.getDisplayLabel();
if (item.getDisplayLabelId() != null)
{
String label = Application.getMessage(context, item.getDisplayLabelId());
if (label != null)
{
displayLabel = label;
}
}
assocComp.setDisplayLabel(displayLabel);
// if this property sheet is set as read only or the config says the property
// should be read only set it as such
if (isReadOnly() || item.isReadOnly())
{
assocComp.setReadOnly(true);
}
this.getChildren().add(assocComp);
if (logger.isDebugEnabled())
logger.debug("Created child association component " + assocComp + "(" +
assocComp.getClientId(context) +
") for '" + item.getName() +
"' and added it to property sheet " + this);
}
}
}
}

View File

@@ -0,0 +1,357 @@
/*
* 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.component.shelf;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import javax.faces.component.NamingContainer;
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.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.FacesEvent;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.clipboard.ClipboardItem;
import org.alfresco.web.bean.clipboard.ClipboardStatus;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.ui.common.Utils;
import org.alfresco.web.ui.repo.WebResources;
/**
* @author Kevin Roast
*/
public class UIClipboardShelfItem extends UIShelfItem
{
// ------------------------------------------------------------------------------
// Component Impl
/**
* @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.collections = (List)values[1];
this.pasteActionListener = (MethodBinding)values[2];
}
/**
* @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
*/
public Object saveState(FacesContext context)
{
Object values[] = new Object[3];
// standard component attributes are saved by the super class
values[0] = super.saveState(context);
values[1] = this.collections;
values[2] = this.pasteActionListener;
return values;
}
/**
* @see javax.faces.component.UIComponentBase#decode(javax.faces.context.FacesContext)
*/
public void decode(FacesContext context)
{
Map requestMap = context.getExternalContext().getRequestParameterMap();
String fieldId = getHiddenFieldName();
String value = (String)requestMap.get(fieldId);
if (value != null && value.length() != 0)
{
// decode the values - we are expecting an action identifier and an index
int sepIndex = value.indexOf(NamingContainer.SEPARATOR_CHAR);
int action = Integer.parseInt(value.substring(0, sepIndex));
int index = Integer.parseInt(value.substring(sepIndex + 1));
// raise an event to process the action later in the lifecycle
ClipboardEvent event = new ClipboardEvent(this, action, index);
this.queueEvent(event);
}
}
/**
* @see javax.faces.component.UIComponentBase#broadcast(javax.faces.event.FacesEvent)
*/
public void broadcast(FacesEvent event) throws AbortProcessingException
{
if (event instanceof ClipboardEvent)
{
// found an event we should handle
ClipboardEvent clipEvent = (ClipboardEvent)event;
List<ClipboardItem> items = getCollections();
if (items.size() > clipEvent.Index)
{
// process the action
switch (clipEvent.Action)
{
case ACTION_REMOVE_ALL:
items.clear();
break;
case ACTION_REMOVE_ITEM:
items.remove(clipEvent.Index);
break;
case ACTION_PASTE_ALL:
case ACTION_PASTE_ITEM:
Utils.processActionMethod(getFacesContext(), getPasteActionListener(), clipEvent);
break;
}
}
}
else
{
super.broadcast(event);
}
}
/**
* @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext)
*/
public void encodeBegin(FacesContext context) throws IOException
{
if (isRendered() == false)
{
return;
}
ResponseWriter out = context.getResponseWriter();
List<ClipboardItem> items = getCollections();
out.write(SHELF_START);
if (items.size() != 0)
{
DictionaryService dd = Repository.getServiceRegistry(context).getDictionaryService();
ResourceBundle bundle = Application.getBundle(context);
for (int i=0; i<items.size(); i++)
{
ClipboardItem item = items.get(i);
// start row with cut/copy state icon
out.write("<tr><td>");
if (item.Mode == ClipboardStatus.COPY)
{
out.write(Utils.buildImageTag(context, WebResources.IMAGE_COPY, 14, 16, bundle.getString(MSG_COPY), null, "absmiddle"));
}
else
{
out.write(Utils.buildImageTag(context, WebResources.IMAGE_CUT, 13, 16, bundle.getString(MSG_CUT), null, "absmiddle"));
}
out.write("</td><td>");
if (dd.isSubClass(item.Node.getType(), ContentModel.TYPE_FOLDER))
{
// start row with Space icon
out.write(Utils.buildImageTag(context, WebResources.IMAGE_SPACE, 16, 16, null, null, "absmiddle"));
}
else if (dd.isSubClass(item.Node.getType(), ContentModel.TYPE_CONTENT))
{
String image = Utils.getFileTypeImage(item.Node.getName(), true);
out.write(Utils.buildImageTag(context, image, 16, 16, null, null, "absmiddle"));
}
// output cropped item label - we also output with no breaks, this is ok
// as the copped label will ensure a sensible maximum width
out.write("</td><td width=100%><nobr>&nbsp;");
out.write(Utils.cropEncode(item.Node.getName()));
// output actions
out.write("</nobr></td><td align=right><nobr>");
out.write(buildActionLink(ACTION_REMOVE_ITEM, i, bundle.getString(MSG_REMOVE_ITEM), WebResources.IMAGE_REMOVE));
out.write("&nbsp;");
out.write(buildActionLink(ACTION_PASTE_ITEM, i, bundle.getString(MSG_PASTE_ITEM), WebResources.IMAGE_PASTE));
// end actions cell and end row
out.write("</nobr></td></tr>");
}
// output general actions if any clipboard items are present
out.write("<tr><td colspan=3><nobr>");
out.write(buildActionLink(ACTION_PASTE_ALL, -1, bundle.getString(MSG_PASTE_ALL), null));
out.write("&nbsp;");
out.write(buildActionLink(ACTION_REMOVE_ALL, -1, bundle.getString(MSG_REMOVE_ALL), null));
out.write("</nobr></td><td></td></tr>");
}
out.write(SHELF_END);
}
// ------------------------------------------------------------------------------
// Strongly typed component property accessors
/**
* @param collections Set the clipboard item collections to use
*/
public void setCollections(List<ClipboardItem> collections)
{
this.collections = collections;
}
/**
* @return The clipboard item collections to use
*/
public List<ClipboardItem> getCollections()
{
ValueBinding vb = getValueBinding("collections");
if (vb != null)
{
this.collections = (List<ClipboardItem>)vb.getValue(getFacesContext());
}
return this.collections;
}
/**
* @param binding The MethodBinding to call when Paste is selected by the user
*/
public void setPasteActionListener(MethodBinding binding)
{
this.pasteActionListener = binding;
}
/**
* @return The MethodBinding to call when Paste is selected by the user
*/
public MethodBinding getPasteActionListener()
{
return this.pasteActionListener;
}
// ------------------------------------------------------------------------------
// Private helpers
/**
* We use a hidden field name on the assumption that only one clipboard shelf item instance
* is present on a single page.
*
* @return hidden field name
*/
private String getHiddenFieldName()
{
return getClientId(getFacesContext());
}
/**
* Encode the specified values for output to a hidden field
*
* @param action Action identifer
* @param index Index of the clipboard item the action is for
*
* @return encoded values
*/
private static String encodeValues(int action, int index)
{
return Integer.toString(action) + NamingContainer.SEPARATOR_CHAR + Integer.toString(index);
}
/**
* Build HTML for an link representing a clipboard action
*
* @param action action indentifier to represent
* @param index index of the clipboard item this action relates too
* @param text of the action to display
* @param image image icon to display
*
* @return HTML for action link
*/
private String buildActionLink(int action, int index, String text, String image)
{
FacesContext context = getFacesContext();
StringBuilder buf = new StringBuilder(256);
buf.append("<a href='#' onclick=\"");
// generate JavaScript to set a hidden form field and submit
// a form which request attributes that we can decode
buf.append(Utils.generateFormSubmit(context, this, getHiddenFieldName(), encodeValues(action, index)));
buf.append("\">");
if (image != null)
{
buf.append(Utils.buildImageTag(context, image, text));
}
else
{
buf.append(Utils.encode(text));
}
buf.append("</a>");
return buf.toString();
}
// ------------------------------------------------------------------------------
// Inner classes
/**
* Class representing the an action relevant to the Clipboard element.
*/
public static class ClipboardEvent extends ActionEvent
{
public ClipboardEvent(UIComponent component, int action, int index)
{
super(component);
Action = action;
Index = index;
}
public int Action;
public int Index;
}
// ------------------------------------------------------------------------------
// Private data
/** I18N messages */
private static final String MSG_REMOVE_ALL = "remove_all";
private static final String MSG_PASTE_ALL = "paste_all";
private static final String MSG_PASTE_ITEM = "paste_item";
private static final String MSG_REMOVE_ITEM = "remove_item";
private static final String MSG_CUT = "cut";
private static final String MSG_COPY = "copy";
private final static int ACTION_REMOVE_ITEM = 0;
private final static int ACTION_REMOVE_ALL = 1;
private final static int ACTION_PASTE_ITEM = 2;
private final static int ACTION_PASTE_ALL = 3;
/** the current list of clipboard items */
private List<ClipboardItem> collections;
/** action listener called when a paste action occurs */
private MethodBinding pasteActionListener;
}

View File

@@ -0,0 +1,287 @@
/*
* 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.component.shelf;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.faces.component.NamingContainer;
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.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.FacesEvent;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.ui.common.Utils;
import org.alfresco.web.ui.repo.WebResources;
/**
* JSF Component providing UI for a real-time updated list of the most recently visited Spaces.
*
* @author Kevin Roast
*/
public class UIRecentSpacesShelfItem extends UIShelfItem
{
// ------------------------------------------------------------------------------
// Component Impl
/**
* @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.value = values[1];
this.navigateActionListener = (MethodBinding)values[2];
}
/**
* @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
*/
public Object saveState(FacesContext context)
{
Object values[] = new Object[3];
// standard component attributes are saved by the super class
values[0] = super.saveState(context);
values[1] = this.value;
values[2] = this.navigateActionListener;
return (values);
}
/**
* Get the value (for this component the value is used as the List of recent Space Nodes)
*
* @return the value
*/
public Object getValue()
{
if (this.value == null)
{
ValueBinding vb = getValueBinding("value");
if (vb != null)
{
this.value = vb.getValue(getFacesContext());
}
}
return this.value;
}
/**
* Set the value (for this component the value is used as the List of recent Space Nodes)
*
* @param value the value
*/
public void setValue(Object value)
{
this.value = value;
}
/**
* @param binding The MethodBinding to call when Navigate is performed by the user
*/
public void setNavigateActionListener(MethodBinding binding)
{
this.navigateActionListener = binding;
}
/**
* @return The MethodBinding to call when Navigate is performed by the user
*/
public MethodBinding getNavigateActionListener()
{
return this.navigateActionListener;
}
/**
* @see javax.faces.component.UIComponentBase#decode(javax.faces.context.FacesContext)
*/
public void decode(FacesContext context)
{
Map requestMap = context.getExternalContext().getRequestParameterMap();
String fieldId = getHiddenFieldName();
String value = (String)requestMap.get(fieldId);
if (value != null && value.length() != 0)
{
// decode the values - we are expecting an action identifier and an index
int sepIndex = value.indexOf(NamingContainer.SEPARATOR_CHAR);
int action = Integer.parseInt(value.substring(0, sepIndex));
int index = Integer.parseInt(value.substring(sepIndex + 1));
// raise an event to process the action later in the lifecycle
RecentSpacesEvent event = new RecentSpacesEvent(this, action, index);
this.queueEvent(event);
}
}
/**
* @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext)
*/
public void encodeBegin(FacesContext context) throws IOException
{
if (isRendered() == false)
{
return;
}
ResponseWriter out = context.getResponseWriter();
List<Node> items = (List<Node>)getValue();
out.write(SHELF_START);
for (int i=0; i<items.size(); i++)
{
Node item = items.get(i);
// start row with Space icon
out.write("<tr><td>");
out.write(Utils.buildImageTag(context, WebResources.IMAGE_SPACE, 16, 16, null, null, "absmiddle"));
// output cropped item label - we also output with no breaks, this is ok
// as the copped label will ensure a sensible maximum width
out.write("</td><td width=100%><nobr>&nbsp;");
out.write(buildActionLink(ACTION_NAVIGATE_ITEM, i, item.getName()));
// end actions cell and end row
out.write("</nobr></td></tr>");
}
out.write(SHELF_END);
}
/**
* @see javax.faces.component.UIComponentBase#broadcast(javax.faces.event.FacesEvent)
*/
public void broadcast(FacesEvent event) throws AbortProcessingException
{
if (event instanceof RecentSpacesEvent)
{
// found an event we should handle
RecentSpacesEvent spaceEvent = (RecentSpacesEvent)event;
List<Node> items = (List<Node>)getValue();
if (items.size() > spaceEvent.Index)
{
// process the action
switch (spaceEvent.Action)
{
case ACTION_NAVIGATE_ITEM:
Utils.processActionMethod(getFacesContext(), getNavigateActionListener(), spaceEvent);
break;
}
}
}
else
{
super.broadcast(event);
}
}
// ------------------------------------------------------------------------------
// Private helpers
/**
* We use a hidden field name on the assumption that only one Recent Spaces shelf item
* instance is present on a single page.
*
* @return hidden field name
*/
private String getHiddenFieldName()
{
return getClientId(getFacesContext());
}
/**
* Build HTML for an link representing a Recent Space action
*
* @param action action indentifier to represent
* @param index index of the Node item this action relates too
* @param text of the action to display
*
* @return HTML for action link
*/
private String buildActionLink(int action, int index, String text)
{
FacesContext context = getFacesContext();
StringBuilder buf = new StringBuilder(200);
buf.append("<a href='#' onclick=\"");
// generate JavaScript to set a hidden form field and submit
// a form which request attributes that we can decode
buf.append(Utils.generateFormSubmit(context, this, getHiddenFieldName(), encodeValues(action, index)));
buf.append("\">");
buf.append(Utils.cropEncode(text));
buf.append("</a>");
return buf.toString();
}
/**
* Encode the specified values for output to a hidden field
*
* @param action Action identifer
* @param index Index of the Node item the action is for
*
* @return encoded values
*/
private static String encodeValues(int action, int index)
{
return Integer.toString(action) + NamingContainer.SEPARATOR_CHAR + Integer.toString(index);
}
// ------------------------------------------------------------------------------
// Inner classes
/**
* Class representing the an action relevant to the Recent Spaces element.
*/
public static class RecentSpacesEvent extends ActionEvent
{
public RecentSpacesEvent(UIComponent component, int action, int index)
{
super(component);
Action = action;
Index = index;
}
public int Action;
public int Index;
}
// ------------------------------------------------------------------------------
// Private data
private final static int ACTION_NAVIGATE_ITEM = 0;
/** for this component the value is used as the List of recent Space Nodes */
private Object value = null;
/** action listener called when a Navigate action occurs */
private MethodBinding navigateActionListener;
}

View File

@@ -0,0 +1,491 @@
/*
* 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.component.shelf;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import javax.faces.component.NamingContainer;
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.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.FacesEvent;
import org.alfresco.web.ui.common.PanelGenerator;
import org.alfresco.web.ui.common.Utils;
import org.alfresco.web.ui.common.WebResources;
import org.alfresco.web.ui.common.component.SelfRenderingComponent;
/**
* @author Kevin Roast
*/
public class UIShelf extends SelfRenderingComponent
{
// ------------------------------------------------------------------------------
// Component Impl
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.Shelf";
}
/**
* @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.groupPanel = (String)values[1];
this.groupBgcolor = (String)values[2];
this.selectedGroupPanel = (String)values[3];
this.selectedGroupBgcolor = (String)values[4];
this.innerGroupPanel = (String)values[5];
this.innerGroupBgcolor = (String)values[6];
this.groupExpandedActionListener = (MethodBinding)values[7];
}
/**
* @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
*/
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.groupPanel;
values[2] = this.groupBgcolor;
values[3] = this.selectedGroupPanel;
values[4] = this.selectedGroupBgcolor;
values[5] = this.innerGroupPanel;
values[6] = this.innerGroupBgcolor;
values[7] = this.groupExpandedActionListener;
return values;
}
/**
* @param binding The MethodBinding to call when the Group expand action is performed by the user
*/
public void setGroupExpandedActionListener(MethodBinding binding)
{
this.groupExpandedActionListener = binding;
}
/**
* @return The MethodBinding to call for the Group expand action
*/
public MethodBinding getGroupExpandedActionListener()
{
return this.groupExpandedActionListener;
}
/**
* @see javax.faces.component.UIComponentBase#decode(javax.faces.context.FacesContext)
*/
public void decode(FacesContext context)
{
Map requestMap = context.getExternalContext().getRequestParameterMap();
String fieldId = getHiddenFieldName();
String value = (String)requestMap.get(fieldId);
// we encoded the value to start with our Id
if (value != null && value.length() != 0)
{
int sepIndex = value.indexOf(NamingContainer.SEPARATOR_CHAR);
int groupIndex = Integer.parseInt( value.substring(0, sepIndex) );
boolean expanded = Boolean.parseBoolean( value.substring(sepIndex + 1) );
// fire an event here to indicate the change that occured
ShelfEvent event = new ShelfEvent(this, groupIndex, expanded);
this.queueEvent(event);
}
}
/**
* @see javax.faces.component.UIComponentBase#broadcast(javax.faces.event.FacesEvent)
*/
public void broadcast(FacesEvent event) throws AbortProcessingException
{
if (event instanceof ShelfEvent)
{
ShelfEvent shelfEvent = (ShelfEvent)event;
// set the new expanded state of the appropriate shelf item
int index = 0;
for (Iterator i=this.getChildren().iterator(); i.hasNext(); index++)
{
UIComponent child = (UIComponent)i.next();
if (index == shelfEvent.Index && child instanceof UIShelfGroup)
{
// found correct child - set the new state
((UIShelfGroup)child).setExpanded(shelfEvent.Expanded);
break;
}
}
// if an action event is registered to be notified then fire that next
if (getGroupExpandedActionListener() != null)
{
Utils.processActionMethod(getFacesContext(), getGroupExpandedActionListener(), shelfEvent);
}
}
else
{
super.broadcast(event);
}
}
/**
* @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext)
*/
public void encodeBegin(FacesContext context) throws IOException
{
if (isRendered() == false)
{
return;
}
ResponseWriter out = context.getResponseWriter();
// TODO: allow config of spacing between ShelfGroup components
out.write("<table border=0 cellspacing=2 cellpadding=0 width=100%>");
}
/**
* @see javax.faces.component.UIComponentBase#encodeChildren(javax.faces.context.FacesContext)
*/
public void encodeChildren(FacesContext context) throws IOException
{
if (isRendered() == false)
{
return;
}
ResponseWriter out = context.getResponseWriter();
// output each shelf group in turn
int index = 0;
for (Iterator i=this.getChildren().iterator(); i.hasNext(); index++)
{
UIComponent child = (UIComponent)i.next();
if (child instanceof UIShelfGroup)
{
UIShelfGroup group = (UIShelfGroup)child;
if (group.isRendered() == true)
{
// output the surrounding structure then call the component to render itself and children
boolean isExpanded = group.isExpanded(); // TODO: get this from Shelf or ShelfGroup?
out.write("<tr><td>");
String contextPath = context.getExternalContext().getRequestContextPath();
// output appropriate panel start section and bgcolor
String groupPanel;
String groupBgcolor;
if (isExpanded == false)
{
groupPanel = getGroupPanel();
groupBgcolor = getGroupBgcolor();
}
else
{
groupPanel = getSelectedGroupPanel();
groupBgcolor = getSelectedGroupBgcolor();
}
if (groupBgcolor == null)
{
groupBgcolor = PanelGenerator.BGCOLOR_WHITE;
}
if (groupPanel != null)
{
PanelGenerator.generatePanelStart(out, contextPath, groupPanel, groupBgcolor);
}
// output appropriate expanded icon state
out.write("<div style='padding-top:2px;padding-bottom:4px'><nobr>");
out.write("<a href='#' onclick=\"");
// encode value as the index of the ShelfGroup clicked and the new state
String value = Integer.toString(index) + NamingContainer.SEPARATOR_CHAR + Boolean.toString(!isExpanded);
out.write(Utils.generateFormSubmit(context, this, getHiddenFieldName(), value));
out.write("\">");
if (isExpanded == true)
{
out.write(Utils.buildImageTag(context, WebResources.IMAGE_EXPANDED, 11, 11, ""));
}
else
{
out.write(Utils.buildImageTag(context, WebResources.IMAGE_COLLAPSED, 11, 11, ""));
}
out.write("</a>&nbsp;");
// output title label text
String label = group.getLabel();
out.write("<span");
outputAttribute(out, group.getAttributes().get("style"), "style");
outputAttribute(out, group.getAttributes().get("styleClass"), "class");
out.write('>');
out.write(Utils.encode(label));
out.write("</span>");
out.write("</nobr></div>");
if (isExpanded == true)
{
// if this is the expanded group, output the inner panel
String innerGroupPanel = getInnerGroupPanel();
String innerGroupBgcolor = getInnerGroupBgcolor();
if (innerGroupBgcolor == null)
{
innerGroupBgcolor = PanelGenerator.BGCOLOR_WHITE;
}
if (innerGroupPanel != null)
{
PanelGenerator.generatePanelStart(out, contextPath, innerGroupPanel, innerGroupBgcolor);
}
// allow child components to render themselves
Utils.encodeRecursive(context, group);
if (innerGroupPanel != null)
{
PanelGenerator.generatePanelEnd(out, contextPath, innerGroupPanel);
}
}
// output panel and group end elements
PanelGenerator.generatePanelEnd(out, contextPath, groupPanel);
out.write("</td></tr>");
}
}
}
}
/**
* @see javax.faces.component.UIComponentBase#encodeEnd(javax.faces.context.FacesContext)
*/
public void encodeEnd(FacesContext context) throws IOException
{
if (isRendered() == false)
{
return;
}
ResponseWriter out = context.getResponseWriter();
out.write("</table>");
}
/**
* @see javax.faces.component.UIComponentBase#getRendersChildren()
*/
public boolean getRendersChildren()
{
return true;
}
// ------------------------------------------------------------------------------
// Strongly typed component property accessors
/**
* @return Returns the group panel name.
*/
public String getGroupPanel()
{
ValueBinding vb = getValueBinding("groupPanel");
if (vb != null)
{
this.groupPanel = (String)vb.getValue(getFacesContext());
}
return this.groupPanel;
}
/**
* @param groupPanel The group panel name to set.
*/
public void setGroupPanel(String groupPanel)
{
this.groupPanel = groupPanel;
}
/**
* @return Returns the group background colour.
*/
public String getGroupBgcolor()
{
ValueBinding vb = getValueBinding("groupBgcolor");
if (vb != null)
{
this.groupBgcolor = (String)vb.getValue(getFacesContext());
}
return this.groupBgcolor;
}
/**
* @param groupBgcolor The group background colour to set.
*/
public void setGroupBgcolor(String groupBgcolor)
{
this.groupBgcolor = groupBgcolor;
}
/**
* @return Returns the selected group panel name.
*/
public String getSelectedGroupPanel()
{
ValueBinding vb = getValueBinding("selectedGroupPanel");
if (vb != null)
{
this.selectedGroupPanel = (String)vb.getValue(getFacesContext());
}
return this.selectedGroupPanel;
}
/**
* @param selectedGroupPanel The selected group panel name to set.
*/
public void setSelectedGroupPanel(String selectedGroupPanel)
{
this.selectedGroupPanel = selectedGroupPanel;
}
/**
* @return Returns the selected group background colour.
*/
public String getSelectedGroupBgcolor()
{
ValueBinding vb = getValueBinding("selectedGroupBgcolor");
if (vb != null)
{
this.selectedGroupBgcolor = (String)vb.getValue(getFacesContext());
}
return this.selectedGroupBgcolor;
}
/**
* @param selectedGroupBgcolor The selected group background colour to set.
*/
public void setSelectedGroupBgcolor(String selectedGroupBgcolor)
{
this.selectedGroupBgcolor = selectedGroupBgcolor;
}
/**
* @return Returns the inner group panel name.
*/
public String getInnerGroupPanel()
{
ValueBinding vb = getValueBinding("innerGroupPanel");
if (vb != null)
{
this.innerGroupPanel = (String)vb.getValue(getFacesContext());
}
return this.innerGroupPanel;
}
/**
* @param innerGroupPanel The inner group panel name to set.
*/
public void setInnerGroupPanel(String innerGroupPanel)
{
this.innerGroupPanel = innerGroupPanel;
}
/**
* @return Returns the inner group background colour.
*/
public String getInnerGroupBgcolor()
{
ValueBinding vb = getValueBinding("innerGroupBgcolor");
if (vb != null)
{
this.innerGroupBgcolor = (String)vb.getValue(getFacesContext());
}
return this.innerGroupBgcolor;
}
/**
* @param innerGroupBgcolor The inner group background colour to set.
*/
public void setInnerGroupBgcolor(String innerGroupBgcolor)
{
this.innerGroupBgcolor = innerGroupBgcolor;
}
// ------------------------------------------------------------------------------
// Private helpers
/**
* We use a hidden field name on the assumption that very few shelf instances will
* be present on a single page.
*
* @return hidden field name
*/
private String getHiddenFieldName()
{
return getClientId(getFacesContext());
}
// ------------------------------------------------------------------------------
// Inner classes
/**
* Class representing the an action event relevant to the Shelf.
*/
public static class ShelfEvent extends ActionEvent
{
public ShelfEvent(UIComponent component, int index, boolean expanded)
{
super(component);
Expanded = expanded;
Index = index;
}
public boolean Expanded;
public int Index;
}
// ------------------------------------------------------------------------------
// Private data
/** component properties */
private String groupPanel;
private String groupBgcolor;
private String selectedGroupPanel;
private String selectedGroupBgcolor;
private String innerGroupPanel;
private String innerGroupBgcolor;
private MethodBinding groupExpandedActionListener;
}

View File

@@ -0,0 +1,162 @@
/*
* 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.component.shelf;
import java.io.IOException;
import java.util.Iterator;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.el.ValueBinding;
import org.alfresco.web.ui.common.Utils;
import org.alfresco.web.ui.common.component.SelfRenderingComponent;
/**
* @author Kevin Roast
*/
public class UIShelfGroup extends SelfRenderingComponent
{
// ------------------------------------------------------------------------------
// Component Impl
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.Shelf";
}
/**
* @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.expanded = ((Boolean)values[1]).booleanValue();
this.label = (String)values[2];
}
/**
* @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
*/
public Object saveState(FacesContext context)
{
Object values[] = new Object[3];
// standard component attributes are saved by the super class
values[0] = super.saveState(context);
values[1] = (this.expanded ? Boolean.TRUE : Boolean.FALSE);
values[2] = this.label;
return values;
}
/**
* @see javax.faces.component.UIComponentBase#encodeChildren(javax.faces.context.FacesContext)
*/
public void encodeChildren(FacesContext context) throws IOException
{
if (isRendered() == false)
{
return;
}
ResponseWriter out = context.getResponseWriter();
// output each shelf group component in turn
out.write("<table cellspacing=0 cellpadding=0 border=0 width=100%>");
for (Iterator i=this.getChildren().iterator(); i.hasNext(); /**/)
{
UIComponent child = (UIComponent)i.next();
if (child instanceof UIShelfItem)
{
// render child items
out.write("<tr><td>");
Utils.encodeRecursive(context, child);
out.write("</tr></td>");
}
}
out.write("</table>");
}
/**
* @see javax.faces.component.UIComponentBase#getRendersChildren()
*/
public boolean getRendersChildren()
{
return true;
}
// ------------------------------------------------------------------------------
// Strongly typed component property accessors
/**
* @return Returns the label.
*/
public String getLabel()
{
ValueBinding vb = getValueBinding("label");
if (vb != null)
{
this.label = (String)vb.getValue(getFacesContext());
}
return this.label;
}
/**
* @param label The label to set.
*/
public void setLabel(String label)
{
this.label = label;
}
/**
* Returns whether the component show allow rendering of its child components.
*/
public boolean isExpanded()
{
ValueBinding vb = getValueBinding("expanded");
if (vb != null)
{
Boolean expanded = (Boolean)vb.getValue(getFacesContext());
if (expanded != null)
{
this.expanded = expanded.booleanValue();
}
}
return this.expanded;
}
/**
* Sets whether the group is expanded
*/
public void setExpanded(boolean expanded)
{
this.expanded = expanded;
}
private String label = null;
private boolean expanded = false;
}

View File

@@ -0,0 +1,40 @@
/*
* 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.component.shelf;
import org.alfresco.web.ui.common.component.SelfRenderingComponent;
/**
* @author Kevin Roast
*/
public class UIShelfItem extends SelfRenderingComponent
{
protected final static String SHELF_START = "<table border=0 cellspacing=3 cellpadding=0 width=100% valign=top>";
protected final static String SHELF_END = "</table>";
// ------------------------------------------------------------------------------
// Component Impl
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.Shelf";
}
}

View File

@@ -0,0 +1,373 @@
/*
* 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.component.shelf;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.faces.component.NamingContainer;
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.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.FacesEvent;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.ui.common.Utils;
import org.alfresco.web.ui.repo.WebResources;
/**
* JSF Component providing UI for a list of user defined shortcuts to favorite nodes.
*
* @author Kevin Roast
*/
public class UIShortcutsShelfItem extends UIShelfItem
{
// ------------------------------------------------------------------------------
// Component Impl
/**
* @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.value = values[1];
this.clickActionListener = (MethodBinding)values[2];
this.removeActionListener = (MethodBinding)values[3];
}
/**
* @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
*/
public Object saveState(FacesContext context)
{
Object values[] = new Object[4];
// standard component attributes are saved by the super class
values[0] = super.saveState(context);
values[1] = this.value;
values[2] = this.clickActionListener;
values[3] = this.removeActionListener;
return (values);
}
/**
* Get the value (for this component the value is used as the List of shortcut nodes)
*
* @return the value
*/
public Object getValue()
{
if (this.value == null)
{
ValueBinding vb = getValueBinding("value");
if (vb != null)
{
this.value = vb.getValue(getFacesContext());
}
}
return this.value;
}
/**
* Set the value (for this component the value is used as the List of shortcut nodes)
*
* @param value the value
*/
public void setValue(Object value)
{
this.value = value;
}
/**
* @param binding The MethodBinding to call when Click is performed by the user
*/
public void setClickActionListener(MethodBinding binding)
{
this.clickActionListener = binding;
}
/**
* @return The MethodBinding to call when Click is performed by the user
*/
public MethodBinding getClickActionListener()
{
return this.clickActionListener;
}
/**
* @param binding The MethodBinding to call when Remove is performed by the user
*/
public void setRemoveActionListener(MethodBinding binding)
{
this.removeActionListener = binding;
}
/**
* @return The MethodBinding to call when Remove is performed by the user
*/
public MethodBinding getRemoveActionListener()
{
return this.removeActionListener;
}
/**
* @see javax.faces.component.UIComponentBase#decode(javax.faces.context.FacesContext)
*/
public void decode(FacesContext context)
{
Map requestMap = context.getExternalContext().getRequestParameterMap();
String fieldId = getHiddenFieldName();
String value = (String)requestMap.get(fieldId);
if (value != null && value.length() != 0)
{
// decode the values - we are expecting an action identifier and an index
int sepIndex = value.indexOf(NamingContainer.SEPARATOR_CHAR);
int action = Integer.parseInt(value.substring(0, sepIndex));
int index = Integer.parseInt(value.substring(sepIndex + 1));
// raise an event to process the action later in the lifecycle
ShortcutEvent event = new ShortcutEvent(this, action, index);
this.queueEvent(event);
}
}
/**
* @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext)
*/
public void encodeBegin(FacesContext context) throws IOException
{
if (isRendered() == false)
{
return;
}
ResponseWriter out = context.getResponseWriter();
List<Node> items = (List<Node>)getValue();
out.write(SHELF_START);
if (items != null)
{
DictionaryService dd = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getDictionaryService();
for (int i=0; i<items.size(); i++)
{
Node item = items.get(i);
out.write("<tr><td>");
if (dd.isSubClass(item.getType(), ContentModel.TYPE_FOLDER))
{
// start row with Space icon
out.write(Utils.buildImageTag(context, WebResources.IMAGE_SPACE, 16, 16, null, null, "absmiddle"));
}
else if (dd.isSubClass(item.getType(), ContentModel.TYPE_CONTENT))
{
String image = Utils.getFileTypeImage(item.getName(), true);
out.write(Utils.buildImageTag(context, image, 16, 16, null, null, "absmiddle"));
}
// output cropped item label - we also output with no breaks, this is ok
// as the copped label will ensure a sensible maximum width
out.write("</td><td width=100%><nobr>&nbsp;");
out.write(buildActionLink(ACTION_CLICK_ITEM, i, item.getName()));
// output actions
out.write("</nobr></td><td align=right><nobr>");
out.write(buildActionLink(ACTION_REMOVE_ITEM, i, Application.getMessage(context, MSG_REMOVE_ITEM), WebResources.IMAGE_REMOVE));
// TODO: add view details action here?
// end actions cell and end row
out.write("</nobr></td></tr>");
}
}
out.write(SHELF_END);
}
/**
* @see javax.faces.component.UIComponentBase#broadcast(javax.faces.event.FacesEvent)
*/
public void broadcast(FacesEvent event) throws AbortProcessingException
{
if (event instanceof ShortcutEvent)
{
// found an event we should handle
ShortcutEvent shortcutEvent = (ShortcutEvent)event;
List<Node> items = (List<Node>)getValue();
if (items != null && items.size() > shortcutEvent.Index)
{
// process the action
switch (shortcutEvent.Action)
{
case ACTION_CLICK_ITEM:
Utils.processActionMethod(getFacesContext(), getClickActionListener(), shortcutEvent);
break;
case ACTION_REMOVE_ITEM:
Utils.processActionMethod(getFacesContext(), getRemoveActionListener(), shortcutEvent);
break;
}
}
}
else
{
super.broadcast(event);
}
}
// ------------------------------------------------------------------------------
// Private helpers
/**
* We use a hidden field name on the assumption that only one Shortcut Shelf item
* instance is present on a single page.
*
* @return hidden field name
*/
private String getHiddenFieldName()
{
return getClientId(getFacesContext());
}
/**
* Build HTML for an link representing a Shortcut action
*
* @param action action indentifier to represent
* @param index index of the Node item this action relates too
* @param text of the action to display
*
* @return HTML for action link
*/
private String buildActionLink(int action, int index, String text)
{
FacesContext context = getFacesContext();
StringBuilder buf = new StringBuilder(200);
buf.append("<a href='#' onclick=\"");
// generate JavaScript to set a hidden form field and submit
// a form which request attributes that we can decode
buf.append(Utils.generateFormSubmit(context, this, getHiddenFieldName(), encodeValues(action, index)));
buf.append("\">");
buf.append(Utils.cropEncode(text));
buf.append("</a>");
return buf.toString();
}
/**
* Build HTML for an link representing a clipboard action
*
* @param action action indentifier to represent
* @param index index of the clipboard item this action relates too
* @param text of the action to display
* @param image image icon to display
*
* @return HTML for action link
*/
private String buildActionLink(int action, int index, String text, String image)
{
FacesContext context = getFacesContext();
StringBuilder buf = new StringBuilder(256);
buf.append("<a href='#' onclick=\"");
// generate JavaScript to set a hidden form field and submit
// a form which request attributes that we can decode
buf.append(Utils.generateFormSubmit(context, this, getHiddenFieldName(), encodeValues(action, index)));
buf.append("\">");
if (image != null)
{
buf.append(Utils.buildImageTag(context, image, text));
}
else
{
buf.append(Utils.encode(text));
}
buf.append("</a>");
return buf.toString();
}
/**
* Encode the specified values for output to a hidden field
*
* @param action Action identifer
* @param index Index of the Node item the action is for
*
* @return encoded values
*/
private static String encodeValues(int action, int index)
{
return Integer.toString(action) + NamingContainer.SEPARATOR_CHAR + Integer.toString(index);
}
// ------------------------------------------------------------------------------
// Inner classes
/**
* Class representing the an action relevant to the Shortcut element.
*/
public static class ShortcutEvent extends ActionEvent
{
public ShortcutEvent(UIComponent component, int action, int index)
{
super(component);
Action = action;
Index = index;
}
public int Action;
public int Index;
}
// ------------------------------------------------------------------------------
// Private data
/** I18N messages */
private static final String MSG_REMOVE_ITEM = "remove_item";
private final static int ACTION_CLICK_ITEM = 0;
private final static int ACTION_REMOVE_ITEM = 1;
/** for this component the value is used as the List of shortcut Nodes */
private Object value = null;
/** action listener called when a Click action occurs */
private MethodBinding clickActionListener;
/** action listener called when a Remove action occurs */
private MethodBinding removeActionListener;
}

View File

@@ -0,0 +1,292 @@
/*
* 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.component.template;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import org.alfresco.config.ConfigService;
import org.alfresco.repo.template.DateCompareMethod;
import org.alfresco.repo.template.HasAspectMethod;
import org.alfresco.repo.template.I18NMessageMethod;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.TemplateException;
import org.alfresco.service.cmr.repository.TemplateImageResolver;
import org.alfresco.service.cmr.repository.TemplateNode;
import org.alfresco.service.cmr.repository.TemplateService;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.bean.repository.User;
import org.alfresco.web.config.ClientConfigElement;
import org.alfresco.web.ui.common.Utils;
import org.alfresco.web.ui.common.component.SelfRenderingComponent;
import org.apache.log4j.Logger;
/**
* @author Kevin Roast
*/
public class UITemplate extends SelfRenderingComponent
{
private final static String ENGINE_DEFAULT = "freemarker";
private final static String TEMPLATE_KEY = "_template_";
private static Logger logger = Logger.getLogger(UITemplate.class);
/** Template engine name */
private String engine = null;
/** Template name/path */
private String template = null;
/** Data model reference */
private Object model = null;
// ------------------------------------------------------------------------------
// Component implementation
/**
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily()
{
return "org.alfresco.faces.Template";
}
/**
* @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.engine = (String)values[1];
this.template = (String)values[2];
this.model = (Object)values[3];
}
/**
* @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
*/
public Object saveState(FacesContext context)
{
Object values[] = new Object[4];
// standard component attributes are saved by the super class
values[0] = super.saveState(context);
values[1] = this.engine;
values[2] = this.template;
values[3] = this.model;
return (values);
}
/**
* @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext)
*/
public void encodeBegin(FacesContext context) throws IOException
{
if (isRendered() == false)
{
return;
}
// get the data model to use - building default if required
Object model = getModel();
// get the configservice to find the appropriate processor
ConfigService service = Application.getConfigService(context);
ClientConfigElement clientConfig = (ClientConfigElement)service.getGlobalConfig().getConfigElement(
ClientConfigElement.CONFIG_ELEMENT_ID);
// get the template to process
String template = getTemplate();
if (template != null && template.length() != 0)
{
// get the class name of the processor to instantiate
String engine = getEngine();
long startTime = 0;
if (logger.isDebugEnabled())
{
logger.debug("Using template processor name: " + engine);
startTime = System.currentTimeMillis();
}
// process the template against the model
try
{
TemplateService templateService = Repository.getServiceRegistry(context).getTemplateService();
templateService.processTemplate(engine, getTemplate(), model, context.getResponseWriter());
}
catch (TemplateException err)
{
Utils.addErrorMessage(err.getMessage(), err);
}
if (logger.isDebugEnabled())
{
long endTime = System.currentTimeMillis();
logger.debug("Time to process template: " + (endTime - startTime) + "ms");
}
}
}
// ------------------------------------------------------------------------------
// Strongly typed component property accessors
/**
* @return the name of the template engine to use.
*/
public String getEngine()
{
ValueBinding vb = getValueBinding("engine");
if (vb != null)
{
this.engine = (String)vb.getValue(getFacesContext());
}
return this.engine != null ? this.engine : ENGINE_DEFAULT;
}
/**
* @param engine the name of the template engine to use. A default is provided if none is set.
*/
public void setEngine(String engine)
{
this.engine = engine;
}
/**
* Return the data model to bind template against.
* <p>
* By default we return a Map model containing root references to the Company Home Space,
* the users Home Space and the Person Node for the current user.
*
* @return Returns the data model to bind template against.
*/
public Object getModel()
{
if (this.model == null)
{
Object model = null;
ValueBinding vb = getValueBinding("model");
if (vb != null)
{
model = vb.getValue(getFacesContext());
}
if (getEngine().equals(ENGINE_DEFAULT))
{
// create FreeMarker default model and merge
Map root = new HashMap(11, 1.0f);
FacesContext context = FacesContext.getCurrentInstance();
ServiceRegistry services = Repository.getServiceRegistry(context);
// supply the CompanyHome space as "companyhome"
NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId());
TemplateNode companyRootNode = new TemplateNode(companyRootRef, services, imageResolver);
root.put("companyhome", companyRootNode);
// supply the users Home Space as "userhome"
User user = Application.getCurrentUser(context);
NodeRef userRootRef = new NodeRef(Repository.getStoreRef(), user.getHomeSpaceId());
TemplateNode userRootNode = new TemplateNode(userRootRef, services, imageResolver);
root.put("userhome", userRootNode);
// supply the current user Node as "person"
root.put("person", new TemplateNode(user.getPerson(), services, imageResolver));
// current date/time is useful to have and isn't supplied by FreeMarker by default
root.put("date", new Date());
// add custom method objects
root.put("hasAspect", new HasAspectMethod());
root.put("message", new I18NMessageMethod());
root.put("dateCompare", new DateCompareMethod());
// merge models
if (model instanceof Map)
{
if (logger.isDebugEnabled())
logger.debug("Found valid Map model to merge with FreeMarker: " + model);
root.putAll((Map)model);
}
model = root;
}
return model;
}
else
{
return this.model;
}
}
/**
* @param model The model to set.
*/
public void setModel(Object model)
{
this.model = model;
}
/**
* @return Returns the template name.
*/
public String getTemplate()
{
ValueBinding vb = getValueBinding("template");
if (vb != null)
{
// convert object to string - then we can handle either a path, NodeRef instance or noderef string
Object val = vb.getValue(getFacesContext());
if (val != null)
{
this.template = val.toString();
}
}
return this.template;
}
/**
* @param template The template name to set.
*/
public void setTemplate(String template)
{
this.template = template;
}
/** Template Image resolver helper */
private TemplateImageResolver imageResolver = new TemplateImageResolver()
{
public String resolveImagePathForName(String filename, boolean small)
{
return Utils.getFileTypeImage(filename, small);
}
};
}