jboss richfaces capitulo 7 terminando la aplicacion

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:

Upload: henry-jimenez-sanchez

Post on 03-Jan-2016

191 views

Category:

Documents


2 download

TRANSCRIPT

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 va </a:outputPanel>

lue="#{group.description}"/>

</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" warn <rich:colorPicker

Class="warnmsg"/>

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.