Package org.apache.myfaces.custom.tree2

Source Code of org.apache.myfaces.custom.tree2.HtmlTreeRenderer

/*
* Copyright 2004 The Apache Software Foundation.
*
* 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.apache.myfaces.custom.tree2;


import org.apache.myfaces.component.html.util.AddResource;
import org.apache.myfaces.renderkit.html.HtmlRendererUtils;
import org.apache.myfaces.renderkit.html.HTML;
import org.apache.myfaces.renderkit.JSFAttr;
import org.apache.myfaces.renderkit.RendererUtils;

import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UICommand;
import javax.faces.component.UIGraphic;
import javax.faces.component.UIViewRoot;
import javax.faces.component.UIParameter;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.Renderer;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map;
import java.util.Iterator;
import java.net.URLDecoder;
import javax.servlet.http.Cookie;
import java.util.HashMap;

/**
* @author Sean Schofield
* @author Chris Barlow
* @author Hans Bergsten (Some code taken from an example in his O'Reilly JavaServer Faces book. Copied with permission)
* @version $Revision: 292066 $ $Date: 2005-09-27 18:46:08 -0400 (Tue, 27 Sep 2005) $
*/
public class HtmlTreeRenderer extends Renderer
{
    protected static final String TOGGLE_SPAN = "org.apache.myfaces.tree.TOGGLE_SPAN";
    protected static final String ROOT_NODE_ID = "0";

    private static final String JAVASCRIPT_ENCODED = "org.apache.myfaces.tree.JAVASCRIPT_ENCODED";
    private static final String NAV_COMMAND = "org.apache.myfaces.tree.NAV_COMMAND";
    private static final String ENCODING = "UTF-8";
    private static final String ATTRIB_DELIM = ";";
    private static final String ATTRIB_KEYVAL = "=";
    private static final String NODE_STATE_EXPANDED = "x";
    private static final String NODE_STATE_CLOSED = "c";
    private static final String SEPARATOR = String.valueOf(NamingContainer.SEPARATOR_CHAR);
    private static final String IMAGE_PREFIX = "t2";
    private static final String TOGGLE_ID = "t2g";

    private static final int NOTHING = 0;
    private static final int CHILDREN = 1;
    private static final int EXPANDED = 2;
    private static final int LINES = 4;
    private static final int LAST = 8;

    // see superclass for documentation
    public boolean getRendersChildren()
    {
        return true;
    }

    private void restoreStateFromCookies(FacesContext context, UIComponent component) {
        String nodeId = null;
        HtmlTree tree = (HtmlTree)component;
        String originalNodeId = tree.getNodeId();
       
        Map cookieMap = context.getExternalContext().getRequestCookieMap();
        Cookie treeCookie = (Cookie)cookieMap.get(component.getId());
        if (treeCookie == null || treeCookie.getValue() == null)
        {
            return;
        }

        String nodeState = null;
        Map attrMap = getCookieAttr(treeCookie);
        Iterator i = attrMap.keySet().iterator();
        while (i.hasNext())
        {
            nodeId = (String)i.next();
            nodeState = (String)attrMap.get(nodeId);

            if (NODE_STATE_EXPANDED.equals(nodeState))
            {
                tree.setNodeId(nodeId);
                if (!tree.isNodeExpanded())
                {
                    tree.toggleExpanded();
                }
                tree.setNodeId(originalNodeId);
            }
            else if (NODE_STATE_CLOSED.equals(nodeState))
            {
                tree.setNodeId(nodeId);
                if (tree.isNodeExpanded())
                {
                    tree.toggleExpanded();
                }
                tree.setNodeId(originalNodeId);
            }
        }
    }
   

