logo
Home Article
How to create actionLink component

Introduction

First thing you wonder after seeing the name of the article, why we need to know how to create standard JSF component. Probably you will be surprised to know that there are a lot of reasons for you to start looking on implementation of standard components.

Let’s take a look at the most important reasons.

  • You can make your own version of standard components that fix defects and bugs in specification or implementation of JSF. For examples on defects and bugs look at the following links:
    https://javaserverfaces-spec-public.dev.java.net/issues/show_bug.cgi?id=12
    http://forum.java.sun.com/thread.jspa?threadID=546270&start=15&tstart=0

  • You can use newer version of JSF. Some people prefer to use the most recent version of technology. But in some cases it's not possible, because of frequent bugs in not final version of the product. Good example of this would be problems using commandLink with JSF 1.2. In case we have our version of commandLink, it would be easier to adapt the component to the latest version of framework.

  • Possibility to make better or more appropriate version of standard components. For example, simple refactoring of components using the latest version of Java. Or you can make some component that render tableless layout instead of table-based layout. It's known, that there are plenty of possibilities to make the same functionality using different ways.

  • You can make standard set of JSF components independent from core framework classes. One way to do so is to define set of helper classes that use packages starting from “javax.faces”. Helper classes would make more consistent approach in implementation of JSF components. You may even be able to make lifecycle of standard components more independent from lifecycle of whole JSF framework.

  • If you know how implement standard components that will be much easier to implement additional components. To deserve mention that reference implementation of standard components is very good resource to learn how implement your own components. For example, knowing how implement commandLink component will help in implementation of menu, page navigator or other navigation related components.

So looking at those reasons we have decided to develop our own set of JSF components. There are some differences in our approach how to create set of JSF components comparing to other approaches.

  • Implementation of lbrary will leverage new language features of Java version 5.

  • Functionality of components will be tested only on the latest internet browsers such as Internet Explorer 6.0 and FireFox 1.0.

  • We will try to separate whole set of JSF components including standard components from JSF implementation such as Sun’s reference implementation JSF and MyFaces. To reach this goal our components will use only packages with names starting from javax.faces. Also that we will provide utilities classes to make process of developing components easier.

  • We will provide ways implementation of component with the same functionality in a couple different ways. For example, developer will have a choice to pickup component that is rendering html with table based layout or with tableless layout. In near future we are planning to provide tools to generate components more adapted to specific user requirements such as changes in syntax or package names. Very often developer need, to make slight changes in already existing component and we will try to create components that easy-to-adapt to the specific requirements.

This article is the our first attempt to show that there is a good chance to make improvements to the standard JSFcomponent. Here you will see how to implement and make enhancements to the most popular component in the framework. Now as you can guess we are going to talk about the commandLink.

New identity for commandLink

We believe that actionLink name is more appropriate than commandLink. There are several reasons to support this idea.

  • commandLink use ActionSource interface

  • the most important attributes for commandLink are action and actionListener

  • render of commandLink create actionEvent in decoding method

In addition to the reasons above in Borland Delphi exists TAction class with the similar purpose as commandLink. So, let's start to develop component named actionLink.

Differences between actionLink and commandLink components

Syntax changes

Instead of attribute “value” more appropiate to use attribute “label”.

Here is the example:

<s:commandLink action="myFirstAction" value="My first Action"/>
			
<s:actionLink action="myFirstAction" label="My first Action"/>
					
Attribute “disabled”

Let's include additional attribute named disabled in the component. Just in case if somebody doesn't know - this kind of attribute is absent in commandLink. Disabled attribute have only two values “false” (by default) and “true”. In case disabled=”true” link not fire action and by default appears with style=”color: GrayText;” User of component have posibility to change default style for disabled state of component by using disabledClass attribute. As you can see we already introduced not one but two additional attributes.

Here is example of how to use the attributes:

<s:actionLink action="myAction" label="My Action" disabled="true" disabledClass="disabledLink"/>
					
