Sei sulla pagina 1di 27

7

Terminando la aplicación
Casi hemos terminado nuestra aplicación. En este capítulo, vamos a desarrollar una
característica muy útil que nos servirá para aprender a utilizar otros componentes RichFaces en
una aplicación real.

Vamos a ver muchas características interesantes que se pueden reutilizar en nuestra


aplicación con pocos cambios.

Tomando nota de todos los contactos


Por cada contacto que tenemos en nuestra libreta de direcciones, queremos ser capaces de
añadir una breve nota acerca de ellos. Esto puede ser muy útil en una gran cantidad de
escenarios y es muy fácil de poner en práctica.

Un editor rico
Vamos a utilizar el componente de edición rich:editor del marco RichFaces. Se crea un
editor WYSIWYG basado en el código JavaScript de TinyMCE (http://tinymce.moxiecode.com).
Este componente es muy fácil de utilizar, pero al mismo tiempo, se puede personalizar y
extender utilizando los plugins de TinyMCE.

Con el fin de conectar el editor de la propiedad note de la entidad Contact, vamos a abrir el
archivo /view/main/contactEdit.xhtml e insertaremos el siguiente código, entre las
etiquetas </a:region> y </rich:graphValidator>, antes de rich:toolBar.

<rich:spacer height="10"/>
<rich:separator width="320px"/>
<rich:spacer height="15"/>
<h:outputText value="#{messages['note']}"/>
<rich:editor
value="#{homeSelectedContactHelper.selectedContact.note}"
width="320" height="150"/>

La primera parte es simplemente para crear un separador y una etiqueta, la parte más
importante es uno de los más destacados, hemos vinculado rich:editor a la propiedad
note, y definimos dos atributos (width y height) para controlar la dimensión del área de
edición.

El resultado es muy agradable:


De forma predeterminada, el tema que el editor utiliza es simple, así que tenemos un conjunto
limitado de controles en la barra de botones, por lo que vamos a establecer el tema a
advanced, de esta manera:

<rich:editor
value="#{homeSelectedContactHelper.selectedContact.note}"
theme="advanced" width="320" height="150"/>

El resultado es:

Las capacidades de personalización no se limitan a fijar el tema, también podemos agregar


más botones (desde, por ejemplo complementos externos) o para cambiar la disposición.

En el código de ejemplo siguiente, vamos a mostrar las bases de la personalización, añadiendo


el plugin para pegar y algunos botones, además colocaremos la barra de herramientas en la
parte superior:

<rich:editor
value="#{homeSelectedContactHelper.selectedContact.note}
width="320" height="150" theme="advanced" plugins="paste">
<f:param name="theme_advanced_buttons1" value="cut,copy,paste,
pasteword,|,bold,italic,underline"/>
<f:param name="theme_advanced_toolbar_location" value="top"/>
<f:param name="theme_advanced_toolbar_align" value="left"/>
</rich:editor>

Este es el resultado:
Observe que las barras de herramientas se encuentran ahora en la parte superior y la primera
barra tiene un botón diferente como se decidió (botones paste y pasteword provienen del
plugin paste que añadimos).

Usando la etiqueta f:param, puede configurar todas las opciones de TinyMCE (lea la
documentación de TinyMCE para más información, la opción va dentro del atributo name y el
valor de la opción va en el atributo value.

Veamos nuestro grupo de contactos


Sería muy útil para nuestro grupo de contactos y mantener diferentes listas de contactos para
buscar rápidamente una o la otra.

Con el fin de hacer eso, vamos a aplicar la característica de grupo -básicamente-, puede
agregar tantos grupos como desee y cada contacto puede quedarse en uno o más grupos.
Cada grupo tiene un color y una descripción. Vamos a ver cómo implementarlo.

Listar, agregar y eliminar grupos


Vamos a utilizar el primer cuadro que, hasta ahora, contiene todos los enlaces a los contactos,
el fin de hacer eso, vamos a abrir /view/main/contactsGroup.xhtml y empezaremos a
escribir la tabla principal que contiene el enlace a todos los contactos y los demás enlaces a los
otros grupos.

Vamos a sustituir el código: <h:panelGrid>...</h:panelGrid> con esto:

<table width="100%" border="0">


<tr>
<td width="14" align="center">
<h:graphicImage value="/img/right_arrow_small.png"
rendered="#{homeContactsListHelper.groupFilter==null}"/>
</td>
<td colspan="3">
<h:graphicImage value="/img/contact_small.png"/>
<a:commandLink value="#{messages['allContacts']}"
ajaxSingle="true"
style="margin-left: 5px;"
reRender="contactsList, contactsGroups">
<f:setPropertyActionListener value="#{null}"
target="#{homeContactsListHelper.contactsList}"/>
</a:commandLink>
</td>
</tr>
</table>

En este caso, no utilizar el componente rich:dataTable porque es la mezcla de un enlace


estándar con una lista (la lista de grupos) que no contiene todos los enlaces de contactos, de
modo, que si quiere tener una tabla única que contenga ambas, habrá que hacerlo de esta
manera. Gracias a Facelets es posible mezclar código XHTML normal, sin ningún problema.

Como se puede ver en el código, nos referimos también a la propiedad groupFilter de


homeContactsListHelper que se utiliza para mostrar el contenido del grupo en la lista de
contactos. Todos los contactos se mostrarán sólo si groupFilter es null y es por eso que
lo ponemos a null en el enlace de todos los contactos.

Por otra parte, la primera columna de la tabla contiene una pequeña flecha que será visible
cuando la propiedad groupFilter es nula (de modo que todos los contactos se muestran).

Para llevar a cabo esto, vamos a abrir la clase HomeContactsListHelper y agregaremos la


nueva propiedad:

private ContactGroup groupFilter;

public ContactGroup getGroupFilter() {


return groupFilter;
}

public void setGroupFilter(ContactGroup groupFilter) {


this.groupFilter = groupFilter;
}

Ahora vamos a cambiar el método getContactList() para hacer uso de la nueva propiedad:

public List<Contact> getContactsList() {


if (contactsList == null) {
// Creating the query
String queryString;
if (getGroupFilter() == null) {
queryString = "from Contact c where c.contact.id=:
fatherId";
} else {
queryString = "select cig.contact from ContactInGroup
cig
where cig.id.contactGroup=:groupFilterId";
}

// Creating the query


Query query = entityManager.createQuery(queryString);
if (getGroupFilter() == null) {
query = query.setParameter("fatherId", loggedUser.
getId());
} else {
query = query.setParameter("groupFilterId",
getGroupFilter().getId());
}
// Getting the contacts list
contactsList = (List<Contact>) query.getResultList();
}
return contactsList;
}

Además, queremos mostrar que la tabla de lista de contactos es filtrada cambiando el texto de
cabecera de la tabla y el color (de acuerdo con el actual grupo de colores filtrados).

Vamos a abrir el archivo contactsList.xhtml y editar la cabecera de dataTable para


hacerlo de esta manera:

<f:facet name="header">
<rich:columnGroup>
<rich:column colspan="3">
<h:outputText value="#{messages['allContacts']}"
rendered="#{homeContactsListHelper.groupFilter==null}"/>
<h:outputText value="#{messages['contactsInGroup']}:
#{homeContactsListHelper.groupFilter.name}"
style="color: #{homeContactsListHelper.
groupFilter.color};margin-left: 5px;"
rendered="#{homeContactsListHelper.
groupFilter!=null}"/>
</rich:column>
<rich:column breakBefore="true">
<h:outputText value="#{messages['name']}"/>
</rich:column>
<rich:column>
<h:outputText value="#{messages['surname']}"/>
</rich:column>
<rich:column>
<rich:spacer/>
</rich:column>
</rich:columnGroup>
</f:facet>

