Tvorba komponent v JSF

Cílem tohoto článku je ukázat, jakým způsobem je možné vytvořit vlastní grafickou komponentu při práci s webovým frameworkem JSF a Facelets.

Používáme-li „čisté“ JSF, je vytvoření vlastní komponenty poměrně složitou záležitostí. Framework Facelets nám umožňuje vytvořit snadno a rychle komponenty vlastní. Pravda, takto vytvářené komponenty nemohou být vždy tak komplexní, jako komponenty ostatní (tj. ty, které jsou naprogramovány pomocí Java tříd), na vyřešení spousty požadavků však bohatě stačí.

V tomto příspěvku si ukážeme, jak vyřešit následující úkol:

Vytvořte znovupoužitelnou komponentu, která po stisknutí tlačítka zobrazí pop up okno se seznamem uživatelů, přičem po vybrání uživatele se pop up okno zavře, ID vybraného uživatele se uloží do JSF managed beany a ve formuláři v hlavním okně se do input políčka načte příjmení vybraného uživatele.

Možná vás napadne, proč pro tento úkol vytvářet nějakou komponentu. Pokud bychom tuto funkčnost použili v aplikaci jen jednou, nemělo by to samozřejmě žádný smysl. V aplikaci, kterou nyní vyvíjíme, je však výše zmíněná funkčnost potřeba hned na několika místech. Díky vlastní komponentě nebudeme v aplikaci psát několikrát stejný kód a navíc je možné, že vytvořenou komponentu použijeme i jinde.

Jak tedy začít? Nejprve je nutné vytvořit soubor, ve kterém budeme definovat naše nové komponenty – tzn. jejich jméno a cestu k nim. Tento soubor umístěme např. sem:

  • aplikace/WebContent/WEB-INF/facelets/tags/components.taglib.xml

    Zde je ukázka tohoto souboru:

    <?xml version="1.0"?>
    <!DOCTYPE facelet-taglib PUBLIC
      "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
      "facelet-taglib_1_0.dtd">
    <facelet-taglib>
          <namespace>http://www.bcvsolutions.eu/jsf</namespace>
          
          <tag>
          	<!-- 
          	Atributy:
          	
          	labelName: Nadpis modálního panelu. (muze byt null)
          	panelId: Id panelu. (muze byt null)
          	reRender: Id políčka (nebo políček), které se po výběru identity obnovi. (muze byt null)
          	selectedIdentity: Atribut, do ktereho se bude setovat vybraný uživatel. (musi se nastavit)
          	
          	 -->
    		<tag-name>findUserPanel</tag-name>
    		<source>find-user-panel-component.xhtml</source>
    	  </tag>
          
    </facelet-taglib>
    

    Náše nová komponenta se tedy bude jmenovat findUserPanel a bude definována v souboru find-user-panel-component.xhtml, který bude umístěn ve stejném adresáři jako components.taglib.xml.

    Nyní je nutné v konfiguračním souboru web.xml naši definovanou knihovnu facelets tagů (komponent):

    <context-param>
            <param-name>facelets.LIBRARIES</param-name>
            <param-value>/WEB-INF/facelets/tags/components.taglib.xml</param-value>
     </context-param>
    

    Definice naší komponenty (soubor find-user-panel-component.xhtml) bude vypadat takto:

    <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <ui:composition xmlns="http://www.w3.org/1999/xhtml"
    	xmlns:s="http://jboss.com/products/seam/taglib"
    	xmlns:ui="http://java.sun.com/jsf/facelets"
    	xmlns:f="http://java.sun.com/jsf/core"
    	xmlns:h="http://java.sun.com/jsf/html"
    	xmlns:rich="http://richfaces.org/rich"
    	xmlns:a4j="http://richfaces.org/a4j"
    	xmlns:c="http://java.sun.com/jstl/core">
    
    	<c:if test="${empty labelName}">
        	     <c:set var="labelName" value="#{messages.admin_users_find_panel_label}" />
    	</c:if>
    	
    	<c:if test="${empty panelId}">
        	     <c:set var="panelId" value="identityPanel" />
    	</c:if>
    	
    	<c:if test="${empty setupNullLabel}">
        	     <c:set var="setupNullLabel" value="#{messages.admin_workflow_btn_runAs_defalut}" />
    	</c:if>
    
    	<rich:modalPanel id="#{panelId}" width="650" height="400">
    		<f:facet name="header">
    			<h:panelGroup>
    				<h:outputText value="#{labelName}"></h:outputText>
    			</h:panelGroup>
    		</f:facet>
    		<f:facet name="controls">
    			<h:panelGroup>
    				<h:graphicImage url="/img/close.png" alt="close"
    					styleClass="hidelink" id="hidelink#{panelId}" />
    				<rich:componentControl for="#{panelId}"
    					attachTo="hidelink#{panelId}" operation="hide" event="onclick" />
    			</h:panelGroup>
    		</f:facet>
    
    		<a4j:commandButton value="#{setupNullLabel}" 
    			reRender="#{reRender}"
    			oncomplete="#{rich:component(panelId)}.hide()"
    			immediate="true">
    			<f:setPropertyActionListener value=""
    				target="#{selectedIdentity}" />
    		</a4j:commandButton>
    		
    		<br/>
    
    		<rich:dataTable value="#{findUserComponentBean.users}" var="user" rows="20"
    			reRender="ds" id="simpletable"
    			onRowMouseOver="this.style.backgroundColor='#F1F1F1'"
    			onRowMouseOut="this.style.backgroundColor='#{a4jSkin.tableBackgroundColor}'">
    			<f:facet name="header">
    				<h:panelGroup>
    					<h:outputText value="#{messages.admin_users_find_table_title}"></h:outputText>
    				</h:panelGroup>
    			</f:facet>
    			<rich:column sortBy="#{user}">
    				<f:facet name="header">
    					<h:outputText value="#{messages.admin_user_id}" />
    				</f:facet>
    				<h:outputText value="#{user}" />
    			</rich:column>
    
    			<rich:column>
    				<f:facet name="header">
    					<h:outputText value="#{messages.cmn_action}" />
    				</f:facet>
    
    				<a4j:region>
    					<a4j:commandButton value="#{messages.cmn_btn_select}" reRender="#{reRender}"
    						oncomplete="#{rich:component(panelId)}.hide()"
    						immediate="true">
    						<f:setPropertyActionListener value="#{user}"
    							target="#{selectedIdentity}" />
    					</a4j:commandButton>
    				</a4j:region>
    
    			</rich:column>
    			<f:facet name="footer">
    				<rich:datascroller id="ds" renderIfSinglePage="false" maxPages="1"></rich:datascroller>
    			</f:facet>
    		</rich:dataTable>
    
    	</rich:modalPanel>
    
    </ui:composition>
    

    A nyní si kód popišme:

  • Nejprve testujeme, jestli byla na vstupu zadána proměnná labelName – pokud ne, vyplníme ji defaultní hodnotou. Text obsažený v proměnné bude zobrazen jako nadpis okna.
  • Obdobným způsobem testujeme proměnnou panelId. Každá komponenta na JSF stránce musí mít jedinečné ID, mohlo by se stát, že bychom naši komponentu chtěli použít vícekrát na stejné stránce a podmínka na stejné ID by byla porušena – proto umožňujeme ID nastavit.
  • Opět stejný způsob – test, definice proměnné setupNullLabel – hodnota proměnné bude textem tlačítka, které zajistí vynulování hodnoty v managed beaně a v input políčku.
  • Definujeme komponentu rich:modal panel, která zajistí zobrazení pop up okna.
  • Tlačítko a4j:commandButton nastaví prázdný řetězec do proměnné selectedIdentity, zavře pop up okno a provede refresh input boxu (respektive komponenty s ID = proměnné reRender)
  • Dále definujeme tabulku, která zobrazí seznam uživatelů. U každého uživatele bude tlačítko, po jehož stisku se nastaví informace o vybraném uživateli na managed beanu, provede se refresh input políčka a zavře se „vyskakovací“ okno.

