Page 1 of 1

[CODE] finding component ids easier

Posted: 06 Sep 2012, 20:55
by mkienenb
I'm considering ideas to make finding component ids easier. The switch from other component libraries to primefaces has caused a lot of pain due to the additional naming containers for tabView, accordionPanel, dataTable, dataGrid, dataList, subTable, etc. When we couple this with replacing a4j:regions by specific component is naming in process & update, we have a lot of issues with determining names. Using absolute names problematic due to our templating and includes, and even where we could do that, it makes page changes harder.

As a workaround up to this point, I have stored a component whose id I know I will need later using ui:param, but this has scaling issues since we now have the possibility of reusing the same name. And it forces us to take an extra step any time we use a component reference somewhere. A similar workaround might be to use css classes and jquery -style selectors, with the same disadvantages.

Code: Select all

<some:component id="someComponentId"/>
<ui:param name="storedSomeComponentId" value=":#{p:component('someComponentId')}"/>

        [...  Somewhere else, possibly in a p:dataTable]

            <p:ajax update="#{storedSomeComponentId}"/>
Ideally, the problem would be solved by supporting relative path constructs, such as update="..:someOtherNamingContainerAtSameLevelAsMyMaingContainer:someComponentId" supported by UIComponent.findComponent() itself, but that's not likely to happen any time soon.

So I've been considering EL functions to aid with this, like myFunction:findComponent('someComponentId'). At this point, we either define a specific search pattern that is more flexible than the standard findComponent, such as "search at my current level, then search at my parent level, then search my parent's parent level, etc, eventually returning the original value if nothing is found", or implement something that understands a relative path construct. The second seems more useful.

Thoughts?

Re: finding component ids easier

Posted: 20 Sep 2012, 22:39
by mkienenb
Here is a tested, working implementation of the function, and the facelet-taglib definition to install it.

Code: Select all

	<function>
		<function-name>findAbsoluteComponentClientId</function-name>
		<function-class>my.jsf.functions.FindComponentFunctions</function-class>
		<function-signature>java.lang.String findAbsoluteComponentClientId(java.lang.String)</function-signature>
	</function>


Code: Select all

/*
   Copyright 2012 Mike Kienenberger

   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 my.jsf.functions;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.faces.application.ProjectStage;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UINamingContainer;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;

/***************************************************************************
 *  FindComponentFunctions.java
 *
 *	Class containing functions for locating components.
 ***************************************************************************/
public class FindComponentFunctions {
    static public class PathResolutionException extends Exception {
		private static final long serialVersionUID = 1L;
		
		public PathResolutionException(String message) {
			super(message);
		}
	}

	private static final Logger log = Logger.getLogger(FindComponentFunctions.class.getName());
	
	public static String findAbsoluteComponentClientId(String idExpr) {
		// If no input, return empty string
        if (idExpr == null)
        {
            return "";
        }
        if (idExpr.length() == 0)
        {
            return "";
        }

		UIComponent ui;
		try {
			ui = findComponent(idExpr);
		} catch (PathResolutionException e) {
            Level level = getFacesContext().isProjectStage(ProjectStage.Production) ? Level.FINE : Level.WARNING;
            if (log.isLoggable(level))
            {
                log.log(level, e.getMessage());
            }
            ui = null;
		}
		if (null != ui) {
	        char separatorChar = UINamingContainer.getSeparatorChar(getFacesContext());
			return separatorChar + ui.getClientId();
		}
		
		// If nothing found, return the original id and let the standard mechanism deal with it.
		return idExpr;
	}

	private static final String FIND_PARENT_INDICATOR = "..";
	
	// All code below this point is modified Apache Myfaces code.

	/*
	 * 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.
	 */
	static private FacesContext getFacesContext() {
		return FacesContext.getCurrentInstance();
	}
	static public UIComponent findComponent(String expr) throws PathResolutionException
    {
        if (expr == null)
        {
            throw new NullPointerException("expr");
        }
        if (expr.length() == 0)
        {
            return null;
        }

    	UIComponent currentComponent = UIComponent.getCurrentComponent(getFacesContext());
    	return findComponent(currentComponent, expr);
	}
	
	static public UIComponent findComponent(UIComponent currentComponent, String expr) throws PathResolutionException
    {
        if (expr == null)
        {
            throw new NullPointerException("expr");
        }
        if (expr.length() == 0)
        {
            return null;
        }

        char separatorChar = UINamingContainer.getSeparatorChar(getFacesContext());
        UIComponent findBase;
        if (expr.charAt(0) == separatorChar)
        {
            findBase = getRootComponent(currentComponent);
            expr = expr.substring(1);
        }
        else
        {
            if (isNamingContainerOrViewRoot(currentComponent))
            {
                findBase = currentComponent;
            }
            else
            {
                findBase = findParentNamingContainer(currentComponent, true /* root if not found */);
            }
        }

        int separator = expr.indexOf(separatorChar);
        if (separator == -1)
        {
        	UIComponent foundComponent = findComponent(findBase, expr, separatorChar);
        	if (null == foundComponent) {
                throw new PathResolutionException("Component not found for id '" + expr + "' in findBase=" + findBase + " named " + findBase.getClientId(getFacesContext()));
        	}
			return foundComponent;
        }

        String id = expr.substring(0, separator);
        findBase = findComponent(findBase, id, separatorChar);
        if (findBase == null)
        {
            return null;
        }

        if (!isNamingContainerOrViewRoot(findBase))
        {
            throw new IllegalArgumentException("Intermediate identifier " + id + " in search expression " + expr
                    + " identifies a UIComponent that is not a NamingContainer or a UIViewRoot");
        }

        UIComponent foundComponent = findComponent(findBase, expr.substring(separator + 1));
    	if (null == foundComponent) {
            throw new PathResolutionException("Component not found for id '" + expr.substring(separator + 1) + "' in findBase=" + findBase + " named " + findBase.getClientId(getFacesContext()));
    	}
		return foundComponent;

    }
    