Tenemos ya lista la lógica para filtrar la lista de contactos por grupo.

Tenemos que crear el bean que administra la lista de grupos, el grupo de adición y eliminación,
vamos a crear una nueva clase llamada GroupsListHelper dentro nuevo paquete
book.richfaces.advcm.modules.main.groups de la siguiente manera:

@Name("groupsListHelper")
@Scope(ScopeType.CONVERSATION)
public class GroupsListHelper {
@In(create = true)
EntityManager entityManager;
@In(required = true)
Contact loggedUser;
@In
FacesMessages facesMessages;
}

Vamos a llenar los componentes vacíos de Seam con la lógica a la lista de grupos:

private List<ContactGroup> groups;

public List<ContactGroup> getGroups() {


if (groups == null) {
// Creating the query
String query = "from ContactGroup cg where cg.contact.id=:
fatherId order by cg.name";
// Getting the contacts list
groups = (List<ContactGroup>) entityManager.createQuery(query)
.setParameter("fatherId", loggedUser.getId())
.getResultList();
}
return groups;
}

public void setGroups(List<ContactGroup> groups) {


this.groups = groups;
}

Hemos visto este tipo de código en otra parte del libro que hemos desarrollado -no es sólo una
propiedad con un accesor y un modificador, sino que en el interior del captador, está el código
que carga la lista utilizando el código estándar JPA.

Volvamos al código XHTML. Ábra el archivo contactsGroups.xhtml y agregue la lógica a la


lista del grupo dentro de la tabla.

Inserte el siguiente código justo antes de la etiqueta de cierre </table> del archivo:

<a:repeat value="#{groupsListHelper.groups}" var="group">


<tr>
<td width="14" align="center" valign="top">
<h:graphicImage value="/img/right_arrow_small.png"
rendered="#{homeContactsListHelper.groupFilter.id==group.id}"/>
</td>
<td>
<rich:spacer width="10" height="10" style="background-color:
#{group.color}"/>
<a:commandLink value="#{group.name}" reRender="contactsList,
contactsGroups">
<f:setPropertyActionListener value="#{group}"
target="#{homeContactsListHelper.groupFilter}"/>
<f:setPropertyActionListener value="#{null}"
target="#{homeContactsListHelper.contactsList}"/>
</a:commandLink>
<rich:toolTip value="#{group.description}"/>
</td>
</tr>
</a:repeat>

Uso de la etiqueta a:repeat (que, como hemos visto en otros capítulos, trabaja como los
componentes de iteración de los otros datos), podemos insertar una nueva fila en la tabla para
cada grupo.

Esta vez la flecha de la primera columna se muestra sólo si el grupo en esa fila es la que se
muestra en ese momento.

También estamos utilizando rich:spacer con la propiedad de fondo CSS con el valor del
grupo de colores, a fin de mostrar un recuadro de color para cada grupo pequeño, veremos
cómo configurar el color más adelante en este capítulo.

Después de que el comando de enlace que activa el grupo de filtrado y vuelve a cargar la lista
de contactos para mostrar la nueva tabla de filtrado de contactos, podemos ver una nueva
etiqueta llamada rich:toolTip esta etiqueta es muy útil para mostrar una herramienta que
muestra una breve descripción del grupo, cuando nos posicionamos sobre la celda de la tabla
que contiene el nombre del grupo.

Aquí hay una captura de pantalla de lo que tenemos hasta ahora:

Otras características del componente rich:tool:


Como hemos visto, la forma más sencilla de utilizar los componentes rich:toolTip es
colocarlo dentro de un contenedor con el atributo valor con información, como en el siguiente
código:

<a:outputPanel>
<rich:toolTip value="This is the text of my tooltip!" />
</a:outputPanel>

Otra manera de adjuntar la herramienta de ventana de ayuda contextual es mediante el atributo


for de la siguiente manera:

<a:outputPanel id="myPanel">
<rich:toolTip value="This is the text of my tooltip!"
for="myPanel"/>
</a:outputPanel>

Muy simple.

Nota que cuando se utiliza el atributo de la etiqueta rich:toolTip no tiene que ser anidada
dentro del componente (por ejemplo un a:outputPanel en nuestro caso).