Attribute href as replacement for onclick

The important distinguish our version of component from standard one using attribute href as replacement for onclick. Using href instead of onclick is better solution because that allowed us to keep current scrolling position on page. No more jumps on page! Existing implementation of commandLink component scrolls page to the top of window before sending the request to server. Beside the advantage described above we got additional one because we eliminate using attrribute "onclick" from specific script rendered by component. That means user now can assign some script on “onclick” attribute and that will not interfere with script rendering by component. Implementation of commandLink doesn’t support onclick attribute. Just in case if you have doubts regarding absenting onclick atribute on standard component check out links:
http://forum.java.sun.com/thread.jspa?forumID=427&threadID=506830
http://java.sun.com/j2ee/javaserverfaces/1.1/docs/tlddocs/index.html
So now we can start using onclick attribute on actionLink. That will be third attribute we have added to our variation of commandLink component.

For additional information about the issue follow the link: https://javaserverfaces.dev.java.net/issues/show_bug.cgi?id=4

Implementation actionLink component

User interface component - UIAction

First step is creating user interface component. All user interface components must extend UIComponent class directly or indirectly. In many situations the most convenient way is to extend custom components from UIComponentBase. This class provides useful implementations of nearly every UIComponentmethod, allowing us to focus on the unique characteristics of a particular UIComponent implementation. ActionLink component must to implement ActionSource interface to support functionality to generate action event when user click on link. For begining let us to define two important identifiers of actionLink such as component type and component family. Take a look how it is appears in the code:

public class UIAction extends UIComponentBase implements ActionSource {
  public static final String COMPONENT_TYPE = "org.semanticprogrammer.semanticfaces.Action";
  public static final String COMPONENT_FAMILY ="org.semanticprogrammer.semanticfaces.Action";

  public String getFamily() {
    return COMPONENT_FAMILY;
  }
  
  public UIAction() {
    super();
  }
}
		

Tip

Good practice to define name of user interface component with prefix "UI". Examples:UIData or UIAction.

Next things that define properties for UIAction class. Because the class is implementing ActionSource interface we need to define properties such as action, actionListener and immediate. Value of immediate property indicates whether ActionListeners executed during the Apply Request Value phase or the Invoke Application phase of the Request Processing Lifecycle. In addition to properties above make sense to implement properties label and disabled. Label property present text that appears on link. Destination of disabled property already has described in this article. See related code:

  public MethodBinding getAction() {
    return action;
  }

  public void setAction(MethodBinding action) {
    this.action = action;
  }

  public MethodBinding getActionListener() {
    return (this.actionListener);
  }

  public void setActionListener(MethodBinding actionListener) {
    this.actionListener = actionListener;
  }

  public boolean isImmediate() {
    if (immediate != null) return immediate;
    ValueBinding vb = getValueBinding("immediate");
    return (vb != null) ? ((Boolean)vb.getValue(getFacesContext())) : false;
  }

  public void setImmediate(boolean immediate) {
    this.immediate = immediate;
  }
  
  public Object getLabel() {
    if (label != null) return label;
    ValueBinding vb = getValueBinding("label");
    return (vb != null) ? vb.getValue(getFacesContext()) : null;
  }

  public void setLabel(Object label) {
    this.label = label;
  }

  public boolean isDisabled() {
    if (disabled != null) return disabled;
    ValueBinding vb = getValueBinding("disabled");
    return (vb != null) ? ((Boolean)vb.getValue(getFacesContext())) : false;
  }

  public void setDisabled(boolean disabled) {
    this.disabled = disabled;
  }
		

Tip

Don't define render related properties such as style or layout in user interface component. Also it's not good practice to define in user interface component default render type because there is a chance that it will be deprecated and it doesn't look very consistent if in one case you have to define render type and in other case you just using default type of render. One more doubtful approach is to override method getRendersChildren. This method return true if your encoding methods display child components. But you never know how in future will be rendered user interface component. So it just doesn't make sense to define methods related to rendering inside user interface component. To make things simpler just follow the rule: Implementation of user interface component must be free from anything associated to rendering process.

