Недопустимый синтаксис для операции Set: как сообщить JSF, что я не «хочу» установщик

#jsf #jsf-2 #primefaces #el #jsf-2.2

#jsf #jsf-2 #простые #el #jsf-2.2

Вопрос:

Этот вопрос может быть скорее типа «концептуальный» или «я не понимаю JSF».

Мой сценарий: у меня есть страница JSF ( index.xhtml ), где я использую p:accordionPanel (но я не думаю, что имеет значение, какой это компонент). Что я хочу сделать, так это установить activeIndexes его.

 <p:accordionPanel multiple="true" activeIndex="#{myController.getActiveIndexesForSections('whatever')}">
// bla bla...
</p:accordionPanel>
  

И (упрощенный) метод в вспомогательном компоненте:

 public String getActiveIndexesForSections(String holderName){
    String activeSections = "";
    for(Section s : sectionMap.get(holderName)){
        if (s.isActive())
        //add to the string
    }
    return activeSections;
}
  

Теперь это отлично работает при обычной загрузке страницы.

Но если я нажму на p:commandButton ajax=false ) (или что-нибудь еще, что «отправляет» данные обратно на сервер, я думаю) — я получаю следующее исключение:

 /WEB-INF/tags/normalTextSection.xhtml @8,112 activeIndex="#{myController.getActiveIndexesForSections(name)}": Illegal Syntax for Set Operation
// bla..
Caused by: javax.el.PropertyNotWritableException: Illegal Syntax for Set Operation
  

После некоторого поиска в Google / чтения сообщения об ошибке я обнаружил, что мне нужен setter .

Прежде всего: мне не нужен установщик — мне действительно он нужен или есть способ сообщить JSF, что я не хочу такого «поведения».

Во-вторых, я понял, что не так «просто» предоставить сеттер, потому что мой метод имеет параметр (so public void setActiveIndexesForSections(String name, String activeIndexes) или public void setActiveIndexesForSections(String name) не будет работать). В итоге я пришел к следующему:

Создайте (общий) «Псевдо-класс свойств»:

 // just a dummy class since the class is recreated at every request
public class Property<T> implements Serializable {

    private T val;

    public Property(T val) {
        this.val= val;
    }

    public T getVal() {
        return val;
    }

            //no need to do anyhting
    public void setVal(T val) {
    }
}
  

Измените метод bean:

 public Property<String> getActiveIndexesForSections(String holderName){
    String activeSections = "";
    for(Section s : sectionMap.get(holderName)){
        if (s.isActive())
        //add to the string
    }
    return new Property<String>(activeSections);
}
  

И вызовите его из index.xhtml :

 <p:accordionPanel multiple="true" activeIndex="#{myController.getActiveIndexesForSections('whatever').val}">
// bla bla...
</p:accordionPanel>
  

Это работает, но, очевидно, является уродливым взломом / обходным путем.

Как правильно справиться с подобной ситуацией? Или то, что я делаю, просто совершенно неправильно?

Ответ №1:

Установщик необходим для запоминания активных индексов такими, какими они были при отправке формы. По сути, вам нужно привязать его как выражение значения (со свойством), а не как выражение метода (например, метод действия) или к неизменяемой коллекции (например activeIndex="#{param.tab}" ). Точно так же, как и с входными значениями. Технически, вы действительно делаете это «просто совершенно неправильно» 😉

Однако требование понятно. Учитывая, что вы действительно не заинтересованы в измененных активных индексах и, следовательно, хотите сбросить их до значений по умолчанию при каждой отправке формы, вы можете обойти это, сохранив результат в качестве атрибута запроса с помощью <c:set> . Таким образом, вы обманете EL, чтобы установить его в карте атрибутов запроса вместо свойства intented bean.

 <c:set var="activeIndex" value="#{myController.getActiveIndexesForSections('whatever')}" scope="request" />
<p:accordionPanel multiple="true" activeIndex="#{activeIndex}">
    <!-- bla bla... -->
</p:accordionPanel>
  

Под прикрытием это будет в основном выполняться externalContext.getRequestMap().put("activeIndex", value) как операция setter, которая, очевидно, будет просто работать.


Обновление: после проверки исходного кода AccordionPanel компонента я увидел другое обходное решение, учитывая тот факт, что значение activeIndex не будет установлено при rendered оценке атрибута false . Поэтому просто измените rendered атрибут, чтобы он вел себя именно так: оценивайте false во время фазы обновления значений модели (4-я фаза).

 <p:accordionPanel multiple="true" 
    activeIndex="#{myController.getActiveIndexesForSections('whatever')}"
    rendered="#{facesContext.currentPhaseId.ordinal ne 4}">
    <!-- bla bla... -->
</p:accordionPanel>
  

Комментарии:

1. Спасибо, что сжалились надо мной 😉 Не могли бы вы порекомендовать другой способ сделать это — поскольку это «просто» неправильно? (Во что я не могу поверить, так это в то, что этот вариант использования настолько редок, что никто (ожидайте меня) никогда не хотел этого делать — так что мне действительно интересно, не понял ли я общую картину …) — Также мои accordionPanel s генерируются в теге, где ‘whatever’ передается в качестве параметра -могу ли я даже использовать параметр как var из c:set , а затем как activeIndex из accordionPanel ? Если да, то как?

2. Правильным способом в этом конкретном случае будет возврат пользовательской Map реализации. И нет, вы не можете использовать EL в var атрибуте, поэтому файл тега будет неприятным. В любом случае, после проверки исходного кода <p:accordionPanel> , я увидел другое обходное решение, которое лучше использовать повторно в файлах тегов. Смотрите Обновление ответа.

3. Спасибо. Попробую это, а также «обычай» map завтра. И если это сработает, примите «щедрость» вас 🙂

4. Пожалуйста. Я не могу конкретизировать ответ на пользовательскую Map реализацию, поскольку вопрос не содержит всей необходимой информации об этой Section вещи. Но, по сути, вы должны иметь возможность использовать его в этой форме activeIndex="#{myController.activeIndexesForSections[whateverVariable]}" , где getActiveIndexForSections() возвращает a Map<String, String> и whateverVariable , таким образом, является той переменной, представляющей ключ. Соответственно Map#get() , и Map#put() будет вызываться при выполнении операций get и set . Вам нужно переопределить эти методы, чтобы вернуть и установить желаемое значение.

5. Ну, если вы хотите, я могу предоставить вам информацию — или даже мой код, и вы его просмотрите 😉 В любом случае: оба решения, которые вы предоставили, o действительно работают — так что еще раз спасибо за это. Я попробовал карту ( gist.github.com/anonymous/ea9a9afd46a11ecdbf95 ) — Я не совсем уверен, доволен ли я этим — я действительно не хочу переносить свою логику cookieController изнутри класса map, поскольку метод set-cookie (который выполняется в tabChange-Event ) по-прежнему выполняется внутри контроллера cookie. Я склонен использовать rendered метод, который вы упомянули в своем обновлении…