Otra característica importante es la carga de Ajax del contenido:

<a:outputPanel>
<rich:toolTip mode="ajax" value="#{myBean.myProperty}">
<f:facet name="defaultContent">
<h:outputText value="Loading... " />
</f:facet>
</rich:tooltip>
</a:outputPanel>

Este código mostrará la herramienta ventana contextual y dentro el texto Loading…, hasta
que el contenido de la propiedad de la envolvente (myProperty) se cargue utilizando Ajax, y
esté listo para ser mostrado.

Hay otros atributos útiles (como showEvent, showDelay, hideDelay, followmouse y


direction) que usted puede utilizar para personalizar el comportamiento de herramienta de
ventana contextual. También puede utilizar la API de JavaScript para llamar desde el interior de
un método JavaScript.

Agregar y editar grupos


Volvamos a nuestra aplicación, lo que necesitamos ahora es darle la capacidad de agregar y
editar un grupo.

Vamos a hacer esto dentro de la tabla del grupo, por lo que el grupo que está editando (o
agregando) se muestra como un formulario con entradas para agregar o modificar y no como
un enlace.

Para entender mejor esto, esta es una captura de pantalla de la caracteristica deseada:

Como puede ver, el formulario de edición se encuentra dentro de la tabla, en el lugar de edicion
del grupo (para añadir el formulario será en la último renglón de la tabla).

Empezaremos editando el componente Seam GroupsListHelper, añadiendo las nuevas


características que necesitamos.

En primer lugar, vamos a agregar la propiedad groupEditing que contiene el grupo que se
está editando:

private ContactGroup groupEditing;

public ContactGroup getGroupEditing() {


return groupEditing;
}

public void setGroupEditing(ContactGroup groupEditing) {


this.groupEditing = groupEditing;
}

No hay nada nuevo aquí, sólo es una propiedad con un accesor y un modificador.
Ahora, hay que asegurarse de que al añadir y editar botones llenemos esta propiedad cuando
se haga clic en él.

Para la función de edición, sólo puedo añadir la siguiente columna a la tabla del grupo dentro
del archivo contactsGroups.xhtml:

<td align="center" valign="top">


<a:commandLink
reRender="contactsGroups">
<h:graphicImage value="/img/edit_small.png"/>
<f:setPropertyActionListener value="#{group}"
target="#{groupsListHelper.groupEditing}"/>
</a:commandLink>
</td>

Como puede ver, a:commandLink establece la propiedad groupEditing y recargamos la


tabla del grupo de contactos.

Para el botón Agregar, tenemos que utilizar un método que crea una nueva instancia
ContactGroup y lo inserte en la lista.

Volvamos a abrir la clase GroupsListHelper nuevamente y agreguemos el siguiente


método:

public void addGroup() {


ContactGroup newGroup = new ContactGroup();

getGroups().add(newGroup);
setGroupEditing(newGroup);
}

Ahora, podemos agregar una barra de herramientas con el botón Agregar grupo justo debajo
de la tabla de los grupos, vamos a cambiar al archivo contactsGroups.xhtml y agregar
este código después de la etiqueta de cierre </rich:panel>:

<rich:toolBar>
<rich:toolBarGroup>
<a:commandButton
image="/img/addgroup.png"
ajaxSingle="true"
reRender="contactsGroups"
action="#{groupsListHelper.addGroup}"/>
</rich:toolBarGroup>
</rich:toolBar>

El botón que hemos añadido llama al método addGroup() y hace que aparezca la tabla de
grupos de contacto.

Agregar / modificar formularios


Tenemos listo el disparador agregar / modificar, ahora tenemos que mostrar el formulario (en
lugar de un simple comando de enlace) cuando nos encontramos con un grupo que está en el
modo de edición.
Para lograr esto, editaremos el archivo contactsGroups.xhtml, agregando el código dentro
de la segunda columna tabla (la que muestra el cuadro y el enlace del grupo) dentro de la
etiqueta a:outputPanel con valores en el atributo rendered:

<td>
<a:outputPanel
rendered="#{groupsListHelper.groupEditing.id!=group.id}">
<rich:spacer width="10" height="10"
style="background-color: #{group.color}"/>
<a:commandLink value="#{group.name}"
reRender="contactsList, contactsGroups">
<f:setPropertyActionListener value="#{group}"
target="#{homeContactsListHelper.groupFilter}"/>
<f:setPropertyActionListener value="#{null}"
target="#{homeContactsListHelper.contactsList}"/>
</a:commandLink>
<rich:toolTip value="#{group.description}"/>
</a:outputPanel>
</td>

Al hacer esto, se cierra el código que muestra únicamente el grupo recargado y no está en el
modo de edición.

Después de este panel, se tendrá que insertar el panel que contiene el código para los grupos
en el modo de edición, de modo que el usuario podrá editar las propiedades de un grupo que
está en el modo de edición.

Para hacerlo, agregaremos el código siguiente después de la etiqueta de cierre


<a/outputPanel>:

<rich:panel rendered="#{groupsListHelper.groupEditing.id==group.id}">
<f:facet name="header">
<h:panelGroup>
<h:outputText value="#{messages['editGroup']}"
rendered="#{groupsListHelper.groupEditing.id!=null}"/>
<h:outputText value="#{messages['addGroup']}"
rendered="#{groupsListHelper.groupEditing.id==null}"/>
</h:panelGroup>
</f:facet>
<a:region>
<h:panelGrid columns="1">
<h:inputText id="newGroupName"
value="#{groupsListHelper.groupEditing.name}"
required="true">
<rich:beanValidator/>
</h:inputText >
<rich:message for="newGroupName"
styleClass="messagesingle"
errorClass="errormsg"
infoClass="infomsg"
warnClass="warnmsg"/>
<rich:colorPicker
value="#{groupsListHelper.groupEditing.color}"/>
<rich:editor
value="#{groupsListHelper.groupEditing.description}"/>
<a:outputPanel>
<a:commandButton value="#{messages['save']}"
action="#{groupsListHelper.saveGroupEditing}"
reRender="contactsGroups"/>
<a:commandButton
value="#{messages['cancelEditing']}"
ajaxSingle="true"
reRender="contactsGroups">
<f:setPropertyActionListener
value="#{null}"
target="#{groupsListHelper.groupEditing}"/>
</a:commandButton>
</a:outputPanel>
</h:panelGrid>
</a:region>
</rich:panel>