Because our class implementing ActionSource interface, we also need to provide the following methods:

  public void addActionListener(ActionListener listener) {
    addFacesListener(listener);
  }

  public ActionListener[] getActionListeners() {
    return (ActionListener [])getFacesListeners(ActionListener.class);
  }

  public void removeActionListener(ActionListener listener) {
    removeFacesListener(listener);
  }
		

Our action component should notify all action listeners and that functionality implemented by method void broadcast(FacesEvent event) located in class UIComponentBase. We need to override this method to support executing action listener registered with our component. Here is code:

  public void broadcast(FacesEvent event) throws AbortProcessingException {
    super.broadcast(event);
    if (event instanceof ActionEvent) {
      FacesContext context = getFacesContext();
      MethodBinding mb = getActionListener();
      if (mb != null)
        mb.invoke(context, new Object[] { event });
      ActionListener listener = context.getApplication().getActionListener();
      if (listener != null)
        listener.processAction((ActionEvent) event);
    }
  }			
			

Earlier we defined property immediate and now let's to define the way of using this property. If immediate is true the component suppose to process action events during Apply Request Values phase and if immediate is false events will process during Invoke Application phase. The queueEvent method is right place to use immediate property because this method provided the possibility for events to be queued. See the code:

  public void queueEvent(FacesEvent e) {
    if (e instanceof ActionEvent) {
      if (isImmediate())
        e.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
      else
        e.setPhaseId(PhaseId.INVOKE_APPLICATION);
    }
    super.queueEvent(e);
  }
			

Now, we can see that action event is executed during the proper lifecycle phase depending on the value of immediate.

Couple more methods that we need to implement inside UIAction class is methods related to state management of component. State management of UIAction is implemented same way like for any typical JSF component, so we will put just code here without any additional explanations.

  public Object saveState(FacesContext context) {
    Object values[] = new Object[6];
    values[0] = super.saveState(context);
    values[1] = saveAttachedState(context, action);
    values[2] = saveAttachedState(context, actionListener);
    values[3] = immediate;
    values[4] = label;
    values[5] = disabled;    
    return (values);
  }

  public void restoreState(FacesContext context, Object state) {
    Object values[] = (Object[]) state;
    super.restoreState(context, values[0]);
    action = (MethodBinding) restoreAttachedState(context, values[1]);
    actionListener = (MethodBinding) restoreAttachedState(context, values[2]);
    immediate = (Boolean)values[3];
    label = values[4];
    disabled = (Boolean)values[5];
  }
			

Registering the component

Custom component must be registered in faces-config.xml file.

We will put here required minimum amount of metadata:

<component>
	<display-name>Action Link</display-name>
	<component-type>org.semanticprogrammer.semanticfaces.Action</component-type>
	<component-class>org.semanticprogrammer.semanticfaces.component.UIAction</component-class>
</component>			
			

Renderer component - ActionLinkRenderer

The main goal of the rendered component is to provide appearance in HTML of the associated UIAction. All rendered components extend Renderer directly or indirectly. Every rendered component must implement the decoding and encoding functionality. First thing to do is to define name of render type:

  public static final String RENDERER_TYPE = "org.semanticprogrammer.semanticfaces.Link";
			

Our encoding methods must render the following HTML code:

<script type="text/javascript">
<!--
document.getElementById('_id0').addParam = function(paramName, paramValue) {
  var param = document.createElement("input");
  param.type = "hidden";
  param.name = paramName; param.value = paramValue;
  this.appendChild(param);
}
//-->
</script>

<a href="javascript:document.forms['_id0'].addParam('_id0:_id3','_id0:_id3');document.forms['_id0'].addParam('paramName1','paramValue1'); 
	 document.forms['_id0'].submit(); void(0)">My Link</a>
			