    public void decode(FacesContext context, UIComponent component)
    {
        super.decode(context, component);

        // see if one of the nav nodes was clicked, if so, then toggle appropriate node
        String nodeId = null;
        HtmlTree tree = (HtmlTree)component;
        String originalNodeId = tree.getNodeId();

        if (getBoolean(component, JSFAttr.CLIENT_SIDE_TOGGLE, true))
        {
          restoreStateFromCookies(context, component);
        }
        else
        {
            nodeId = (String)context.getExternalContext().getRequestParameterMap().get(tree.getId() + SEPARATOR + NAV_COMMAND);

            if (nodeId == null || nodeId.equals(""))
            {
                return;
            }
           
            component.queueEvent(new ToggleExpandedEvent(component, nodeId));
        }
    }

    public void encodeBegin(FacesContext context, UIComponent component) throws IOException
    {
        HtmlTree tree = (HtmlTree)component;
        // try to restore the tree state from cookies if no session scoped TreeState is supplied and preserveToggle is true in client mode
        if (!tree.getDataModel().getTreeState().isTransient() && getBoolean(component, JSFAttr.CLIENT_SIDE_TOGGLE, true) && getBoolean(component, JSFAttr.PRESERVE_TOGGLE, true))
            restoreStateFromCookies(context, component);
       
        // write javascript functions
        encodeJavascript(context, component);
    }

    /**
     * Renders the whole tree.  It generates a <code>&lt;span></code> element with an <code>id</code>
     * attribute if the component has been given an explicit ID.  The model nodes are rendered
     * recursively by the private <code>encodeNodes</code> method.
     *
     * @param context FacesContext
     * @param component The component whose children are to be rendered
     * @throws IOException
     */
    public void encodeChildren(FacesContext context, UIComponent component) throws IOException
    {
        HtmlTree tree = (HtmlTree)component;
        boolean showRootNode = getBoolean(tree, JSFAttr.SHOW_ROOT_NODE, true);

        if (!component.isRendered()) return;

        if (tree.getValue() == null) return;

        ResponseWriter out = context.getResponseWriter();
        String clientId = null;

        if (component.getId() != null && !component.getId().startsWith(UIViewRoot.UNIQUE_ID_PREFIX))
        {
            clientId = component.getClientId(context);
        }

        boolean isOuterSpanUsed = false;

        if (clientId != null)
        {
            isOuterSpanUsed = true;
            out.startElement("span", component);
            out.writeAttribute("id", clientId, "id");
        }

        if (showRootNode)
        {
            // encode the tree (starting with the root node)
            encodeTree(context, out, tree, null, 0);
        }
        else
        {
            tree.setNodeId("0");
            if(!tree.isNodeExpanded())
            {
                tree.toggleExpanded();
            }
            TreeNode rootNode = tree.getNode();
            List rootChildren = rootNode.getChildren();
            int kidId = 0;

            for (int i = 0; i < rootChildren.size(); i++)
            {
                encodeTree(context, out, tree, ROOT_NODE_ID, kidId++);
            }
        }

        tree.setNodeId(null);

        if (isOuterSpanUsed)
        {
            out.endElement("span");
        }
    }

    /**
     * Encodes the tree and its children.
     *
     * @param context FacesContext
     * @param out ResponseWriter
     * @param tree HtmlTree
     * @param parentId The parent's node id (where parent is the parent of the node we are about to render.)
     * @param childCount If this node is a child of another node, the count indicates which child number it is
     *  (used to construct the id of the next node to render.)
     * @throws IOException
     */
    protected void encodeTree(FacesContext context, ResponseWriter out, HtmlTree tree, String parentId, int childCount)
        throws IOException
    {
        boolean clientSideToggle = getBoolean(tree, JSFAttr.CLIENT_SIDE_TOGGLE, true);

        String nodeId = (parentId != null) ? parentId + NamingContainer.SEPARATOR_CHAR + childCount : ROOT_NODE_ID;
        String spanId = TOGGLE_SPAN + ":" + tree.getId() + ":" + nodeId;

        tree.setNodeId(nodeId);
        TreeNode node = tree.getNode();

        // encode the current node
        HtmlRendererUtils.writePrettyLineSeparator(context);
        beforeNodeEncode(context, out, tree);
        encodeCurrentNode(context, out, tree);
        afterNodeEncode(context, out);

        // only encode the children if clientSideToggle is true or if this node is expanded (regardless of clientSideToggle)
        if (clientSideToggle == true || tree.isNodeExpanded())
        {
            int kidId = 0;
            String currId = tree.getNodeId();
            List children = node.getChildren();

            // if client side toggling is on, add a span to be used for displaying/hiding children
            if (clientSideToggle)
            {
                out.startElement(HTML.SPAN_ELEM, tree);
                out.writeAttribute(HTML.ID_ATTR, spanId, null);

                if (tree.isNodeExpanded())
                {
                    out.writeAttribute(HTML.STYLE_ATTR, "display:block", null);
                }
                else
                {
                    out.writeAttribute(HTML.STYLE_ATTR, "display:none", null);
                }
            }

            for (int i = 0; i < children.size(); i++)
            {
                encodeTree(context, out, tree, currId, kidId++);
            }

            if (clientSideToggle)
            {
                out.endElement(HTML.SPAN_ELEM);
            }
        }
    }

