/*
* Copyright 2012 OmniFaces.
*
* Licensed 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.omnifaces.component.tree;
import static javax.faces.component.visit.VisitHint.SKIP_ITERATION;
import static org.omnifaces.util.Components.getClosestParent;
import static org.omnifaces.util.Components.validateHasParent;
import java.io.IOException;
import javax.faces.component.FacesComponent;
import javax.faces.component.UIComponent;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseId;
import org.omnifaces.model.tree.TreeModel;
import org.omnifaces.util.Callback;
/**
* <p>
* The <code><o:treeNodeItem></code> is an {@link UIComponent} that represents a single child tree node within a
* parent {@link TreeNode} component. Within this component, the <code>var</code> attribute of the parent {@link Tree}
* component will expose the child tree node.
* <p>
* This component allows a child component of type {@link TreeInsertChildren} which indicates the place to insert
* the children of the current child tree node recursively by a {@link TreeNode} component associated with the
* children's level in the same parent {@link Tree} component.
*
* @author Bauke Scholtz
* @see TreeNode
* @see TreeInsertChildren
*/
@FacesComponent(TreeNodeItem.COMPONENT_TYPE)
public class TreeNodeItem extends TreeFamily {
// Public constants -----------------------------------------------------------------------------------------------
/** The standard component type. */
public static final String COMPONENT_TYPE = "org.omnifaces.component.tree.TreeNodeItem";
// Private constants ----------------------------------------------------------------------------------------------
private static final String ERROR_NESTING_DISALLOWED =
"Nesting TreeNodeItem components is disallowed. Use TreeNode instead to markup specific levels.";
// Actions --------------------------------------------------------------------------------------------------------
/**
* Validate the component hierarchy.
* @throws IllegalArgumentException When there is no parent of type {@link Tree}, or when this component is nested
* in another {@link TreeNodeItem}.
*/
@Override
protected void validateHierarchy() {
validateHasParent(this, Tree.class);
if (getClosestParent(this, TreeNodeItem.class) != null) {
throw new IllegalArgumentException(ERROR_NESTING_DISALLOWED);
}
}
/**
* Suppress default behavior of {@link #encodeAll(FacesContext)} (which also checks {@link #isRendered()}) by
* delegating directly to {@link #encodeChildren(FacesContext)}.
*/
@Override
public void encodeAll(FacesContext context) throws IOException {
encodeChildren(context);
}
/**
* Loop over children of the current model node, set the child as the current model node and continue processing
* this component according to the rules of the given phase ID.
* @param context The faces context to work with.
* @param phaseId The current phase ID.
* @see Tree#setCurrentModelNode(FacesContext, TreeModel)
*/
@Override
@SuppressWarnings({ "rawtypes", "unchecked" }) // For TreeModel. We don't care about its actual type anyway.
protected void process(final FacesContext context, final PhaseId phaseId) {
if (getChildCount() == 0) {
return;
}
process(context, new Callback.ReturningWithArgument<Void, Tree>() {
@Override
public Void invoke(Tree tree) {
if (tree.getCurrentModelNode() != null) {
for (TreeModel childModelNode : (Iterable<TreeModel>) tree.getCurrentModelNode()) {
tree.setCurrentModelNode(context, childModelNode);
if (isRendered()) {
processSuper(context, phaseId);
}
}
}
return null;
}
});
}
/**
* Loop over children of the current model node, set the child as the current model node and continue visiting
* this component according to the given visit context and callback.
* @param context The visit context to work with.
* @param callback The visit callback to work with.
* @see Tree#setCurrentModelNode(FacesContext, TreeModel)
*/
@Override
@SuppressWarnings({ "rawtypes", "unchecked" }) // For TreeModel. We don't care about its actual type anyway.
public boolean visitTree(final VisitContext context, final VisitCallback callback) {
if (context.getHints().contains(SKIP_ITERATION)) {
return super.visitTree(context, callback);
}
if (!isVisitable(context) || getChildCount() == 0) {
return false;
}
return process(context.getFacesContext(), new Callback.ReturningWithArgument<Boolean, Tree>() {
@Override
public Boolean invoke(Tree tree) {
if (tree.getCurrentModelNode() != null) {
for (TreeModel childModelNode : (Iterable<TreeModel>) tree.getCurrentModelNode()) {
tree.setCurrentModelNode(context.getFacesContext(), childModelNode);
if (TreeNodeItem.super.visitTree(context, callback)) {
return true;
}
}
}
return false;
}
});
}
/**
* Convenience method to handle both {@link #process(FacesContext, PhaseId)} and
* {@link #visitTree(VisitContext, VisitCallback)} without code duplication.
* @param context The faces context to work with.
* @param phaseId The current phase ID (not used so far in this implementation).
* @param callback The callback to be invoked.
* @return The callback result.
*/
@SuppressWarnings("rawtypes") // For TreeModel. We don't care about its actual type anyway.
private <R> R process(FacesContext context, Callback.ReturningWithArgument<R, Tree> callback) {
Tree tree = getClosestParent(this, Tree.class);
TreeModel originalModelNode = tree.getCurrentModelNode();
try {
return callback.invoke(tree);
}
finally {
tree.setCurrentModelNode(context, originalModelNode);
}
}
}