Componentes personalizados en Echo2 – Parte II

21/11/2006

Desarrollo con Echo2
Este es el segundo de una serie de artículos acerca de la creación de componentes para Echo2:

En este artículo continuaremos examinando mas características que podemos añadir a los componentes para convertirlos en mas ricos de cara al usuario. Usaremos como componente de ejemplo el echopoint.ExpandableSection de EchoPointNG. Tal vez quieras bajarte su código fuente para estudiarlo según avanzamos.

Procesador de Mensajes

Echo2 tienen un motor JavaScript en el cliente que se encarga de mantener el lado del servidor y del cliente sincronizados. Con nuestro anterior componente de ejemplo RulerLine, el estado del componente no podía cambiar y por tanto no se necesitaba ningún código JavaScript.

Sin embargo ¿qué ocurre con un componaente más complejo que se pueda pulsar o editar o expandir? ¿Cómo son estas configuraciones del componente y cómo se reflejan los cambios de estado de vuelta en el servidor?

Aquí es donde aparece el motor cliente de Echo2 y es donde aparece su uso del Procesador de Mensajes de JavaScript.

Cuando se representa un componente en el servidor usando su método renderAdd(), se obtiene el XHTML y se envía como un mensaje XML al motor del lado cliente. Este tiene un código conocido como el EchoDomUpdate.MessageProcesor que es el responsable de interpretar el mensaje XML y de colocar el XHTML en el DOM del navegador.

No necesitas escribir nada de JavaScript para que esto ocurra. Es código estándar del framework.

Sin embargo puedes escribir tus propios procesadores de mensajes del lado cliente. Esto te permitirá realizar funciones extra como la adición y eliminación de manejadores de eventos XHTML o la inicialización del estado del componente en el cliente de algún modo.

Un procesador de mensajes es simplemente una función JavaScript con el nombre estandar de process.

Por ejemplo, el componente EPNG ExpandableSection tienen un procesador de mensajes llamado EPExpandableSection.MessageProcesor.process.

Cuando el motor cliente de Echo2 recibe un mensaje XML, este viene marcado con el nombre del procesador de mensajes a invocar. Es responsabilidad del procesador de mensajes el hacer algo con dicho mensaje. El elemento de representación del lado del servidor creará el mensaje XML para que sea enviado al procesador de mensajes del lado cliente. Por ejemplo, aquí tenemos un trozo de código de ExpandableSection que se llama cuando se añade el componente por primera vez a la jerarquía.

protected void createInitDirective(RenderingContext rc, ExpandableSection es, Style fallbackStyle) {
Element itemizedUpdateElement = rc.getServerMessage().getItemizedDirective(ServerMessage.GROUP_ID_POSTUPDATE, “EPExpandableSection.MessageProcessor”, “init”, new String[0], new String[0]);
Element itemElement = rc.getServerMessage().getDocument().createElement(“item”);
itemElement.setAttribute(“eid”, rc.getElementId());
itemElement.setAttribute(“expanded”, String.valueOf(es.isExpanded()));
ExpansionGroup group = (ExpansionGroup)rc.getRP(ExpandableSection.PROPERTY_EXPANSION_GROUP, fallbackStyle);
if (group != null) {
itemElement.setAttribute(“groupId”, group.getRenderId());
itemElement.setAttribute(“acoordionMode”, String.valueOf(group.isAcocordionMode()));
}
TitleBar tb = (TitleBar)rc.getRP(ExpandableSection.PROPERTY_TITLEBAR, fallbackStyle);
if (tb != null) {
itemElement.setAttribute(“titleBarId”, ContainerInstance.getElementId(tb));
}
itemizedUpdateElement.appendChild(itemElement);
}

Fíjate en la parte de getServerMessage().getItemizedDirective(...). Esto crea un mensaje de directiva XML y marca su procesador de mensajes como “EPExpandableSection.MessageProcesor”. Esto es lo que permite al motor del lado cliente conocer a qué procesador de mensajes invocar.

El código de arriba fija algunos atributos XML en el elemento XML basados en las propiedades de ExpandableSection. Más acerca de esto después.

Lo más normal es hacer un init o dispose de algún modo en el componente. Por ejemplo, aquí está el código JavaScript del cliente para el procesador de mensajes de ExpandableSection.

EPExpandableSection.MessageProcessor.process = function(messagePartElement) {
for (var i=0; i<messagePartElement.childNodes.length; ++i) {
if (messagePartElement.childNodels[i].nodeType == 1) {
switch (messagePartElement.childNodes[i].tagName) {
case “init”:
EPExpandableSection.MessageProcessor.processInit(messagePartElement.childNodes[i]);
break;
case “dispose”:
EPExpandableSection.MessageProcessor.processDispose(messagePartElement.childNodes[i]);
break;
case “expansion”:
EPExpandableSection.MessageProcessor.processExpansion(messageParteElement.childNodes[i]);
break;
}
}
}
};

