Составной компонент JSF 2 с необязательным атрибутом слушателя в f:ajax

У меня есть составной компонент, который выглядит примерно так:

<!DOCTYPE html>
<html 
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:dm="http://davemaple.com/dm-taglib"
    xmlns:rich="http://richfaces.org/rich"
    xmlns:cc="http://java.sun.com/jsf/composite"
    xmlns:fn="http://java.sun.com/jsp/jstl/functions"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:a4j="http://richfaces.org/a4j">

<cc:interface>
    <cc:attribute name="styleClass" />
    <cc:attribute name="textBoxStyleClass" />
    <cc:attribute name="inputTextId" />
    <cc:attribute name="labelText" />
    <cc:attribute name="tabindex" />
    <cc:attribute name="required" default="false" />
    <cc:attribute name="requiredMessage" />
    <cc:attribute name="validatorId" />
    <cc:attribute name="converterId" />
    <cc:attribute name="title"/>
    <cc:attribute name="style"/>
    <cc:attribute name="unicodeSupport" default="false"/>
    <cc:attribute name="tooltip" default="false"/>
    <cc:attribute name="tooltipText" default=""/>    
    <cc:attribute name="tooltipText" default=""/>
    <cc:attribute name="onfail" default=""/>
    <cc:attribute name="onpass" default=""/>
</cc:interface>

<cc:implementation>

        <ui:param name="converterId" value="#{! empty cc.attrs.converterId ? cc.attrs.converterId : 'universalConverter'}" />
        <ui:param name="validatorId" value="#{! empty cc.attrs.validatorId ? cc.attrs.validatorId : 'universalValidator'}" />
        <ui:param name="component" value="#{formFieldBean.getComponent(cc.attrs.inputTextId)}" />
        <ui:param name="componentValid" value="#{((facesContext.maximumSeverity == null and empty component.valid) or component.valid) ? true : false}" />
        <ui:param name="requiredMessage" value="#{! empty cc.attrs.requiredMessage ? cc.attrs.requiredMessage : msg['validation.generic.requiredMessage']}" />
        <ui:param name="clientIdEscaped" value="#{fn:replace(cc.clientId, ':', '\\\\\\\\:')}" />

        <h:panelGroup layout="block" id="#{cc.attrs.inputTextId}ValidPanel" style="display:none;">
            <input type="hidden" id="#{cc.attrs.inputTextId}Valid" value="#{componentValid}" />
        </h:panelGroup>

        <dm:outputLabel for="#{cc.clientId}:#{cc.attrs.inputTextId}" id="#{cc.attrs.inputTextId}Label">#{cc.attrs.labelText}</dm:outputLabel>
        <dm:inputText 
            styleClass="#{cc.attrs.textBoxStyleClass}"
               tabindex="#{cc.attrs.tabindex}"
               id="#{cc.attrs.inputTextId}"
               required="#{cc.attrs.required}"
               requiredMessage="#{requiredMessage}"
               title="#{cc.attrs.title}"
               unicodeSupport="#{cc.attrs.unicodeSupport}">
            <f:validator validatorId="#{validatorId}" />
            <f:converter converterId="#{converterId}" />
            <cc:insertChildren />
            <f:ajax 
                event="blur" 
                execute="@this" 
                render="#{cc.attrs.inputTextId}ValidPanel #{cc.attrs.inputTextId}Msg" 
                onevent="on#{cc.attrs.inputTextId}Event" />
        </dm:inputText>
        <rich:message for="#{cc.clientId}:#{cc.attrs.inputTextId}" id="#{cc.attrs.inputTextId}Msg" style="display: none;" />
        <script>
            function on#{cc.attrs.inputTextId}Event(e) {
                if(e.status == 'success') {
                    $('##{clientIdEscaped}\\:#{cc.attrs.inputTextId}').trigger($('##{cc.attrs.inputTextId}Valid').val()=='true'?'pass':'fail');
                }
            }
            $('##{clientIdEscaped}\\:#{cc.attrs.inputTextId}').bind('fail', function() {
                $('##{clientIdEscaped}\\:#{cc.attrs.inputTextId}, ##{clientIdEscaped}\\:#{cc.attrs.inputTextId}Label, ##{cc.attrs.inputTextId}Msg, ##{cc.id}Msg').addClass('error');
                $('##{cc.id}Msg').html($('##{clientIdEscaped}\\:#{cc.attrs.inputTextId}Msg').html());
                #{cc.attrs.onfail}
            }).bind('pass', function() {
                $('##{clientIdEscaped}\\:#{cc.attrs.inputTextId}, ##{clientIdEscaped}\\:#{cc.attrs.inputTextId}Label, ##{cc.attrs.inputTextId}Msg, ##{cc.id}Msg').removeClass('error');
                $('##{cc.id}Msg').html($('##{clientIdEscaped}\\:#{cc.attrs.inputTextId}Msg').html());
                #{cc.attrs.onpass}
            });
        </script>
        <a4j:region rendered="#{facesContext.maximumSeverity != null and !componentValid}">
            <script>
                $(document).ready(function() { 
                    $('##{clientIdEscaped}\\:#{cc.attrs.inputTextId}').trigger('fail');
                });
            </script>
        </a4j:region>
