2
votes

I'm working on integrating a JavaScript framework (Aurelia) for frontend components into our existing JSF application. I'm using JSF renderers to place the custom tags into the output, e.g.:

@FacesRenderer(componentFamily = "frontend.component", rendererType = "frontend.mytag")
public class MyTagRenderer extends Renderer {

    @Override
    public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
        final String tag = "my-tag";
        int id = (Integer) component.getAttributes().get("id");
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement(tag, component);
        writer.writeAttribute("id.bind", id, null);
        writer.endElement(tag);
    }
}

The problem is that while initializing the JS framework I have to know which custom tags need to be bootstrapped. At first I tried doing this server-side, by adding the tagname as a property to my JSF frontend component class, setting it in the custom renderer and walking the JSF component tree to get a list:

class MyTagRenderer {
    public void encodeEnd(FacesContext context, UIComponent component) {
        ...
        ((FrontendComponent) component).setTagName("my-tag:);
    }
}

@ManagedBean
@RequestScoped
class FrontendBootstrapHandler {
    public String getTagList() {
        List<String> tags = walkRecursive(FacesContext.getCurrentInstance().getViewRoot());
        return "'" + String.join("','", tags + "'";
    }
}

However I found that the order in which JSF renders parts of the page is not entirely predictable, and sometimes the walkRecursive was called before setTagName.

For now I've settled on rendering a <script> snippet alongside each custom tag to keep the list of tags to bootstrap client-side, but this is hardly ideal. Can anybody offer me a more elegant solution?

1
I've taken a look at aurelia and seems to be a framework similar to AngularJs, which is a complete MVC framework, not like jQuery, which adds just syntax sugar to the JS. JSF does the same as Aurelia, but at server side. If you're replacing JSF by Aurelia, I would suggest you replacing entire views and placing proper controllers (could be Spring MVC or other framework) at server side instead of using the managed beans. The scripts you write will replace the managed bean functionality. Otherwise, trying to mix both of them at the same time, seems to be going to end up in a headache. - Xtreme Biker
@Xtreme: JSF/Facelets can as good be used to generate (stateless) HTML views for other frameworks. Based on question history OP appears to be familiar with JAX-RS, so that would be recommended over Spring MVC REST controllers (just in case). - BalusC
All of which I know, but I can't rewrite a 10 year old legacy application from JSF to JavaScript overnight. Inconvenient though it may be, I'm going to need to use both for the time being. - Romke van der Meulen

1 Answers

1
votes

I've found a way to maintain the list of components server-side. Rather than setting the tag name to bootstrap in the renderer, which may be called at any time, I set it in a ComponentHandler:

my.taglib.xml

<tag>
    <tag-name>my-tag</tag-name>
    <attribute>
        <name>id.bind</name>
        <required>true</required>
        <type>java.lang.Integer</type>
    </attribute>
    <component>
        <component-type>frontend.component</component-type>
        <renderer-type>frontend.renderer</renderer-type>
        <handler-class>frontend.FrontendComponentHandler</handler-class>
    </component>
</tag>

FrontendComponentHandler.java

public class FrontendComponentHandler extends javax.faces.view.facelets.ComponentHandler {

    public FrontendComponentHandler(ComponentConfig config) {
        super(config);
    }

    @Override
    public void onComponentCreated(FaceletContext ctx, UIComponent c, UIComponent parent) {
        FrontendComponent component = (FrontendComponent) c;
        component.setTagName(tag.getLocalName());
    }
}

FrontendBootstrapHandler.java

@ManagedBean
@RequestScoped
class FrontendBootstrapHandler {
    public String getTagList() {
        List<String> tags = walkRecursive(FacesContext.getCurrentInstance().getViewRoot());
        return "'" + String.join("','", tags + "'";
    }
}