2014-06-02 1 views
2

Я пишу приложение JSF, которое должно быть интернационализировано. Для этого я создал MultilingualString:Компонентный компонент JSF для объекта MultilingualString

public class MultilingualString { 
    /* The Language class is basically a wrapper for a java.util.Locale */ 
    private Map<Language, String> strings; 

    /* business methods, getters, setters */ 
} 

Теперь, Есть несколько форм, которые необходимо заполнить в MultilingualString, и это довольно некрасиво повторить переменный ток: Foreach цикла каждый раз, когда мне нужно поставить такой объект в форма. Поэтому я слышал о составных компонентах JSF Composite, и я попытался написать их для этой цели.

Вот мой inputMultilingualString.xhtml:

<ui:component xmlns:h="http://xmlns.jcp.org/jsf/html" 
       xmlns:composite="http://xmlns.jcp.org/jsf/composite" 
       xmlns:f="http://xmlns.jcp.org/jsf/core" 
       xmlns:ui="http://xmlns.jcp.org/jsf/facelets"> 
    <composite:interface componentType="inputMultilingualString"> 
     <composite:attribute name="value" required="true" 
          type="com.tob.entities.internationalization.MultilingualString"/> 
     <composite:attribute name="languages" type="java.util.List" default="#{null}"/> 
    </composite:interface> 

    <composite:implementation> 
     <f:event type="preRenderComponent" listener="#{cc.init}"/> 
     <h:dataTable id="#{cc.clientId}" value="#{cc.languages}" var="language"> 
      <h:column> 
       <h:outputLabel value="#{language}"/> 
      </h:column> 
      <h:column> 
       <h:inputText binding="#{cc.inputs[language]}"/> 
      </h:column> 
     </h:dataTable> 
    </composite:implementation> 
</ui:component> 

Так что я хочу, чтобы значения атрибута, чтобы быть экземпляром MultilingualString и языки приписывают быть экземпляром списка языка. Если атрибут languages ​​имеет значение null, я хочу, чтобы композитный компонент отображал строку в dataTable для каждой записи на карте, содержащейся в MultilingualString.

Теперь вот моя «поддержка компонент» в InputMultilingualString.java:

@FacesComponent(value = "inputMultilingualString", createTag = true) 
public class InputMultilingualString extends UIInput implements NamingContainer { 

    private final Map<Language, UIInput> inputs = new HashMap(); 
    private List<Language> languages; 

    @Override 
    public String getFamily() { 
     return (UINamingContainer.COMPONENT_FAMILY); 
    } 

    public void init() { 
     List<Language> ls = (List<Language>) this.getAttributes().get("languages"); 
     MultilingualString ms = (MultilingualString) this.getValue(); 

     /* Setting languages */ 
     if (ls != null) { 
      this.setLanguages(ls); 
     } else { 
      this.languages = new ArrayList(); 
      this.languages.addAll(ms.getStrings().keySet()); 
     } 

     /* Initializing inputs */ 
     UIInput tmp; 
     for (Language l : this.languages) { 
      tmp = new UIInput(); 
      tmp.setValue(ms.getString(l));// 
      this.inputs.put(l, tmp); 
     } 
    } 

    @Override 
    public String getSubmittedValue() { 
     String ret = new String(); 

     for (Map.Entry<Language, UIInput> entry : this.inputs.entrySet()) { 
      if (entry.getValue() != null) { 
       if (!ret.isEmpty()) { 
        ret += ','; 
       } 
       ret += entry.getKey().getLanguageTag(); // NullPointerException here when the form is submitted 
       ret += "=" + entry.getValue().getSubmittedValue(); 
      } 
     } 
     return (ret); 
    } 

    @Override 
    protected Object getConvertedValue(FacesContext context, Object submittedValue) { 
     MultilingualString ms = (MultilingualString) this.getValue(); 
     String[] entries = ((String) submittedValue).split(","); 
     String[] pair; 
     Language language; 

     for (String entry : entries) { 
      pair = entry.split("="); 
      language = new Language(); 
      language.setLanguageTag(pair[0]); 
      ms.addString(language, pair[1]); 
     } 
     return (ms); 
    } 

    public List<Language> getLanguages() { 
     return (this.languages); 
    } 

    public void setLanguages(List<Language> languages) { 
     this.languages = languages; 
    } 

    public Map<Language, UIInput> getInputs() { 
     return (this.inputs); 
    } 
} 

В целях реализации правила на каких языках я хочу, чтобы отобразить вход, я добавил языки атрибутов к компоненту основы и инициализировал его в методе init, который вызывается в событии preRenderComponent. Список языков правильно инициализирован.

Вот как я использую мой составной компонент:

