Archiv pro štítek: JSF

JSF – použití AJAXu s frameworkem Rich Faces


Cílem tohoto článku je ukázat, jak snadno můžeme používat AJAX s webovými frameworky Java Server Faces a Rich Faces.

AJAX (Asynchronous Javascript and XML) je technologie, pomocí které měníme obsah webové stránky bez nutnosti jejího opětovného načtení. Tento způsob obnovy dat na stránce je rychlejší, šetrnější k serverovému výkonu a uživatelsky příjemnější než způsob, při kterém znovu načítáme celou stránku.

V aplikaci kterou nyní vyvíjíme jsme se rozhodli technologii AJAX používat. Prezentační vrstvu programujeme ve frameworku JSF 1.2 (neřešme proč nepoužíváme JSF 2.0, kde je podpora AJAXu lepší). Společně se standardními komponentami, které JSF mají, používáme komponenty Rich Faces.

Rich Faces je knihovna, která obsahuje velké množstní hotových grafických komponent a dále pak komponenty, které „za nás“ implementují Ajaxové volání mezi klientem a serverem. Tato funkcionalita výrazně urychluje vývoj a je jednoduchá na naučení. Jednoduchost použití budu demostrovat na příkladu, který bude řešit následující úkol:

Vytvořte webovou stránku, na které zobrazte 2 tabulky se jmény sportů. V tabulce A bude seznam všech sportů, které bude možné přidat do tabulky B. Přidání se provede kliknutím na odkaz „Pridat“, tzn. že po jeho stisknutí se jméno přidá do tabulky B a odebere z tabulky A. V tabulce B bude tedy seznam vybraných sportů a bude možné sport odebrat. Odebrání se provede kliknutím na odkaz „Odebrat“, tzn. že po jeho stisknutí se název přidá do tabulky A a odebre z tabulky B. Navíc se po každé akci zobrazí v info panelu pod tabulkami informace o naposledy provedené akci.

Nejprve si vytvoříme tzv. JSF Managed Beanu (pro jednoduchost se data načítají přímo v konstruktoru a nepoužívají se konstanty za texty):

package eu.bcvsolutions.ui.bean;

import java.util.ArrayList;
import java.util.List;

public class TestBean {

	private List selectedSports;
	private List allSports;
	private String sport;
	private String infoMessage;
	
	public TestBean() {
		selectedSports = new ArrayList();
		allSports = new ArrayList();
		
		allSports.add("Fotbal");
		allSports.add("Basketbal");
		allSports.add("Hokej");
		allSports.add("Formule");
		allSports.add("Tenis");
	}
	
	public String addToSelectedSports() {
		selectedSports.add(sport);
		allSports.remove(sport);
		setupAddActionToInfoMessage();
		return null;
	}
	
	public String removeFromSelectedSports() {
		selectedSports.remove(sport);
		allSports.add(sport);
		setupRemoveActionToInfoMessage();
		return null;
	}

	public List getSelectedSports() {
		return selectedSports;
	}

	private void setupAddActionToInfoMessage() {
		String message = "Uspesne jste pridali uzivatele %s";
		infoMessage = String.format(message, sport);
	}
	
	private void setupRemoveActionToInfoMessage() {
		String message = "Uspesne jste odebrali uzivatele %s";
		infoMessage = String.format(message, sport);
	}
	
	public void setSelectedSports(List selectedSports) {
		this.selectedSports= selectedSports;
	}

	public List getSllSports() {
		return allSports;
	}

	public void setSllSports(List allSports) {
		this.allSports= allSports;
	}

	public String getSport() {
		return sport;
	}

	public void setSport(String sport) {
		this.sport= sport;
	}

	public String getInfoMessage() {
		return infoMessage;
	}

	public void setInfoMessage(String infoMessage) {
		this.infoMessage = infoMessage;
	}
}

V této ukázce je nastaven scope na hodnotu „page“. Nastavení scope provádím v souboru components.xml (používám navíc interační framework Seam), pokud používáte „čisté“ JSF, nastavíte scope ve faces-config.xml.

