Professional Documents
Culture Documents
Creación de la lista de
contactos con sus detalles
En este capítulo, vamos a desarrollar la parte de administración de contactos de nuestra
aplicación, que incluyen listar, agregar, editar y eliminar contactos, todos al estilo Ajax.
Por otra parte, vamos a aprender nuevos conceptos sobre el marco de componentes
RichFaces y Ajax.
El diseño principal
Vamos a iniciar la preparación del espacio para las principales características de la aplicación.
Como hemos visto en el capítulo 4, la aplicación, queremos un diseño en tres columnas para
los grupos, la lista de contactos y los detalles del contacto. Vamos a abrir el archivo
home.xhtml y agregamos un panel de cuadrículas de tres columnas en el interior del cuerpo:
<h:panelGrid columns="3"
width="100%"
columnClasses="main-group-column, main-contacts-listcolumn,
main-contact-detail-column">
</h:panelGrid>
Estamos utilizando tres nuevas clases CSS (uno para cada columna). Vamos a abrir el archivo
/view/stylesheet/theme.css y agregamos el siguiente código:
.main-group-column {
width: 20%;
vertical-align: top;
}
.main-contacts-list-column {
width: 40%;
vertical-align: top;
}
.main-contact-detail-column {
width: 40%;
vertical-align: top;
}
Las columnas principales están listas, y ahora queremos dividir el contenido de cada columna
en un archivo aparte (para no tener un archivo grande y difícil de leer) usando las capacidades
de plantillas de Facelets, vamos a crear una nueva carpeta dentro de la carpeta /view llamada
main y vamos a crear los siguientes archivos vacíos en su interior:
• contactsGroups.xhtml
• contactsList.xhtml
• contactEdit.xhtml
• contactView.xhtml
Ahora vamos a abrirlos y escribir el código estándar para un archivo vacío (included):
Ahora, tenemos todas las piezas listas para ser incluido en el archivo home.xhtml, vamos a
abrirlo y empezar a añadir la primera columna dentro de h:panelGrid:
<a:outputPanel id="contactsGroups">
<ui:include src="main/contactsGroups.xhtml"/>
</a:outputPanel>
Como puede ver, hemos encerrado include con un a:outputPanel que se utiliza como
marcador de posición para la nueva presentación.
Incluir una etiqueta Facelets (ui:include) dentro de a:outputPanel que hemos utilizado
con el fin de incluir la página en ese punto.
RichFaces tiene un componente que puede ser utilizado como un marcador de posición
a4j:outputPanel.
<h:form>
<h:inputText value="#{aBean.myText}">
<a4j:support event="onkeyup" reRender="out1" />
</h:inputText>
</h:form>
<h:outputText
id="out1"
value="#{aBean.myText}"
rendered="#{not empty aBean.myText}"/>
Este código parece ser el mismo que el ejemplo de a4j:support, pero no funciona.
<h:form>
<h:inputText value="#{aBean.myText}">
<a4j:support event="onkeyup" reRender="out2" />
</h:inputText>
</h:form>
<a4j:outputPanel id="out2">
<h:outputText
id="out1"
rendered="#{not empty aBean.myText}"
value="#{aBean.myText}" />
</a4j:outputPanel>
Como puede ver, sólo tienes que poner el componente out1 dentro A4j:outputPanel
(llamado out2) y decirle a4j:support que visualice out2 en lugar de out1.
Inicialmente, out2 será visualizado, pero estará vacío (porque out1 no se muestra). Después
de la respuesta de Ajax, el out2 vacío serán sustituidos por elementos de marcado que
también contienen el componente de out1 (que ahora es visible, porque la propiedad myText
no está vacía después de la actualización del Ajax y la propiedad rendered es verdadero).
Un concepto muy importante a tener en cuenta durante el desarrollo es que el marco Ajax no
puede agregar o eliminar, sólo puede reemplazar los elementos existentes de la página. Por
esta razón, si desea añadir algo de código, usted necesita utilizar un marcador de posición.
No vamos a implementar las funcionalidades del cuadro de grupo en este capítulo. Por lo tanto,
por ahora la columna de grupo es sólo un rich:panel con un enlace para actualizar la lista
de contactos.
<h:form>
<rich:panel>
<f:facet name="header">
<h:outputText value="#{messages['groups']}" />
</f:facet>
<h:panelGrid columns="1">
<a:commandLink value="#{messages['allContacts']}"
ajaxSingle="true"
reRender="contactsList">
<f:setPropertyActionListener value="#{null}"
target="#{homeContactsListHelper.contactsList}" />
</a:commandLink>
</h:panelGrid>
</rich:panel>
</h:form>
Como puede ver, hemos puesto tres columnas h:panelGrid (que se utilizará en el futuro) y
a:commandLink, que sólo establece la propiedad de contactos del bean
homeContactListHelper (que veremos en la siguiente sección) a nula, a fin de que la lista
se lea nuevamente. Al final la interacción de Ajax, volverá a hacer la columna de contactos a fin
de mostrar los nuevos datos.
También, observe que todavía estamos dando soporte a todos los mensajes de texto
utilizando la propiedad messages, la tarea de llenar el archivo de messages_XX.properties
se deja como ejercicio para el usuario.
La lista de contactos
La segunda columna en el interior h:panelGrid de home.xhtml se ve algo como:
<a:outputPanel id="contactsList">
<ui:include src="main/contactsList.xhtml"/>
</a:outputPanel>
En cuanto a los grupos, se utilizó un marcador de posición que rodean la interfaz de usuario, la
etiqueta ui:include.
<h:form>
<rich:dataTable id="contactsTable"
reRender="contactsTableDS"
rows="20"
value="#{homeContactsListHelper.contactsList}" var="contact">
<rich:column width="45%">
<h:outputText value="#{contact.name}"/>
</rich:column>
<rich:column width="45%">
<h:outputText value="#{contact.surname}"/>
</rich:column>
<f:facet name="footer">
<rich:datascroller id="contactsTableDS"
for="contactsTable"
renderIfSinglePage="false"/>
</f:facet>
</rich:dataTable>
<h:outputText value="#{messages['noContactsInList']}"
rendered="#{homeContactsListHelper.contactsList.size()==0}"/>
</h:form>
Otras características son de fila y columna con soporte extendido (lo comentaremos en la
sección columnas y grupos de columnas), fuera de la caja de filtro y ordenación (que se
examinan en la sección de filtrado y ordenación), más los controladores de eventos de
JavaScript (como onRowClick, onRowContextMenu, onRowDblClick, y así
sucesivamente) y el atributo reRender.
Al igual que otros componentes de iteración de datos el marco RichFaces, también da soporte
a la actualización parcial de la fila (véase el Capítulo 10, Técnicas avanzadas para más
información).
Paginación de datos
La implementación de paginación de datos en Ajax usando RichFaces es muy simple, sólo hay
que decidir el número de filas que se mostrarán en cada página mediante el establecimiento de
atributo rows de DataTable (en nuestro caso, hemos elegido 20 filas por página), y despues
“adjuntamos” al componente rich:datascroller para llenar el atributo for con el ID de
DataTable:
<rich:datascroller id="contactsTableDS"
for="contactsTable"
renderIfSinglePage="false"/>
Aquí puedes ver otro atributo muy útil (renderIfSinglePage) que oculta el componente
cuando sólo hay una sola página en la lista (esto significa que la lista contiene un número de
elementos menor o igual al valor del atributo rows) .
Hay que tener en cuenta que el componente rich:datascroller debe permanecer dentro
del componente de formulario (h:form o a:form) para que funcione.
• pages
• controlsSeparator
• first, first_disabled
• last, last_disabled
• next, next_disabled
• previous, previous_disabled
• fastforward, fastforward_disabled
• fastrewind, fastrewinf_disabled
Usted puede usar una imagen (u otro componente) en lugar de texto, con el fin de crear su
scroller a su medida.
El resultado es:
El atributo page podría estar vinculado a una propiedad de un bean, con el fin de cambiar a
una página colocando el número - un caso de uso simple podría ser utilizando un inputText y
un CommandButton, a fin de que el cliente introduzca el número de página que el/ella quieran.
<rich:datascroller
for="contactsList" maxPages="20" fastControls="hide"
page="#{customDataScrollerExampleHelper.scrollerPage}"
pagesVar="pages"
id="ds">
<f:facet name="first">
<h:outputText value="First" />
</f:facet>
<f:facet name="first_disabled">
<h:outputText value="First" />
</f:facet>
<f:facet name="last">
<h:outputText value="Last" />
</f:facet>
<f:facet name="last_disabled">
<h:outputText value="Last" />
</f:facet>
<f:facet name="previous">
<h:outputText value="Previous" />
</f:facet>
<f:facet name="previous_disabled">
<h:outputText value="Previous" />
</f:facet>
<f:facet name="next">
<h:outputText value="Next" />
</f:facet>
<f:facet name="next_disabled">
<h:outputText value="Next" />
</f:facet>
<f:facet name="pages">
<h:panelGroup>
<h:outputText value="Page "/>
<h:inputText
value="#{customDataScrollerExampleHelper.
scrollerPage}"
size="4">
<f:validateLongRange minimum="0" />
<a:support event="onkeyup" timeout="500"
oncomplete="#{rich:component('ds')}.
switchToPage(this.value)" />
</h:inputText>
<h:outputText value=" of #{pages}"/>
</h:panelGroup>
</f:facet>
</rich:datascroller>
Como puede ver, además de personalizar el texto de las secciones First, Last, Previous, y
Next. A continuación, se define un facet pages mediante la inserción de h:inputText
conectado con un valor entero dentro de un bean de respaldo. También hemos añadido la
etiqueta a:support, con objeto de reducir el cambio de página después del evento keyup se
ha completado. También hemos establecido el atributo de timeout, para llamar al servidor
cada 500 ms y no cada vez que el usuario escriba
<rich:column>
<f:facet name="header">
<h:outputText value="my header" />
</f:facet>
...
</rich:column>
Este método también funciona para el componente estándar h:dataTable, RichFaces mejora
las capacidades de la tabla de la partida, al permitir la agrupación, mediante el componente
rich:columnGroup.
Por lo tanto, volviendo a nuestra aplicación, podemos poner el siguiente código dentro de la
etiqueta rich:dataTable a fin de definir el encabezado de la dataTable:
<rich:dataTable id="contactsTable"
value="#{homeContactsListHelper.contactsList}" var="contact">
<rich:column>
<h:outputText value="#{contact.name}"/> </rich:column>
<rich:column>
<h:outputText value="#{contact.surname}"/> </rich:column>
<rich:column>
<a:commandButton image="/img/view.png" /> </rich:column>
</rich:dataTable>
<rich:dataTable id="contactsTable"
value="#{homeContactsListHelper.contactsList}"
var="contact">
<rich:column colspan="2">
<h:outputText value="#{contact.name}"/>
</rich:column>
<rich:column breakBefore="true">
<h:outputText value="#{contact.surname}"/>
</rich:column>
<rich:column>
<a:commandButton image="/img/view.png" />
</rich:column>
</rich:dataTable>
Les hemos dicho a la primera columna de "span" (usted puede conocer el significado porque es
un atributo estándar de una columna de tablas HTML), dos columnas y el segundo "break
before", en el sentido de cierre de la fila (colocar el código HTML etiqueta </ tr>).
Así, la primera columna llena el espacio de dos columnas y la segunda se visualiza en la otra
fila, sencillo, ¿no?
También puede utilizar el atributo rowspan con el fin de abarcar filas en lugar de columnas,
como estándar para las tablas HTML.
<rich:dataTable id="contactsTable"
value="#{homeContactsListHelper.contactsList}"
var="contact">
<rich:column colspan="2">
<h:outputText value="#{contact.name}"/>
</rich:column>
<rich:columnGroup>
<rich:column>
<h:outputText value="#{contact.surname}"/>
</rich:column>
<rich:column>
<a:commandButton image="/img/view.png"/>
</rich:column>
</rich:columnGroup>
</rich:dataTable>
rich:column contiene más atributos muy útiles que span, breakBefore, filtrado y
ordenación de los atributos (que vamos a ver en la siguiente sección), que no encontramos en
el componente estándar h:column.
Por ejemplo, en nuestra aplicación, usamos el atributo width con el fin de establecer el ancho
para cada columna, sin necesidad de utilizar una clase CSS sólo para eso.
Característica de filtrado y ordenación
Otra característica importante que hemos visto en un ejemplo sencillo en el capítulo 3 es la de
que fuera de la caja hay controles de filtrado y ordenación que el componente
rich:dataTable proporciona.
Con el fin de añadir esta función a nuestra tabla, vamos a editar la etiqueta rich:column para
el nombre y apellido, como se muestra en el siguiente código:
<rich:column width="45%"
sortBy="#{contact.name}"
filterBy="#{contact.name}">
<h:outputText value="#{contact.name}"/>
</rich:column>
<rich:column width="45%"
sortBy="#{contact.surname}"
filterBy="#{contact.surname}">
<h:outputText value="#{contact.surname}"/>
</rich:column>
Usted tendrá la característica de filtro y ordenación para su tabla sólo agregando estos dos
atributos.
En el capítulo 10, vamos a explicar de una manera más personalizada para la administración
de filtrado y ordenación.
<rich:toolBar>
<rich:toolBarGroup>
<!-- my action buttons here -->
</rich:toolBarGroup>
</rich:toolBar>
El bean de respaldo
Hemos visto la conexión de la tabla con un bean de apoyo llamado
homeContactsListHelper, vamos a crearla!
El componente bean es muy simple, ya que acaba de recuperar la lista de contactos de la base
de datos (los grupos no son administrados por ahora) y podría tener el siguiente aspecto:
@Name("homeContactsListHelper")
@Scope(ScopeType.CONVERSATION)
public class HomeContactsListHelper {
@In(create=true)
EntityManager entityManager;
@In(required = true)
Contact loggedUser;
private List<Contact> contactsList;
public List<Contact> getContactsList() {
if (contactsList ==null) {
// Creating the query
String query="from Contact c where c.contact.id=:fatherId";
// Getting the contacts list
contactsList = (List<Contact>)
entityManager.createQuery(query)
.setParameter("fatherId",
loggedUser.getId())
.getResultList();
}
return contactsList;
}
public void setContactsList(List<Contact> contactsList) {
this.contactsList = contactsList;
}
Para resumir, la anotación @Name define el nombre del componente Seam / JSF del bean de
respaldo, @Scope define el alcance del componente, se inyecta (con la anotación @In), el
componente entityManager (para consultar la base de datos con JPA) y la instancia de
contacto referenciando al usuario conectado durante la fase de inicio de sesión.
Además, el bean tiene una propiedad llamada contactsList que es lentamente inicializado
en el método getContactsList() consultando la base de datos.
Así que, ahora, cuando el usuario acceda a la página de inicio, una nueva conversación se
crea si no hay ninguno. Si no, la existente se mantendrá (join = "true").
Los detalles del contacto
Para la tercera columna, nos gustaría mostrar tres estados diferentes:
Una vista de la caja sólo cuando no estamos en el modo de edición (la propiedad
selectedContactEditing se establece en false)
Por lo tanto, vamos a abrir la página home.xhtml e insertaremos la tercera columna dentro de
la cuadricula del panel con los tres estados:
<a:outputPanel id="contactDetail">
<a:outputPanel rendered="#{homeSelectedContactHelper.
selectedContact==null}">
<rich:panel>
<h:outputText
value="#{messages['noContactSelected']}"/>
</rich:panel>
</a:outputPanel>
<a:outputPanel
rendered="#{homeSelectedContactHelper.
selectedContact!=null and
homeSelectedContactHelper.
selectedContactEditing==false}">
<ui:include src="main/contactView.xhtml"/>
</a:outputPanel>
<a:outputPanel
rendered="#{homeSelectedContactHelper.
selectedContact!=null and
homeSelectedContactHelper.
selectedContactEditing==true}">
<ui:include src="main/contactEdit.xhtml"/>
</a:outputPanel>
</a:outputPanel>
Antes de empezar a escribir las secciones include, vamos a ver cómo el bean principal para el
contacto seleccionado se vé y conectarlo con la tabla de datos para seleccionar el contacto de
éste.
El bean de soporte
Vamos a crear una nueva clase llamada HomeSelectedContactHelper dentro del paquete
book.richfaces.advcm.modules.main, la clase puede ser algo como esto:
@Name("homeSelectedContactHelper")
@Scope(ScopeType.CONVERSATION)
public class HomeSelectedContactHelper {
@In(create = true)
EntityManager entityManager;
@In(required = true)
Contact loggedUser;
@In
FacesMessages facesMessages;
// My code here
}
Este es un componente estándar de JBoss Seam, como hemos visto en los otros capítulos y
ahora vamos a añadir nuestras propiedades.
El bean que vamos a utilizar para ver y editar las características es muy sencillo de entender ya
que sólo contiene dos propiedades (es decir, selectedContact y
selectedContactEditing) y algunos métodos de acción para su administración.
Como puede ver, simplemente hemos añadido dos propiedades con el estándar getter y setter.
electedContact());
}
No es difícil entender lo que hacen, sin embargo, con el fin de ser claros, vamos a describir lo
que cada método hace.
Después de que el usuario ha hecho clic en "Añadir contacto" se incluirán los datos de
contacto, él / ella ha de persistir esta nueva instancia de los datos en la base de datos. Se lleva
a cabo por el insertNewContact () método, llamado cuando se hace clic en el botón Insertar.
Hemos incrementado el valor del atributo colspan y se agregó una nuevo encabezado (vació)
de la columna.
<a:commandButton image="/img/addcontact.png"
reRender="contactDetail"
action="#{homeSelectedContactHelper.createNewEmptyContactInstance}">
<f:setPropertyActionListener value="#{true}"
target="#{homeSelectedContactHelper.selectedContactEditing}"/>
</a:commandButton>
<h:form>
<rich:panel>
<f:facet name="header">
<h:outputText
value="#{homeSelectedContactHelper.selectedContact.name}
#{homeSelectedContactHelper.selectedContact.surname}"/>
</f:facet>
<h:panelGrid columns="2" rowClasses="prop"
columnClasses="name,value">
<h:outputText value="#{messages['name']}:"/>
<h:outputText
value="#{homeSelectedContactHelper.selectedContact.name}"/>
<h:outputText value="#{messages['surname']}:"/>
<h:outputText
value="#{homeSelectedContactHelper.selectedContact.surname}"/>
<h:outputText value="#{messages['company']}:"/>
<h:outputText
value="#{homeSelectedContactHelper.selectedContact.company}"/>
<h:outputText value="#{messages['email']}:"/>
<h:outputText
value="#{homeSelectedContactHelper.selectedContact.email}"/>
</h:panelGrid>
</rich:panel>
<rich:toolBar>
<rich:toolBarGroup>
<a:commandLink ajaxSingle="true"
reRender="contactDetail"
styleClass="image-command-link">
<f:setPropertyActionListener value="#{true}"
target="#{homeSelectedContactHelper.selectedContactEditing}"/>
<h:graphicImage value="/img/edit.png" />
<h:outputText value="#{messages['edit']}" />
</a:commandLink>
</rich:toolBarGroup>
</rich:toolBar>
</h:form>
También hemos añadido una clase CSS dentro del archivo /view/stylesheet/theme.css
para manejar el diseño de los enlaces de comandos con las imágenes:
.image-command-link {
text-decoration: none;
}
.image-command-link img {
vertical-align: middle;
padding-right: 3px;
}
<h:form>
<rich:panel>
<f:facet name="header">
<h:panelGroup>
<h:outputText
value="#{homeSelectedContactHelper.selectedContact.name}
#{homeSelectedContactHelper.selectedContact.surname}"
rendered="#{homeSelectedContactHelper.selectedContactManaged}"/>
<h:outputText
value="#{messages['newContact']}"
rendered="#{!homeSelectedContactHelper.selectedContactManaged}"/>
</h:panelGroup>
</f:facet>
<!-- my code here -->
</rich:panel>
<!-- my code here -->
</h:form>
Este es un panel estándar rich:panel con un encabezado personalizado que tiene dos
componentes h:outputText que se mostrará en función del atributo (si se trata de un nuevo
contacto o no).
Más de un componente dentro de f:facet Recuerde que f:facet debe tener un solo hijo,
por lo que, para poner más de un componente, tienes que usar un h:panelGroup o algo
similar.
En el interior del panel, se van a poner h:panelGrid contiene los componentes para la
edición de datos:
<rich:graphValidator>
<h:panelGrid columns="3" rowClasses="prop"
columnClasses="name,value,validatormsg">
<h:outputLabel for="scName"
value="#{messages['name']}:"/>
<h:inputText id="scName"
value="#{homeSelectedContactHelper.selectedContact.name}"/>
<rich:message for="scName" styleClass="messagesingle"
errorClass="errormsg"
infoClass="infomsg"
warnClass="warnmsg"/>
<h:outputLabel for="scSurname"
value="#{messages['surname']}:"/>
<h:inputText id="scSurname"
value="#{homeSelectedContactHelper.selectedContact.surname}"/>
<rich:message for="scSurname"
styleClass="messagesingle"
errorClass="errormsg"
infoClass="infomsg"
warnClass="warnmsg"/>
<h:outputLabel for="scCompany"
value="#{messages['company']}:"/>
<h:inputText id="scCompany"
value="#{homeSelectedContactHelper.selectedContact.company}"/>
<rich:message for="scCompany"
styleClass="messagesingle"
errorClass="errormsg"
infoClass="infomsg"
warnClass="warnmsg"/>
<h:outputLabel for="scEmail"
value="#{messages['email']}:"/>
<h:inputText id="scEmail"
value="#{homeSelectedContactHelper.selectedContact.email}"/>
<rich:message for="scEmail" styleClass="messagesingle"
errorClass="errormsg"
infoClass="infomsg"
warnClass="warnmsg"/>
</h:panelGrid>
<rich:graphValidator>
<rich:toolBar> <rich:toolBarGroup>
<!-- my action buttons here -->
</rich:toolBarGroup> </rich:toolBar>
Empecemos por la inserción de los botones de acción para introducir un nuevo contacto:
<a:commandLink reRender="contactsList,contactDetail"
action="#{homeSelectedContactHelper.insertNewContact}"
rendered="#{!homeSelectedCon tactHelper.selectedContactManaged}"
styleClass="image-command-link">
<f:setPropertyActionListener value="#{null}"
target="#{homeContactsListHelper.contactsList}"/>
<h:graphicImage value="/img/insert.png"/>
<h:outputText value="#{messages['insert']}"/>
</a:commandLink>
Este botón no llama a cualquier método de acción, sino que sólo establece la propiedad
selectedContact a null y la propiedad selectedContactEditing a false para
"cancelar" la acción de inserción.
<a:commandLink reRender="contactsList,contactDetail"
action="#{homeSelectedContactHelper.saveContactData}"
rendered="#{homeSelectedContactHelper.selectedContactManaged}"
styleClass="image-command-link">
<f:setPropertyActionListener value="#{false}"
target="#{homeSelectedContactHelper.selectedContactEditing}"/>
<f:setPropertyActionListener value="#{null}"
target="#{homeContactsListHelper.contactsList}"/>
<h:graphicImage value="/img/save.png"/>
<h:outputText value="#{messages['save']}"/>
</a:commandLink>
El botón Cancelar de un contacto existente es casi el mismo que el de los nuevos contactos:
La única diferencia es que no establece el selectedContact a null como nos gustaría ver
el contacto en el modo de vista después de cancelar la edición. El último botón que vamos a
introducir es la de la eliminación:
<a:commandLink ajaxSingle="true"
reRender="contactDetail,contactsList"
action="#{homeSelectedContactHelper.deleteSelectedContact}"
rendered="#{homeSelectedContactHelper.selectedContactManaged}"
styleClass="image-command-link">
<f:setPropertyActionListener value="#{null}"
target="#{homeContactsListHelper.contactsList}"/>
<h:graphicImage value="/img/delete.png"/>
<h:outputText value="#{messages['delete']}"/>
</a:commandLink>
Aquí hay una captura de pantalla del cuadro de edición con la barra de herramientas:
Los atributos ajaxSingle y process
La propiedad ajaxSingle es muy útil para controlar el envío del formulario cuando
ajaxSingle se establece en true, el formulario no se envía sólo los datos de los
componentes Ajax.
Este atributo se encuentra disponible en todos los componentes de acción Ajax y podemos
usarlo para llamar a una acción de un botón, saltar la validación de formularios (como la
propiedad immediate de JSF), o para enviar el valor de entrada de sólo una en una forma sin
la validación y la la presentación de los otros.
El segundo caso de uso puede ser utilizado, por ejemplo, cuando necesitamos un menú de
entrada que cambia dinámicamente el valor de otras entradas, sin presentar el formulario
completo:
<h:form>
<!-- other input controls -->
<h:selectOneMenu id="country"
value="#{myBean.selectedCountry}">
<f:selectItems value="#{myBean.myCountries}">
<a:support event="onchange" ajaxSingle="true"
reRender="city" />
</h:selectOneMenu>
<h:selectOneMenu id="city"
value="#{myBean.selectedCity}">
<f:selectItems value="#{myBean.myCities}">
</h:selectOneMenu>
<!-- other input controls -->
</h:form>
En este ejemplo, cada vez que el usuario selecciona un país nuevo, el valor es envíado al bean
que vuelve a calcular la propiedad myCities para el nuevo país, después de que el menú de
la ciudad se vuelven a representar para mostrar las nuevas ciudades.
Todo lo que no se envía del formulario o se bloquean los cambios a causa de algún problema
de validación.
¿Qué pasa si usted desea enviar más de un valor, pero todavía no todo el formulario?
Podemos utilizar las regiones Ajax (que veremos en las siguientes secciones), o podemos usar
el atributo process. Este contiene una lista de componentes para el proceso, mientras que se
envía la acción Ajax:
<h:form>
<!-- other input controls -->
<h:inputText id="input1" ... />
<h:selectOneMenu id="country"
value="#{myBean.selectedCountry}">
<f:selectItems value="#{myBean.myCountries}">
<a:support event="onchange"
ajaxSingle="true"
process="input2, input3"
reRender="city" />
</h:selectOneMenu>
<h:inputText id="input2" ... />
<h:inputText id="input3" ... />
<h:selectOneMenu id="city"
value="#{myBean.selectedCity}">
<f:selectItems value="#{myBean.myCities}">
</h:selectOneMenu>
<!-- other input controls -->
</h:form>
En este ejemplo, también queríamos presentar los valores input2 y input3, junto con el
nuevo país, porque son útiles para la recuperación de la nueva lista de las ciudades,
simplemente estableciendo el atributo process con la lista de identificación y durante la
presentación, se procesarán. Así input1 no se enviará.
Además, para los componentes de acción, tales como botones, puede decidir qué enviar
utilizando los atributos ajaxSingle y process.
Más Ajax
Por cada contacto, nos gustaría añadir más campos personalizables, así que vamos a utilizar la
entidad ContactField conectado a todas las instancias de Contact.
@Name("homeSelectedContactOtherFieldsHelper")
@Scope(ScopeType.CONVERSATION)
public class HomeSelectedContactOtherFieldsHelper {
@In(create = true)
EntityManager entityManager;
@In(required = true)
Contact loggedUser;
@In
FacesMessages facesMessages;
@In(required = true)
HomeSelectedContactHelper homeSelectedContactHelper;
// my code
}
Ahora, vamos a añadir la propiedad que contiene la lista de campos personalizados para los
contactos seleccionados:
private List<ContactField> contactFieldsList;
public List<ContactField> getContactFieldsList() {
if (contactFieldsList == null) {
// Getting the list of all the contact fields
String query = "from ContactField cf where cf.contact.id=:
idContactOwner order by cf.id";
contactFieldsList = (List<ContactField>)
entityManager.createQuery(query)
.setParameter("idContactOwner",
homeSelectedContactHelper.getSelectedContact()
.getId()).getResultList();
}
return contactFieldsList;
}
public void setContactFieldsList(List<ContactField>
contactFieldsList) {
this.contactFieldsList = contactFieldsList;
}
Como puede ver, es una característica normal inicializarlo usando el método de acceso. Esta
consulta la base de datos para recuperar la lista de campos personalizados para el contacto
seleccionado. Tenemos que poner en el bean de algún otro método útil para administrar el
campo personalizado (adición y eliminación de campo y de la base de datos), vamos a añadir
los métodos:
field.setContact(homeSelectedContactHelper.getSelectedContact());
entityManager.persist(field);
}
public void deleteContactField(ContactField field) {
// If it is in the database, delete it
if (isContactFieldManaged(field)) {
entityManager.remove(field);
}
// Removing the field from the list
getContactFieldsList().remove(field);
}
public boolean isContactFieldManaged(ContactField field) {
return field != null && entityManager.contains(field);
}
Después de que el usuario ha rellenado los valores, él / ella presiona un botón que llama al
método persistNewContactField () para guardar los nuevos datos en la base de datos.
<a:repeat value="#{homeSelectedContactOtherFieldsHelper.
contactFieldsList}" var="field">
<h:panelGrid columns="2" rowClasses="prop"
columnClasses="name,value">
<h:outputText value="#{field.type} (#{field.label}):"/>
<h:outputText value="#{field.value}"/>
</h:panelGrid>
</a:repeat>
Estamos utilizando un nuevo componente de iteración de datos RichFaces que nos permite
iterar sobre una colección y poner los datos que queremos (el componente rich:dataTable
crea una tabla con la lista de elementos).
En nuestro caso, el bloque h:panelGrid se repetirá para cada uno de los elementos de la
colección (para cada campo personalizado).
<a:region>
<a:outputPanel id="otherFieldsList">
<a:repeat value="#{homeSelectedContactOtherFieldsHelper.
contactFieldsList}"
var="field">
<h:panelGrid columns="3" rowClasses="prop"
columnClasses="name,value,validatormsg">
<h:panelGroup>
<h:inputText id="scOtherFieldType"
value="#{field.type}"
required="true" size="5">
<a:support event="onblur"
ajaxSingle="true"/>
</h:inputText>
<h:outputText value=" ("/>
<h:inputText id="scOtherFieldLabel"
value="#{field.label}"
size="5">
<a:support event="onblur"
ajaxSingle="true"/>
</h:inputText>
<h:outputText value=")"/><br/>
<rich:message for="scOtherFieldType"
styleClass="messagesingle"
errorClass="errormsg"
infoClass="infomsg"
warnClass="warnmsg"/>
</h:panelGroup>
<h:panelGroup>
<h:inputText id="scOtherFieldValue"
value="#{field.value}"
required="true">
<a:support event="onblur"
ajaxSingle="true"/>
</h:inputText><br/>
<rich:message for="scOtherFieldValue"
styleClass="messagesingle"
errorClass="errormsg"
infoClass="infomsg"
warnClass="warnmsg"/>
</h:panelGroup>
<h:panelGroup>
<a:commandButton image="/img/add.png"
reRender="otherFieldsList"
action="#{homeSelectedContactOtherFieldsHelper.
persistNewContactField(field)}"
rendered="#{!homeSelectedContactOtherFieldsHelper.
isContactFieldManaged(field)}">
</a:commandButton>
<a:commandButton image="/img/remove.png"
reRender="otherFieldsList" ajaxSingle="true"
action="#{homeSelectedContactOtherFieldsHelper.
deleteContactField(field)}">
</a:commandButton>
</h:panelGroup>
</h:panelGrid>
</a:repeat>
<a:commandLink reRender="otherFieldsList"
ajaxSingle="true"
action="#{homeSelectedContactOtherFieldsHelper.
createNewContactFieldInstance}"
rendered="#{homeSelectedContactHelper.
selectedContactManaged}"
styleClass="image-command-link">
<h:graphicImage value="/img/add.png"/>
<h:outputText value="#{messages['addNewField']}"/>
</a:commandLink>
</a:outputPanel>
</a:region>
El código es muy similar a la del cuadro de vista, excepto por los botones de acción (para
añadir una nueva instancia, persistencia, guardar o eliminar) y, por la presencia de la etiqueta
que rodea a:region (resaltado). Esto es muy importante para asegurarse de que la forma
funciona correctamente, vamos a ver por qué en la siguiente sección.
También observe que todos los componentes de entrada tiene la etiqueta a:support como un
hijo que va a actualizar el bean editando el valor en el evento onblur (lo que significa que cada
vez que cambia el foco a otro componente, el valor de la última es enviado). Así que, si elimina
o añade un campo, ahora se perderán los valores editados en otros campos. También se utiliza
para la validación del Ajax, y el usuario es informado de que el valor no es válido cuando se
mueve el cursor a otra entrada.
Aquí hay una captura de pantalla con la nueva característica en el cuadro de edición:
Usando a:support para la validación de Ajax
Si desea utilizar la a:support únicamente para fines de validación, recuerde poner el atributo
bypassUpdates en true, por lo que el proceso sería más rápido que el modelo de
actualización de JSF y las fases de invocación de la aplicación no serán invocadas.
Contenedores Ajax
Durante el desarrollo de una aplicación web con RichFaces, es muy útil saber cómo utilizar los
contenedores Ajax (como el comoponente a:region) a fin de optimizar las peticiones Ajax.
Es un componente muy importante del marco de trabajo, ya que puede definir las áreas Ajax
para limitar la parte del árbol de componentes que serán procesados en una petición Ajax.
Las regiones se pueden anidar en una petición Ajax y el más cercano será utilizado.
Otro atributo importante es selfRendered, estableciendo ésta a true indica el marco para
crear la respuesta basándose en el árbol de componentes sin hacer referencia al código de la
página, es más rápido, pero todos los elementos transitorios que no se guardan en el árbol
(como f:verbatim ó código HTML escrita directamente sin necesidad de utilizar
componentes JSF) se perderán la primera vez de refrescado, por lo que no se pueden utilizar
en este caso.
En resumen, es muy útil para controlar el proceso de representación y optimizar, con el fin de
limitar los elementos de un formulario para enviar durante una petición Ajax sin problemas de
validación, para mostrar los diferentes indicadores de la condición Ajax.
En este ejemplo, mientras el usuario está escribiendo en el valor del text1 de inputText,
a:support envía una petición Ajax que contiene sólo los valores it1 y it2 de inputText.
En este caso, de hecho, a:region limíta los componentes enviados por cada petición Ajax se
originó en el interior de la región. Por lo tanto, la petición Ajax sólo se actualizará
aBean.text1 y aBean.text2.
Envolver sólo un componente dentro de una región del Ajax es el equivalente de usar la
propiedad ajaxSingle establecida a true.
Volviendo a nuestra aplicación, como todos los campos personalizados dentro del mismo
componente de formulario, nos rodean cada uno de ellos con la etiqueta a:region. De esta
manera, el campo simplemente se presenta, independientemente de los demás.
Por ejemplo, sin utilizar a:region, si el usuario se vacía el valor de entrada del nombre y
luego trata de insertar un campo personalizado nuevo, el proceso fallará porque el nombre de
entrada no está validado. Si usamos el componente a:region, el campo de nombre no será
procesada y un nuevo campo será insertado.
Ahora que sabemos cómo utilizar la etiqueta a:region, podemos combinarlo con
ajaxSingle y process para decidir qué enviar en cada solicitud y para optimizar mejor las
interacciones Ajax en la aplicación.
Echemos un vistazo de nuevo en una versión simple de los rich:dataTable que hemos
utilizado para la lista de contactos:
<rich:dataTable
value="#{homeContactsListHelper.contactsList}"
var="contact">
<rich:column>
<h:outputText value="#{contact.name}"/>
</rich:column>
<rich:column>
<h:outputText value="#{contact.surname}"/>
</rich:column>
</rich:dataTable>
El resultado de este código es simplemente previsible una tabla con dos columnas, una para el
nombre y el otro para el apellido.
Ahora, supongamos que no quiero que el formato de tabla, sino una lista de contactos, lo único
que tienes que hacer es utilizar el componente rich:dataList la misma forma que
rich:dataTable:
<rich:dataList value="#{homeContactsListHelper.contactsList}"
var="contact">
<h:outputText value="#{contact.name} #{contact.surname}"/>
</rich:dataList>
Una mención especial para los rich:dataGrid que tiene algunas diferencias:
<rich:dataGrid value="#{homeContactsListHelper.contactsList}"
var="contact"
columns="3">
<h:outputText
value="#{contact.name} #{contact.surname}"/>
</rich:dataGrid>
Como puede ver, hemos tenido que poner el atributo columns para saber cuándo termina la
fila y empieza una nueva. Aquí está el resultado:
<rich:dataGrid value="#{homeContactsListHelper.contactsList}"
var="contact"
columns="3">
<rich:panel>
<f:facet name="header">
<h:outputText value="Contact" />
</f:facet>
<h:outputText
value="#{contact.name} #{contact.surname}"/>
</rich:panel>
</rich:dataGrid>
Y el resultado es:
Paginación de datos con los componentes de la iteración de datos
En cuanto a h:dataTable (y rich:dataTable), puede adjuntar datascroller a cada
componente de iteración de datos de la misma forma que lo hizo con su DataTable:
<h:form>
<rich:dataList id="contactsList"
value="#{homeContactsListHelper.contactsList}"
var="contact"
rows="3">
<h:outputText value="#{contact.name} #{contact.surname}"/>
</rich:dataList>
<rich:datascroller for="contactsList" />
</h:form>
Sólo recuerde colocar datascroller denttro de form, el atributo set, y establecer el atributo
de rows para uno de los componentes de iteración de datos.
El resultado es el siguiente:
Además, en este caso, existe una pequeña excepción para el componente rich:dataGrid,
ya que no tiene el atributo rows, pero el correspondiente es elements (se puede averiguar
por qué). Por lo tanto, nuestro ejemplo será:
<h:form>
<rich:dataGrid id="contactsGrid"
value="#{homeContactsListHelper.contactsList}"
var="contact"
columns="3"
elements="3">
<rich:panel>
<f:facet name="header">
<h:outputText value="Contact" />
</f:facet>
<h:outputText
value="#{contact.name} #{contact.surname}"/>
</rich:panel>
</rich:dataGrid>
<rich:datascroller for="contactsGrid" />
</h:form>
Y el resultado sería el siguiente:
Administración de direcciones
Otra pieza que nos gustaría hacer es la administración de direcciones, utilizando la entidad
ContactAddress. Este es el mismo mecanismo de trabajo que en el campo personalizado,
por lo que se deja como ejercicio para el lector.
El formulario de edición de contacto (se nota en los botones para agregar / eliminar más
campos y direcciones) aparece como sigue:
Y finalmente tu puedes ver y agregar el formulario de contactos:
Resumen
En este capítulo, hemos desarrollado la característica principal de nuestra aplicación de
administración de contactos. Hemos aprendido sobre la interacción del Ajax y los contenedores
y sobre los nuevos componentes que Ajax RichFaces ofrece.