    /**
     * Encodes the current node.  It is protected so that custom {@link Renderer}s can extend it.  That might be useful
     * if you would like to render additional per node information besides the tree node.
     *
     * @param context FacesContext
     * @param out ResponseWriter
     * @param tree HtmlTree
     * @throws IOException
     */
    protected void encodeCurrentNode(FacesContext context, ResponseWriter out, HtmlTree tree)
        throws IOException
    {
        TreeNode node = tree.getNode();

        // set configurable values
        boolean showRootNode = getBoolean(tree, JSFAttr.SHOW_ROOT_NODE, true);
        boolean showNav = getBoolean(tree, JSFAttr.SHOW_NAV, true);
        boolean showLines = getBoolean(tree, JSFAttr.SHOW_LINES, true);
        boolean clientSideToggle = getBoolean(tree, JSFAttr.CLIENT_SIDE_TOGGLE, true);

        if (clientSideToggle)
        {
            // we must show the nav icons if client side toggle is enabled (regardless of what user says)
            showNav = true;
        }

        UIComponent nodeTypeFacet = tree.getFacet(node.getType());
        UIComponent nodeImgFacet = null;

        if (nodeTypeFacet == null)
        {
            throw new IllegalArgumentException("Unable to locate facet with the name: " + node.getType());
        }

        // render node padding
        String[] pathInfo = tree.getPathInformation(tree.getNodeId());
        int paddingLevel = pathInfo.length - 1;

        for (int i = (showRootNode ? 0 : 1); i < paddingLevel; i++)
        {
            boolean lastChild = tree.isLastChild((String)pathInfo[i]);
            String lineSrc = (!lastChild && showLines)
                             ? getImageSrc(context, tree, "line-trunk.gif")
                             : getImageSrc(context, tree, "spacer.gif");

            out.startElement(HTML.TD_ELEM, tree);
            out.writeAttribute(HTML.WIDTH_ATTR, "19", null);
            out.writeAttribute(HTML.HEIGHT_ATTR, "100%", null);
            out.writeURIAttribute("background", lineSrc, null);
            out.startElement(HTML.IMG_ELEM, tree);
            out.writeURIAttribute(HTML.SRC_ATTR, lineSrc, null);
            out.writeAttribute(HTML.WIDTH_ATTR, "19", null);
            out.writeAttribute(HTML.HEIGHT_ATTR, "18", null);
            out.writeAttribute(HTML.BORDER_ATTR, "0", null);
            out.endElement(HTML.IMG_ELEM);
            out.endElement(HTML.TD_ELEM);
        }

        if (showNav)
        {
            nodeImgFacet = encodeNavigation(context, out, tree);
        }

        // render node
        out.startElement(HTML.TD_ELEM, tree);
        if (nodeImgFacet != null)
        {
            RendererUtils.renderChild(context, nodeImgFacet);
        }
        RendererUtils.renderChild(context, nodeTypeFacet);
        out.endElement(HTML.TD_ELEM);
    }