Následující kód je JSF managed beana, kterou komponenta používá:

public class FindUserComponentBean {
	
	/** Seznam uživatelských jmen nalezených identit. */
	private List users;
	/** Vybraný uživatel z tabulky */
	private String selectedUser;
	public FindUserComponentBean(){
	}	
	public String search(){
		users = Data.find(View.Type.USER, new Criteria());
		return null;
	}
	public String selectUser(){
		return null;
	}
	public List getUsers() {
		if(users == null){
		      search();
		}
		return users;
	}

	public void setUsers(List users) {
		this.users = users;
	}

	public String getSelectedUser() {
		return selectedUser;
	}

	public void setSelectedUser(String selectedUser) {
		this.selectedUser = selectedUser;
	}
}

Nyní se konečně dostáváme v ukázce použití této komponenty:

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
	xmlns:s="http://jboss.com/products/seam/taglib"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:h="http://java.sun.com/jsf/html" 
	xmlns:a4j="http://richfaces.org/a4j" 
	xmlns:bcv="http://www.bcvsolutions.eu/jsf" 
	xmlns:rich="http://richfaces.org/rich" 
	xmlns:c="http://java.sun.com/jstl/core" 
	template="usertemplate.xhtml">

<h:form>
    <table>
	<tr>
	   <th><h:outputText value="#{messages.name}"/></th>
	   <td><h:inputText value=“#{myBean.name}“</td>
	</tr>
	<tr>
	   <th><h:outputText value="#{messages.boss}"/></th>
	   <td>
               <h:inputText value="#{myBean.boss}" id="boss" disabled="true"/>
                   <h:outputLink value="#" id="linkBoss">
                      <h:outputText value="#{messages.cmn_btn_select}"/>
                      <rich:componentControl for="selectIdentity" attachTo="linkBoss" operation="show" event="onclick"/>
                  </h:outputLink>
          </td>
        </tr>
     </table>

	<bcv:findUserPanel panelId="selectBoss" reRender="boss" 
		selectedIdentity="#{myBean.boss}"
		setupNullLabel="#{messages.no_boss}"/>
	. . . 
	</h:form>
</ui:composition>

Všiměme si definice namespacu bcv:, pomocí něho se odkazujeme na naše komponenty. Celou komponentu by bylo možné (vhodné) upravit např. tak, aby i vytvořila input políčko a definici modal panelu vložila za něj. Dále by bylo možné doplnit do pop up okna tlačítko hledat a input boxy pro zadávání vyhledávacích kritérií.

A jaké jsou vaše zkušenosti s tvorbou vlastních komponent ve webových aplikacích?