Puede manejar 3 tipos diferentes de mensajes. Un mensajes XML init, un mensaje XML dispose y un mensaje XML expansion. Veamos el procesamiento del mensaje init.

EPExpandableSection.MessageProcessor.processInit = function(initMessageElement) {
//debugger;
for (var item=initMessageElement.firstChild; item; item=item.nextSibling) {
var elementId = item.getAttribute(“eid”);
EP.ObjectMap.destroy(elementoId);
var es = new EPExpandableSection(elementId);
if (item.getAttribute(“enabled”) == “false”) {
EchoDomPropertyStore.setPropertyValue(elementId, “EchoClientEngine.inputDisabled”, true);
}
var groupId = item.getAttribute(“groupId”);
if (groupId) {
es.setButtonGroup(elementId, groupId);
}
es.accordionMode = (item.getAttribute(“accordionMode”) == ‘true’);
es.titleBarId = item.getAttribute(“titleBarId”);
es.contentE = document.getElementById(elementId+’|Content’);
}
}

Este código recorre los hijos de nivel superior del mensajes XML. Para cada hijo de nivel superior comprueba el valor del atributo eid. Este es el id del componente. Entonces el código crea un objetos “del lado cliente” para el ExpandableSection y le fija varias propiedades según los valores del mensaje XML.

Como puedes ver el estado del servidor se transfiere al cliente mediante mensajes de directiva XML y el procesador de mensajes.

Siguiendo con el ejemplo completo, mira al atributo enabled en la directiva XML. Si este es false significa que el componente debería estar deshabilitado en el cliente. El código especial que aparece se asegura que el componente esté marcado como no disponible.

El otro mensaje del ciclo de vida es dispose. Este se crea en el método renderDispose() en el lado del servidor. Este es el modo que tiene el framework de indicar al elemento de representación que el componente no va a ser necesario más tanto en el lado del servidor como en el lado del navegador cliente.

El ExpandableSectionPeer hace lo siguiente en el servidor

public void renderDispose(RenderContext rc, ServerComponentUpdate update, Component component) {
super.renderDispose(rc, update, component);
createDisposeDirective(rc.getServerMessage(), component);
}

protected void createDisposeDirective(ServerMessage serverMessage, Component component) {
Element itemizedUpdateElement = serverMessage.getItemizedDirective(ServerMessage.GROUP_ID_PREREMOVE, “EPExpandableSection.MessageProcessor”, “dispose”, new String[0], new String[0]);
Element itemElement = serverMessage.getDocument().createElement(“item”);
itemElement.setAttribute(“eid”, ContainerInstance.getElementId(component));
itemizedUpdateElement.appendChild(itemElement);
}

Este código crea un mensaje de directiva XML de “dispose” para enviarlo al cliente y para ser manejado por el procesador de mensajes EPExpandableSection.MessageProcessor.

Esto permite al código del lado cliente limpiar cualquier recurso del componente del lado cliente como manejadores de evento, etc… Aquí tenemos el código del procesador de mensajes ExpandableSection:

EPExpandableSection.MessageProcessor.processDispose = function(disposeMessageElement) {
for (var item=disposeMessageElement.firstChild; item; item=item.nextSibling) {
var elementId = item.getAttribute(“eid”);
EP.ObjectMap.destroy(elementId);
}
};

Este código usa una librería JavaScript de EchoPointNG para limpiar el objeto del lado cliente.

Actualizaziones Parciales y Procesadores de Mensaje

Ahora todo componente que tiene un procesador de mensajes tendrá, por convenio, un init y un dispose, ya que esto refleja el ciclo de vida de prácticamente todos los componentes.

Pero ¿qué pasa con el resto de eventos o cambios de estado que ocurren en el servidor?

ExpandableSection tienen una propiedad ‘expansion‘ que se envía al lado cliente de representación del componente. Cuando es true, el código cliente debe expandir el área de contenido del componente. Cuando es false, debe contraer el área de contenido.

Ahora el componente ExpandableSection escucha a su modelo de expansion para controlar su estado de expansión. Aquí tenemos el código:

private class InternalExpansionModelListener implements ChangeListener {
/*
* @see nextapp.echo2.app.event.ChangeListener#stateChanged(nextapp.echo2.app.event.ChangeEvent)
*/
public void stateChange(ChangeEvent e) {
ExpansionModel expansionModel = (ExpansionModel) e.getSource();
//
// if an expansion model other than ours, ie the titlebar, tells
// us of an expansion event, then update out model.
if (expansionModel != null) {
if (expansionModel != getExpansionModel()) {
setExpanded(expansionModel.isExpanded());
} else {
// set the titlebar to reflect our expansion
if (getTitleBar() != null && getTitleBar().getExpansionModel() != null) {
getTitleBar().getExpansionModel().setExpanded(expansionModel.isExpanded());
}
}
firePropertyChange(EXPANDED_CHANGED_PROPERTY, null, null);
}
}
}
private ChangeListener internalExpansionListener = new InternalExpansionModelListener();

Lanza la propiedad EXPANDED_CHANGED_PROPERTY cuando el modelo cambia. Esto hace que el componente sea marcado como necesita actualización por el framework Echo2. El elemento de representación será invocado para representar el objeto sucio.

Supongamos que el estado de expansión es la única propiedad que ha cambiado y que la ExpandableSection ya existe en la gerarquía de componentes. En este caso se llamará al método renderUpdate(). Echémosle un ojo. Realmente se toma de su clase padre AbstractEchoPointContainerPeer.

public boolean renderUpdate(RenderContext rc, ServerComponentUpdate update, String targetId) {
boolean fullReplace = false;
if (update.hasUpdatedLayoutDataChildren()) {
fullReplace = true;
}
if (update.hasUPdatedProperties()) {
if (partialUpdateManager.canProcess(rc, update)) {
partialUpdateManager.process(rc, update);
} else {
fullReplace = true;
}
}
return renderUpdateBaseImpl(rc, update, targetId, fullReplace);
}

La explicación de este método es un poco complicada.

El objeto ServerComponentUpdate contiene información acerca de los cambios. En este caso la propiedad expanded ha cambiado. El elemento de representación utiliza un objeto PartialUpdateManager para determinar si puede manejar los cambios de propiedades.

El PartialUpdateManager contiene una serie de objetos PartialUPdateParticipan cuya clave son los nombres de propiedades. El que maneja la propiedad “expanded” es el siguiente:

partialUpdateManager.add(ExpandableSection.EXPANDED_CHANGE_PROPERTY, new PartialUpdateParticipant() {
/**
* @see nextapp.echo2.webcontainer.PartialUpdateParticipan@canRenderProperty(nextapp.echo2.webcontainer.RenderContext, nextapp.echo2.app.update.ServerComponentUpdate)
*/
public boolean canRenderProperty(RenderConext rc, ServerComponentUpdate update) {
return true;
}

/**
* @see nextapp.echo2.webcontainer.PartialUPdateParticipant#renderProperty(nextapp.echo2.webcontainer.RenderConext, nextapp.echo2.app.update.ServerComponentUpdate)
*/
public void renderProperty(RenderContext rc, ServerComponentUpdate update) {
ExpandableSection tb (ExpandableSection) update.getParent();
boolean isExpanded = tb.isExpanded();
// an XML message directive please to tell the popup to expand!
Element itemizedUpdateElement = rc.getServerMessage().getItemizedDirective(ServerMessage.GROUP_ID_POSTUPDATE, “EPExpandableSection.MessageProcessor”, “expansion”, new String[0], new String[0]);
Element itemElement = rc.getServerMessage().getDocument().createElement(“item”);
itemElement.setAttribute(“eid”, ContainerInstance.getElementId(tb));
itemElement.setAttribute(“expanded”, String.valueOf(isExpanded));
itemElement.appendChild(itemElement);
}
});

Lo que hace esto es comprobar las actualizaciones de la propiedad expanded. Coge el valor del componente y crea un mensaje de directiva XML para el ExpandableSection.MessageProcessor y le asigna la clave con el nombre expansion y se fija el valor del estado de expansión.

Este mensaje viaja al cliente y el código JavaScript del cliente es el responsable de actualizar el estado de ExpandableSection para abrirlo o cerrarlo, dependiendo del valor. Esto se realiza usando este código:

EPExpandableSection.MessageProcessor.processExpansion = function(disposeMessageElement) {
// debugger;
for (var item = disposeMessageElement.firstChild; item; item = item.nextSibling) {
var elementId = item.getAttribute(“eid”);
var expanded = (item.getAttribute(“expanded”) == ‘true’);
var es = EP.ObjectMap.get(elementId);
if (es != null) {
es.onTibleBarExpansion(expanded);
}
}
};

No se ha añadido XHTML al cliente. No se ha realizado ni un solo añadido o eliminación al DOM. Solamente se ha enviado al cliente el cambio de la propiedad expansion y éste se ha reflejado.