Hemos resaltado uno de los nuevos componentes rich:colorPicker.

Este componente permite al usuario elegir un color de forma visual y es muy útil y fácil de usar.

La siguente captura de pantalla muestra el componente cuando se abre:

Puede utilizar CSS para personalizarlo (como ocurre con todos los componentes de
RichFaces), y usar dos facets para sobreescribir las estándard establecimiento el facet icon,
puede personalizar el icono que abre el panel, mientras que con los facets arrows, puede
cambiar el color de las flechas de selección.

Si nos fijamos en otro componente, la lógica de trabajo de esta formulario es limpia, cuando el
grupo está en el modo de edición, se muestra este formulario con la propiedad ContactGroup
vinculado al componente de entrada, al final del formulario, tenemos dos botones, uno para
confirmar y guardar el grupo editado en la base de datos y la otra para cancelar la edición (que
sólo establece la propiedad groupEditing a nulo y recarga la lista de grupos).

Para guardar el grupo en la base de datos hay que agregar otro método a la clase
GroupsListHelper:

public void saveGroupEditing() {


if (entityManager.contains(getGroupEditing())) {
// Save the object changes
entityManager.merge(getGroupEditing());
} else {
// Associate the new group to the current logged user
getGroupEditing().setContact(loggedUser);
// Persist the object into the database
entityManager.persist(getGroupEditing());
}
// Empty the instance (exits from edit mode)
setGroupEditing(null);
}

La última funcionalidad para la administración de grupos es el botón de borrar, primero vamos


a agregar el método en la clase GroupsListHelper como sigue:

@In (create = true)


HomeContactsListHelper homeContactsListHelper;
public void deleteGroup(ContactGroup group) {
// If the group to be deleted is selected
if (homeContactsListHelper.getGroupFilter().getId()==
group.getId()) {
// Deselect it
homeContactsListHelper.setGroupFilter(null);
homeContactsListHelper.setContactsList(null);
}
// Remove the group from the database
entityManager.remove(group);
}

En la primera parte del método, se comprueba si el grupo que se eliminará está seleccionado:
Si es así, quitamos la selección y luego lo eliminamos de la base de datos. Tenemos que
obtener y establecer la propiedad groupFilter que está dentro del componente
homeContactsListHelper, por eso se inyecta en una propiedad local, utilizando el código
fuera del método.

Ahora tenemos que crear el botón en XHTML y vincularlo con este método. Vamos a abrir el
archivo contactsGroups.xhtml y agregamos otra columna después para su edición:

<td align="center" valign="top">


<a:commandLink
action="#{groupsListHelper.deleteGroup(group)}"
reRender="contactsGroups,contactsList">
<h:graphicImage value="/img/delete_small.png"/>
<f:setPropertyActionListener value="#{null}"
target="#{groupsListHelper.groups}"/>
</a:commandLink>
</td>

Hemos terminado nuestras funciones de administración de grupos, ahora tenemos que hacer
posible que el usuario pueda insertar los contactos en los grupos.

Agregar contactos a un grupo usando la funcionalidad de arrastrar


y soltar
Vamos a utilizar la funcionalidad de RichFaces drag-and-drop, por lo que se puede ver lo fácil
que es hacer esto. Cuando un usuario arrastra un contacto sobre un nombre de grupo, el
contacto será insertado en ese grupo, así que vamos a empezar a definir las columnas de la
tabla de contacto, que contienen el nombre del contacto y apellidos al área de arrastrar.

Vamos a abrir el archivo contactsList.xhtml y cambiaremos las dos columnas que


contienen el nombre y el apellido de esta manera:

<rich:column width="45%"
sortBy="#{contact.name}"
filterBy="#{contact.name}">
<h:outputText value="#{contact.name}"/>
<rich:dragSupport dragType="contact"
dragIndicator="contactDragIndicator"
dragValue="#{contact}">
<rich:dndParam type="drag" name="label"
value="#{contact.name} #{contact.surname}"/>
</rich:dragSupport>
</rich:column>
<rich:column width="45%"
sortBy="#{contact.surname}"
filterBy="#{contact.surname}">
<h:outputText value="#{contact.surname}"/>
<rich:dragSupport dragType="contact"
dragIndicator="contactDragIndicator"
dragValue="#{contact}">
<rich:dndParam type="drag" name="label"
value="#{contact.name} #{contact.surname}"/>
</rich:dragSupport>
</rich:column>

Destacamos la importancia de código (la primera y la segunda columna es el mismo), estamos


definiendo un área de arrastrar del tipo contact que utiliza un indicador de arrastre
personalizada (lo veremos muy pronto) y lleva a la instancia contact como dragValue.

También se define un parámetro llamado arrastre label con el nombre y apellido del contacto.
Vamos a utilizarlo en el indicador de arrastre.

Un indicador de arrastre es un panel HTML que sigue el ratón mientras se arrastra, no tenemos
definido nuestro indicador personal. Sin embargo, en este caso, queremos mostrar alguna
información sobre el elemento arrastrado (el nombre y apellido) dentro del panel.

Este es el panel de arrastre estándar sin dragIndicator:

Es sólo un rectángulo de puntos, que se convierte en verde cuando el tema es arrastrado por
un panel drop, que la acepta.

El uso básico de la dragIndicator es muy simple, sólo basta con colocar el siguiente código
al final de la página, antes del cierre </ ui:composición> tag:

<rich:dragIndicator id="contactDragIndicator" />

En nuestra aplicación, sólo estamos pasando el valor de la etiqueta con el parámetro