    protected void beforeNodeEncode(FacesContext context, ResponseWriter out, HtmlTree tree)
        throws IOException
    {
        out.startElement(HTML.TABLE_ELEM, tree);
        out.writeAttribute(HTML.CELLPADDING_ATTR, "0", null);
        out.writeAttribute(HTML.CELLSPACING_ATTR, "0", null);
        out.writeAttribute(HTML.BORDER_ATTR, "0", null);
        out.startElement(HTML.TR_ELEM, tree);
    }

    protected void afterNodeEncode(FacesContext context, ResponseWriter out)
        throws IOException
    {
        out.endElement(HTML.TR_ELEM);
        out.endElement(HTML.TABLE_ELEM);
    }

    /**
     * Handles the encoding related to the navigation functionality.
     *
     * @param context FacesContext
     * @param out ResponseWriter
     * @param tree HtmlTree
     * @return The additional navigation image to display inside the node (if any).  Only used with client-side toggle.
     * @throws IOException
     */
    private UIComponent encodeNavigation(FacesContext context, ResponseWriter out, HtmlTree tree)
        throws IOException
    {
        TreeNode node = tree.getNode();
        String nodeId = tree.getNodeId();
        String spanId = TOGGLE_SPAN + ":" + tree.getId() + ":" + nodeId;//TOGGLE_SPAN + nodeId;
        boolean showLines = getBoolean(tree, JSFAttr.SHOW_LINES, true);
        boolean clientSideToggle = getBoolean(tree, JSFAttr.CLIENT_SIDE_TOGGLE, true);
        UIComponent nodeTypeFacet = tree.getFacet(node.getType());
        String navSrc = null;
        String altSrc = null;
        UIComponent nodeImgFacet = null;

        int bitMask = NOTHING;
        bitMask += (node.isLeaf()) ? NOTHING : CHILDREN;
        bitMask += (tree.isNodeExpanded()) ? EXPANDED : NOTHING;
        bitMask += (tree.isLastChild(tree.getNodeId())) ? LAST : NOTHING;
        bitMask += (showLines) ? LINES : NOTHING;

        switch (bitMask)
        {
            case (NOTHING):

            case (LAST):
                navSrc = "spacer.gif";
                break;

            case (LINES):
                navSrc = "line-middle.gif";
                break;

            case (LINES + LAST):
                navSrc = "line-last.gif";
                break;

            case (CHILDREN):

            case (CHILDREN + LAST):
                navSrc = "nav-plus.gif";
                altSrc = "nav-minus.gif";
                break;

            case (CHILDREN + LINES):

                navSrc = "nav-plus-line-middle.gif";
                altSrc = "nav-minus-line-middle.gif";
                break;

            case (CHILDREN + LINES + LAST):

                navSrc = "nav-plus-line-last.gif";
                altSrc = "nav-minus-line-last.gif";
                break;

            case (CHILDREN + EXPANDED):

            case (CHILDREN + EXPANDED + LAST):
                navSrc = "nav-minus.gif";
                altSrc = "nav-plus.gif";
                break;

            case (CHILDREN + EXPANDED + LINES):
                navSrc = "nav-minus-line-middle.gif";
                altSrc = "nav-plus-line-middle.gif";
                break;

            case (CHILDREN + EXPANDED + LINES + LAST):
                navSrc = "nav-minus-line-last.gif";
                altSrc = "nav-plus-line-last.gif";
                break;

            // unacceptable bitmask combinations
           
            case (EXPANDED + LINES):
            case (EXPANDED + LINES + LAST):
            case (EXPANDED):
            case (EXPANDED + LAST):
               
                throw new IllegalStateException("Encountered a node ["+ nodeId + "] + with an illogical state.  " +
                                                "Node is expanded but it is also considered a leaf (a leaf cannot be considered expanded.");                                               

            default:
                // catch all for any other combinations
                throw new IllegalArgumentException("Invalid bit mask of " + bitMask);
        }

        // adjust navSrc and altSrc so that the images can be retrieved using the extensions filter
        String navSrcUrl = getImageSrc(null, tree, navSrc);
        navSrc = getImageSrc(context, tree, navSrc);
        altSrc = getImageSrc(context, tree, altSrc);

        // render nav cell
        out.startElement(HTML.TD_ELEM, tree);
        out.writeAttribute(HTML.WIDTH_ATTR, "19", null);
        out.writeAttribute(HTML.HEIGHT_ATTR, "100%", null);
        out.writeAttribute("valign", "top", null);

        if ((bitMask & LINES)!=0 && (bitMask & LAST)==0)
        {
            out.writeURIAttribute("background", getImageSrc(context, tree, "line-trunk.gif"), null);
        }

//      add the appropriate image for the nav control
        UIGraphic image = new UIGraphic();
        image.setId(IMAGE_PREFIX);
        image.setUrl(navSrcUrl);
        Map imageAttrs = image.getAttributes();
        imageAttrs.put(HTML.WIDTH_ATTR, "19");
        imageAttrs.put(HTML.HEIGHT_ATTR, "18");
        imageAttrs.put(HTML.BORDER_ATTR, "0");

        if (clientSideToggle)
        {
            /**
             * With client side toggle, user has the option to specify open/closed images for the node (in addition to
             * the navigtion ones provided by the component.)
             */
            String expandImgSrc = "";
            String collapseImgSrc = "";
            String nodeImageId = "";

            UIComponent expandFacet = nodeTypeFacet.getFacet("expand");
            if (expandFacet != null)
            {
                UIGraphic expandImg = (UIGraphic)expandFacet;
                expandImgSrc = context.getApplication().getViewHandler().getResourceURL(context, expandImg.getUrl());
                if (expandImg.isRendered())
                {
                    expandImg.setId(IMAGE_PREFIX + NODE_STATE_EXPANDED);
                    expandImg.setParent(tree);
                    nodeImageId = expandImg.getClientId(context);
                    nodeImgFacet = expandFacet;
                }
            }

            UIComponent collapseFacet = nodeTypeFacet.getFacet("collapse");
            if (collapseFacet != null)
            {
                UIGraphic collapseImg = (UIGraphic)collapseFacet;
                collapseImgSrc = context.getApplication().getViewHandler().getResourceURL(context, collapseImg.getUrl());
                if (collapseImg.isRendered())
                {
                    collapseImg.setId(IMAGE_PREFIX + NODE_STATE_CLOSED);
                    collapseImg.setParent(tree);
                    nodeImageId = collapseImg.getClientId(context);
                    nodeImgFacet = collapseFacet;
                }
            }

            image.setParent(tree);
            if (node.getChildCount() > 0)
            {
                String onClick = new StringBuffer()
                    .append("treeNavClick('")
                    .append(spanId)
                    .append("', '")
                    .append(image.getClientId(context))
                    .append("', '")
                    .append(navSrc)
                    .append("', '")
                    .append(altSrc)
                    .append("', '")
                    .append(nodeImageId)
                    .append("', '")
                    .append(expandImgSrc)
                    .append("', '")
                    .append(collapseImgSrc)
                    .append("', '")
                    .append(tree.getId())
                    .append("', '")
                    .append(nodeId)
                    .append("');")
                    .toString();

                imageAttrs.put(HTML.ONCLICK_ATTR, onClick);
                imageAttrs.put(HTML.STYLE_ATTR, "cursor:hand;cursor:pointer");
            }
            RendererUtils.renderChild(context, image);

        }
        else
        {
            // set up the expand control and remove whatever children (if any) this control had previously
            UICommand expandControl = tree.getExpandControl();
            expandControl.getChildren().clear();
            expandControl.setId(TOGGLE_ID);

            UIParameter param = new UIParameter();
            param.setName(tree.getId() + NamingContainer.SEPARATOR_CHAR + NAV_COMMAND);
            param.setValue(tree.getNodeId());
            expandControl.getChildren().add(param);
            expandControl.getChildren().add(image);

            RendererUtils.renderChild(context, expandControl);
        }
        out.endElement(HTML.TD_ELEM);

        return nodeImgFacet;
    }

