Search code examples
javajsfjsf-2

JSF commandLink created programmatically inside table doesn't work


Is this possible? After few hours of fight I give up.

The JSF table is created programmatically and commandLinks inside also.

Below piece of code with commandLink outside and inside the table. Both are created similar way. XHTML full code:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">

<f:view contentType="text/html" locale="pl">
    <h:body>
        <h:form id="form">
            <h:dataTable binding="#{dynamicDataTable.table}" value="#{dynamicDataTable.tableContent}" />
            <!-- this one below works fine -->
            <h:commandLink binding="#{dynamicDataTable.link}" />
        </h:form>
    </h:body>   
</f:view>
</html>

DynamicDataTable bean:

@Named
@ViewScoped
public class DynamicDataTable implements Serializable {

    private static final long serialVersionUID = 1L;

    private HtmlDataTable table;
    private HtmlCommandLink link;
    private List<String> tableContent;

    public void action() {
        System.out.println("Action performed");
    }

    public HtmlDataTable getTable() {
        table = new HtmlDataTable();
        HtmlCommandLink inlink = new HtmlCommandLink();
        inlink.setValue("Inside link");
        inlink.setActionExpression(createMethodExpression("#{dynamicDataTable.action}", String.class));
        UIColumn column = new UIColumn();
        column.getChildren().add(inlink);
        table.getChildren().add(column);
        return table;
    }

    public List<String> getTableContent() {
        tableContent = new ArrayList<String>();
        tableContent.add("a");
        tableContent.add("b");
        return tableContent;
    }

    public void setTableContent(List<String> tableContent) {
        this.tableContent = tableContent;
    }

    public void setTable(HtmlDataTable table) {
        this.table = table;
    }

    public HtmlCommandLink getLink() {
        link = new HtmlCommandLink();
        link.setValue("Outside link");
        link.setActionExpression(createMethodExpression("#{dynamicDataTable.action}", String.class));
        return link;
    }

    public void setLink(HtmlCommandLink link) {
        this.link = link;
    }

    public static MethodExpression createMethodExpression(String expression, Class<?> returnType) {
        FacesContext context = FacesContext.getCurrentInstance();
        return context.getApplication().getExpressionFactory().createMethodExpression(
                context.getELContext(), expression, returnType, new Class[0]);
    }
}

The links inside the table are created, but don't work. The one created outside the table, also created programmatically works fine.

Any idea?


Solution

  • A brief explanation

    The problem you are hitting above is caused by the fact that the viewhandler is unable to handle your action link because it can not generate an id for the link component - meaning the reference to the component activated is actually missing.

    Both of your examples are actually not quite right. However with the link generated outside of the table, the view handler is somehow able to figure out an id for the component and ends up in the right place when executing the action.

    Looking at the code

    If we look at what JSF Mojarra generates when we invoke the above code we can see the following:

    The links inside the table get a link to form:j_idt3:0:j_id3 but there is no id attributed to the component, so the link will not work.

    The link outside the table gets a link to form:j_idt5 and the id j_id1:javax.faces.ViewState:0 is generated. So while it does indeed invoke the action - the link/id correlation is not quite right.

    So clearly the renderer is able to figure out a generated id for the link, but it never sets that generated id onto the actual component/tag. This is somewhat of a problem.

    Solving the issue

    The solution here is to help JSF and the view handler to figure out the path to the component with the action. You can do this by forcefully setting an id when you programmatically generate the command link - inside the getTable() method, add the following code;

    inlink.setId("link");
    

    This should allow the the renderer to render a valid page with working action links.