#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()
возвращает aMap<String, String>
иwhateverVariable
, таким образом, является той переменной, представляющей ключ. СоответственноMap#get()
, иMap#put()
будет вызываться при выполнении операций get и set . Вам нужно переопределить эти методы, чтобы вернуть и установить желаемое значение.5. Ну, если вы хотите, я могу предоставить вам информацию — или даже мой код, и вы его просмотрите 😉 В любом случае: оба решения, которые вы предоставили, o действительно работают — так что еще раз спасибо за это. Я попробовал карту ( gist.github.com/anonymous/ea9a9afd46a11ecdbf95 ) — Я не совсем уверен, доволен ли я этим — я действительно не хочу переносить свою логику
cookieController
изнутри класса map, поскольку метод set-cookie (который выполняется вtabChange-Event
) по-прежнему выполняется внутри контроллера cookie. Я склонен использоватьrendered
метод, который вы упомянули в своем обновлении…