dndParam que usted ha visto, por lo que el resultado de tener un indicador de arrastre es:
Cuando el objeto es arrastrado por un panel drop que lo acepta, el icono de arrastre cambia a
indicador, como se muestra en la siguiente pantalla:

Podemos personalizar cada parte del indicador de arrastre usando CSS y facets.

Ahora tenemos que crear una area drop de cada grupo que acepta los elementos arrastrados
del tipo de contacto.

Vamos a abrir el archivo contactsGroups.xhtml y añade este código dentro del grupo
commandLink:

<a:commandLink value="#{group.name}"
style="color: #{group.color};margin-left: 5px;"
reRender="contactsList, contactsGroups">
<f:setPropertyActionListener value="#{group}"
target="#{homeContactsListHelper.groupFilter}"/>
<f:setPropertyActionListener value="#{null}"
target="#{homeContactsListHelper.contactsList}"/>
<rich:dropSupport acceptedTypes="contact"
dropListener="#{groupsListHelper.
processDropAddContactToGroup}"
dropValue="#{group}"/>
</a:commandLink>

El código en negrita añade el soporte drop a todos los enlaces del grupo. Se acepta sólo el tipo
de contact arrastrando elementos y tiene un valor desplegable que contiene la instancia de
group actual.

El listener definido para el evento soltar será llamado cada vez que un elemento se ha soltado,
vamos a hacerlo en el interior de la clase GroupsListHelper:

Contact droppedContact = (Contact) dropEvent.getDragValue();


// Get the ContactGroup instance
ContactGroup droppedGroup = (ContactGroup) dropEvent.
getDropValue();
// Check if the contact exists
ContactInGroupId cingid = new ContactInGroupId
(droppedGroup.getId(), droppedContact.getId());
// If it doesn't exist
if (entityManager.find(ContactInGroup.class, cingid) == null) {

// Create the association


ContactInGroup cing = new ContactInGroup(cingid,
droppedGroup,droppedContact);
// Save into the database
entityManager.persist(cing);facesMessages.
addFromResourceBundle(StatusMessage.Severity.INFO,
"contactAddedToTheGroup");
} else { // If it exists
facesMessages.addFromResourceBundle
(StatusMessage.Severity.INFO,
"contactAlreadyInTheGroup");
}
}

En este método, se obtiene los valores dragValue y dropValue, y los utilizan para comprobar si
el contacto está dentro del grupo y si desea o no insertar.

Esta es una manera muy simple y eficaz para agregar arrastrar y colocar su solicitud.

El tipo de arrastrar/soltar es muy útil para definir diferentes lógicas de arrastrar/soltar en función
del tipo de elemento arrastrado.

Eliminación de contactos de un grupo usando arrastrar y soltar


También queremos definir un área por debajo de la barra de herramientas del grupo donde
puedes colocar los contactos que se van a eliminar, dentro de un grupo.

Esta área debe ser sólo aparece cuando la lista de contactos está mostrando el contenido de
un grupo por lo tanto, se muestra cuando la propiedad
homeContactsListHelper.groupFilter no es nulo.

Esto podría tener este aspecto:

Dejamos como ejercicio para el lector. Puede encontrar el código fuente completo descargando
el código de la aplicación.

Adjuntar archivos
Nos gustaría asociar uno o más archivos a cada contacto. Por ejemplo, podemos adjuntar una
imagen, o el CV, u otra información útil sobre el contacto.

Mediante la aplicación de esta función, vamos a introducir tres nuevos componentes y


explicaremos cómo usarlos de manera productiva.

Creación de la asistente
Empecemos por el asistente para cargar el archivo, se trata de un sencillo asistente de una
página que permite al usuario subir y luego revisar los archivos, finalmente añadir una nota a
cada uno.

Vamos a crear el directorio /view/main/uploadFiles/ y dentro de él, un archivo vacío


llamado wizardFirstStepUploadFiles.xhtml (que será la página del primer paso
nuestro asistente) con el siguiente contenido:

<!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:a="http://richfaces.org/a4j">
<!-- my code -->
</ui:composition>

Ahora tenemos que introducir (en lugar del comentario <!-- my code --> ) el formulario
con la carga de archivo de RichFaces:

<h:form>
<h:outputText value="#{messages['selectFilesToUpload']}" />
<rich:fileUpload acceptedTypes="gif,jpg,png,pdf,doc,xls"
allowFlash="auto" autoclear="false"
maxFilesQuantity="10" immediateUpload="true"
fileUploadListener="#{filesUploadHelper.listener}">
<a:support event="onuploadcomplete" reRender="nextBtn" />
</rich:fileUpload>
</h:form>

La forma de trabajo es muy intuitivo para cada archivo suba, un método de escucha se ejecuta,
por lo que la aplicación puede manejar el archivo cargado.

Los otros atributos que nos hemos fijado son fáciles de entender, pero una mención especial va
para el atributo allowFlash, que permite la habilitación de un panel de componentes de
carga Flash. Si se activa el plugin de Flash, permitirá al usuario elegir más de un archivo a
cargar a la vez.

¿Dónde están almacenados los archivos subidos depende de createTempFile, un


parametro init-param colocado en el archivo web.xml. Por defecto, su valor es true, y los
archivos se almacenan en una carpeta temporal. Si el parámetro se establece en false, los
archivos subidos se guardarán en la memoria RAM (es un modo mejor si ha cargado archivos
pequeños).

Para cambiar el valor del parámetro, hay que abrir el archivo web.xml y añadir este código:

<init-param>
<param-name>createTempFiles</param-name>
<param-value>false</param-value>
</init-param>
Antes de crear el bean, tenemos que definir la ruta donde los archivos subidos se guardarán, y
podemos usar el componente uiOptions para eso. Sólo tienes que abrir la clase UIOption y
agregar la siguiente propiedad:

private String fileSavePath;


