Search code examples
jsfprimefacesjstlcomposite-component

EL not resolved when passed as attribute to composite component inside datatable


I'm facing a problem with an EL expression not being resolved at the initialization of a composite component. Here is the code:

<p:column sortable="false" toggleable="false" width="20%">
    <div styleClass="panelActions">
    <div styleClass="btn_view_documents">
            <w:commandButton buttonType="text" value="#{msg.lbl_quick_view}"
                id="viewerDocument"
                isDefault="true"
                onclick="IB_#{dataItemForIB.document.documentId}_Viewer();"
                ajax="false">
                 <f:param name="documentId" value="#{dataItemForIB.document.documentId}"/>
                <tags:documentViewerLoader 
                    id="#{'IB_'.concat(dataItemForIB.document.documentId).concat('_Viewer')}"
                    documentId="#{dataItemForIB.document.documentId}" 
                    documentViewerId="DocViewer"
                    mainPanelId="XXXX"
                    processDocumentLoad="#{SomeBean.someMethod()}"
                 />
            </w:commandButton>

Here is the component declaration:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition 
xmlns="http://www.w3.org/1999/xhtml" 
xmlns:h="http://java.sun.com/jsf/html" 
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets" 
xmlns:c="http://java.sun.com/jsp/jstl/core" 
xmlns:p="http://primefaces.org/ui" 
xmlns:w="http://xxx.xxx.xxx/w/components"
xmlns:composite="http://java.sun.com/jsf/composite">

<composite:interface shortDescription="Callable command for load document">
    <composite:attribute name="id" default="defaultDocumentLoader"/>
    <composite:attribute name="documentId" type="java.lang.String"/>
    <composite:attribute name="documentViewerId" type="java.lang.String"/>
    <composite:attribute name="mainPanelId" type="java.lang.String"/>
    <composite:attribute name="isCrop" type="java.lang.String" default="false"/>
    <composite:attribute name="processDocumentLoad" method-signature="void action()"/>
</composite:interface>

<composite:implementation>
    <script>
        #{cc.attrs.id} = function(){
            DocViewer_quickViewCached(
                    '#{cc.attrs.id}',
                    '#{view.locale.language}', 
                    '#{cc.attrs.mainPanelId}', 
                    '#{cc.attrs.documentId}',
                    '#{cc.attrs.isCrop}');
        }
    </script>
    
    <p:commandButton 
        style="display:none" 
        action="#{cc.attrs.processDocumentLoad}" 
        ajax="true" 
        oncomplete="DocViewer_quickViewNoCached('#{cc.attrs.id}','#{view.locale.language}', '# 
{cc.attrs.documentId}', '#{cc.attrs.mainPanelId}', '#{cc.attrs.isCrop}', xhr, status,args)" 
        widgetVar="#{cc.attrs.id}_documentLoadInvocator">
        <f:param name="id" value="#{cc.attrs.id}"/>
        <f:param name="documentId" value="#{cc.attrs.documentId}"/>
    </p:commandButton>
</composite:implementation>

</ui:composition>

And here is the HTML output:

<script>
    IB__Viewer = function(){
        DocViewer_quickViewCached(
                'IB__Viewer',
                'en', 
                'XXXX', 
                'id00000001336875',
                'false');
    }
</script>
        


<button id="XXXX:tbl_groupType_IB:0:IB__Viewer:j_idt46395" 
name="XXXX:tbl_groupType_IB:0:IB__Viewer:j_idt46395" class="" onclick="PrimeFaces.ab({s:"XXXX:tbl_groupType_IB:0:IB__Viewer:j_idt46395",onco:function(xhr,status,args){DocViewer_quickViewNoCached('IB__Viewer','en', 'id00000001336875', 'XXXX:mainPanel', 'false', xhr, status,args);},pa:[{name:"id",value:"IB__Viewer"},{name:"documentId",value:"id00000001336875"}]});return false;" style="display:none" type="submit" role="button" aria-disabled="false">
    <span class="ui-button-text ui-c">ui-button</span>
</button>


<button id="XXXX:tbl_groupType_IB:0:viewerDocument" name="XXXX:tbl_groupType_IB:0:viewerDocument" 
class="b-button-medium primary" onclick="IB_id00000001336875_Viewer();" type="button" role="button" 
aria-disabled="false">
    <span class="ui-button-text ui-c">View</span>
</button>

The problem here is the ID attribute of DocumentsViewerLoader is not resolving the

id="#{'IB_'.concat(dataItemForIB.document.documentId).concat('_Viewer')}" 

while one line below it is indeed resolving it in the attribute

documentId="#{dataItemForIB.document.documentId}" 

hence the script is rendered as you can see above instead of

IB_SomeDocumentID_Viewer = function(){
        DocViewer_quickViewCached(
                'IB_SomeDocumentID_Viewer',
                'en', 
                'XXXX', 
                'id00000001336875',
                'false');

I've tried many things as :

id="IB_#{dataItemForIB.document.documentId}_Viewer" 
id="#{'IB_'.concat(dataItemForIB.document.documentId).concat('_Viewer')}" 

<c:set var="docId" value="IB_#{dataItemForIB.document.documentId}_Viewer" />
id="#{docId}" 

and even using the rowIndexVar attribute of the datatable and none of them worked.

Also read a lot of questions prior to ask this one but none of them were related to this problem. My initial guess was it has to do with JSF lifecycle but I can't understand why the same exact expression is well resolved on attribute "documentId" and not in "id".

Can anyone please lend a hand with this?


Solution

  • Finally it seems the problem was the name of the atribute "id" itself.

    After several tests I got this stack trace:

    java.lang.IllegalArgumentException: Empty id attribute is not allowed
        at javax.faces.component.UIComponentBase.validateId(UIComponentBase.java:581)
        at javax.faces.component.UIComponentBase.setId(UIComponentBase.java:413)
        at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.assignUniqueId(ComponentTagHandlerDelegateImpl.java:442)
        at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:179)
    

    As you can see above in the component's xhtml the ID attribute is not mandatory, and it seems it was trying to validate it as if it was the ID of a UIComponent, so I decided to rename the attribute to "loaderId" and it got solved.

    I still don't have a clear vision of why this was happening so any further explanation is well received.

    Thank you for your time @Selaron

    P.S: It is the first time I end up answering my own question, if moderators think this should go in a comment feel free to edit it or let me know.