	static boolean isNamingContainerOrViewRoot(UIComponent component) {
		return (component instanceof NamingContainer) || (component instanceof UIViewRoot);
	}
	
    static UIComponent getRootComponent(UIComponent component)
    {
        UIComponent parent;
        for (;;)
        {
            parent = component.getParent();
            if (parent == null)
            {
                return component;
            }
            component = parent;
        }
    }

    /**
     * Find the component with the specified id starting from the specified component.
     * <p>
     * Param id must not contain any NamingContainer.SEPARATOR_CHAR characters (ie ":"). This method explicitly does
     * <i>not</i> search into any child naming container components; this is expected to be handled by the caller of
     * this method.
     * <p>
     * For an implementation of findComponent which does descend into child naming components, see
     * org.apache.myfaces.custom.util.ComponentUtils.
     * 
     * @return findBase, a descendant of findBase, or null.
     */
    static UIComponent findComponent(UIComponent findBase, String id, final char separatorChar)
    {
        if (FIND_PARENT_INDICATOR.equals(id)) {
        	if (findBase instanceof UIViewRoot) {
        		// If we are already at UIViewRoot, we cannot go up another level
                Level level = getFacesContext().isProjectStage(ProjectStage.Production) ? Level.FINE : Level.WARNING;
                if (log.isLoggable(level))
                {
                    log.log(level, "Attempted to find parent of UIViewRoot");
                }
                return null;
        	}
            UIComponent parent = findParentNamingContainer(findBase, true /* UIViewRoot if not found */);
            return parent;
        }
        
        // We do not want to compare to UIViewRoot here either
        if (!isNamingContainerOrViewRoot(findBase) && idsAreEqual(id, findBase, separatorChar))
        {
            return findBase;
        }

        int facetCount = findBase.getFacetCount();
        if (facetCount > 0)
        {
            for (UIComponent facet : findBase.getFacets().values())
            {
                if (!(facet instanceof NamingContainer))
                {
                    UIComponent find = findComponent(facet, id, separatorChar);
                    if (find != null)
                    {
                        return find;
                    }
                }
                else if (idsAreEqual(id, facet, separatorChar))
                {
                    return facet;
                }
            }
        }
        
        for (int i = 0, childCount = findBase.getChildCount(); i < childCount; i++)
        {
            UIComponent child = findBase.getChildren().get(i);
            if (!(child instanceof NamingContainer))
            {
                UIComponent find = findComponent(child, id, separatorChar);
                if (find != null)
                {
                    return find;
                }
            }
            else if (idsAreEqual(id, child, separatorChar))
            {
                return child;
            }
        }

        // No need to check UIViewRoot here as it has no id
        if (findBase instanceof NamingContainer && idsAreEqual(id, findBase, separatorChar))
        {
            return findBase;
        }

        return null;
    }
    
    static UIComponent findParentNamingContainer(UIComponent component, boolean returnRootIfNotFound)
    {
        UIComponent parent = component.getParent();
        if (returnRootIfNotFound && parent == null)
        {
            return component;
        }
        while (parent != null)
        {
            if (parent instanceof NamingContainer)
            {
                return parent;
            }
            if (returnRootIfNotFound)
            {
                UIComponent nextParent = parent.getParent();
                if (nextParent == null)
                {
                    return parent; // Root
                }
                parent = nextParent;
            }
            else
            {
                parent = parent.getParent();
            }
        }
        return null;
    }

    /*
     * Return true if the specified component matches the provided id. This needs some quirks to handle components whose
     * id value gets dynamically "tweaked", eg a UIData component whose id gets the current row index appended to it.
     */
    private static boolean idsAreEqual(String id, UIComponent cmp, final char separatorChar)
    {
        if (id.equals(cmp.getId()))
        {
            return true;
        }

        /* By the spec, findComponent algorithm does not take into account UIData.rowIndex() property,
         * because it just scan over nested plain ids. 
        if (cmp instanceof UIData)
        {
            UIData uiData = ((UIData)cmp);

            if (uiData.getRowIndex() == -1)
            {
                return dynamicIdIsEqual(id, cmp.getId());
            }
            return id.equals(cmp.getId() + separatorChar + uiData.getRowIndex());
        }
        */

        return false;
    }

}
Example usage:

Code: Select all

<form id="masterForm">
    <f:subview id="subView">
         ...
   </f:subview>
   <h:panelGroup id="itemListPanel">
   <p:dataTable id="itemList" >
                <f:facet name="header">
                        <p:commandButton
                            update="#{my:findAbsoluteComponentClientId('..:itemListPanel')}"   />
You can also do much more complicated expressions if necessary. Here's a totally contrived expression that does the same thing, but navigates up to view root, back down to some subView, then back up to the panelGroup.

Code: Select all

                            update="#{my:findAbsoluteComponentClientId('..:..:masterForm:subView:..:itemListPanel')}"

Re: [CODE] finding component ids easier

Posted: 21 Sep 2012, 20:40
by tandraschko
Hi,

sorry for the late response.
The idea is nice and of course usefull in some cases.
Would you like to add it to primefaces-extensions?

Oleg, what do you think?

Regards,
Thomas

Re: [CODE] finding component ids easier

Posted: 21 Sep 2012, 22:51
by mkienenb
zoigl wrote: Would you like to add it to primefaces-extensions?
I put the idea out to see if there was any interest.
I've seen a few situations on the forums where such a function would provide an easy solution.
I think others may find it useful.