public String getFileSavePath() {
return fileSavePath;
}
public void setFileSavePath(String fileSavePath) {
this.fileSavePath = fileSavePath;
}

Ahora tenemos que configurar en el archivo components.xml. Vamos a abrirlo y añadir una
propiedad a la inicialización de componentes UIOption:

<property name="fileSavePath">/my/file/path/</property>

Por razones de seguridad, es altamente recomendable poner la ruta del archivo fuera de la
aplicación, veremos cómo habilitar el acceso a través de la aplicación más adelante. Ahora,
necesitamos el componente de Seam que administra el proceso de carga de archivos
(filesUploadHelper). Vamos a crear una nueva clase llamada FilesUploadHelper
dentro del paquete book.richfaces.advcm.modules.main.files:

@Name("filesUploadHelper")
@Scope(ScopeType.CONVERSATION)
public class FilesUploadHelper { @In(create = true)
EntityManager entityManager;
@In(required = true)
HomeSelectedContactHelper homeSelectedContactHelper;
@In
UIOptions uiOptions;
}

Además de la EntityManager, necesitamos el componente uiOptions a causa de la


propiedad fileSavePath. También necesitamos homeSelectedContactHelper para
obtener el contacto seleccionado a los archivos de asociar.

Ahora vamos a insertar el método listener:

public void listener(UploadEvent event) throws Exception {


UploadItem item = event.getUploadItem();
// Creating the instance
ContactFile newFile = new ContactFile(homeSelectedContactHelper.
getSelectedContact(), item.getFileName(),
item.getContentType());
// Persisting it into the database
entityManager.persist(newFile);
// Copying the files into the disk using the new id
copyFile(new FileInputStream(item.getFile()),
new FileOutputStream(uiOptions.
getFileSavePath()
+ newFile.getId()));
}

Como puede ver, este método obtiene la instancia UploadItem del archivo subido, crea la
asociación con el contacto seleccionado así como la persistencia en la base de datos. Después
de que copia el archivo temporal a nuestra posición preferida, el método CopyFile siguiente
se utiliza para esto:

private void copyFile(FileInputStream sourceStream,


FileOutputStream destinationStream)
throws IOException {
FileChannel inChannel = sourceStream.getChannel();
FileChannel outChannel = destinationStream.getChannel();
try {
inChannel.transferTo(0, inChannel.size(),
outChannel);
}
catch (IOException e) {
throw e;
}
finally {
if (inChannel != null) {
try {
inChannel.close();
} catch (IOException ioe) { }
}
if (outChannel != null) outChannel.close();
}
}

Este es un bean casi general que se puede utilizar en su aplicación con sólo cambiar el código
de la base de datos.

El paso de revisión de archivos


El asistente tiene dos etapas -hemos visto la primera que permite al usuario seleccionar y
cargar archivos. El segundo se usa para revisar los archivos y eventualmente añadir una nota a
cada uno.

Antes de crear la segunda página, vamos a añadir el código de navegación a la primera,


después de la etiqueta de cierre </rich:fileUpload>:

<a:commandLink id="nextBtn" action="next"


style="float:left;" styleClass="image-command-link">
<h:graphicImage value="/img/next.png" />
<h:outputText value="#{messages['next']}" />
</a:commandLink>

Este botón permite al usuario navegar a la página siguiente utilizando las reglas de navegación
de JSF desde donde next viene. Vamos a abrir el archivo faces-config.xml y agregar el
código siguiente después de la etiqueta de cierre </application>:

<navigation-rule>
<from-view-id>/main/uploadFiles/wizardFirstStepUploadFiles.xhtml
</from-view-id>
<navigation-case>
<from-outcome>next</from-outcome>
<to-view-id>/main/uploadFiles/wizardSecondStepUploadFiles.xhtml
</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<from-view-id>
/main/uploadFiles/wizardSecondStepUploadFiles.xhtml
</from-view-id>
<navigation-case>
<from-outcome>previous</from-outcome>
<to-view-id>
/main/uploadFiles/wizardFirstStepUploadFiles.xhtml
</to-view-id>
</navigation-case>
</navigation-rule>

Esta es un JSF estándar que permite la navegación a la página del segundo paso en el caso
de los resultados que viene, y volver a la primera en caso de que el resultado anterior.

La captura de pantalla de el primer paso del asistente es el siguiente:

Ahora vamos a crear un archivo vacío (usando el mostrado previamente plantilla) vuelva a
llamar wizardSecondStepUploadFiles.xhtml y agregue el siguiente código:

<h:form>
<ui:include src="showCurrentContactFiles.xhtml">
<ui:param name="edit" value="true"/>
<ui:param name="columns" value="2"/>
</ui:include>
<a:commandLink ajaxSingle="true" action="previous"
reRender="uploadImagesWizard" style="float:left;"
styleClass="image-command-link">
<h:graphicImage value="/img/previous.png"/>
<h:outputText value="#{messages['previous']}"/>
</a:commandLink>
</h:form>

Se puede notar que hay un archivo (utilizando etiqueta la de inclusión Facelets ui:include)
con dos parámetros que se pasan, eso es porque vamos a volver a utilizar el código para
mostrar la lista de los archivos para otra característica (se verá más adelante).

El otro componente para volver al primer paso es commandLink.

Ahora, vamos a crear el archivo showCurrentContactFiles.xhtml utilizando la plantilla


vacía y agregar el siguiente código:

<a:outputPanel style="width: 500px; height: 400px;


overflow: auto;" layout="block">
<rich:dataGrid value="#{filesListHelper.files}"
var="file" columns="#{columns}">
<a:outputPanel layout="block" rendered="#{edit==true}"
style="text-align: center;">
<h:outputText value="#{file.fileName}"/>
<br/><br/>
<h:inputTextarea value="#{file.description}"
style="width: 150px; height: 50px;"/>
</a:outputPanel>
</rich:dataGrid>
<h:outputText value="#{messages['noFilesFound']}"
rendered="#{empty filesListHelper.files}"/>
</a:outputPanel>

