Components
Author : Cedric Dumoulin
Date : 22 Sep. 2000
We want to be able to build web pages by assembling components. There are two kinds of components : coarse grain components and fine grain components. Coarse grain components are components made of several html instructions. For example, a component rendering an address or a header is a coarse grain component. Examples of fine grain components are an input/output field performing I18n formatting, a popup menu, ... For now, we only focus on coarse grain components.
Following are the main goals that we want to reach :
Following illustrate how we want to build a page by assembling components :
The page is made of a header, a menu and a body. Body itself is made of two others components. This is a simple example, and we want to be able to build more complex pages.
The servlet “include” mechanism already responds to some of
our goals. But, in order to reach them completely, we need some additional
materials.
Basically, a component is a JSP file that will be included in another file or component. We associate to component a “component context”, helping passing parameters. Component contexts, and parameters, are only visible inside the components. They are not visible by others sub-components, neither by parents.
Coarse grain component can be seen like a java method : you include / call the component, passing it some parameters. Unlike method, component can’t return something.
A component can be developed independently from its caller. You just have to specify which parameters are needed. This independence allows developing reusable components.
Component context contains properties associated to component, and only accessible by this component. Parent including a component can set properties.
Context is needed in order to be able to have independent component : like this, you don’t have to care if a component attribute (parameter) has the same name as another JSP attribute.
One can say that component context is the same as the jsp “page scope”. This is partially true : jsp “page scope” doesn’t allows addition of attributes outside the page, which forbid passing parameters from parent.
Furthermore, context will be used for future features, like associating JavaScript to a component.
As a component is a JSP file, all facilities provided by JSP can be used to write the component. In addition, the component can access its parameters, provided by its “parent”.
Following is an example of component :
<%@ taglib uri="/WEB-INF/components.tld" prefix="comp" %>
<br> This is a component
<br> It uses properties passed by its parent
<br> title = <comp:getAttribute name=”title” />
<br> This component has index number <comp:getAttribute name=”index” />
<br> That’s all folks !
This example is not very useful, it just illustrate how component work.
[todo : write a more realistic example]
A component can be included anywhere in a page or in another component. You need to use a special include tag, allowing passing parameters.
For example, to include previous component, write something like that :
<%@ taglib uri="/WEB-INF/components.tld" prefix="comp" %>
…
<comp:include page=”comp.jsp”>
<comp:putAttribute name=“title” value=”Welcome”>
<comp:putAttribute name=“index” value=”3”>
</comp:include>
…
There is nothing special to assemble components : you do it by including each component where you want it to reside.
A component used to layout other components can be call a “Layout Container”. We will define a set of basic layout containers, and reuse them whenever needed.
Examples of layout containers are :
o Header-menu-footer-body container
o Define a classical page
o Grid container
o Layout components in a fixed grid (table ?).
o Box container
o Components are “stacked” (horizontally or vertically)
o Flow container
o Components are rendered one after the other.
o Columns container
o Layout components in columns. You need to provide lists of components and the number of columns.
o …
Following is the code for the header-menu-footer-body container. Note that in this file, we only focus on the layout of components, not on their contents.
<%@ taglib uri="/WEB-INF/components.tld" prefix="comp" %>
<%-- parameters : title, header, menu, body, footer
--%>
<comp:useAttribute name="title" classname="String" />
<HTML>
<HEAD>
<title<comp:include attribute='title'/></title>
</HEAD>
<body>
<table border="0" width="100%" cellspacing="5">
<tr><td colspan="2"><comp:include attribute="header" /></td></tr>
<tr><td colspan="2"><hr></td></tr>
<tr>
<td width="120" valign="top">
<comp:include attribute='menu'/>
</td>
<td valign="top">
<comp:include attribute='body' />
</td>
</tr>
<tr><td colspan="2"><hr></td></tr>
<tr><td colspan="2"><comp:include attribute="footer" /></td></tr>
</table>
</body>
</html>
To use this container/component, include it and pass needed parameters, as in the example :
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/components.tld" prefix="comp" %>
<comp:include component="/layout/classicLayout.jsp" flush="true">
<comp:putAttribute name="title" value="index.title" />
<comp:putAttribute name="header" value="/common/header.jsp" />
<comp:putAttribute name="menu" value="/common/menu.jsp" />
<comp:putAttribute name="footer" value="/common/footer.jsp" />
<comp:putAttribute name="body" value="/bodies/index.jsp" />
</comp:include>
Components usually require some arguments to work properly. We provide a way to associate predefined arguments to a component. This association is a kind of “instance” of a component. Such instance is referenced by a name which can be used in include tags or in forward directives in struts-config.xml.
It is possible to declare a component instance, and use the same instance at different place.
All component instance declarations are done in a XML file. Syntax is similar as the one used in include tag. Following is an example of declaration :
<component-instances>
<!— Page instance description -->
<instance name="portal" path="classicLayout.jsp">
<putAttribute name="title" value="Welcome to our Portal Demo" />
<putAttribute name="header" value="header.jsp" />
<putAttribute name="footer" value="footer.jsp" />
<putAttribute name="menu" value="menu.jsp" />
<putAttribute name="body" value="body.jsp" />
</instance>
…
</component-instances>
You can see that the declaration is very similar than including a component. The declared name will be the instance name, and will allow you to identify the instance.
One XML file can contains several declarations. This allows easy modification of your instances.
Using a component instance is similar as using a component : you specify where you want to include your instance, like in the following example :
<%@ taglib uri="/WEB-INF/components.tld" prefix="comp" %>
<%-- Example of instance inclusion --%>
<comp:include instance="portal" flush="true"/>
In the include tag, you specify the name of the instance you want to include.
In the instance description file, you can partially instanciate a component, specifying only some of the parameters. Others parameters must be passed when including component.
You can also provide parameters in the description file, and override them while including instance. This gives default parameters for an instance.
In the following example, we override the title parameter :
<%@ taglib uri="/WEB-INF/components.tld" prefix="comp" %>
<%-- Example of component instanciation, passing directly parameters --%>
<comp:include instance="portal" flush="true">
<comp:putAttribute name="title" value="Overridden title" />
</comp:include>
It is possible to pass the name of an instance as another instance parameter.
In following example, portal’s menu is itself an instance of menu.jsp.
<component-instances>
…
<!-- Page instance description -->
<instance name="portal" path="classicLayout.jsp">
<putAttribute name="title" value="Welcome to our Portal Demo" />
<putAttribute name="header" value="header.jsp" />
<putAttribute name="footer" value="footer.jsp" />
<putAttribute name="menu" value="portalMenu" />
<putAttribute name="body" value="body.jsp" />
</instance>
…
<!-- Component instance description -->
<instance name="portalMenu" path="menu.jsp">
<putAttribute name="index" value="2" />
<putAttribute name="title" value="Portal Menu" />
</instance>
</component-instances>
With this mechanism, we can have a “more generic” menu, requiring more parameters (menu title, index), and have specific instance of this component (portalMenu, userMenu, …).
Struts allows to specify “forward pages” to be called after a struts action is executed. It could be interesting to be able to specify also component instances.
For that, we provide a servlet extending the original struts servlet, and taking in charge forwarding request to instances, rather than to page. If no instance is found, servlet behavior is the same as the struts one.
To be able to specify instances or pages in “struts action forward”, simply use the new servlet. In your application web.xml, change your action servlet declaration with something like following :
<!-- Action Servlet Configuration -->
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>ui.jsp.components.ActionInstancesServlet</servlet-class>
<init-param>
<param-name>instances</param-name>
<param-value>>/WEB-INF/componentInstances.xml </param-value>
</init-param>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/action.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>2</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
Notes : All struts parameters can still working.
Component Instances can describe a final web page, containing <body>…</body> tags. It could be interesting to associate an instance to an URL, allowing all requests sent to this URL to be forwarded to the instance.
The simplest way to do that is to have a jsp file corresponding to the URL, and containing an include tag.
Following is an example including a defined instance.
<%@ taglib uri="/WEB-INF/components.tld" prefix="comp" %>
<%-- include a component --%>
<comp:include instance="main" flush="true"/>
This example work well, but it need to have the defined instances table initialized (see Include tag syntax).
Next example do the same thing, but instance parameters are defined in the include tag.
<%@ taglib uri="/WEB-INF/components.tld" prefix="comp" %>
<%-- Example of component instanciation, passing directly parameters --%>
<comp:include component="classicLayout.jsp" flush="true">
<comp:putAttribute name="title" value="Welcome to our demo" />
<comp:putAttribute name="header" value="header.jsp" />
<comp:putAttribute name="footer" value="footer.jsp" />
<comp:putAttribute name="menu" value="menu.jsp" />
<comp:putAttribute name="body" value="body.jsp" />
</comp:include>
Comments :
Advantages :
Drawbacks :
Another approach consists to have a servlet taking in charge mapping between URLs and Instances.
This servlet could be initialized with a description file, describing mappings, like in following example :
<component-instances>
<!— Url mapping -->
<mapping url=”index.jsp" instance="main">
</component-instances>
In this approachs, we also need to redirect concerned URL to the servlet. This is done in the application description file (web.xml). Care must be taken to redirect only concerned URLs, and not “all” urls. For example, one can thing to specify “*.jsp” while deploying the servlet. This is possible, but need some care : if you do so, all URLs ending in “.jsp” are sent to the servlet, even those that are forwarded inside the servlet itself ! This could result in an endless recursive call. To avoid this you need, inside servlet, to forward to the original servlet serving “.jsp”. This is feasible, but is not guaranteed to be portable across jsp servers.
Need more investigation to have a better solution.
See example in the portal application
[to do : complete this example]
Following is an example of a Portal Page made by assembling components.
See example in the portal application
[to do : complete this example]
Following schema illustrate how we can build a component or page by including twice the same component.
See example in the comp-struts and comp-struts-instance application
[to do : complete this example]
See the installation page for detailed installation guide.
Also refer to the Struts framework installation for more helps.
All tags using components instances need to find the instances list. Search for the list is done in following order : page, request, session, and then application. You must put the instances list in one of this scope before using such tags.
Usually, list is placed in the application scope, at initialization time.
Typical code look like this :
String fileName = "/WEB-INF/instances.xml";
try
{
InputStream input = pageContext.getServletContext().getResourceAsStream(fileName);
if(input == null )
throw new JspException( "Can't open '"+ fileName + "'");
instances = new ComponentInstances( input );
}
catch( Exception ex )
{
ex.printStackTrace();
throw new JspException( "Error while creating instances. See console" );
}
pageContext.setAttribute(ComponentConstants.COMPONENT_INSTANCES,
instances
pageContext.APPLICATION_SCOPE );
One possibility is to have an initialization servlet doing that. You can use the provided servlet ComponentActionServlet instead of the Struts original one. See installation page for details.
Another possibility is to use a miscellaneous tag, checking and loading requested list if necessary.
<comp:initComponentInstances file=”instances.xml” scope=”application” />