</cc:implementation>

</html>

Я хотел бы иметь возможность добавить необязательный атрибут «прослушиватель», который, если он определен, добавит прослушиватель событий к моему f: ajax, но мне трудно понять, как это сделать. Любая помощь будет оценена по достоинству.


person Dave Maple    schedule 06.07.2012    source источник


Ответы (3)


Вам необходимо указать атрибут method-signature для <cc:attribute>, чтобы рассматривать значение атрибута как выражение метода. Вы можете использовать тег времени сборки представления JSTL <c:if> для условного добавления тега <f:ajax>.

<cc:interface>
    <cc:attribute name="listener" method-signature="void listener()" />
</cc:interface>
<cc:implementation>
    <h:someComponent>
        <c:if test="#{cc.getValueExpression('listener') != null}">
            <f:ajax listener="#{cc.attrs.listener}" />
        </c:if>
    </h:someComponent>
</cc:implementation>

(#{not empty cc.attrs.listener} не будет работать, так как EL будет неявно оценивать атрибут как выражение значения)

Затем вы можете использовать его следующим образом:

<my:someComposite listener="#{bean.listener}" />

Или, если ваша среда не поддерживает EL 2.2, создайте вспомогательный компонент:

@FacesComponent("someComponent")
public class SomeComponent extends UINamingContainer {

    public boolean isHasListener() {
        return getValueExpression("listener") != null;
    }

}

который должен быть объявлен и использован как

<cc:interface type="someComponent">
    <cc:attribute name="listener" method-signature="void listener()" />
</cc:interface>
<cc:implementation>
    <h:someComponent>
        <c:if test="#{cc.hasListener}">
            <f:ajax listener="#{cc.attrs.listener}" />
        </c:if>
    </h:someComponent>
</cc:implementation>
person BalusC    schedule 06.07.2012
comment
Ok. я зашел так далеко, но я хочу, чтобы это был необязательный атрибут. Когда он не предоставляется клиентом компонента, я не хочу, чтобы он использовался f: ajax. Есть ли способ сделать это необязательным для f: ajax? - person Dave Maple; 06.07.2012
comment
Хм, так не пойдет. <c:if> пытается разрешить его как выражение значения:/ Извините, мне придется отменить свой ответ. - person BalusC; 06.07.2012
comment
OK. Вариант 2 не имеет красивого интерфейса, но я думаю, что выберу его, чтобы я мог спрятать другие функции f: ajax под капотом. - person Dave Maple; 07.07.2012
comment
Теоретически это должно быть выполнимо при использовании вспомогательного компонента (<cc:interface componentType>), у которого есть метод получения, который проверяет, был ли определен атрибут слушателя, чтобы вы могли использовать <c:if test="#{cc.hasListener}"> или около того. Невозможно сказать EL, чтобы он не разрешал это как выражение значения. Даже #{not cc.attrs.containsKey('listener')} не будет работать из-за EL-преобразователя #{cc.attrs}. Я обновил ответ. - person BalusC; 07.07.2012
comment
Более того, #{not empty cc.getValueExpression('listener')} у меня работает. Извините за много обновлений, у меня тоже такое впервые :) - person BalusC; 07.07.2012
comment
вообще не надо извиняться. Я очень ценю ваше время. Последнее решение отлично работает, а интерфейс — именно то, на что я надеялся. - person Dave Maple; 07.07.2012

У меня тоже те же проблемы, и мое решение заключалось в том, чтобы создать значение по умолчанию для метода действия. Мне нужно только создать класс: MyComponent.java, который содержит все сигнатуры методов по умолчанию.

<cc:interface>
    <cc:attribute name="listener" method-signature="void listener()"
                  default="#{myComponent.doNothing()}" />
</cc:interface>
<cc:implementation>
    <h:someComponent>
        <f:ajax listener="#{cc.attrs.listener}" />
    </h:someComponent>
</cc:implementation>
person Harun    schedule 18.03.2016

К сожалению, все вышеперечисленное не сработало для меня, поэтому я повозился и придумал следующее решение.

<cc:interface type="someComponent">
    <cc:attribute name="listener" method-signature="void listener()" />
</cc:interface>
<cc:implementation>
    <h:someComponent>
        <p:ajax listener="#{cc.attrs.listener}" onstart="#{cc.attrs.containsKey('listener') ? '' : 'return false'}" />
    </h:someComponent>
</cc:implementation>

Это всегда будет связывать поведение ajax, но выполнять его только в том случае, если на самом деле есть слушатель.

person Christian Beikov    schedule 05.12.2017