Important

The most important thing in this script to see that we are creating dynamically new element in method addParam. And good for us is fact that every time page have been reloaded all just created new elements will be disappeared from the document. That means unlike from standard component implementation our component don't have to use and clean hidden fields.

Another benefit from using dynamically created elements that we can easy eliminate existing dependency between form and commandLink renderers. For more information regarding this issue see the link: https://javaserverfaces.dev.java.net/issues/show_bug.cgi?id=26

Note

The standard FormRenderer class rendering hidden field for every commandLink on the form and clearFormHiddenParams function for every form instance on the page. To eliminate this already unnecessary functionality Semanticfaces library provide own variation of the FormRenderer class. Keep in mind that in any case you can use the actionLink with standard form renderer.

Semanticfaces library included demo application called action-component. There you can open page on wich located only commandLink and actionLink components. In case you open source code of the page you will see how differently render code this two components.

Probably no needs in further explanations regarding how to implement rendering of action link component. Just take a look on the code:

public class ActionLinkRenderer extends Renderer {
  public static final String RENDERER_TYPE = "org.semanticprogrammer.semanticfaces.Link";

  public ActionLinkRenderer() {
    super();
  }

  public void decode(FacesContext context, UIComponent component) {
    if (context == null || component == null) {
      throw new NullPointerException(Util.getExceptionMessageString(
          Message.parameters_null.name()));
    }
    if (Util.componentIsDisabledOrReadonly(component)) 
      return;
    String clientId = component.getClientId(context);
    Map requestParameterMap = context.getExternalContext().getRequestParameterMap();
    String value = (String) requestParameterMap.get(clientId);
    if (value == null || value.equals("") || !clientId.equals(value))
      return;
    ActionEvent actionEvent = new ActionEvent(component);
    component.queueEvent(actionEvent);
  }
  
  public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
    if (context == null || component == null) {
      throw new NullPointerException(Util.getExceptionMessageString(
          Message.parameters_null.name()));
    }
    if (!component.isRendered()) 
      return;
    ResponseWriter writer = context.getResponseWriter();
    assert (writer != null);
    String clientId = component.getClientId(context);
    UIForm uiform = Util.getParentForm(component);
    if (uiform == null) 
      return;
    String formClientId = uiform.getClientId(context);
    Param paramList[] = Util.getParamList(component);
    Map requestMap = context.getExternalContext().getRequestMap();   
    String key = Util.ADD_PARAM_METHOD_NAME + "_" + formClientId;
    if (requestMap.get(key) == null) {
      Util.renderAddParamToFormJavaScript(writer, formClientId);
      requestMap.put(key, Boolean.TRUE);
    }
    writer.startElement("a", component);
    Util.writeIdAttributeIfNecessary(context, writer, component);
    Util.renderPassThruAttributes(context, writer, component, new String[]{"target"});
    if (!((UIAction)component).isDisabled()) {
      StringBuffer sb = new StringBuffer();
      sb.append("document.forms['");
      sb.append(formClientId);
      sb.append("']." + Util.ADD_PARAM_METHOD_NAME + "('");
      sb.append(clientId);
      sb.append("','");
      sb.append(clientId);
      sb.append("');");
      for (Param param : paramList) {
        sb.append("document.forms['");
        sb.append(formClientId);
        sb.append("']." + Util.ADD_PARAM_METHOD_NAME + "('");
        sb.append(param.getName());
        sb.append("','");
        sb.append(param.getValue());
        sb.append("');");
      }
      String target = (String) component.getAttributes().get("target");
      if (target != null && target.trim().length() > 0) {
        sb.append(" document.forms[");
        sb.append("'");
        sb.append(formClientId);
        sb.append("'");
        sb.append("].target='");
        sb.append(target);
        sb.append("';");
      }    
      sb.append(" document.forms['");
      sb.append(formClientId);
      sb.append("'].submit(); void(0)");
      writer.writeAttribute("href", "javascript:" + sb.toString(), "href");
      String styleClass = (String)component.getAttributes().get("styleClass");
      if (styleClass != null) {
        writer.writeAttribute("class", styleClass, "styleClass");
      }
    }
    else {
      writer.writeAttribute("href", "javascript:void(0)", "href");
      String disabledClass = (String)component.getAttributes().get("disabledClass");
      if (disabledClass != null)
        writer.writeAttribute("class", disabledClass, "disabledClass");
      else
        writer.writeAttribute("style", "color: GrayText;", "style");
    }
    Object label = ((UIAction)component).getLabel();
    if (label != null) {
      String s = label.toString();
      if (s != null && s.length() != 0) 
        writer.write(s);
    }    
    writer.flush();
  }

  public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
    if (context == null || component == null) {
      throw new NullPointerException(Util.getExceptionMessageString(
          Message.parameters_null.name()));
    }
    if (!component.isRendered()) {
      return;
    }
    ResponseWriter writer = context.getResponseWriter();
    assert (writer != null);
    writer.endElement("a");
  }
}
			