Aquí tenemos rich:dataGrid con el número de columnas como un parámetro Facelets


(columnas) que hace que un cuadro de edición para cada archivo cargado con
h:inputTextarea para que el usuario agregue la descripción del archivo.

Como se puede ver (el código en negrita), este panel se muestra sólo cuando el parámetro de
Facelets editar se establece en true. Más tarde agregará otro panel para administrar el caso
de que modificar es el valor false.

Este es el resultado con algunos archivos asociados:

Creación del panel modal


Queremos mostrar el asistente de carga dentro de un panel modal. Por lo tanto, vamos a
empezar a crear el código para mostrarlo.

Vamos a crear un nuevo archivo (con la plantilla vacía que hemos visto) dentro de la carpeta
/view/main/uploadFiles/, el cual llamaremos uploadFilesModalPanel.xhtml, y
escribiremos el código siguiente dentro de la etiqueta ui:component:

<rich:modalPanel id="uploadFilesMP"
minHeight="300"
minWidth="350"
autosized="true"
moveable="true"
resizeable="false">
<f:facet name="header">
<h:outputText value="#{messages['uploadNewFiles']}"/>
</f:facet>
<!-- my code -->
</rich:modalPanel>

Aquí, he definido el componente rich:modalPanel estableciendo valores para ID y algunos


atributos como minWidth, minheight, autosized, muebles, y de tamaño variable. Además, he
añadido la f: tag faceta de personalizar la cabecera del panel.

Esta es una muestra de lo que hemos hecho:


El código para abrir el panel de modos de transporte es muy sencillo.

Vamos a abrir el archivo contactEdit.xhtml y agregue el nuevo grupo de barra de


herramientas al último componente rich:toolBar:

<rich:toolBarGroup location="right">
<a:commandLink
onclick="#{rich:component('uploadFilesMP')}.show();"
styleClass="image-command-link">
<h:graphicImage value="/img/upload.png"/>
<h:outputText value="#{messages['uploadFiles']}"/>
</a:commandLink>
</rich:toolBarGroup>

La línea resaltada es la que hace el truco para cerrar el panel (por ejemplo, mediante un botón
en su interior). Podemos utilizar el mismo código, pero llame el JavaScript hide() en lugar del
método show(), muy sencillo realmente.

Recuerde que para que funcione, tenemos que incluir el archivo en nuestra página, así que
vamos a abrir el archivo home.xhtml y agregue el código siguiente al final, después de la
etiqueta de cierre </h:panelGrid>:

<ui:include src="main/uploadFiles/uploadFilesModalPanel.xhtml" />

Componentes de control sin JavaScript


Otra forma de controlar el componente rich:modalPanel (y los otros componentes de la API
de JavaScript, que permiten el control de los mismos) es utilizar el componente
rich:componentControl. Permite, en efecto, para llamar a funciones JavaScript de un
componente después de un evento específico.

Hagamos un ejemplo de uso mediante una llamada al show() la función de nuestro panel
modal utilizando rich:componentControl:

<a:commandLink styleClass="image-command-link">
<h:graphicImage value="/img/upload.png"/>
<h:outputText value="#{messages['uploadFiles']}"/>
<rich:componentControl for="uploadFilesMP"
event="onclick" operation="show"/>
</a:commandLink>

Al insertar el componente dentro de a:commandLink, automáticamente conectado a ella, sin


embargo, también es posible especificar el componente a adjuntar para que use el atributo
attachTo:

<a:commandLink id="showPanelBtn" styleClass="image-command-link">


<h:graphicImage value="/img/upload.png"/>
<h:outputText value="#{messages['uploadFiles']}"/>
</a:commandLink>
<rich:componentControl for="uploadFilesMP" attachTo="showPanelBtn"
event="onclick" operation="show"/>

Inserción de la asistente dentro del panel de modal


Ahora estamos listos para insertar el asistente en el interior del panel modal y permitir una
navegación AJAX utilizando las normas de navegación estándar. Vamos a usar los
componentes a:include para hacer esto posible!

Vamos a abrir el archivo /view/main/uploadFiles/uploadFilesModalPanel.xhtml de


nuevo y sustituiremos el comentario <!-- my code --> con el código que incluye (a la
manera de Ajax!), el primer paso del asistente:

<a:outputPanel id="uploadFilesWizard">
<a:include ajaxRendered="true"
viewId="/main/uploadFiles/wizardFirstStepUploadFiles.xhtml"/>
</a:outputPanel>

Ahora, vamos a agregar el botón de cierre en el primer paso del asistente (al archivo
/view/main/uploadFiles/wizardFirstStepUploadFiles.xhtml):

<a:commandLink onclick="#{rich:component('uploadFilesMP')}.hide();"
style="float:right;" styleClass="image-command-link">
<h:graphicImage value="/img/close.png" />
<h:outputText value="#{messages['close']}" />
</a:commandLink>

Esto es sólo el código estándar que hemos visto para el cierre de un panel de modal (observa
la llamada al método hide() ).

En la segunda etapa, también queremos salvar los cambios antes de cerrar el panel, por lo que
hay que agregar el código ( al archivo
/view/main/uploadFiles/wizardSecondStepUploadFiles.xhtml ) es ligeramente diferente:

<a:commandLink
action="#{filesListHelper.updateList}"
oncomplete="#{rich:component('uploadFilesMP')}.hide();"
style="float:right;" styleClass="image-command-link">
<h:graphicImage value="/img/close.png"/>
<h:outputText value="#{messages['finish']}"/>
</a:commandLink>

Aquí, el marco primero llama al método de acción filesListHelper.updateList y


después (onComplete) cierra el panel.

Aquí están las dos capturas de pantalla de los pasos dentro del panel modal:
Terminando la función de carga de archivos
La última funcionalidad que vamos a agregar es el panel de archivos para mostrar (o editar si
está en el modo de edición) los archivos asociados a un contacto.