    /**
     * Encodes any stand-alone javascript functions that are needed.  Uses either the extension filter, or a
     * user-supplied location for the javascript files.
     *
     * @param context FacesContext
     * @param component UIComponent
     * @throws IOException
     */
    private void encodeJavascript(FacesContext context, UIComponent component) throws IOException
    {
        // check to see if javascript has already been written (which could happen if more than one tree on the same page)
        if (context.getExternalContext().getRequestMap().containsKey(JAVASCRIPT_ENCODED))
        {
            return;
        }

        // render javascript function for client-side toggle (it won't be used if user has opted for server-side toggle)
        ResponseWriter out = context.getResponseWriter();
        String javascriptLocation = (String)component.getAttributes().get(JSFAttr.JAVASCRIPT_LOCATION);
        if (javascriptLocation == null)
        {
            AddResource.addJavaScriptHere(HtmlTreeRenderer.class, "javascript/tree.js", context, component);
            AddResource.addJavaScriptHere(HtmlTreeRenderer.class, "javascript/cookielib.js", context, component);
        }
        else
        {
            out.startElement(HTML.SCRIPT_ELEM, component);
            out.writeAttribute(HTML.TYPE_ATTR, "text/javascript", null);
            out.writeAttribute(HTML.SRC_ATTR,
                               javascriptLocation + "/tree.js", null);
            out.endElement(HTML.SCRIPT_ELEM);

            out.startElement(HTML.SCRIPT_ELEM, component);
            out.writeAttribute(HTML.TYPE_ATTR, "text/javascript", null);
            out.writeAttribute(HTML.SRC_ATTR,
                               javascriptLocation + "/cookielib.js", null);
            out.endElement(HTML.SCRIPT_ELEM);
        }

        context.getExternalContext().getRequestMap().put(JAVASCRIPT_ENCODED, Boolean.TRUE);
    }

