You are here

Mapping server side resources with Shale Remoting

Simon Hutchinson's picture
Simon Hutchinson

Introduction

This article is not intended to be an in-depth analysis of the Apache Shale web application framework but rather to show a method for increased modularisation of your JSF components.

In adopting Java Server Faces for their web client Alfresco have chosen an architecture that allows developers to customise and extend the application using a component-based approach. There is a good Alfresco wiki page describing how to package and deploy such extensions for single file deployment.

This example will show you a method for packaging resources such as JavaScript or .css files as part of a jar file along with your JSF component and using a library to load these resources directly from the jar. There are a number of technologies available to allow for such resource loading such as weblets however this article is going to show you a method using Apache's Shale Remoting whilst hopefully proving a gentle introduction to component development in Alfresco.

Background

You have been given the extremely important task of creating a new input component for the Alfresco client that will echo the text entered into a text input box upon the input box losing focus as shown in the image below:

Modify Content Properties

Step 1 - A simple prototype

First we knock up some HTML that will accomplish our requirement.
An HTML input box will pass its value to a JavaScript function when the text box loses focus and a div will display the resulting "echoed" message.
Note: that the echo div has its style property set to display: none making it initially invisible to the client, the echo function will update this value to inline to make it visible.

<input type="text" onblur="echo(this.value);" name="myechoinput"/>
<div id="echo" style="display: none; color: red;">

Next we need to implement the JavaScript for performing the echo. To simplify our task we are going to use the prototype.js library (This is massive overhead but suitable for this example which is showing how to load resources) and write our function in a single JavaScript file named echo.js. Prototype.js provides the nice $ utility method that returns the element with the matching id from our document, in this case the "echo" div.

function echo (s) {
    var echoDiv = $("echo");
    echoDiv.style.display = "inline";
    echoDiv.style.color = "red";
    echoDiv.innerHTML = "<span>" + s + "</span>";
}

Step 2 - Creating the JSF component

Now that we have the basic HTML and JavaScript we need to implement this as a JSF component.

Our intention is to provide the complete solution as a single jar file containing the Java classes, the JSF faces-config.xml and the JavaScript files (echo.js and prototype.js). The resulting structure of our jar will look like this:

> com > ixxus > blog > shale > IxxusComponentConstants.class > generator
> EchoTextFieldGenerator.class > renderer > EchoRenderer.class > META-INF
> faces-config.xml > com > ixxus > blog > shale > scripts > echo.js > prototype.js

Notice that as well as our packaged class files the META-INF directory of our jar will contain both the faces-config.xml and our required JavaScript resources.

A JSF Renderer is the object responsible for rendering your component such that it is visible to the client device. The code for this is shown below

package com.ixxus.blog.shale.renderer;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.Renderer;

import org.apache.shale.remoting.Mechanism;
import org.apache.shale.remoting.XhtmlHelper;

/** * @author Simon Hutchinson */

public class EchoRenderer extends Renderer {

private static final String ECHO_SCRIPT_RESOURCE = "/META-INF/com/ixxus/blog/shale/scripts/echo.js"
private static final String PROTOTYPE_RESOURCE = "/META-INF/com/ixxus/blog/shale/scripts/prototype.js";
private static final String SIZE_KEY = "size";

private static XhtmlHelper helper = new XhtmlHelper();

public void encodeEnd(FacesContext context, UIComponent component)
throws IOException {

int size = (Integer) component.getAttributes().get(SIZE_KEY);
String clientId = component.getClientId(context);
ResponseWriter writer = context.getResponseWriter();

/*Resource loading with Shale */

helper.linkJavascript(context, component, writer,
Mechanism.CLASS_RESOURCE, PROTOTYPE_RESOURCE);
helper.linkJavascript(context, component, writer,
Mechanism.CLASS_RESOURCE, ECHO_SCRIPT_RESOURCE);

/*The text input component */

writer.startElement("input", component);
writer.writeAttribute("id", clientId, null);
writer.writeAttribute("type", "text", null);
writer.writeAttribute("name", clientId, null);
writer.writeAttribute("size", size, null);
writer.writeAttribute("maxlength", maxLength, null);
writer.writeAttribute("onblur", "echo(this.value);", null);

/*Initially hidden div for echoing input */

writer.startElement("div", component);
writer.writeAttribute("id", "echo", null);
writer.writeAttribute("style", "display: none;", null);
writer.endElement("div");
}
}

Lines 34 to 46 use helper methods on the faces ResponseWriter class to assist with rendering the requisite mark-up, however the real magic can be seen on lines 27 to 31 where we use the linkJavascript method from Shale's XhtmlHelper class to load our JavaScript resources.