<ui:composition template="/Templates/Common.xhtml" 
       xmlns="http://www.w3.org/1999/xhtml" 
       xmlns:h="http://xmlns.jcp.org/jsf/html" 
       xmlns:ui="http://xmlns.jcp.org/jsf/facelets" 
       xmlns:tob="http://xmlns.jcp.org/jsf/composite/components" 
       xmlns:p="http://primefaces.org/ui"> 
    <ui:define name="content"> 
     <h:form id="testForm"> 
      <tob:inputMultilingualString value="#{testBean.ms}" languages="#{testBean.languages}"/> 
      <!-- The testBean.ms contains : 
        [English] => string-English 
        [français] => string-français 
        [русский] => string-русский 

       And the testBean.languages contains a list of Language objects for English, French, and Russian --> 
      <p:commandButton value="Submit" action="#{testBean.submit()}"/> 
     </h:form> 
    </ui:define> 
</ui:composition> 

Проблемы:

  • Если MultilingualString введен в качестве значения уже содержит некоторые строки, они не отображаются в inputText как это если вы заполните атрибут value inputText (я прочитал article of BalusC on the topic, и ему не нужно заполнять атрибут value, чтобы его выпадающие списки имели правильное значение). Я прочитал ответ BalusC где-то в stackoverflow, что UIInput, на который ссылается атрибут привязки inputText, создается, если его оценивают как null, поэтому я попытался инициализировать их в методе init, но пока не повезло.
  • Когда я отправляю форму, я получаю исключение NullPointerException при вызове getKey() в методе getSubmittedValue(). Как это возможно?

Надеюсь, это было ясно и что кто-то может мне помочь! Спасибо!

Edit: Я использую GlassFish 4 и я вручную обновил Mojarra на 2.2.6

+0

Вся страница не должна отображаться вообще, но уже не выполняется во время загрузки страницы с исключением EL на 'binding'. Вы не можете использовать 'binding' в переменной времени рендеринга, которая является' null' во время времени сборки. Вы действительно использовали '' в своем коде? Разве это не ''? Я могу указать на фактическую ошибку в отношении описанного сбоя во время обратной передачи, но данный код уже не работает во время отображения, поэтому я сейчас запутался. – BalusC

+0

@BalusC Я скопировал код, который я написал в вопросе, и страница отображается. Поэтому я понимаю, что здесь невозможно использовать ''. Но не будет ли проблема такой же: ''? Я все равно не смогу инициализировать мой «Map ». – Unda

+0

Что такое JSF impl или версия? Он терпит неудачу в Мохарре 2.2.6. Но в любом случае это требование понимается. Это все возможно без необходимости в компоненте поддержки, я буду испечь ответ. – BalusC

ответ

2

Есть 2 технических проблем в коде размещены до сих пор:

  1. Вы с помощью binding на переменную, которая доступна только во время визуализации представления. Атрибут binding выполняется во время времени просмотра, а не во время визуализации визуализации. В этом конкретном случае, когда выполняется binding, #{language} является null. См. Также How does the 'binding' attribute work in JSF? When and how should it be used?. Также вы ожидаете, что будут созданы несколько компонентов <h:inputText>, но это неверно. Существует только один, который многократно используется во время рендеринга представления.Только если вы использовали <c:forEach> вместо <h:dataTable>, то действительно физически будет создано несколько компонентов <h:inputText>. См. Также JSTL in JSF2 Facelets... makes sense?

  2. Вы не сохраняете состояние компонента для обратной передачи. Вы должны удалить свойство languages и предоставить делегату-получателю и сеттерам getStateHelper(). См. Также How to save state when extending UIComponentBase.

Однако общий подход неуклюж. Для функционального требования вам не нужен компонент поддержки. Просто добавьте List<Languages> геттер в MultilingualString и используйте его как default из languages.

Так что, если вы добавите это MultilingualString:

public List<Language> getLanguages() { 
    return new ArrayList<>(strings.keySet()); 
} 

А затем просто ссылаться на attribtues через #{cc.attrs}:

<cc:interface> 
    <cc:attribute name="value" required="true" type="com.tob.entities.internationalization.MultilingualString"/> 
    <cc:attribute name="languages" type="java.util.List" default="#{cc.attrs.value.languages}" /> 
</cc:interface> 

<cc:implementation> 
    <h:dataTable value="#{cc.attrs.languages}" var="language"> 
     <h:column> 
      <h:outputLabel value="#{language}"/> 
     </h:column> 
     <h:column> 
      <h:inputText value="#{cc.attrs.value.strings[language]}" /> 
     </h:column> 
    </h:dataTable> 
</cc:implementation> 

Затем он должен работать как intented. Обратите внимание на возможность использовать текстовую привязку [] для ссылки на динамический ключ карты. Это, возможно, весь ключ к вашему решению (вы, кажется, не знаете об этом и, следовательно, работаете над сложным решением).

+0

Действительно, увидев ваш ответ, начальный подход явно был сложным. Спасибо за ответ и 'MultilingualString.getLanguages ​​()' трюк! – Unda

+0

Добро пожаловать. – BalusC

 Смежные вопросы

  • Нет связанных вопросов^_^