Kód beany je vcelku jasný – beana udržuje informace o všech názvech, o vybraných názvech, o vybraném sportu, je zde umístěn text informačního panelu a obsluhují se události po stisknutí tlačítek.

Dále vytvoříme následující xhtml stránku:

<!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" >

<html>
<head>
<title>BCV solutions s.r.o. - Ukazka pouziti technologii JSF, AJAX, Rich Faces</title>
</head>

<body>
	<a4j:form>		
	<table>
	<tbody>
	<tr>
	<td valign="top">
	  Vybrane sporty
	  <br/>
	  <rich:dataTable value="#{testBean.selectedSports}" var="sport" id="selectedTable">
									
	    <rich:column>
	      <f:facet name="header">
	         Nazev
	      </f:facet>
	      <h:outputText value="#{sport}" />
	   </rich:column>
	   <rich:column>
	      <f:facet name="header">
	        Akce
	      </f:facet>
	      <a4j:commandLink value="Odebrat" action="#{testBean.removeFromSelectedSports}"
                    reRender="selectedTable, allTable, infoMessage">
                    <f:setPropertyActionListener value="#{sport}" target="#{testBean.sport}"/>
	      </a4j:commandLink>
	    </rich:column>	
	  </rich:dataTable>
	</td>
	<td valign="top">
	  Seznam sportu
	  <br/>
	  <rich:dataTable value="#{testBean.allSports}" var="sport" id="allTable">
	    <rich:column>
	      <f:facet name="header">
	        Nazev
	      </f:facet>
	      <h:outputText value="#{sport}" />
	    </rich:column>
	    <rich:column>
	      <f:facet name="header">
	        Akce
	      </f:facet>
	      <a4j:commandLink value="Pridat" action="#{testBean.addToSelectedSports}"
                            reRender="selectedTable, allTable, infoMessage">
                  <f:setPropertyActionListener value="#{sport}" target="#{testBean.sport}"/>
	      </a4j:commandLink>
	    </rich:column>	
	  </rich:dataTable>
	</td>
	</tr>
	</tbody>
	</table>
	<h:outputText id="infoMessage" 
		value="#{testBean.infoMessage}"
		style="background-color: #ffff99"/>
	</a4j:form>
</body>
</html>

</ui:composition>

Na této stránce není vůbec nic složitého. Jediné, co stačilo udělat proto, aby se nemusela znovu přenášet všechna data a posílal se jen obsah tabulek a obsah info panelu (nyní na stránce žádný další obsah není, ale v reálné aplikaci by zajisté byl) bylo použití komponenty a4j:commandLink. Rozdílem oproti standardnímu h:commandLink je v parametru reRender. Tomu jsme předali ID komponent, jejichž obsah má být obnoven. Dále jsme místo standardní formulářové komponenty h:form použili komponentu frameworku Rich Faces a4j:form (v tomto případě by to však ani nebylo nutné).

Nyní si stručně popišme použité komponenty:

  • a4j:form

    • obdoba h:form, která řeší 2 základní nedostatky:

      • pokud je nastaven parametr ajaxSubmit na true, umožňuje komvertovat všechny „neajaxové“ volání komponent na ajaxové
      • umožňuje obnovení komponenty h:commandLink bez nutnosti obnovení celého formuláře
  • a4j:commandLink

    • obdoba h:commandLink, rozdíl je v tom, že tato komponenta generuje Ajax request a umožňuje updatovat jenom část stránky

Výsledek naší ukázky vypadá takto:

Dá se říct, že naš kód je obdobou komponenty rich:pickList. My jsme však tuto komponentu nevyužili, protože jsme potřebovali komplexnější funkcionalitu. Podobným způsobem, kterým jsme vytvářeli tyto dvě tabulky je např. možné po stisknutí zobrazit editační formulář, modální panel do kterého pomocí ajaxu dotáhneme kompletní data o sportu apod.


Vytváříte ve vaši firmě webové aplikace na platformě Java? Potřebujete navrhnout architekturu jádra aplikace či webového rozhraní? Neváhejte se obrátit na tým našich vývojářů a konzultantů.

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?