Registering the renderer

Custom renderer must be registered in faces-config.xml file. See how to do it below:

<renderer>
	<display-name>Action Link</display-name>
	<component-family>org.semanticprogrammer.semanticfaces.Action</component-family>
	<renderer-type>org.semanticprogrammer.semanticfaces.Link</renderer-type>
	<renderer-class>org.semanticprogrammer.semanticfaces.renderkit.ActionLinkRenderer</renderer-class>
</renderer>
			

Tag Handler Class - ActionLinkTag

Tag handler class used to define tag for action link component. The tag provided reference to associated component on the page and provides the rendering of it. All tag handler classes must:

  • extend from UIComponentTag class directly or indirectly

  • implement a getComponentType method, which returns the type of the component associated with the tag.

  • implement a getRendererType method, which returns the type of renderer that is used.

  • implement setProperties and release methods, which sets and release the component attribute values.

The code that implement of tag handler class can make you laugh, so we will provide it only inside supplementary files.

Registering the tag

The tag can be registered in the Tag Library Descriptor (TLD). The TLD file is an XML document that describes all custom tags.

Tip

Always put the TLD file under folder META-INF.

There is nothing specific in the way we register tag inside TLD file. You will find corresponding code inside supplementary files.

<renderer>
	<display-name>Action Link</display-name>
	<component-family>org.semanticprogrammer.semanticfaces.Action</component-family>
	<renderer-type>org.semanticprogrammer.semanticfaces.Link</renderer-type>
	<renderer-class>org.semanticprogrammer.semanticfaces.renderkit.ActionLinkRenderer</renderer-class>
</renderer>
			

Note

Inside Semantic Faces library included beside actionLink component new implementation of commandLink for backward compatibility, and simplified Form component.

SemanticFaces library included also actionButton component as alternative to commandButton. Implementation of actionButton component using similar approach as in actionLink. The actionButton component support additional functionality such as working with parameters.

How to use Action Link component

Semanticfaces provide demo application action-component that show how to use actionLink and actionButton components.

Note

Don't forget that such component as actionLink will not work outside of form.

Summary

In conclusion let's see what are the advantages provide actionLink compared to commandLink:

  • More appropriate name and syntax

  • Supporting disabled state

  • Ability to use onclick attribute with user defined script.

  • Possibility to keep current scrolling position on page.

  • Eliminating existing dependency between form and commandLink renderers

  • Eliminating necessity to use hidden field for such component as commandLink

Your feedback is important to us.

Acknowledgements

We want to thank Sergey Smirnov for extensive review, several useful suggestions and testing for occurrence new problems.

Our thanks also to Alexandr Ilyin for submitting issue regarding using href and for some advices related to this approach.

Resources

Download the SemanticFaces Source Code
Download ready-to-use SemanticFaces Library

SemanticFaces library is base to make some experiments in area JSF components and related utils classes.