3

Our application is using Mojarra 2.1.29-03 and we are having a problem with attributes in our custom tags as they are being copied to nested tags as well.

For example, given the following tag definition:

cc.taglib.xml

<?xml version="1.0" encoding="UTF-8" ?>
<facelet-taglib version="2.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd">
    <namespace>http://temp.com/jsf/customcomponents</namespace>
    <tag>
        <tag-name>wrapper</tag-name>
        <source>wrapper.xhtml</source>
        <attribute>
            <description>The style class for the component</description>
            <name>styleClass</name>
        </attribute>
    </tag>
</facelet-taglib>

wrapper.xhtml

<?xml version="1.0" encoding="UTF-8" ?>
<!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">
    <ui:component xmlns:ui="http://java.sun.com/jsf/facelets">
        <div style="border: 1px solid black; margin: 5px;">
            <p>styleClass: #{styleClass}</p>
            <ui:insert />
        </div>
    </ui:component>
</html>

And a client page:

<?xml version="1.0" encoding="UTF-8"?>
<!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:cc="http://temp.com/jsf/customcomponents">
     <h:body>
        <cc:wrapper styleClass="cc-style">
            <cc:wrapper />
        </cc:wrapper>
    </h:body>
</html>

The result is as follows:

styleClass: cc-style

styleClass: cc-style

... so the styleClass attribute is also being applied to the inner tag even though the client page does not set it.

We have noted that we can workaround this by processing all our client pages to set styleClass="" if it is not explicitly set but this is an approach we would like to avoid (it's a very ugly solution and cannot be enforced going forward).

Is this a bug? Is there any way to work around it other than that mentioned above - preferably with the workaround in the tag rather than the client pages?

Thanks Ivor

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
haggisandchips
  • 523
  • 2
  • 11

2 Answers2

3

This is not strictly a bug, but this is indeed unintuitive and undesired behavior which should have been addressed in the JSF/Facelets spec.

The work around solution is not trivial, a custom taghandler is needed to clear out the Facelet scope. JSF utility library OmniFaces has since version 2.1 exactly such one: <o:tagAttribute> (source code here).

Usage (do note that prolog, doctype and html tags are unnecessary, this is the file in its entirety):

<ui:composition 
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:o="http://omnifaces.org/ui"
>
    <o:tagAttribute name="styleClass" />

    <div style="border: 1px solid black; margin: 5px;">
        <p>styleClass: #{styleClass}</p>
        <ui:insert />
    </div>
</ui:composition>

Unrelated to the concrete problem, this is not a custom component, but a tagfile. And, in JSF 2.x, the term "cc" is usually associated with composite components, which is a completely different subject altogether. To get your knowledge and terminology right (so that you don't confuse yourself or others while reading your or other's code), head to When to use <ui:include>, tag files, composite components and/or custom components?

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Many thanks BalusC - we're stuck on Mojarra 2.1.x and that handler is not available in 1.11 (as you stated) but making use of the source you linked to has fixed our problem. BTW: I am familiar with composite components - should have realised that cc would be confusing. Thank you very much for your very quick and helpful response. – haggisandchips Jul 13 '15 at 14:28
1

For info we identifed an alternative approach that might be of use to others as it is slightly easier to implement and understand.

First I need to add a bit of context to the situation that we found ourselves in ... we had recently migrated from 2.1.12 to 2.1.29-03 and as part of that process we changed one of our building blocks from a composite component (which has isolated scope for its attributes) to a custom tag (which has the behaviour described above). The same behaviour regarding custom tag attributes existed in 2.1.12 but we were unaware of it so we considered the following approach instead as it would restore the behaviour we had prior to migration because we would only need to apply it to the component that we changed.

wrapper.xhtml (I've changed the namespace to 'ct' rather than 'cc')

<ui:component xmlns:ct="http://temp.com/jsf/customtags" xmlns:ui="http://java.sun.com/jsf/facelets">
    <ct:tagAttribute name="styleClass" />
    <div style="border: 1px solid black; margin: 5px;">
        <p>styleClass: #{styleClass}</p>
        <ct:eraseAttribute name="styleClass" />
        <ui:insert />
    </div>
</ui:component>

Where tagAttribute is the solution suggested by BalusC to prevent the tag inheriting attributes from its parents and in addition we call eraseAttribute tag before calling ui:insert to remove the attribute from scope (this obviously requires that the custom tag is finished with the attribute). This means that this one tag neither inherits nor leaks attributes and other tags could remain unchanged and maintain the same behaviour they had prior to migration.

ct.taglib.xhtml (snippet)

<tag>
    <tag-name>eraseAttribute</tag-name>
    <handler-class>com.temp.jsf.customtags.EraseTagAttribute</handler-class>
    <attribute>
        <name>name</name>
        <required>true</required>
        <type>java.lang.String</type>
    </attribute>
</tag>

EraseTagAttribute.java

package com.temp.jsf.customtags;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.TagConfig;
import javax.faces.view.facelets.TagHandler;

public class EraseTagAttribute extends TagHandler {

    private final String name;

    public EraseTagAttribute(TagConfig config) {

        super(config);

        name = getRequiredAttribute("name").getValue();
    }

    public void apply(FaceletContext context, UIComponent parent) throws IOException {

        context.getVariableMapper().setVariable(name, null);
    }
}

Ultimately we did not use it however as we felt the answer provided by BalusC (which we applied to every attribute in all our custom tags) was the correct and cleaner approach even though it might have additional consequences in our very specific situation.

haggisandchips
  • 523
  • 2
  • 11