Para hacer eso, vamos a utilizar lo que hemos visto hasta ahora, así que vamos a empezar a
crear un nuevo archivo XHTML con un panel llamado showFilesModalPanel.xhtml modal
que contiene el código siguiente:

<ui:component
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:a="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich">

<rich:modalPanel id="showFilesMP" minHeight="400"


minWidth="500" autosized="true"
moveable="true" resizeable="false">
<f:facet name="header">
<h:outputText value="#{messages['showFiles']}"/>
</f:facet>

<h:form>
<a:outputPanel id="showFilesWizard">
<ui:include src="showCurrentContactFiles.xhtml">
<ui:param name="edit"
value="#{homeSelectedContactHelper.
selectedContactEditing}"/>
<ui:param name="columns" value="3"/>
</ui:include>
<br/>
<h:panelGroup style="float:right;">
<a:commandLink action="#{filesListHelper.updateList}"
oncomplete="#{rich:component('showFilesMP')}.hide();"
rendered="#{homeSelectedContactHelper.
selectedContactEditing}"
styleClass="image-command-link">
<h:graphicImage value="/img/files.png"/>
<h:outputText value="#{messages['save']}"/>
</a:commandLink>
<rich:spacer width="5"/>
<a:commandLink action="previous"
onclick="#{rich:component('showFilesMP')}.hide();"
styleClass="image-command-link">
<h:graphicImage value="/img/close.png"/>
<h:outputText value="#{messages['close']}"/>
</a:commandLink>
</h:panelGroup>
</a:outputPanel>
</h:form>
</rich:modalPanel>
</ui:component>

Aquí, hemos creado un grupo especial de paneles modales que incluye el archivo
showCurrentContactFiles.xhtml ( que hemos creado) para ver los archivos en las
diferentes rejillas. Esta vez, el parámetro Facelets edit depende de la propiedad
homeSelectedContactHelper.selectedContactEditing (como se ve en el código en
negrita) así, se establece en true en el modo edit, pero false en caso contrario.

Después de ui:include, tenemos dos botones, uno es el botón Save (sólo se muestra
cuando se está en el modo edit ) y el otro es el botón Close.

Es hora de implementar la recarga del panel cuando no se está en el modo edit dentro del
archivo showCurrentContactFiles.xhtml, vamos a abrirlo de nuevo y agregaremos el
siguiente código justo después de la etiqueta <rich:dataGrid ..>:

<a:outputPanel layout="block"
rendered="#{edit==false}"
style="text-align: center;">
<h:outputText value="#{file.fileName}"
style="font-weight: bold;"/>
<br/><br/>
<h:outputText value="#{file.description}" escape="false"/>
<br/><br/>
<s:link action="#{fileDownloadHelper.download}"
styleClass="image-command-link">
<f:param name="cid" value="#{file.id}"/>
<h:graphicImage value="/img/download.png"/>
<h:outputText value="#{messages['download']}"/>
</s:link>
</a:outputPanel>

Este panel se revisualiza cuando el parametro Facelets edit se establece en false.

El método download del bean FileDownloadHelper que hemos implementado es el


siguiente:

public void download() {


ContactFile contactFile = entityManager.find(ContactFile.class,
contactFileId);
try {
// Get the file
File file = new File(appOptions.getFileSavePath() +
contactFile.getId());
long fileLength = file.length();
// Create the stream
FileInputStream fileIS = new FileInputStream(file);
// Get the data
byte fileContent[] = new byte[(int) fileLength];
fileIS.read(fileContent);
// Stream the content
FacesContext facesContext =
FacesContext.getCurrentInstance();
if (!facesContext.getResponseComplete()) {
HttpServletResponse response = (HttpServletResponse)
facesContext.getExternalContext().getResponse();
response.setContentType(contactFile.getFileType());
response.setContentLength((int) fileLength);
response.setHeader("Content-disposition",
"attachment; filename=" + contactFile.getFileName());

ServletOutputStream out;

out = response.getOutputStream();
out.write(fileContent);
out.flush();

facesContext.responseComplete();

}
} catch (IOException e) {
e.printStackTrace();
}
}

Es útil para la descarga de archivos pequeños (menor que Integer.MAX_VALUE), para los
grandes es mejor utilizar un servlet o un recurso Seam a la medida.

El último paso es añadir el código para abrir el panel de modos de transporte, tanto en la vista y
en modo de edición.

Vamos a abrir el archivo contactView.xhtml y vamos a agregar otra etiqueta


rich:toolBarGroup dentro de rich:toolBar::

<rich:toolBarGroup location="right">
<a:commandLink
oncomplete="#{rich:component('showFilesMP')}.show();"
ajaxSingle="true" reRender="showFilesPanel"
styleClass="image-command-link">
<f:setPropertyActionListener value="#{null}"
target="#{filesListHelper.files}"/>
<h:graphicImage value="/img/files.png"/>
<h:outputText value="#{messages['files']}"/>
</a:commandLink>
</rich:toolBarGroup>
commandLink obligará a que se vuelva a leer la lista de archivos (mediante el establecimiento
a null) y volverá a reconstruir a showFilesPanel para sincronizar los cambios, después de
que el panel modal será abierto por el código JavaScript onComplete.

Aquí hay una captura de pantalla de la barra de herramientas final: rich:toolBar en el modo
vista:

El mismo commandLink se agrega en el archivo contactEdit.xhtml, cerca del botón


Upload, tal como aparece en la pantalla siguiente:

Nuestro nuevo panel está listo, vamos a ver cómo se ve en el modo vista:

Y en el modo de edición aparece como sigue:

Resumen
En este capítulo, hemos terminado nuestra aplicación.

Hasta ahora, hemos aprendido a utilizar los componentes de RichFaces y cómo


personalizarlas.
En los próximos capítulos, vamos a tocar temas como la creación y personalización de temas,
técnicas avanzadas y una introducción a el kit de desarrollo de componentes, a fin de
desarrollar nuestros componentes JSF AJAX utilizando el marco RichFaces.

Potrebbero piacerti anche