JBoss.orgCommunity Documentation
WebUI is the name of GateIn's own webframework. GateIn Portal is built with it and also many applications available in the GateIn platform suites. Using its own framework means that the way the portal is build will not interfere with any web technology used by the portlet deployed. Using a particular technology would have make it more difficult for a user to use a different version (newer or older) of the same technology. If one choose to create WebUI portlets, here is some documentation. In general we recommend to use JSF as the framework of choice to develop new portlets.
The WebUI framework is a component tree based framework. The key aspects of WebUI are :
Events based flow
Components configuration by annotation
Section 6.3, “Groovy Templates” for rendering
Portlet API friendly
Section 6.4, “Portlet Lifecycle” : GateIn portlets are built with WebUI
Section 6.4, “Portlet Lifecycle” : GateIn portal itself is a WebUI application
How-to: Section 6.6, “Create a WebUI Portlet” : Learn how to write your own app with WebUI
It is very easy to create and manage Ajax calls in our framework. Just a few lines to write in your template file and your java class. For simple Ajax update of a component, you don't even have to write any line of JavaScript.
Our portlets can use specific
ActionListener
s to receive and process Ajax calls. To do that, you must
create an inner static class named following this convention :
action name followed by ActionListener
Example :
ParentClass
is the class in which you are writing.
static public class SaveActionListener extends EventListener<ParentClass>
Don't forget to declare this listener in the configuration of your portlet, like this :
listeners = ParentClass.SaveActionListener.class
along with the correct annotation
ComponentConfig
,
EventConfig
, etc.,
For example, the configuration for
UIAccountForm
:
...
@ComponentConfig(
lifecycle = UIFormLifecycle.class,
template = "system:/groovy/webui/form/UIFormTabPane.gtmpl",
initParams = {
@ParamConfig(
name = "AccountTemplateConfigOption",
value = "app:/WEB-INF/conf/uiconf/account/webui/component/model/AccountTemplateConfigOption.groovy"
),
@ParamConfig(
name = "help.UIAccountFormQuickHelp",
value = "app:/WEB-INF/conf/uiconf/account/webui/component/model/UIAccountFormQuickHelp.xhtml"
)
},
events = {
@EventConfig(listeners = UIAccountForm.SaveActionListener.class ),
@EventConfig(listeners = UIAccountForm.ResetActionListener.class, phase = Phase.DECODE),
@EventConfig(listeners = UIAccountForm.SearchUserActionListener.class, phase = Phase.DECODE)
}
)
...
Inside this class, you will have to create an
execute
method like this :
public void execute(Event<ParentClass> event) throws Exception
This method is called every time the listener gets an event from the client, hence you can process this event in it, using the even attribute. Use it to get parameters from a form in your client, to modify the status of your portlet, etc.,
Possible ways to use the event attribute :
String value = event.getRequestContext().getRequestParameter("name"); // to get a value from a form
ParentClass parent = event.getSource(); // to get the parent object (the portlet that threw and caugth the event)
UIMyComponent portal = parent.getAncestorOfType(UIMyComponent.class); // to get any node in the hierarchy of UIComponents
If your action has to update an element on your client's
interface, you must call
addUIComponentToUpdateByAjax()
at the end of the
execute
method:
event.getRequestContext().addUIComponentToUpdateByAjax(uicomponent);
The target component must be provided as parameter (the component that will be updated). We will come back on this later.
You must create one inner action listener class for each Ajax call you want to handle on this portlet. All these classes must be declared in the configuration annotations of the main class, otherwise you will get an error.
Done. Your portlet is ready to accept Ajax calls from your client.
Your server being configured to receive Ajax calls, you must configure the client interface to make these calls.
In the groovy template file associated with your portlet class
(
ParentClass
here), you just have to add :
uicomponent.event("YourOperation"); // YourOperation is the same as in the ActionListener class (Save in our example above)
in a groovy code block. The event function will create an
url
starting
with
javascript:
so you have to make sure this
code can be executed in your
environment.
If your operation must update the content of a component, you have to make sure that the target component is well rendered. Basically, just type this :
uicomponent.renderChild(UITargetComponent.class) ;
in a groovy code block.
UITargetComponent
is the class of the component that will be updated when
event.getRequestContext().addUIComponentToUpdateByAjax(uicomponent) ;
is called. Hence,
uicomponent
must be of type
UITargetComponent
. If this component is not rendered by default, when the
portlet loads, don't forget to set its
rendered
attribute to false :
mycomponent.setRendered(false);
in the constructor of your portlet.
All the javascript is managed by the file
02eXoresources:javascript/eXo/portal/PortalHttpRequest.js
in
the portal project.
In this class, you will find 4 functions/classes (detailed below):
PortletResponse
PortalResponse
AjaxRequest
HttpResponseHandler
and 6 functions:
ajaxGet(url, callback)
This is the main entry method for every Ajax calls to the GateIn Portal It is simply a dispatcher method that fills some init fields before calling the doRequest() method
ajaxPost(formElement, callback) // Calls doRequest with an url in POST mode
This method is called when a HTTP POST should be done but in an AJAX case some maniputalions are needed. Once the content of the form is placed into a string object, the call is delegated to the doRequest() method
doRequest(method, url, queryString, callback)
The doRequest() method takes incoming request from GET and POST calls The second argument is the URL to target on the server The third argument is the query string object which is created out of a form element, this value is not null only when there is a POST request.
An AjaxRequest object is instanciated, it holds the reference to the XHR method
An HttpResponseHandler object is instantiated and its methods like ajaxResponse, ajaxLoading, ajaxTimeout are associated with the one from the AjaxRequest and will be called by the XHR during the process method
ajaxAbort()
Cancels the current request
ajaxAsyncGetRequest(url, async)
Allows to create and execute a sync or async GET request
ajaxRedirect(url)
A simple javascript redirection with window.location.href that are the entry points of these classes. You shouldn't have to call explicitly these functions, since the template file and the portlet class manage everything.
This class doesn't contain any method. On creation, it just gets the response elements from the xml returned by Ajax, and store them in the corresponding attributes :
portletId
portletTitle
portletMode // View, Edit, Help or Config
portletState // Decode, Render
portletData // The updated data to put in the component
script //The javascript code to update the component
blocksToUpdate // An array containing the containers to update with this script
You can access these attributes just by calling them from your
PortletResponse
instance.
Contains an array of
PortletResponse
s (
portletResponses
) and two other attributes :
data // Data to update
script // Javascript code to update
By far the most important class of this file. Wraps the XMLHttpRequest object with some functions and attributes, to make it easier to use. You can find the complete documentation here : http://www.ajaxtoolbox.com/request/documentation.php
This class provides methods to handle the Ajax response.
executeScript // execute some javascript
updateBlocks // update some html components
ajaxTimeout // a function called when the timeout of the ajax call exceeds. Just cancel the request
ajaxResponse // creates a PortalResponse object from the data from the Ajax request
ajaxLoading // shows the loading popup and mask layer
{PortalResponse} | |--->{PortletResponse} | |--->{PortletResponse} | |-->{portletId} | |-->{portletTitle} | |-->{portletMode} | |-->{portletState} | | | |-->{Data} | | | | | |--->{BlockToUpdate} | | | |-->{blockId} | | | |-->{data} | | | | | |--->{BlockToUpdate} | |--->{Script} | |--->{Data} | | | |--->{BlockToUpdate} | | |-->{blockId} | | |-->{data} | | | |--->{BlockToUpdate} |--->{Script}
If you have several actions that need to appear in a popup, you can use this technique to manage the different popup windows easily:
Create a UIPopupAction
in your main portlet class:
addChild(UIPopupAction.class, null, null);
and render it in your template file:
uicomponent.renderChild(UIPopupAction.class) ;
By default, this just create an empty container (popup) that will receive the new content by Ajax.
Get this component in your action listener class, and update its content:
UIPopupAction uiPopupAction = uiMainPortlet.getChild(UIPopupAction.class) ; uiPopupAction.activate(UIReferencesList.class, 600) ;
UIReferenceList is the component that will appear in the popup. You don't have to declare it in the main portlet class. The activate method takes care of the creation of the component, and its rendering in the popup window. See the javadoc for more information on this class.
Make this component updatable by Ajax:
event.getRequestContext().addUIComponentToUpdateByAjax(uiPopupAction) ;
For each component that you want that component to appear in a popup window, add a action listener class and repeat the steps above with the appropriate component type.
This article gives a glance at the Groovy language, and explains how to configure the portlet and and the groovy template.
It's recommended to read also Section 6.2, “AJAX in GateIn Framework” in order to understand better the communication between the Groovy Template and the portlet.
The structure of a template is very easy :
The HTML code
zero or more groovy language code blocks, enclosed by <% ... %>
The HTML code in the template doesn't have to contain the
html
, or
body
tags. Hence, you can use a groovy template for a component that
will be rendered in another component.
Example :
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <% import org.exoplatform.webui.core.UIComponent; def currentPage = uicomponent.getCurrentPage(); ... %> ... <div class="$uicomponent.skin" id="UIPortalApplication"> <%uicomponent.renderChildren();%> </div>
Groovy is a scripting language for Java. Here are a few examples on how to use it, but you can find more information in the full documentation .
This language looks like Java a lot, so it's very easy to use. Examples :
Variables definition :
int min = 1; def totalPage = uicomponent.getAvailablePage(); String name = "uiPortlet"; categories = uicomponent.getItemCategories(); String columns = uicomponent.getColumns();
Other expressions :
for(category in categories) { ... } for(i in min..max) { ... } // min and max are int variables println "</div>" ; println """ <div class="Item"> <div class="OverflowContainer"> """; <%=uicomponent.getToolbarStyle();%> // <%= to avoid a call of println method import org.exoplatform.portal.config.model.PageNode;
The configuration of a portlet is partly made with
ComponentConfig
annotations (others are
ComponentConfigs, EventConfig, etc). One of the parameters
of
this annotation is called
template
, where you can define the path
to
the template file associated with this portlet.
To specify this parameter to your portlet, just add this statement to your configuration annotation, for example in src:/portlet/exoadmin/src/main/java/org/exoplatform/applicationregistry/webui/component/ you find UIApplicationForm.java :
@ComponentConfig( lifecycle = UIFormLifecycle.class, template = "system:/groovy/webui/form/UIFormWithTitle.gtmpl", events = { @EventConfig(listeners = UIApplicationForm.SaveActionListener.class), @EventConfig(phase = Phase.DECODE, listeners = UIApplicationForm.CancelActionListener.class) } )
You see that the path is in the namespace called "system", "system" is a reference to the portal webapp. In this webapp you find some reusable groovy templates, just open the folder src:/web/portal/src/main/webapp/groovy/webui/form/ to see them.
As you want to create your own template, create a groovy
file
in your webbapp and refer to it. Please use the
namespace "app"
for refering to the same webapp as your
component. GateIn
always puts the component templates in a
folder like
"/webapp/groovy/your_portlet_name
/webui/component".
template = "app:/groovy/your_portlet_name/webui/component/your_component.gtmpl"
You can now edit your template file.
As we said before, the template file is composed of HTML code and groovy code blocks. There are a few things more that you need to know to fully link your portlet with your template.
If your template defines the UI of a component, you have
an access to
this component instance (the java object) using
the variable
uicomponent
. This should be the case
almost
all the time, but we recommend that you check that
your java
class inherits from UIComponent before you use this
variable.
With this uicomponent
variable, you can access all
the
attributes and functions of your component, to use them
in your
template. Example : UIPageIterator.gtmpl:
<% def currentPage = uicomponent.getCurrentPage(); %> ... <a href="<%=uicomponent.event("ShowPage","$currentPage")%>" class="Icon LastTopPageIcon"> <span></span> </a>
This example shows that uicomponent
can be used to make Ajax
calls,
thanks to the event
method. See
Section 6.2, “AJAX in GateIn Framework”
for more details.
Another variable that you can use is
ctx
. It gives access to the context in which the
template is processed. Hence, you can get some elements
like
the request, the Javscript manager, or the resource
resolver (ctx.appRes).
Examples :
<% def rcontext = ctx.getRequestContext() ; context.getJavascriptManager().importJavascript('GateIn.webui.UIPopupWindow'); ctx.appRes(popupId + ".title."+ title); %>
If you use your template to define the user interface of a
component that includes a form, you can access the instance
of
UIForm in a variable named
uiform
. The UIForm class provides the
methods, begin()
and
end()
,
that write the HTML tags of the
form. Your form class must
inherit from
UIForm
, in this class you add the input elements (fields,
checkboxes,
lists) which you wish to use in your form. In
your groovy
template you can render your input elements using
uiform.renderField(field)
The goal of this chapter is not to talk about the Portlet API specification lifecycle but more about GateIn UI framework to easily develop portlets.
The web framework used here has been completely developed by GateIn and perfectly suits the portal environment, it even allows to send events from the portlet UIComponents to the Portal ones.
Of course using the GateIn web framework to build portlets is not necessary and any other web framework that supports portlet environment can be used. But all GateIn portlets that are part of GateIn products are developed using that framework and we provide several UI components that can be used in different abstracted contexts such as the portal itself or some portlets.
This chapter is not a tutorial on how to write portlets, it will go in the details of the code implementation and logic; hence it is intended for advanced developers. It is also advised to read the Section 5.1, “Portal Lifecycle” article before as the that article explains concepts that are similar and top hierarchy classes that are shared.
The main entry point for configuring a portlet is in the portlet.xml file located in the portlet application WAR. Every portlet that shall be built using the GateIn web framework must reference the PortletApplicationController . The portlet configuration such as the root component is defined in a configuration.xml file. The path to this configuration.xml file is defined in the init-param " webui.configuration " of porlet.xml.
<portlet> <description xml:lang="EN">Content Portlet</description> <portlet-name>ContentPortlet</portlet-name> <display-name xml:lang="EN">Content Portlet</display-name> <portlet-class>org.exoplatform.webui.application.portlet.PortletApplicationController</portlet-class> <init-param> <name>webui.configuration</name> <value>/WEB-INF/conf/portlet/content/ContentPortlet/webui/configuration.xml</value> </init-param> </portlet>
The structure of the configuration.xml file is exactly the same as the webui-configuration.xmlwhich we have already introduced in the Section 5.1, “Portal Lifecycle” article. In the case of the content portlet it looks like:
<webui-configuration> <application> <ui-component-root>org.exoplatform.content.webui.component.UIContentPortlet</ui-component-root> <state-manager>org.exoplatform.webui.application.portlet.ParentAppStateManager</state-manager> </application> </webui-configuration>
The PortletApplicationController class extends the GenericPortlet class defined in the Portlet API specification.
All methods like processAction() or render() are delegated to the PortletApplication. The creation and caching inside the WebController object is described in the following method:
/** * try to obtain the PortletApplication from the WebAppController. * * If it does not exist a new PortletApplication object is created, init and cached in the * controller */ private PortletApplication getPortletApplication() throws Exception { PortalContainer container = PortalContainer.getInstance() ; WebAppController controller = (WebAppController)container.getComponentInstanceOfType(WebAppController.class) ; PortletApplication application = controller.getApplication(applicationId_) ; if(application == null) { application = new PortletApplication(getPortletConfig()) ; application.onInit() ; controller.addApplication(application) ; } return application ; }
When a portlet, that is deployed in GateIn Portal, is using the GateIn web framework then all methods calls go through the PortletApplication object which extends the WebuiApplication.
The code of the method in PortletApplication is described here. The business logic is shown in the javadoc:
/** * The processAction() method is the one modelled according to the Portlet API specification * * The process is quite simple and here are te different steps done in the method: * * 1) The current instance of the WebuiRequestContext (stored in a ThreadLocal in the class) is referenced * 2) A new request context of type PortletRequestContext (which extends the class WebuiRequestContext) is * created as a child of the current context instance * 3) The new context is place inside the ThreadLocal and hence overides its parent one there, * only for the portlet request lifeciclye * 4) The method onStartRequest() is called in all the ApplicationLifecycle objects referenced in the webui * configuration XML file * 5) The StateManager object (in case of portlet it is an object of type ParentAppStateManager) is used to get the RootComponent * also referenced in the XML configuration file * 6) The methods processDecode(UIApplication, WebuiRequestContext) and processAction(UIApplication, WebuiRequestContext) * are then called * 7) Finally, a flag, to tell that the processAction phase was done, in the context is set to true and the parent * context is restored in the Threadlocal */ public void processAction(ActionRequest req, ActionResponse res) throws Exception { WebuiRequestContext parentAppRequestContext = WebuiRequestContext.getCurrentInstance() ; PortletRequestContext context = createRequestContext(req, res, parentAppRequestContext) ; WebuiRequestContext.setCurrentInstance(context) ; try { for(ApplicationLifecycle lifecycle : getApplicationLifecycle()) { lifecycle.onStartRequest(this, context) ; } UIApplication uiApp = getStateManager().restoreUIRootComponent(context) ; context.setUIApplication(uiApp) ; processDecode(uiApp, context) ; if(!images/context.isResponseComplete() &&!images/ context.getProcessRender()) { processAction(uiApp, context) ; } } finally { context.setProcessAction(true) ; WebuiRequestContext.setCurrentInstance(parentAppRequestContext) ; } }
The PortletRequestContext extends WebuiRequestContext class and acts as a wrapper on top of all the portlet request information:
/** * In this method we try to get the PortletRequestContext object from the attribute map of the parent * WebuiRequestContext. * * If it is not cached then we create a new instance, if it is cached then we init it with the correct * writer, request and response objects * * We finally cache it in the parent attribute map * */ private PortletRequestContext createRequestContext(PortletRequest req, PortletResponse res, WebuiRequestContext parentAppRequestContext) throws IOException { String attributeName = getApplicationId() + "$PortletRequest" ; PortletRequestContext context = (PortletRequestContext) parentAppRequestContext.getAttribute(attributeName) ; Writer w = null ; if(res instanceof RenderResponse){ RenderResponse renderRes = (RenderResponse)res; renderRes.setContentType("text/html; charset=UTF-8"); w = renderRes.getWriter() ; } if(context!images/= null) { context.init(w, req, res) ; } else { context = new PortletRequestContext(this, w, req, res) ; parentAppRequestContext.setAttribute(attributeName, context) ; } context.setParentAppRequestContext(parentAppRequestContext) ; return context; }
In the PortletApplication, the line
UIApplication uiApp = getStateManager().restoreUIRootComponent(context); asks the StateManager defined for the portlet to get the UI root component. In the case of a portlet the root component must extend UIPortletApplication.
public class ParentAppStateManager extends StateManager { /** * This method simply delegate the call to the same method of the parent WebuiRequestContext */ @SuppressWarnings("unchecked") public UIApplication restoreUIRootComponent(WebuiRequestContext context) throws Exception { WebuiRequestContext pcontext = (WebuiRequestContext) context.getParentAppRequestContext() ; return pcontext.getStateManager().restoreUIRootComponent(context) ; }
Hence this is the PortalStateManager that will also handle the extraction of the root component.
public UIApplication restoreUIRootComponent(WebuiRequestContext context) throws Exception { context.setStateManager(this) ; WebuiApplication app = (WebuiApplication)context.getApplication() ; /* * If the request context is of type PortletRequestContext, we extract the parent context which will * allow to get access to the PortalApplicationState object thanks to the session id used as the key for the * syncronised Map uiApplications */ if(context instanceof PortletRequestContext) { WebuiRequestContext preqContext = (WebuiRequestContext) context.getParentAppRequestContext() ; PortalApplicationState state = uiApplications.get(preqContext.getSessionId()) ; PortletRequestContext pcontext = (PortletRequestContext) context ; String key = pcontext.getApplication().getApplicationId() ; UIApplication uiApplication = state.get(key) ; if(uiApplication!images/= null) return uiApplication; synchronized(uiApplications) { ConfigurationManager cmanager = app.getConfigurationManager() ; String uirootClass = cmanager.getApplication().getUIRootComponent() ; Class type = Thread.currentThread().getContextClassLoader().loadClass(uirootClass) ; uiApplication = (UIApplication)app.createUIComponent(type, null, null, context) ; state.put(key, uiApplication) ; } return uiApplication ; } }
The render method business logic is quite similar to processAction().
/** * The render method business logic is quite similar to the processAction() one. * * 1) A PortletRequestContext object is created (or extracted from the cache if it already exists) * and initialized * 2) The PortletRequestContext replaces the parent one in the WebuiRequestContext ThreadLocal object * 3) If the portal has already called the portlet processAction() then the call to all onStartRequest of * the ApplicationLifecycle has already been made, otherwise we call them * 4) The ParentStateManager is also used to get the UIApplication, as we have seen it delegates the call * to the PortalStateManager which caches the UI component root associated with the current application * 5) the processRender() method of the UIPortletApplucaton is called * 6) Finally, the method onEndRequest() is called on every ApplicationLifecycle referenced in the portlet * configuration XML file and the parent WebuiRequestContext is restored * */ public void render(RenderRequest req, RenderResponse res) throws Exception { WebuiRequestContext parentAppRequestContext = WebuiRequestContext.getCurrentInstance() ; PortletRequestContext context = createRequestContext(req, res, parentAppRequestContext) ; WebuiRequestContext.setCurrentInstance(context) ; try { if(!context.hasProcessAction()) { for(ApplicationLifecycle lifecycle : getApplicationLifecycle()) { lifecycle.onStartRequest(this, context) ; } } UIApplication uiApp = getStateManager().restoreUIRootComponent(context) ; context.setUIApplication(uiApp) ; if(!context.isResponseComplete()) { UIPortletApplication uiPortletApp = (UIPortletApplication)uiApp; uiPortletApp.processRender(this, context) ; } uiApp.setLastAccessApplication(System.currentTimeMillis()) ; } finally { try { for(ApplicationLifecycle lifecycle : getApplicationLifecycle()) { lifecycle.onEndRequest(this, context) ; } } catch (Exception exception){ log.error("Error while trying to call onEndRequest of the portlet ApplicationLifecycle", exception); } WebuiRequestContext.setCurrentInstance(parentAppRequestContext) ; } }
The processRender() call made on the UIPortletApplication is shown now:
/** * The default processRender for an UIPortletApplication handles two cases: * * A. Ajax is used * ---- * If Ajax is used and that the entire portal should not be re rendered, then an AJAX fragment is * generated with information such as the portlet id, the portlet title, the portlet modes, the window * states as well as the HTML for the block to render * * B. A full render is made * ---- * a simple call to the method super.processRender(context) which will delegate the call to all the * Lifecycle components * */ public void processRender(WebuiApplication app, WebuiRequestContext context) throws Exception { WebuiRequestContext pContext = (WebuiRequestContext)context.getParentAppRequestContext(); if(context.useAjax() &&!images/pContext.getFullRender()) { Writer w = context.getWriter() ; Set<UIComponent> list = context.getUIComponentToUpdateByAjax() ; if(list!images/= null) { if(getUIPopupMessages().hasMessage()) context.addUIComponentToUpdateByAjax(getUIPopupMessages()) ; for(UIComponent uicomponent : list) { renderBlockToUpdate(uicomponent, context, w) ; } return ; } } super.processRender(context) ; }
The Portlet Specifications aims at defining portlets that can be used by any JSR-168 (Portlet 1.0) or JSR-286 (Portlet 2.0) portlet container. Most Java EE portals include one, it is obviously the case for GateIn Portal which includes the GateIn Portlet Container supporting the two versions. This chapter gives a brief overview of the Portlet Specifications but portlet developers are strongly encouraged to read the JSR-286 Portlet Specification .
GateIn Portal is fully JSR-286 compliant, which means any JSR-168 or JSR-286 portlet behaves as it is mandated by the respective specifications inside the portal.
A portal can be seen as pages with different areas, and inside areas, different windows, and each window having one portlet:
A portlet can have different view modes. Three modes are defined by the JSR-286 specification:
view - generates markup reflecting the current state of the portlet.
edit - allows a user to customize the behavior of the portlet.
help - provides information to the user as to how to use the portlet.
Window states are an indicator of how much page real-estate a portlet consumes on any given page. The three states defined by the JSR-168 specification are:
normal - a portlet shares this page with other portlets.
minimized -a portlet may show very little information, or none at all.
maximized - a portlet may be the only portlet displayed on this page.
The tutorials contained in this chapter are targeted toward portlet developers. Although they are a good starting and reference point, it is highly recommend that portlet developers read and understand the JSR-286 Portlet Specification .
This section describes how to deploy a portlet in GateIn
Portal. You will
find the
SimplestHelloWorld
portlet in the
examples
directory at the root of your GateIn Portal binary
package.
This example is using Maven to compile and build the web archive. If you don't have Maven already installed, you will find a version for your operating system here
To compile and package the application, go to the
SimplestHelloWorld
directory and type
mvn package
.
Once successfully packaged, the result should be
available in:
SimplestHelloWorld/target/SimplestHelloWorld-0.0.1.war
. Simply copy that file into
JBOSS_HOME/server/default/deploy
, then start JBoss Application Server if it was not
already started.
Now you will need to create a new page and add that portlet on it.
Like other Java Platform, Enterprise Edition (Java EE) applications, portlets are packaged in WAR files. A typical portlet WAR file can include servlets, resource bundles, images, HTML, JavaServer™ Pages ( JSP™ ), and other static or dynamic files. The following is an example of the directory structure of the HelloWorldPortlet portlet:
|-- SimplestHelloWorld-0.0.1.war | `-- WEB-INF | |-- classes | | `-- org | | `-- gatein | | `-- portal | | `-- examples | | `-- portlets | |`-- SimplestHelloWorldPortlet.class | |-- po
rtlet.xml | `-- we
b.xml
![]() | The compiled Java class implementing javax.portlet.Portlet (through javax.portlet.GenericPortlet ) |
![]() | This is the mandatory descriptor files for portlets. It is used during deployment.. |
![]() | This is the mandatory descriptor for web applications. |
The following file is the
SimplestHelloWorldPortlet/src/main/java/org/gatein/portal/examples/portlets/SimplestHelloWorldPortlet.java
Java source.
package org.gatein.portal.examples.portlets;
import java.io.IOException;
import java.io.PrintWriter;
import javax.portlet.GenericPortlet;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;public class SimplestHelloWorldPortlet extends GenericPortlet
{
public void doView(RenderRequest request,RenderResponse response) throws IOException
{PrintWriter writer = response.getWriter();
writer.write("Hello World !");
writer.close();
}
}
![]() |
All portlets must implement the
|
![]() |
As we extend from
|
![]() | Use the RenderResponse to obtain a writer to be used to produce content. |
![]() | Write the markup to display. |
![]() | Closing the writer. |
Portlets are responsible for generating markup
fragments, as they are
included on a page and are
surrounded by other portlets. In
particular, this
means that a portlet outputting HTML must not
output
any markup that cannot be found in a
<body>
element.
GateIn Portal requires certain descriptors to be included in a portlet WAR file. Thise descriptors are defined by the Jave EE (web.xml) and Portlet Specification (portlet.xml).
The following is an example of the
SimplestHelloWorldPortlet/WEB-INF/portlet.xml
file. This file must adhere to its definition in the
JSR-286 Portlet
Specification. You may define more than
one portlet application in this
file:
<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" version="2.0"> <portlet> <portlet-name>SimplestHelloWorldPortlet</portlet-name> <portlet
-class> org.gatein.portal.examples.portlets.SimplestHelloWorldPortlet </portlet-class> <support
s> <mime-type>text/html</mime-type> </supports> <portlet
-info> <title>Simplest Hello World Portlet</title> </portlet-info> </portlet> </portlet-app>
![]() | Define the portlet name. It does not have to be the class name. |
![]() | The Fully Qualified Name (FQN) of your portlet class must be declared here. |
![]() |
The
|
![]() |
When rendered, the portlet's title is displayed
as the header in
the portlet window, unless it is
overridden programmatically. In
this example, the
title would be
|
Now we will add more features to the previous example and
also use a JSP
page to render the markup. We will use the
portlet tag library to generate
links to our portlet in
different ways and use the other standard portlet
modes.
This example can be found in the directory
JSPHelloUser
.
Use
mvn package
then copy
JSPHelloUser/target/JSPHelloUser-0.0.1.war
in the
deploy
directory of JBoss Application Server.
Point your brwoser
to
, you should see the following:
The
EDIT
button only appears with logged-in users, which is
not the case
on the screenshot
The structure doesn't change much at the exception of adding some JSP files detailed later.
The JSPHelloUser portlet contains the mandatory portlet application descriptors. The following is an example of the directory structure of the JSPHelloUser portlet:
JSPHelloUser-0.0.1.war |-- META-INF | |-- MANIFEST.MF |-- WEB-INF | |-- classes | | `-- org | | `-- gatein | | `-- portal | | `-- examples | | `-- portlets | | `-- JSPHelloUserPortlet.class | |-- portlet.xml | `-- web.xml `-- jsp |-- edit.jsp |-- hello.jsp |-- help.jsp `-- welcome.jsp
The following file is the
JSPHelloUser/src/main/java/org/gatein/portal/examples/portlets/JSPHelloUserPortlet.java
Java source. It is split in different pieces.
package org.gatein.portal.examples.portlets;
import java.io.IOException;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.UnavailableException;
public class JSPHelloUserPortlet extends GenericPortlet
{
public void doView(RenderRequest request, RenderResponse response)throws PortletException, IOException
{
String sYourName = (String) request.getParameter("yourname");if (sYourName != null)
{
request.setAttribute("yourname", sYourName);
PortletRequestDispatcher prd =getPortletContext().getRequestDispatcher("/jsp/hello.jsp");
prd.include(request, response);
}
else
{
PortletRequestDispatcher prd = getPortletContext().getRequestDispatcher("/jsp/welcome.jsp");
prd.include(request, response);
}
}
...
![]() | As in the first portlet, we override the doView method. |
![]() |
Here we try to obtain the value of the render
parameter names
|
![]() | Very similar to the Servlet way, we get a request dispatcher on a file located within the web archive. |
![]() | The last step is to perform the inclusion of the markup obtained from the JSP. |
We have seen the
VIEW
portlet mode, the spec defines two other modes that can
be used called
EDIT
and
HELP
. In order to enable those modes, they will need to be
defined in the
portlet.xml
descriptor as we will see later. Having those modes
defined will enable the
corresponding buttons on the
portlet's window.
The generic portlet that is inherited dispatches the
different views to
methods named:
doView
,
doHelp
and
doEdit
. Let's watch the code for those two last portlet modes.
...
protected void doHelp(RenderRequest rRequest, RenderResponse rResponse) throws PortletException, IOException,
UnavailableException
{
rResponse.setContentType("text/html");
PortletRequestDispatcher prd = getPortletContext().getRequestDispatcher("/jsp/help.jsp");
prd.include(rRequest, rResponse);
}
protected void doEdit(RenderRequest rRequest, RenderResponse rResponse) throws PortletException, IOException,
UnavailableException
{
rResponse.setContentType("text/html");
PortletRequestDispatcher prd = getPortletContext().getRequestDispatcher("/jsp/edit.jsp");
prd.include(rRequest, rResponse);
}
...
If you have read the portlet specification carefully you should have notice that portlet calls happen in one or two phases. One when the portlet is just rendered, two when the portlet is actionned then rendered. An action phase is a phase where some state change. The render phase will have access to render parameters that will be passed each time the portlet is refreshed (with the exception of caching capabilities).
The code to be executed during an action has to be implemented in the processAction method of the portlet.
...public void processAction(ActionRequest aRequest, ActionResponse aResponse) throws PortletException, IOException,
UnavailableException
{String sYourname = (String) aRequest.getParameter("yourname");
aResponse.setRenderParameter("yourname", sYourname);
}
...
![]() |
|
![]() | Here we retrieve the parameter obtained through an action URL . |
![]() |
Here we need to keep the value of
|
The
help.jsp
and
edit.jsp
files are very simple, they simply display some text.
Note that we used CSS
styles as defined in the portlet
specification. It ensures that the portlet
will look
"good" within the theme and accross portal vendors.
<div class="portlet-section-header">Help mode</div>
<div class="portlet-section-body">This is the help mode, a convenient place to give the user some help information.</div>
<div class="portlet-section-header">Edit mode</div>
<div class="portlet-section-body">This is the edit mode, a convenient place to let the user change his portlet preferences.</div>
Now let's have a look at the landing page, it contains the links and form to call our portlet:
<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet" %> <div class="portlet-section-header">Welcome !</div> <br/> <div class="portlet-font">Welcome on the JSP Hello User portlet, my name is GateIn Portal. What's yours ?</div> <br/> <div class="portlet-font">Method 1: We simply pass the parameter to the render phase:<br/> <a href="<port
let:renderURL><portlet:param name="yourname" value="John Doe"/> </portlet:renderURL>">John Doe</a></div> <br/> <div class="portlet-font">Method 2: We pass the parameter to the render phase, using valid XML: Please check the source code to see the difference with Method 1. <portlet:rende
rURL var="myRenderURL"> <portlet:param name="yourname" value='John Doe'/> </portlet:renderURL> <br/> <a href="<%= m
yRenderURL %>">John Doe</a></div> <br/> <div class="portlet-font">Method 3: We use a form:<br/> <portlet:actio
nURL var="myActionURL"/> <form action="
<%= myActionURL %>" method="POST"> <span class="portlet-form-field-label">Name:</span> <input class="portlet-form-input-field" type="text" name="yourname"/> <input class="portlet-form-button" type="Submit"/> </form> </div>
![]() | Since we will use the portlet taglib, we first need to declare it. |
![]() |
The first method showed here is the simplest
one,
|
![]() |
In this method instead of having a tag within
another tag, which
is not XML valid, we use the
|
![]() |
The variable
|
![]() | The third method mixes form submission and action request. Like in the second method, we used a temporary variable to put the created URL into. |
![]() | The action URL is used in the HTML form. |
On the third method, first the action phase is triggered then later in the request, the render phase is triggered, which output some content back to the web browser based on the available render parameters.
Such an example is available in examples/JSFHelloUser, it uses the JBoss Portlet Bridge. The configuration is slightly different from a JSP application, since it is a bit tricky it is usally a good idea to copy an existing application that starting from scratch.
First, as any JSF application, the file
faces-config.xml
is required. It includes
the following required information in it:
<faces-config>
...
<application>
<view-handler>org.jboss.portletbridge.application.PortletViewHandler</view-handler>
<state-manager>org.jboss.portletbridge.application.PortletStateManager</state-manager>
</application>
...
</faces-config>
The portlet bridge libraries must be available and are
usually bundled with the
WEB-INF/lib
directory of the web archive.
The other difference compare to a regular portlet application, can be found in the portlet descriptor. All details about it can be found in the JSR-301 specification that the JBoss Portlet Bridge implements.
<?xml version="1.0" encoding="UTF-8"?> <portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" version="2.0"> <portlet> <portlet-name>JSFHelloUserPortlet</portlet-name> <portlet-class>javax.portlet.faces.GenericFacesPortlet</portlet-class> <supports> <mime-type>text/html</mime-type> <portlet-mode>view</portlet-mode> <portlet-mode>edit</portlet-mode> <portlet-mode>help</portlet-mode> </supports> <portlet-info> <title>JSF Hello User Portlet</title> </portlet-info> <init-param> <name
>javax.portlet.faces.defaultViewId.view</name> <value>/jsf/welcome.jsp</value> </init-param> <init-param> <name
>javax.portlet.faces.defaultViewId.edit</name> <value>/jsf/edit.jsp</value> </init-param> <init-param> <name
>javax.portlet.faces.defaultViewId.help</name> <value>/jsf/help.jsp</value> </init-param> </portlet> </portlet-app>
![]() |
All JSF portlets define
|
![]() | This is a mandatory parameter to define what's the default page to display. |
![]() | This parameter defines which page to display on the 'edit' mode. |
![]() | This parameter defines which page to display on the 'help' mode. |
TODO: Create example (This one doesn't exist anymore). Overall this chapter need to be reviewed, any taker ? :)
This example is based on the testPortlet in portal/trunk/portlet/test.
On Eclipse (or any IDE), create a new Java Project, and create this folder tree :
src | main | |- java | |- resources | |- webapp
Create the pom.xml, at root level of the project, like this :
<project>
<parent>
<groupId>org.exoplatform.portal</groupId>
<artifactId>config</artifactId>
<version>trunk</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>org.gatein.example.portlet.testRomain</artifactId>
<packaging>war</packaging>
<version>1.0.0</version>
<name>gatein.portlets.testRomain</name>
<description>Romain Test Portlet</description>
<dependencies>
<dependency>
<groupId>org.exoplatform.portal</groupId>
<artifactId>exo.portal.webui.portal</artifactId>
<version>${org.exoplatform.portal.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.exoplatform.portal</groupId>
<artifactId>exo.portal.webui.eXo</artifactId>
<version>${org.exoplatform.portal.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>testRomain</finalName>
</build>
</project>
In java/testRomain/portlet/component/, we will create the UITestRomainPortlet.java file for the portlet :
package org.gatein.example.portlet.testRomain;
import org.exoplatform.webui.config.annotation.ComponentConfig;
import org.exoplatform.webui.core.lifecycle.UIApplicationLifecycle;
import org.exoplatform.webui.core.UIPortletApplication;
//this part is configuration of the portlet, we set the path to the template groovy.
@ComponentConfig(
lifecycle = UIApplicationLifecycle.class,
template = "app:/groovy/testRomain/portlet/UITestRomainPortlet.gtmpl"
)
public class UITestRomainPortlet extends UIPortletApplication {
public UITestRomainPortlet() throws Exception {
}
}
In src/main/webapp, create the groovy template for the portlet. The path to this file must match the path you set in the java file, in our case : groovy/testRomain/portlet/UITestRomainPortlet.gtmpl
<div id="<%=uicomponent.getId();%>"> HelloWorld </div>
Create the folder skin in src/main/webapp. We don't fill it now, but in this folder, you can put css stylesheet and images.
Create the folder WEB-INF/classes/locale in src/main/webapp. We don't fill it now, but in this folder, you can put language properties files. See Section 5.3, “Internationalization Configuration”.
Create the file configuration.xml in WEB-INF/conf/portlet/testPortletRomain/. Content of tag <ui-component-root> must match your package organization.
<webui-configuration>
<application>
<ui-component-root>testRomain.portlet.component.UITestRomainPortlet</ui-component-root>
<state-manager>org.exoplatform.webui.application.portlet.ParentAppStateManager</state-manager>
</application>
</webui-configuration>
In WEB-INF, create file portlet.xml :
<?xml version="1.0" encoding="UTF-8"?>
<portlet-app version="1.0" xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd">
<portlet>
<description xml:lang="EN">Test Portlet Romain</description>
<portlet-name>TestRomain</portlet-name>
<display-name xml:lang="EN">Test Portlet Romain</display-name>
<portlet-class>org.exoplatform.webui.application.portlet.PortletApplicationController</portlet-class>
<init-param>
<name>webui.configuration</name>
<!-- must match the path to configuration file -->
<value>/WEB-INF/conf/portlet/testPortletRomain/configuration.xml</value>
</init-param>
<expiration-cache>0</expiration-cache>
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>help</portlet-mode>
</supports>
<supported-locale>en</supported-locale>
<resource-bundle>locale.testRomainPortlet</resource-bundle>
<portlet-info>
<title>TestPortletRomain</title>
<short-title>TestPortlet</short-title>
<keywords>test</keywords>
</portlet-info>
</portlet>
</portlet-app>
In WEB-INF, create file web.xml :
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!-If define the Portlet Application name MUST end with .par->
<display-name>test</display-name>
<description> This application is a portlet. It can not be used outside a portal.
This web.xml file is mandatory in each .par archive file. </description>
<listener>
<listener-class>org.exoplatform.services.portletcontainer.impl.servlet.PortletApplicationListener</listener-class>
</listener>
<servlet>
<servlet-name>PortletWrapper</servlet-name>
<servlet-class>org.exoplatform.services.portletcontainer.impl.servlet.ServletWrapper</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PortletWrapper</servlet-name>
<url-pattern>/PortletWrapper</url-pattern>
</servlet-mapping>
</web-app>
Compile your portlet, deploy it, and add it to the portal.
Now, we will add a button in the portlet. This button will open a popup with a message inside.
In the groovy template, add this code :
<div class="UIAction"> <div class="ActionContainer"> <div class="ActionButton"> <div class="LightBlueStyle"> <div class="ButtonLeft"> <div class="ButtonRight"> <div class="ButtonMiddle"> <a href="<%=uicomponent.event("OpenPopup", "")%>">Open Popup</a> </div> </div> </div> </div> </div> </div> </div>
In the java file, in @ComponentConfig, add this code :
events = {
@EventConfig(listeners = UITestRomainPortlet.OpenPopupActionListener.class)
}
Remark : XXXActionLister.class XXX must match the name you set for the event in the groovy.
static public class OpenPopupActionListener extends EventListener<UITestRomainPortlet> {
public void execute(Event<UITestRomainPortlet> event) throws Exception {
System.out.println("HelloWorld");
}
}
Now, we will add a popup which say "HelloWorld" when you click on the button.
First, create the groovy template of the popup : in webapp/groovy/testRomain/portlet, create UIHelloWorldPopupContent.gtmpl :
<div id="<%=uicomponent.getId();%>"> HelloWorld in a popup </div>
In java/testRomain/portlet/component, create the java file for the popup look like : {code} package testRomain.portlet.component;
package testRomain.portlet.component;
import org.exoplatform.webui.config.annotation.ComponentConfig;
import org.exoplatform.webui.core.UIComponent;
import org.exoplatform.webui.core.lifecycle.UIApplicationLifecycle;
@ComponentConfig(
lifecycle = UIApplicationLifecycle.class,
template = "app:/groovy/testRomain/portlet/UIHelloWorldPopupContent.gtmpl"
)
public class UIHelloWorldPopupContent extends UIComponent {
public UIHelloWorldPopupContent() throws Exception {
}
}
In UITestRomainPortlet.java, we will create the popup at the portlet creation (in the constructor) :
public UITestRomainPortlet() throws Exception {
UIPopupWindow popup = addChild(UIPopupWindow.class, null, null);
popup.setWindowSize(400, 300);
UIHelloWorldPopupContent popupContent = createUIComponent(UIHelloWorldPopupContent.class, null, null);
popup.setUIComponent(popupContent);
popup.setRendered(false);
At the beginning, we set the popup not visible. As you see, we add a children to the Portlet. So, if we want to see the content of it, we must add this in UITestPortletRomain.gtmpl :
<% uicomponent.renderChildren(); %>
This makes the portlet generate the content of all child components.
Change the treatment of the event, replace the println by :
public static class OpenPopupActionListener extends EventListener<UITestRomainPortlet> {
public void execute(Event<UITestRomainPortlet> event) throws Exception {
UITestRomainPortlet portlet = event.getSource();
UIPopupWindow popup = portlet.getChild(UIPopupWindow.class);
popup.setRendered(true);
popup.setShow(true);
}
}
When user clicks on the button, the popup is shown.
Redeploy the portlet and click on the button. You will see "HelloWorld in a popup" in a popup. If you don't change in the portlet, try to redeploy and reboot the tomcat server.