The method parameters include the FacesContext, the UIComponent and the ResponseWriter. The last two parameters are worth exploring in a little more detail

Mechanism.CLASS_RESOURCE tells Shale that we want to load a resource from the classpath i.e from /WEB-INF/classes or in our case from a jar file beneath /WEB-INF/lib (see the Shale Remoting javadocs for other Mechanisms that can be used).

The final argument is the resource id in string format. So to reference our echo.js resource we provide a string representing its location on the classpath in this case /META-INF/com/ixxus/blog/shale/scripts/echo.js.

So what is actually rendered to the browser to allow our resource to be loaded for our Alfresco client?

Firstly Shale uses the mapping of the FacesServlet which for Alfresco is /faces/* and secondly the mapping for ClassResourceProcessor, as this has not been overridden in Alfresco the default mapping of /static/* is used.

So the final mark-up returned will look like this:

<script src="/alfresco/faces/static/META-INF/com/ixxus/blog/shale/scripts/echo.js" type="text/JavaScript">

So when the browser makes the request it will first be mapped to the FacesServlet, then to the ClassResourceProcessor and then finally to a location on the classpath, in this case META-INF/com/ixxus/blog/shale/scripts/echo.js which is the location of the file within our jar.

Other methods on the XhtmlHelper allow for different types of resources to be loaded for example a stylesheet can be loaded in a similar fashion by using the linkStylesheet method.

As we are generating an input component that will use our new renderer we extend Alfresco's TextFieldGenerator (see below) specifying our renderer using the component's setRendererType method.

package com.ixxus.blog.shale.generator;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

import org.alfresco.web.app.servlet.FacesHelper;
import org.alfresco.web.bean.generator.TextFieldGenerator;
import org.alfresco.web.ui.common.ComponentConstants;

import com.ixxus.blog.shale.IxxusComponentConstants;

/** * @author Simon Hutchinson */

public class EchoTextFieldGenerator extends TextFieldGenerator

@Override
@SuppressWarnings("unchecked")
public UIComponent generate(FacesContext context, String id) {

UIComponent component = context.getApplication().createComponent(
ComponentConstants.JAVAX_FACES_INPUT);
component
.setRendererType(IxxusComponentConstants.IXXUS_ECHO_RENDERER);

FacesHelper.setupComponentId(context, component, id);

component.getAttributes().put("size", getSize());

return component;
}
}

Our component is now complete.

Step 3 - Make JSF aware of our new components

JSF needs to be made aware of our generator and renderer so we create a faces-config.xml file and place it in the root of our jar files's META-INF directory which is where Alfresco requires it to be.

<managed-bean>
<description>
Bean that generates an echo-ready text field component
</description>

<managed-bean-name>EchoTextFieldGenerator</managed-bean-name>
<managed-bean-class>com.ixxus.blog.shale.generator.EchoTextFieldGenerator</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>size</property-name>
<value>35</value>
</managed-property>

</managed-bean>

<render-kit>
<renderer>
<component-family>javax.faces.Input</component-family>
<renderer-type>com.ixxus.blog.shale.renderer.EchoRenderer</renderer-type>
<renderer-class>com.ixxus.blog.shale.renderer.EchoRenderer</renderer-class>
</renderer>

</render-kit>

Step 4 - Deployment and configuration

Our new jar file can now be placed with Alfresco's WEB-INF/lib directory along with the shale-remoting jar (this example used shale-remoting-1.0.4.jar).

To allow a text property defined in your content model to use the new component you need to specify the name of the managed-bean that references our new EchoTextFieldGenerator as the value of the component-generator attribute within the property sheet declaration in you web-client-config-custom.xml file.

For example:

<property-sheet>
<show-property name="ixxus:testInput" component-generator="EchoTextFieldGenerator" />
</property-sheet>

Note: That the value of the component-generator attribute is the same as the managed-bean-name in faces-config.xml.

In the exmaple above when the ixxus:testInput property is displayed in the Alfesco web client it will be generated with our EchoTextFieldGenerator and rendered with our EchoRenderer with the required JavaScript files loaded via Shale-remoting from our jar file.

Notes:

This example was tested with Alfresco Enterprise 3.0.1.
All the sources required to build a working jar file are included in ixxus-echo.zip.

Building the source.

To build the jar you will need to have apache ant installed and the alfresco sdk available.

  1. Unzip ixxus-echo.zip to your file system.
  2. Edit the build.properties file and set alfresco.libdir property to point a directory containing alfresco-web-client.jar and alfresco-repository.jar (note: these jars can be found beneath the lib/server directory of the sdk).
  3. Open a shell or command window and navigate the project root (i.e the directory containing build.xml). Simply type ant and the project will be built - you will find the resulting jar at target/ixxus-echo.jar

Add new comment