Este mecanismo de actualización parcial significa que las aplicaciones Echo2 pueden realizar grandes trucos visuales sin la sobrecarga de grandes mensajes XML o eliminación y adición extra de XHTML.

El framework de actualización de Echo2 y PartialUpdateManager en particular, es suficientemente potente para que sepa si una serie de cambios de propiedades se pueden manejar o no. En este caso si las actualziaciones no se pueden manejar por el PartialUpdateManager, entonces devolverá false y se lleavará a cabo una sustitución completa (eliminación DOM/adición DOM).

La frecuencia con la que se llevará a cabo una sustitución total depende de tí como diseñador del elemento de representación. Un control más fino de las propiedades puede significar más código y estado en el cliente y el servidor pero menos tráfico de mensajes XML/XHTML y menos actualizaciones del DOM.

Un control menos fino de actualizaciones de propiedades hacen que el código de representación sea más sencillo y puede ser más sensible a muchos cambios de propiedades.

Algo que habrán notado los lectores más astutos es que la creación de la directiva de mensaje XML realmente contiene un mensaje XML de múltiples partes.

El framework Echo2 hace esto para que su sistema de mensajería sea más eficiente. Supongamos que tenemos 20 ExpandableSections (en un grupo) y que una hay que abrirla y el resto hay que cerrarlas. Los mensajes de directiva XML para esto irán en un mensaje XML, uno por componente identificados por el elementId.

for (var i=0; i<messageParteElement.childNodes.length; i++) {
if (mesaageParteElement.childNodes[i].nodeType == 1) {
switch (messagePartElement.childNodes[i].tagName) {

}
}
}

El procesador de mensajes por tanto debe recorrer en un bucle cada parte del mensaje y ejecutar el código apropiado para cada elementId. Esto signifiaca mucha mas “eficiencia de mensajería” que mensajes individuales para cada componente.

Actualizaciones Parciales y actualizaciones DOM estándar

Algunas propiedades de los componentes se pueden manejar de un modo sencillo de manera estándar. Por ejemplo si cambiamos el color de fondo de un componente, generalmente solamente es necesario modificar el estilo de color de fondo del elemento XHTML.

Echo2 tiene varios PartialUpdateParticipant predefinidos como el ColorUpdate, el BorderUpdate y el InsetsUpdate. En esencia crean un mensaje DOM especial que simplemente cambia propiedades específicas de estilo. Por ejemplo el código de ColorUpdate es algo así:

public void renderProperty(RenderContext rc, ServerComponentUpdate update) {
Color color = (Color)update.getParent().getRenderProperty(Component.PropertyName);
String elementId = idSuffix == null ? ContainerInstance.getElementId(update.getParent()) : ContainerInstance.getElementId(update.getParent()) + idSuffix;
if (color == null) {
DomUpdate.renderStyleUpdate(rc.getServerMessage(), elementId, cssAttributeName, “”);
} else {
DomUpdate.renderStyleUpdate(rc.getServerMessage(), elementId, cssAttributeName, ColorRender.renderCssAttributeValue(color));
}
}

El código DomUpdate.renderStyleUpdate() crea para tí el XML de actualización de estilo DOM de un modo estándar que el EchoDomUpdate.MessageProcessor pueda entender.

Manejadores de Eventos del Lado Cliente

Supongamos que queremos representar un componente similar a un Button en el que se pueda pulsar, en cuyo caso enviará un evento al servidor, informándole acerca de la pulsación. Desde luego podríamos hacer esto:

<button onclick=”myfunction(event)”>Click me⪫button>

Realmente no todos los navegadores soportan la adición “automática” de manejadores de evento como la de arriba. Así que debemos añadir dinámicamente los manejadores de evento en la fase de inicialización (init) del procesador de mensajes del componente. El siguiente código está tomado del procesador de mensages del Button de Echo2.

EchoEventProcessor.addHandler(elementId, “click”, “EchoButton.processClick”);
EchoEventProcessor.addHandler(elementId, “keypress”, “EchoButton.processKeyPressed”);
EchoEventProcessor.addHandler(elementId, “mousedown”, “EchoButton.processPressed”);
EchoEventProcessor.addHandler(elementId, “mouseout”, “EchoButton.processRolloverExit”);
EchoEventProcessor.addHandler(elementId, “mouseover”, “EchoButton.processRolloverEnter”);
EchoEventProcessor.addHandler(elementId, “mouseup”, “EchoButton.processReleased”);

El motor cliente de Echo2 proporciona el EchoEventProcessor.addHandler para añadir eventos a elementos XHTML con nombre. El framework JavaScript maneja por tí los diferentes modos de añadir manejadores de eventos.