    /**
     * Get the appropriate image src location.  Uses the extension filter mechanism for images if no image
     * location has been specified.
     *
     * @param context The {@link FacesContex}.  NOTE: If <code>null</code> then context path information
     *    will not be used with extensions filter (assuming no imageLocation specified.)
     * @param component UIComponent
     * @param imageName The name of the image file to use.
     * @return The image src information.
     */
    private String getImageSrc(FacesContext context, UIComponent component, String imageName)
    {
        String imageLocation = (String)component.getAttributes().get(JSFAttr.IMAGE_LOCATION);
        if (imageLocation == null)
        {
            return AddResource.getResourceMappedPath(HtmlTreeRenderer.class,
                "images/" + imageName, context);
        }
        else
        {
            return imageLocation + "/" + imageName;
        }
    }

    /**
     * Helper method for getting the boolean value of an attribute.  If the attribute is not specified,
     * then return the default value.
     *
     * @param component The component for which the attributes are to be checked.
     * @param attributeName The name of the boolean attribute.
     * @param defaultValue The default value of the attribute (to be returned if no value found).
     * @return boolean
     */
    protected boolean getBoolean(UIComponent component, String attributeName, boolean defaultValue)
    {
        Boolean booleanAttr = (Boolean)component.getAttributes().get(attributeName);

        if (booleanAttr == null)
        {
            return defaultValue;
        }
        else
        {
            return booleanAttr.booleanValue();
        }
    }

    private Map getCookieAttr(Cookie cookie)
    {
        Map attribMap = new HashMap();
        try
        {
            String cookieValue = URLDecoder.decode(cookie.getValue(),ENCODING);
            String[] attribArray = cookieValue.split(ATTRIB_DELIM);
            for (int j = 0; j < attribArray.length; j++)
            {
                int index = attribArray[j].indexOf(ATTRIB_KEYVAL);
                String name = attribArray[j].substring(0, index);
                String value = attribArray[j].substring(index + 1);
                attribMap.put(name, value);
            }
        }
        catch (UnsupportedEncodingException e)
        {
            throw new RuntimeException("Error parsing tree cookies", e);
        }
        return attribMap;
    }
}
TOP

Related Classes of org.apache.myfaces.custom.tree2.HtmlTreeRenderer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.