includeViewParams = true не работает в сочетании с составным компонентом, который перебирает коллекцию - PullRequest
2 голосов
/ 31 января 2012

ПРИМЕЧАНИЕ: Этот вопрос был отредактирован, так как был найден надуманный пример для работы. Тем не менее, проблема остается по существу такой же, как только придуманный пример работает.

Рассмотрим следующую страницу JSF:

<!DOCTYPE html>
<html 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">
    <f:view contentType="text/html">
        <h:head>
            <title>Page</title>
        </h:head>
        <h:body>
            <ui:composition template="/WEB-INF/templates/myLayout.xhtml">
                <ui:define name="metadata">
                    <f:metadata>
                        <f:viewParam name="foobar" value="#{pageBean.foo}"/>
                    </f:metadata>
                </ui:define>
                <ui:define name="content">
                    <h:form>
                        <h:commandLink value="Click"
                                    action="#{util.currentPageAction()}"/>
                    </h:form>
                </ui:define>
            </ui:composition>
        </h:body>
    </f:view>
</html>

И эти бобы:

@Named
@RequestScoped
public class PageBean implements Serializable
{
    public String getFoo()
    {
        return foo;
    }

    public void setFoo(String foo)
    {
        this.foo = foo;
    }

    private String foo;
}
@Named
@ApplicationScoped
public class Util implements Serializable
{
    public String currentPageAction()
    {
        return FacesContext.getCurrentInstance().getViewRoot().getViewId() +
                   "?faces-redirect=true&includeViewParams=true";
    }
}

Этот надуманный пример на самом деле работает, как и ожидалось - в частности, он проходит через исходные значения параметров представления в URL при перенаправлении. Так, например, если исходный URL-адрес равен http://localhost:8080/faces/pages/test.xhtml?foo=bar, когда я нажимаю <h:commandLink/>, полный URL-адрес, включая параметры просмотра, сохранит перенаправление.

Проблема в том, что когда я перехожу из этого надуманного примера к чему-то более реальному (в моем случае, к странице поиска), параметры просмотра не не выдерживают перенаправления.