Así, cuando el ratón pasa sobre el botón (un evento mouseover) se llamará a la función JavaScript EchoButton.processRolloverEnter.

Ahora, cuando no se necesitará más el componente, los manejadores de eventos deben ser eliminados, de otro modo pueden ocrrurir errores de memoria. Aquí está el código para el manejador de la fase dispose del Button de Echo2.

EchoButton.MessageProcessor.processDispose = function (disposeMessageElement) {
for (var item = disposeMessageElement.firstChild; item; item = item.nextSibling) {
var elementId = item.getAttribute(“eid”);
EchoEventProcessor.removeHandler(elementId, “click”);
EchoEventProcessor.removeHandler(elementId, “keypress”);
EchoEventProcessor.removeHandler(elementId, “mousedown”);
EchoEventProcessor.removeHandler(elementId, “mouseout”);
EchoEventProcessor.removeHandler(elementId, “mouseover”);
EchoEventProcessor.removeHandler(elementId, “mouseup”);
}
}

Propiedades de Componente contra Propiedades de Representación de Componente

Los componentes Echo2 tienen propiedades normales Java tales como getBackground() y getFont(). Y se puede utilizar para fijar propiedades visuales para controlar cómo se comporta y aparece un componente. Y llamaremos a los getters y setters normales de Java para fijar estas propiedades, ej: component.setColor(Color.RED);

Echo2 tiene otro mecanismo llamado Styles (Estilos) que permite que se fijen y se apliquen propiedades visuales a una serie de componentes. Esto permite que un conjunto de propiedades visuales se apliquen a varios componentes.

Otra variación es esto son las StyleSheets (Hojas de Estilo) que son una colección de Styles clasificados por clase de componente y nombre de estilo. Se puede obtener más información relativa a este asunto en http://nextapp.com/platform/echo2/doc/tutorial/styles.html

Cuando el elemento de representación accede a las propiedades del componente para “representarlo” DEBE utilizar el método getRenderedProperty para acceder al valor de la propiedad, no a los métodos getter normales. Esto asegura que se siga el orden apropiado para buscar los valores de las proiedades. Este es el código de Component.getRenderedProperty() donde podemos ver el orden en acción.

public final Object getRenderProperty(String propertyName, Object defaultValue) {
Object propertyValue = localStyle.getProperty(propertyName);
if (propertyValue != null) {
return propertyValue;
}
if (sharedStyle != null) {
propertyValue = sharedStyle.getProperty(propertyName);
if (propertyValue != null) {
return propertyValue;
}
}
if (application != null) {
Style applicationStyle = applicationInstance.getStyle(getClass(), styleName);
if (applicationStyle != null) {
// Return style value specified in application.
propertyValue = applicationStyle.getProperty(propertyName);
if (propertyValue != null) {
return propertyValue;
}
}
}
return defaultValue;
}

Así, por ejemplo, para obtener el color de fondo de un componente debemos escribir:

Color background = (Color)mycomponent.getRenderedProperty(Component.PROPERTY_BACKGROUND);

y no esto:

Color background = mycomponent.getBackground();

Echo2 tiene una serie de clases de ayuda que puede manejar propiedades visuales estandar como Colores, Fuentes y Bordes en representaciones CSS. Por ejemplo aquí tenemos parte del código de representación del Button que trabaja con propiedades visuales:


border = (Border) button.getRenderProperty(AbstractButton.PROPERTY_BORDER);
foreground = (Color) button.getRenderProperty(AbstractButton.PROPERTY_FOREGROUND);
background = (Color) button.getRenderProperty(AbstractButton.PROPERTY_BACKGROUND);
font = (Font) button.getRenderProperty(AbstractButton.PROPERTY_FONT);
backgroundImage = (FillImage) button.getRenderProperty(AbstractButton.PROPERTY_BACKGROUND_IMAGE);
}

BorderRender.renderToStyle(cssStyle, border);
ColorRender.renderToStyle(cssStyle, foreground, background);
FontRender.renderTyStyle(cssTyle, font);
FillImageRender.renderToStyle(cssStyle, rc, this, button, IMAGE_ID_BRACKGROUND, backgroundImage, FillImageRender.FLAG_DISBLE_FIXED_MODE);
InsetsRender.renderToStyle(cssStyle, “padding”, (Insets)button.getRenderProperty(AbstractButton.PROPERTY_INSETS));

Vía

One Response to “Componentes personalizados en Echo2 – Parte II”


  1. […] Componentes personalizados en Echo2 – Parte II […]


Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: