
org.apache.myfaces.custom.tree2.UITreeData Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.apache.myfaces.custom.tree2;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
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 javax.faces.event.FacesListener;
import javax.faces.event.PhaseId;
import org.apache.myfaces.shared_tomahawk.util.MessageUtils;
import org.apache.myfaces.tomahawk.util.Constants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* TreeData is a {@link UIComponent} that supports binding data stored in a tree represented
* by a {@link TreeNode} instance. During iterative processing over the tree nodes in the
* data model, the object for the current node is exposed as a request attribute under the key
* specified by the var
property. {@link javax.faces.render.Renderer}s of this
* component should use the appropriate facet to assist in rendering.
*
* @JSFComponent
* @author Sean Schofield
* @author Hans Bergsten (Some code taken from an example in his O'Reilly JavaServer Faces book. Copied with permission)
* @version $Revision: 940138 $ $Date: 2010-05-01 20:34:32 -0500 (Sat, 01 May 2010) $
*/
public class UITreeData extends UIComponentBase implements NamingContainer, Tree {
private Log log = LogFactory.getLog(UITreeData.class);
public static final String COMPONENT_TYPE = "org.apache.myfaces.UITree2";
public static final String COMPONENT_FAMILY = "org.apache.myfaces.HtmlTree2";
//private static final String DEFAULT_RENDERER_TYPE = "org.apache.myfaces.Tree2";
private static final String MISSING_NODE = "org.apache.myfaces.tree2.MISSING_NODE";
private static final int PROCESS_DECODES = 1;
private static final int PROCESS_VALIDATORS = 2;
private static final int PROCESS_UPDATES = 3;
private TreeModel _cachedModel;
private String _nodeId;
private TreeNode _node;
private Object _value;
private String _var;
private Map _saved = new HashMap();
private TreeState _restoredState = null;
/**
* Constructor
*/
public UITreeData()
{
//setRendererType(DEFAULT_RENDERER_TYPE);
}
// see superclass for documentation
public String getFamily()
{
return COMPONENT_FAMILY;
}
// see superclass for documentation
public Object saveState(FacesContext context)
{
Object values[] = new Object[3];
values[0] = super.saveState(context);
values[1] = _var;
values[2] = _restoredState;
return ((Object) (values));
}
// see superclass for documentation
public void restoreState(FacesContext context, Object state)
{
Object values[] = (Object[]) state;
super.restoreState(context, values[0]);
_var = (String)values[1];
_restoredState = (TreeState) values[2];
}
public void encodeEnd(FacesContext context) throws IOException {
super.encodeEnd(context);
// prepare to save the tree state -- fix for MYFACES-618
// should be done in saveState() but Sun RI does not call saveState() and restoreState()
// with javax.faces.STATE_SAVING_METHOD = server
TreeState state = getDataModel().getTreeState();
if ( state == null)
{
// the model supplier has forgotten to return a valid state manager, but we need one
state = new TreeStateBase();
}
// save the state with the component, unless it should explicitly not saved eg. session-scoped model and state
_restoredState = (state.isTransient()) ? null : state;
}
public void queueEvent(FacesEvent event)
{
super.queueEvent(new FacesEventWrapper(event, getNodeId(), this));
}
public void broadcast(FacesEvent event) throws AbortProcessingException
{
if (event instanceof FacesEventWrapper)
{
FacesEventWrapper childEvent = (FacesEventWrapper) event;
String currNodeId = getNodeId();
setNodeId(childEvent.getNodeId());
FacesEvent nodeEvent = childEvent.getFacesEvent();
nodeEvent.getComponent().broadcast(nodeEvent);
setNodeId(currNodeId);
return;
}
else if(event instanceof ToggleExpandedEvent)
{
ToggleExpandedEvent toggleEvent = (ToggleExpandedEvent) event;
String currentNodeId = getNodeId();
setNodeId(toggleEvent.getNodeId());
toggleExpanded();
setNodeId(currentNodeId);
}
else
{
super.broadcast(event);
return;
}
}
// see superclass for documentation
public void processDecodes(FacesContext context)
{
if (context == null) throw new NullPointerException("context");
if (!isRendered()) return;
_cachedModel = null;
_saved = new HashMap();
setNodeId(null);
decode(context);
processNodes(context, PROCESS_DECODES, getDataModel().getTreeWalker());
// After processNodes is executed, the node active is the last one
// we have to set it to null again to avoid inconsistency on outsider
// code (just like UIData components does)
setNodeId(null);
}
// see superclass for documentation
public void processValidators(FacesContext context)
{
if (context == null) throw new NullPointerException("context");
if (!isRendered()) return;
processNodes(context, PROCESS_VALIDATORS, getDataModel().getTreeWalker());
setNodeId(null);
}
// see superclass for documentation
public void processUpdates(FacesContext context)
{
if (context == null) throw new NullPointerException("context");
if (!isRendered()) return;
processNodes(context, PROCESS_UPDATES, getDataModel().getTreeWalker());
setNodeId(null);
}
// see superclass for documentation
public String getClientId(FacesContext context)
{
String ownClientId = super.getClientId(context);
if (_nodeId != null)
{
return ownClientId + NamingContainer.SEPARATOR_CHAR + _nodeId;
} else
{
return ownClientId;
}
}
// see superclass for documentation
public void setValueBinding(String name, ValueBinding binding)
{
if ("value".equals(name))
{
_cachedModel = null;
} else if ("nodeVar".equals(name) || "nodeId".equals(name) || "treeVar".equals(name))
{
throw new IllegalArgumentException("name " + name);
}
super.setValueBinding(name, binding);
}
// see superclass for documentation
public void encodeBegin(FacesContext context) throws IOException
{
/**
* The renderer will handle most of the encoding, but if there are any
* error messages queued for the components (validation errors), we
* do want to keep the saved state so that we can render the node with
* the invalid value.
*/
if (!keepSaved(context))
{
_saved = new HashMap();
}
// FIX for MYFACES-404
// do not use the cached model the render phase
_cachedModel = null;
super.encodeBegin(context);
}
/**
* Sets the value of the TreeData.
*
* @param value The new value
*
* @deprecated
*/
public void setValue(Object value)
{
_cachedModel = null;
_value = value;
}
/**
* Gets the model of the TreeData -
* due to backwards-compatibility, this can also be retrieved by getValue.
*
* @return The value
*/
public Object getModel()
{
return getValue();
}
/**
* Sets the model of the TreeData -
* due to backwards-compatibility, this can also be set by calling setValue.
*
* @param model The new model
*/
public void setModel(Object model)
{
setValue(model);
}
/**
* Gets the value of the TreeData.
*
* @JSFProperty
* required="true"
* @return The value
*
* @deprecated
*/
public Object getValue()
{
if (_value != null) return _value;
ValueBinding vb = getValueBinding("value");
return vb != null ? vb.getValue(getFacesContext()) : null;
}
/**
* Set the request-scope attribute under which the data object for the current node wil be exposed
* when iterating.
*
* @param var The new request-scope attribute name
*/
public void setVar(String var)
{
_var = var;
}
/**
* Return the request-scope attribute under which the data object for the current node will be exposed
* when iterating. This property is not enabled for value binding expressions.
*
* @JSFProperty
* @return The iterator attribute
*/
public String getVar()
{
return _var;
}
/**
* Calls through to the {@link TreeModel} and returns the current {@link TreeNode} or null
.
*
* @return The current node
*/
public TreeNode getNode()
{
return _node;
}
public String getNodeId()
{
return _nodeId;
}
public void setNodeId(String nodeId)
{
saveDescendantState();
_nodeId = nodeId;
TreeModel model = getDataModel();
if (model == null)
{
return;
}
try
{
_node = model.getNodeById(nodeId);
}
//TODO: change to an own exception
catch (IndexOutOfBoundsException aob)
{
/**
* This might happen if we are trying to process a commandLink for a node that node that no longer
* exists. Instead of allowing a RuntimeException to crash the application, we will add a warning
* message so the user can optionally display the warning. Also, we will allow the user to provide
* their own value binding method to be called so they can handle it how they see fit.
*/
FacesMessage message = MessageUtils.getMessageFromBundle(Constants.TOMAHAWK_DEFAULT_BUNDLE, MISSING_NODE, new String[] {nodeId});
message.setSeverity(FacesMessage.SEVERITY_WARN);
FacesContext.getCurrentInstance().addMessage(getId(), message);
/** @todo call hook */
/** @todo figure out whether or not to abort this method gracefully */
}
restoreDescendantState();
if (_var != null)
{
Map requestMap = getFacesContext().getExternalContext().getRequestMap();
if (nodeId == null)
{
requestMap.remove(_var);
} else
{
requestMap.put(_var, getNode());
}
}
}
/**
* Gets an array of String containing the ID's of all of the {@link TreeNode}s in the path to
* the specified node. The path information will be an array of String
objects
* representing node ID's. The array will starting with the ID of the root node and end with
* the ID of the specified node.
*
* @param nodeId The id of the node for whom the path information is needed.
* @return String[]
*/
public String[] getPathInformation(String nodeId)
{
return getDataModel().getPathInformation(nodeId);
}
/**
* Indicates whether or not the specified {@link TreeNode} is the last child in the List
* of children. If the node id provided corresponds to the root node, this returns true
.
*
* @param nodeId The ID of the node to check
* @return boolean
*/
public boolean isLastChild(String nodeId)
{
return getDataModel().isLastChild(nodeId);
}
/**
* Returns a previously cached {@link TreeModel}, if any, or sets the cache variable to either the
* current value (if its a {@link TreeModel}) or to a new instance of {@link TreeModel} (if it's a
* {@link TreeNode}) with the provided value object as the root node.
*
* @return TreeModel
*/
public TreeModel getDataModel()
{
if (_cachedModel != null)
{
return _cachedModel;
}
Object value = getValue();
if (value != null)
{
if (value instanceof TreeModel)
{
_cachedModel = (TreeModel) value;
}
else if (value instanceof TreeNode)
{
_cachedModel = new TreeModelBase((TreeNode) value);
} else
{
throw new IllegalArgumentException("Value must be a TreeModel or TreeNode");
}
}
if (_restoredState != null)
_cachedModel.setTreeState(_restoredState); // set the restored state (if there is one) on the model
return _cachedModel;
}
/**
* Epands all nodes by default.
*/
public void expandAll()
{
toggleAll(true);
}
/**
* Collapse all nodes by default.
*/
public void collapseAll()
{
toggleAll(false);
}
/**
* Toggles all of the nodes to either expanded or collapsed depending on the
* parameter supplied.
*
* @param expanded Expand all of the nodes (a value of false indicates collapse
* all nodes)
*/
private void toggleAll(boolean expanded)
{
TreeWalker walker = getDataModel().getTreeWalker();
walker.reset();
TreeState state = getDataModel().getTreeState();
walker.setCheckState(false);
walker.setTree(this);
while(walker.next())
{
String id = getNodeId();
if ((expanded && !state.isNodeExpanded(id)) || (!expanded && state.isNodeExpanded(id)))
{
state.toggleExpanded(id);
}
}
}
/**
* Expands all of the nodes in the specfied path.
* @param nodePath The path to expand.
*/
public void expandPath(String[] nodePath)
{
getDataModel().getTreeState().expandPath(nodePath);
}
/**
* Expands all of the nodes in the specfied path.
* @param nodePath The path to expand.
*/
public void collapsePath(String[] nodePath)
{
getDataModel().getTreeState().collapsePath(nodePath);
}
protected void processNodes(FacesContext context, int processAction, TreeWalker walker)
{
UIComponent facet = null;
walker.reset();
walker.setTree(this);
while(walker.next())
{
TreeNode node = getNode();
facet = getFacet(node.getType());
if (facet == null)
{
log.warn("Unable to locate facet with the name: " + node.getType());
continue;
//throw new IllegalArgumentException("Unable to locate facet with the name: " + node.getType());
}
switch (processAction)
{
case PROCESS_DECODES:
facet.processDecodes(context);
break;
case PROCESS_VALIDATORS:
facet.processValidators(context);
break;
case PROCESS_UPDATES:
facet.processUpdates(context);
break;
}
}
}
/**
* To support using input components for the nodes (e.g., input fields, checkboxes, and selection
* lists) while still only using one set of components for all nodes, the state held by the components
* for the current node must be saved for a new node is selected.
*/
private void saveDescendantState()
{
FacesContext context = getFacesContext();
Iterator i = getFacets().values().iterator();
while (i.hasNext())
{
UIComponent facet = (UIComponent) i.next();
saveDescendantState(facet, context);
}
}
/**
* Overloaded helper method for the no argument version of this method.
*
* @param component The component whose state needs to be saved
* @param context FacesContext
*/
private void saveDescendantState(UIComponent component, FacesContext context)
{
if (component instanceof EditableValueHolder)
{
EditableValueHolder input = (EditableValueHolder) component;
String clientId = component.getClientId(context);
SavedState state = (SavedState) _saved.get(clientId);
if (state == null)
{
state = new SavedState();
_saved.put(clientId, state);
}
state.setValue(input.getLocalValue());
state.setValid(input.isValid());
state.setSubmittedValue(input.getSubmittedValue());
state.setLocalValueSet(input.isLocalValueSet());
}
List kids = component.getChildren();
for (int i = 0; i < kids.size(); i++)
{
saveDescendantState((UIComponent) kids.get(i), context);
}
}
/**
* Used to configure a new node with the state stored previously.
*/
private void restoreDescendantState()
{
FacesContext context = getFacesContext();
Iterator i = getFacets().values().iterator();
while (i.hasNext())
{
UIComponent facet = (UIComponent) i.next();
restoreDescendantState(facet, context);
}
}
/**
* Overloaded helper method for the no argument version of this method.
*
* @param component The component whose state needs to be restored
* @param context FacesContext
*/
private void restoreDescendantState(UIComponent component, FacesContext context)
{
String id = component.getId();
component.setId(id); // forces the cilent id to be reset
if (component instanceof EditableValueHolder)
{
EditableValueHolder input = (EditableValueHolder) component;
String clientId = component.getClientId(context);
SavedState state = (SavedState) _saved.get(clientId);
if (state == null)
{
state = new SavedState();
}
input.setValue(state.getValue());
input.setValid(state.isValid());
input.setSubmittedValue(state.getSubmittedValue());
input.setLocalValueSet(state.isLocalValueSet());
}
List kids = component.getChildren();
for (int i = 0; i < kids.size(); i++)
{
restoreDescendantState((UIComponent)kids.get(i), context);
}
Map facets = component.getFacets();
for(Iterator i = facets.values().iterator(); i.hasNext();)
{
restoreDescendantState((UIComponent)i.next(), context);
}
}
/**
* A regular bean with accessor methods for all state variables.
*
* @author Sean Schofield
* @author Hans Bergsten (Some code taken from an example in his O'Reilly JavaServer Faces book. Copied with permission)
* @version $Revision: 940138 $ $Date: 2010-05-01 20:34:32 -0500 (Sat, 01 May 2010) $
*/
private static class SavedState implements Serializable
{
private static final long serialVersionUID = 273343276957070557L;
private Object submittedValue;
private boolean valid = true;
private Object value;
private boolean localValueSet;
Object getSubmittedValue()
{
return submittedValue;
}
void setSubmittedValue(Object submittedValue)
{
this.submittedValue = submittedValue;
}
boolean isValid()
{
return valid;
}
void setValid(boolean valid)
{
this.valid = valid;
}
Object getValue()
{
return value;
}
void setValue(Object value)
{
this.value = value;
}
boolean isLocalValueSet()
{
return localValueSet;
}
void setLocalValueSet(boolean localValueSet)
{
this.localValueSet = localValueSet;
}
}
/**
* Inner class used to wrap the original events produced by child components in the tree.
* This will allow the tree to find the appropriate component later when its time to
* broadcast the events to registered listeners. Code is based on a similar private
* class for UIData.
*/
private static class FacesEventWrapper extends FacesEvent
{
private static final long serialVersionUID = -3056153249469828447L;
private FacesEvent _wrappedFacesEvent;
private String _nodeId;
public FacesEventWrapper(FacesEvent facesEvent, String nodeId, UIComponent component)
{
super(component);
_wrappedFacesEvent = facesEvent;
_nodeId = nodeId;
}
public PhaseId getPhaseId()
{
return _wrappedFacesEvent.getPhaseId();
}
public void setPhaseId(PhaseId phaseId)
{
_wrappedFacesEvent.setPhaseId(phaseId);
}
public void queue()
{
_wrappedFacesEvent.queue();
}
public String toString()
{
return _wrappedFacesEvent.toString();
}
public boolean isAppropriateListener(FacesListener faceslistener)
{
// this event type is only intended for wrapping a real event
return false;
}
public void processListener(FacesListener faceslistener)
{
throw new UnsupportedOperationException("This event type is only intended for wrapping a real event");
}
public FacesEvent getFacesEvent()
{
return _wrappedFacesEvent;
}
public String getNodeId()
{
return _nodeId;
}
}
/**
* Returns true if there is an error message queued for at least one of the nodes.
*
* @param context FacesContext
* @return whether an error message is present
*/
private boolean keepSaved(FacesContext context)
{
Iterator clientIds = _saved.keySet().iterator();
while (clientIds.hasNext())
{
String clientId = (String) clientIds.next();
Iterator messages = context.getMessages(clientId);
while (messages.hasNext())
{
FacesMessage message = (FacesMessage) messages.next();
if (message.getSeverity().compareTo(FacesMessage.SEVERITY_ERROR) >= 0)
{
return true;
}
}
}
return false;
}
/**
* Toggle the expanded state of the current node.
*/
public void toggleExpanded()
{
getDataModel().getTreeState().toggleExpanded(getNodeId());
}
/**
* Indicates whether or not the current {@link TreeNode} is expanded.
* @return boolean
*/
public boolean isNodeExpanded()
{
return getDataModel().getTreeState().isNodeExpanded(getNodeId());
}
/**
* Implements the {@link javax.faces.event.ActionListener} interface. Basically, this
* method is used to listen for node selection events (when a user has clicked on a
* leaf node.)
*
* @param event ActionEvent
*/
public void setNodeSelected(ActionEvent event)
{
getDataModel().getTreeState().setSelected(getNodeId());
}
/**
* Indicates whether or not the current {@link TreeNode} is selected.
* @return boolean
*/
public boolean isNodeSelected()
{
return (getNodeId() != null) ? getDataModel().getTreeState().isSelected(getNodeId()) : false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy