jsfparameterscommandlinkphaselistener

Programatically add Parameter to HtmlCommandLink using a Phase Listener


I need to add a component (UIParameter) to a HtmlCommandLink component dinamically through a Phase Listener. What I want to achieve is that every element <h:link outcome="out"> renders as <a href="out_url_parsed + ?param=paramvalue">.Where "param" is my component.

I've tried using this

private void addElement(final PhaseEvent event, final Class clazz, final UIComponent component) {
    final FacesContext fcontext = event.getFacesContext();
    UIViewRoot root = fcontext.getViewRoot();
    if (root == null) {
        return;
    }

    root.visitTree(new FullVisitContext(fcontext), new VisitCallback() {

        @Override
        public VisitResult visit(VisitContext context, UIComponent target) {
            if (clazz.isInstance(target)) {
                LOGGER.info("Element Found");
                UIParameter parameter = new UIParameter();
                parameter.setValue("willberonadom");
                parameter.setId("sessiontoken");
                target.getChildren().add(parameter);

            }
            return VisitResult.ACCEPT;
        }
    });
}

But it's not working. The element is actually found on the tree but the UIParameter does not render.

I've found that the UIViewRoot only has child elements after RENDER_RESPONSE phase. So i think this is why my added element is not rendered at the end of the process.

I'm sure I can add this param editing the views but I don't want to do that since it must be present on all h:link in the application and must be present on any other new added too. So I consider this as a better approach to avoid missing tags

On a similar case I've managed to add input hidden elements to every form on view with this code...

HtmlInputHidden hiddenToken = new HtmlInputHidden();
hiddenToken.setId("sessiontoken");
hiddenToken.setValue("willberandom");
hiddenToken.setRendered(true);
root.addComponentResource(event.getFacesContext(), hiddenToken,"form");

But it doesn't work on anchor tags


Solution

  • There are several mistakes:

    1. You want to add a parameter to a HtmlCommandLink component which represents <h:commandLink>, but you're giving an example with <h:link>, which is represented by HtmlOutcomeTargetLink. What exactly do you want?

    2. A PhaseListener on beforePhase() of RENDER_RESPONSE may be too late on GET requests which would only build the view for the first time during render response. At the moment your PhaseListener runs, the UIViewRoot would have no children at all. You'd better hook on view build time instead. For that, a SystemEventListener on PostAddToViewEvent is the best suitable.

    3. You're setting the parameter name as an id instead of name. Use UIParameter#setName() instead of UIParameter#setId().

    Provided that you actually meant to add them to <h:link> components, then here's a kickoff example how you can achieve that with a SystemEventListener.

    public class YourSystemEventListener implements SystemEventListener {
    
        @Override
        public boolean isListenerForSource(Object source) {
            return source instanceof HtmlOutcomeTargetLink;
        }
    
        @Override
        public void processEvent(SystemEvent event) throws AbortProcessingException {
            UIParameter parameter = new UIParameter();
            parameter.setName("sessiontoken");
            parameter.setValue("willberonadom");
            ((UIComponent) event.getSource()).getChildren().add(parameter);
        }
    
    }
    

    (if you actually want to apply them on <h:commandLink> as well, just extend the isListenerForSource() check with a || source instanceof HtmlCommandLink)

    In order to get it to run, register it as follows in faces-config.xml:

    <application>
        <system-event-listener>
            <system-event-listener-class>com.example.YourSystemEventListener</system-event-listener-class>
            <system-event-class>javax.faces.event.PostAddToViewEvent</system-event-class>
        </system-event-listener>
    </application>