Вот страница реального мира, для которой она не работает. Извините, это довольно долго для типичного вопроса stackoverflow.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:p="http://primefaces.org/ui"
      xmlns:search="http://java.sun.com/jsf/composite/components/search"
      xmlns:oc="http://java.sun.com/jsf/composite/components/mysite"
      xmlns:fn="http://www.mysite.com.au/jsf">
    <h:head>
        <title></title>
    </h:head>
    <h:body>
        <ui:composition template="/WEB-INF/templates/myLayout.xhtml">
            <ui:define name="metadata">
                <f:metadata>
                    <f:viewParam name="address" value="#{searchBean.address}"/>
                    <f:viewParam name="lng" value="#{searchBean.longitude}" required="#{!facesContext.postback}" requiredMessage="Longitude is required" validatorMessage="Longitude is invalid">
                        <f:validateDoubleRange minimum="-180" maximum="180"/>
                    </f:viewParam>
                    <f:viewParam name="lat" value="#{searchBean.latitude}" required="#{!facesContext.postback}" requiredMessage="Latitude is required" validatorMessage="Latitude is invalid">
                        <f:validateDoubleRange minimum="-90" maximum="90"/>
                    </f:viewParam>
                    <f:viewParam name="gender" value="#{searchBean.gender}"/>
                    <f:viewParam name="language" value="#{searchBean.spokenLanguageId}"/>
                    <f:viewParam name="service" value="#{searchBean.serviceId}"/>
                    <f:viewParam name="rangeKm" value="#{searchBean.rangeKm}" validatorMessage="Range (km) is invalid">
                        <f:validateLongRange minimum="1"/>
                    </f:viewParam>
                    <f:viewParam name="weeksOffset" value="#{searchBean.weeksOffset}"/>
                    <f:event type="preRenderView" listener="#{searchBean.preRender(e)}"/>
                </f:metadata>
            </ui:define>
            <ui:define name="windowTitle">#{searchBean.address}</ui:define>
            <ui:define name="scripts">
                <script type="text/javascript">
                    var mysite = mysite || {};
                    mysite.longitude      = #{searchBean.longitude};
                    mysite.latitude       = #{searchBean.latitude};
                    mysite.defaultAddress = "#{applicationBean.defaultAddress}";
                    mysite.region         = "#{applicationBean.region}";
                    mysite.baseUrl        = "#{applicationBean.baseUrl}";
                </script>
                <h:outputScript library="javascript" name="map2.js"/>
                <h:outputScript name="geocode.js" library="javascript" target="head"/>
                <h:outputScript name="search.js" library="javascript" target="head"/>
            </ui:define>

            <ui:define name="rightSidebar">
                <div class="ocSearch_sidebarSection">
                    <oc:map styleClass="search"/>
                    <div>
                        <a style="font-size:11px;text-decoration:underline;" href="#" onclick="mysite.resetMap();return false;">Reset</a>
                    </div>
                </div>

                <oc:context-menu-container isFirstChild="false">
                    <oc:context-menu title="Search Options">
                        <form id="searchForm" method="get" onsubmit="return mysite.geocode(this);">
                            <div style="clear:both;float:left;">
                                <label for="ocSearchRadius">Service Providers within</label>
                                <div id="ocSearchRadius">
                                    <input type="text" id="ocRangeKm" name="rangeKm" value="#{searchBean.rangeKm}" style="width:20px;float:left;"/>
                                    <div style="width:40px;margin-top:5px;float:left;overflow:hidden;text-align:center;vertical-align:bottom;">km of</div>
                                    <input type="text" id="ocAddress" name="address" value="#{searchBean.address}" style="width:104px;float:left;"/>
                                </div>
                            </div>
                            <div style="float:left;">
                                <div style="width:60px;margin-right:10px;float:left;">
                                    <label for="ocGender" style="display:block;">Gender</label>
                                    <select id="ocGender" name="gender" style="width:50px;">
                                        <h:panelGroup rendered="#{searchBean.gender eq 'any'}">
                                            <option value="any" selected="selected">Any</option>
                                            <option value="female">Female</option>
                                            <option value="male">Male</option>
                                        </h:panelGroup>
                                        <h:panelGroup rendered="#{searchBean.gender eq 'female'}">
                                            <option value="any">Any</option>
                                            <option value="female" selected="selected">Female</option>
                                            <option value="male">Male</option>
                                        </h:panelGroup>
                                        <h:panelGroup rendered="#{searchBean.gender eq 'male'}">
                                            <option value="any">Any</option>
                                            <option value="female">Female</option>
                                            <option value="male" selected="selected">Male</option>
                                        </h:panelGroup>
                                    </select>
                                </div>
                                <div style="float:left;">
                                    <label for="ocLanguage">Language</label>
                                    <select id="ocLanguage" name="language" style="width:176px;">
                                        <ui:repeat value="#{commonRequestBean.spokenLanguageItems}" var="item">
                                            <h:panelGroup rendered="#{searchBean.spokenLanguageId eq item.value}">
                                                <option value="#{item.value}" selected="yes">#{item.label}</option>
                                            </h:panelGroup>
                                            <h:panelGroup rendered="#{searchBean.spokenLanguageId ne item.value}">
                                                <option value="#{item.value}">#{item.label}</option>
                                            </h:panelGroup>
                                        </ui:repeat>
                                    </select>
                                </div>
                            </div>
                            <div style="float:left;">
                                <label for="ocService">Reason for visit</label>
                                <select id="ocService" name="service" style="width:176px;" >
                                    <ui:repeat value="#{commonRequestBean.serviceItems}" var="item">
                                        <h:panelGroup rendered="#{searchBean.serviceId eq item.value}">
                                            <option value="#{item.value}" selected="yes">#{item.label}</option>
                                        </h:panelGroup>
                                        <h:panelGroup rendered="#{searchBean.serviceId ne item.value}">
                                            <option value="#{item.value}">#{item.label}</option>
                                        </h:panelGroup>
                                    </ui:repeat>
                                </select>
                            </div>

                            <div style="float:left;">
                                <label for="ocStartDate">From</label>
                                <select id="ocStartDate" name="weeksOffset" style="width:176px;">
                                    <ui:repeat value="#{fn:selectableDates(13)}" var="date" varStatus="dateStatus">
                                        <h:panelGroup rendered="#{date.value eq searchBean.weeksOffset}">
                                            <option value="#{date.value}" selected="yes">#{date.label}</option>
                                        </h:panelGroup>
                                        <h:panelGroup rendered="#{date.value ne searchBean.weeksOffset}">
                                            <option value="#{date.value}">#{date.label}</option>
                                        </h:panelGroup>
                                    </ui:repeat>
                                </select>
                            </div>

                            <div style="margin-top:8px;float:left;">
                                <p:button id="searchAgainButton" value="Search Again"/>
                                <script type="text/javascript">
                                    //<![CDATA[
                                    $(function(){
                                        // Replace the Search Again button with a clone whose type is submit.
                                        var oldButton = $('#searchAgainButton');
                                        var newButton = oldButton.clone(true);
                                        newButton.attr("type","submit");
                                        newButton.attr("id","searchAgainButtonClone");
                                        newButton.removeAttr("onclick");
                                        newButton.insertBefore(oldButton);
                                        oldButton.remove();
                                        newButton.attr("id","searchAgainButton");
                                    });
                                    //]]>
                                </script>
                            </div>

                            <input type="hidden" id="ocLng"     name="lng"     value="#{searchBean.longitude}"/>
                            <input type="hidden" id="ocLat"     name="lat"     value="#{searchBean.latitude}"/>
                        </form>
                        <script>
                            // Ensure the form fields are set to the current values. (Webkit bug workaround.)
                            $(function(){
                                $('#ocRangeKm').val('#{searchBean.rangeKm}');
                                $('#ocAddress').val('#{searchBean.address}');
                                $('#ocGender').val('#{searchBean.gender}');
                                $('#ocLanguage').val('#{searchBean.spokenLanguageId}');
                                $('#ocService').val('#{searchBean.serviceId}');
                                $('#ocStartDate').val('#{searchBean.weeksOffset}');
                            });
                        </script>
                    </oc:context-menu>
                </oc:context-menu-container>
            </ui:define>

            <ui:define name="content">
                <div id="ocSearchResultsHeader" class="fixed">
                    <div style="margin-left:10px;">
                        <h3 style="margin:8px 0 4px 0;">#{searchBean.searchResultsTitle}</h3>
                        <span class="help">Click a time to book #{searchBean.service.indefiniteArticle} #{searchBean.service.name}</span>
                    </div>
                </div>
                <div class="ocSearch_resultSet">
                    <h:form>
                        <h:commandLink value="Foobar" action="#{util.currentPageAction}"/>
                    </h:form>
                    <search:result resultSet="#{searchBean.results}" startDate="#{searchBean.startDate}"/>
                </div>
            </ui:define>
        </ui:composition>
    </h:body>
</html>

Базовый компонент, searchBean, является областью запроса, как и надуманный пример.

Обновление : если я удаляю экземпляр составного компонента <search:result/>, он теперь работает. Это совершенно неожиданно. Точно так же, если я оставляю экземпляр компонента там и удаляю тело самого компонента, это работает. Так что в компоненте есть что-то, что мешает чему-то другому. Weird. Я посмотрю, смогу ли я понять, что это такое.

Обновление : Это похоже на ошибку. Если я заменю составной компонент следующим:

<ui:repeat value="#{searchBean.results} var="item" varStatus="itemStatus">
    <h:outputText value="#{item.mapMarker}"/>
    <br/>
</ui:repeat>

также происходит сбой, как и в случае с составным компонентом.

Аналогично, это не получается:

<h:dataTable value="#{searchBean.results}" var="item">
    <h:column>
        <h:outputText value="#{item.mapMarker}"/>
        <br/>
    </h:column>
</h:dataTable>

В моем конкретном случае есть три результата, поэтому я могу перебирать их вручную так:

<ui:param name="results" value="#{searchBean.results}"/>
<br/>
<h:outputText value="#{results.get(0).mapMarker}"/>
<br/>
<h:outputText value="#{results.get(1).mapMarker}"/>
<br/>
<h:outputText value="#{results.get(2).mapMarker}"/>
<br/>

и это работает.

Так что есть что-то в переборе результатов, которое связано с <h:commandLink/>.

Что, черт возьми, может пойти не так? Еще одна ошибка Mojarra, где простой вариант использования не срабатывает?

Окончательное обновление: Сузили проблему с надуманными примерами и перенесли вопрос сюда .

1 Ответ

1 голос
/ 31 января 2012

Это глупая ошибка, и я не могу понять, является ли это JDK7, Mojarra, контейнером EJB, GlassFish в целом или просто плохим сочетанием двух или более из них.

Рассмотримследующий фрагмент из SearchBean:

public List<NormalizedSearchResult> getResults()
{
    return searchEjb.findByLocation(getRangeKm(),
                                    getLongitude(),
                                    getLatitude(),
                                    gender,
                                    getSpokenLanguageId(),
                                    getServiceId(),
                                    1,
                                    99,
                                    getStartDate().toDateMidnight().toDate(),
                                    7);
}

public List<NormalizedSearchResult> getResults2()
{
    float lng = longitude;
    float lat = latitude;
    return searchEjb.findByLocation(getRangeKm(),
                                    lng,
                                    lat,
                                    gender,
                                    getSpokenLanguageId(),
                                    getServiceId(),
                                    1,
                                    99,
                                    getStartDate().toDateMidnight().toDate(),
                                    7);
}

public List<NormalizedSearchResult> getResults3()
{
    float lng = getLongitude();
    float lat = getLatitude();
    return searchEjb.findByLocation(getRangeKm(),
                                    lng,
                                    lat,
                                    gender,
                                    getSpokenLanguageId(),
                                    getServiceId(),
                                    1,
                                    99,
                                    getStartDate().toDateMidnight().toDate(),
                                    7);
}

public List<NormalizedSearchResult> getResults4()
{
    float lng = longitude;
    float lat = latitude;
    return searchEjb.findByLocation(getRangeKm(),
                                    138.5999594f,
                                    -34.9286212f,
                                    gender,
                                    getSpokenLanguageId(),
                                    getServiceId(),
                                    1,
                                    99,
                                    getStartDate().toDateMidnight().toDate(),
                                    7);
}

public List<NormalizedSearchResult> getResults5()
{
    float lng = 138.5999594f;
    float lat = -34.9286212f;
    return searchEjb.findByLocation(getRangeKm(),
                                    lng,
                                    lat,
                                    gender,
                                    getSpokenLanguageId(),
                                    getServiceId(),
                                    1,
                                    99,
                                    getStartDate().toDateMidnight().toDate(),
                                    7);
}

public Float getLongitude()
{
    return longitude;
}

public void setLongitude(Float longitude)
{
    this.longitude = longitude;
}

public Float getLatitude()
{
    return latitude;
}

public void setLatitude()
{
    this.latitude = latitude;
}

private Float longitude;
private Float latitude;

Любой из этих фрагментов, включенный в search.xhtml, приводит к сбою includeViewParams=true в вышеупомянутом <h:commandLink/>:

<ui:repeat value="#{searchBean.results}"/>
<ui:repeat value="#{searchBean.results2}"/>
<ui:repeat value="#{searchBean.results3}"/>
<ui:repeat value="#{searchBean.results4}"/>

Однако, этот не имеет такого же нежелательного эффекта:

<ui:repeat value="#{searchBean.results5}"/>

И не это:

<ui:param name="results" value="#{pageBean.results}"/>
<h:outputText value="#{results.get(0).mapMarker}"/>
<br/>
<h:outputText value="#{results.get(1).mapMarker}"/>
<br/>
<h:outputText value="#{results.get(2).mapMarker}"/>
<br/>

И, конечно, includeViewParams=true работает, когда ни один из этих фрагментов xhtml не используется.

Затем я изменил тип закрытых переменных longitude и latitude с Float на float и больше не имел этих сбоев ни для одного извышеуказанные случаи.

WTF ?!Серьезно?

Как кто-либо мог предположить, что распаковка параметров вида на более ранних этапах рендеринга вида нарушит includeViewParams=true?

Комментарии на JAVASERVERFACES-2260 .

...