Karthik's Weblog

Taking Spring MVC @Controller method injection a step further

Posted in Java, Spring by karthik on February 3, 2010

Spring MVC @Controllers typically allow you to annotate your controller method parameters making it http agnostic and increasing the testability of the controllers at the same time. But sometimes you are required to repeat the parameter signature as shown below –

@Controller
class AccountController{
@RequestMapping("/create")
public String create(@RequestHeader(value="SM_USERDN")String smUserdn,
@CookieValue(value="smsession") String smSession,
@SessionParam(value="user_id") String userId
){
}
@RequestMapping("/update")
public String update(@RequestHeader(value="SM_USERDN")String smUserdn,
@CookieValue(value="smsession") String smSession,
@SessionParam(value="user_id") String userId
){

}
}

Controller methods ‘create’ & ‘update’ access the same set of servlet environment specific parameters.
I was wondering if it would help if we could move all those parameters to an annotated POJO as shown
below and reuse the POJO in the controller methods –

Note that the Spring MVC @Controller annotations that let you access request, cookie, header values
can be specified at the method parameter level only. I obviously wanted to reuse these annotations and found that to be a limitation only to realize later that I could make use of constructor injection style

public class MyEnv{
private final String smUserdn;
private final String smSession;
private final String userId;
@Autowired
public MyEnv(@RequestHeader(value="SM_USERDN") String smuserdn;
@CookieValue(value="smsession") String smSession,
@SessionParam(value="user_id") String userId
){

this.smuserdn = smuserdn;
this.smSession = smSession;
this.userid = userId;
}

}
@Controller
class AccountController{
@RequestMapping("/create")
public String create(@Composite MyEnv env){

}
@RequestMapping("/update")
public String update(@Composite MyEnv env){
}
}

You need the @Composite custom annotation to trigger this binding.
Do you guys see a need for something like this? I did implement this feature. But wanted to check if people find this useful at all.
thanks!

Tagged with: , ,

My love hate relationship with Spring MVC’s AnnotationMethodHandlerAdapter.

Posted in Spring by karthik on December 16, 2009

I like the Spring 3.0 annotation based controllers. I never understood how Spring “MVC” managed to sustain its popularity all this while with a programming model based on “controller inheritance” especially when you had competing frameworks like Struts2 and Stripes that supported a pure POJO controller model from day one.
So it was heartening to know that Spring 3.0 will deprecate the Inheritance based controllers going forward.

The class that does the majority of the work in Spring @MVC is AnnotationMethodHandlerAdapter (with the help of a few others).

One of the nice features supported by Spring MVC is the ability to selectively turn-off automatic data binding from request parameters to model objects. You usually specify the fields that you don’t want bound in the @InitBinder method.

In this example, field Foo.id is auto-generated and say I don’t want to auto-bind the field “id”, you usually specify that information in your @InitBinder method in the @Controller.

class Foo{
private String name;
private int id;
private String
//getter-s
//setter-s
}

While this is enough to prevent auto-binding,
you might still want to signal this security breach as an error in your controller method.

@Controller
class FooBarController{
@InitBinder
public void myInitBinder(WebDataBinder binder){
//do not bind the field id
binder.setDisallowedFields(new String[]{"id"});
}
@RequestMapping("/foo")
void foo(Foo foo, BindResult bindResult){
String[] suppressedFields = result.getSuppressedFields(); #1
if (suppressedFields.length > 0) { #2
throw new IllegalBindOperationException( #3
"You've attempted to bind fields that aren't allowed by @InitBinder: "
+ StringUtils.arrayToCommaDelimitedString(suppressedFields)); #4
}
//do something useful }
@RequestMapping("/bar")
void bar(Foo foo, BindResult bindResult){
String[] suppressedFields = result.getSuppressedFields(); #1
if (suppressedFields.length > 0) { #2
throw new IllegalBindOperationException( #3
"You've attempted to bind fields that aren't allowed by @InitBinder: "
+ StringUtils.arrayToCommaDelimitedString(suppressedFields)); #4
}
//do something useful }
}

You obviously don’t want to be writing this piece of logic (lines #1 through #4) in every handler method.
May be you can move this check to a utility API and call the utility API instead. But then Spring encourages us to model such concerns as an interceptor.

But how do I access the BindResult object in the interceptor unless the handler method declares it to be a part of its signature? While I’m not a fan of AspectJ, do let me know if there is a way.

Another option could be to achieve this by subclassing AnnotationMethodHandlerAdapter and overriding some relevant methods? But which one? – I couldn’t find any – let me know if you did.

Ok how about HandlerMethodInvoker? anybody?

Finally I realized that while AnnotationMethodHandlerAdapter & HandlerMethodInvoker combine to give us this excellent @MVC programming model, these classes like to keep everything to themselves – pretty much every interesting method /inner class in some cases is private.

Suffice to say that it’s been quite frustrating.

I was hoping for a “redesigned” AnnotationMethodHandlerAdapter class for Spring 3.0. But that hasn’t happened either.
Let me point out at the same time that AnnotationMethodHandlerAdapter is customizable but I feel it could be much better.
I love it but I hate it too.

Learn to customize Spring MVC @Controller method arguments

Posted in Spring by karthik on November 8, 2009

Spring MVC Annotation based controllers offers a lot of flexibility w.r.t Annotated controller handler method signature.
Refer to the Spring MVC documentation to get a detailed list of supported argument types & annotations.

What is interesting is that in addition to the supported controller method argument types , you can add support for custom argument types as well. You just need to provide a WebArgumentResolver interface implementation that knows how to resolve a particular controller method argument.

The interface definition is reproduced here for convenience –

    public interface WebArgumentResolver{ 
Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest);
}

Next let’s build a case for rolling out your own WebArgumentResolver implementation.

If you are trying to extract the value for the servlet request param ‘id’, instead of accessing the “raw” servlet apis as shown below –

@Controller
class MyController{
@RequestMapping("/test")
public void test(HttpServletRequest request){
String id = request.getParameter("id");
}
}

you could use a built-in annotation like this –

@Controller
class MyController{
@RequestMapping("/test")
public void test(@RequestParam(value="id") String id){

}
}

While both the code snippets achieve the same thing, the latter is probably more unit-test friendly and doesn’t require you to mock HttpServletRequest object as is the case with the former. In other words, the annotations preserve the POJO-ness of your @Controller. (In reality, the preceding code snippets don’t actually mean the same thing – Spring takes care of invoking the databinder, registered property editors as well when it sees a @RequestParam annotation.

Now let’s say you want to access a http session variable ‘user’, this is the way you would do it in a Controller –

@Controller
class MyController{
// Spring injects the current HttpSession variable at runtime into the 'test' method.
@RequestMapping("/test")
public void test(HttpSession session){
String user = (String)session.getAttribute("user");
doSomething(user);
}
}

While this works, it doesn’t seem consistent with the annotation based approach.

An annotation similar to @RequestParam albeit for accessing Http Session parameters could look like this –

  package com.springmvc.extensions; 

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SessionParam {
/** * The name of the Session attribute to bind to. */ String value() default "";
/** * Whether the parameter is required. * Default is true, leading to an exception thrown in case * of the parameter missing in the request. Switch this to * false if you prefer a * null in case of the parameter missing. * Alternatively, provide a {@link #defaultValue() defaultValue}, * which implicitely sets this flag to false. */ boolean required() default true;
/** * The default value to use as a fallback. Supplying a default value * implicitely sets {@link #required()} to false. */ String defaultValue() default "";
}

Next, let's define the argument resolver -

  package com.springmvc.extensions;

  import java.lang.annotation.Annotation;

  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpSession;

  import org.springframework.core.MethodParameter;
  import org.springframework.web.HttpSessionRequiredException;
  import org.springframework.web.bind.support.WebArgumentResolver;
  import org.springframework.web.context.request.NativeWebRequest;


  public class SessionParamArgumentResolver implements WebArgumentResolver {

    public Object resolveArgument(MethodParameter param,
        NativeWebRequest request) throws Exception {

      Annotation[] paramAnns = param.getParameterAnnotations();   #1
      Class paramType = param.getParameterType();

      for (Annotation paramAnn : paramAnns) {                     
        if (SessionParam.class.isInstance(paramAnn)) {            #2
          SessionParam sessionParam = (SessionParam) paramAnn;
          String paramName = sessionParam.value();
          boolean required = sessionParam.required();
          String defaultValue = sessionParam.defaultValue();
          HttpServletRequest httprequest = (HttpServletRequest) request
              .getNativeRequest();
          HttpSession session = httprequest.getSession(false);
          Object result = null;
          if (session != null) {
            result = session.getAttribute(paramName);
          }
          if (result == null)
            result = defaultValue;
          if (result == null && required && session == null)
            raiseSessionRequiredException(paramName, paramType);  #3
          if (result == null && required)
            raiseMissingParameterException(paramName, paramType); #4

          return result;
        }
      }

      return WebArgumentResolver.UNRESOLVED;                      #5

    }

    // ..

    protected void raiseMissingParameterException(String paramName,
        Class paramType) throws Exception {
      throw new IllegalStateException("Missing parameter '" + paramName
          + "' of type [" + paramType.getName() + "]");
    }

    protected void raiseSessionRequiredException(String paramName,
        Class paramType) throws Exception {
      throw new HttpSessionRequiredException(
          "No HttpSession found for resolving parameter '" + paramName
              + "' of type [" + paramType.getName() + "]");
    }

  }

Explanation -
#1 looks up the annotations specified on the method argument
#2 checks if the method argument annotation is of type @SessionParam
#3 throws an exception if the param is marked as 'required' and no valid session exists
#4 throws an exception if the param is not found in the http session
#5 You could configure a list of WebArgumentResolver-s. In this example, we have only one.
If the argument resolver fails to resolve the argument successfully, its imperative that you return the special object WebArgumentResolver.UNRESOLVED. That way Spring knows that it needs to consult with other argument resolver-s to resolve the a given controller method parameter. A return value of "null" from a WebArgumentResolver is considered valid and the method parameter is marked as 'resolved'!

Next we will discuss how to configure the SessionParamArgumentResolver class

AnnotationMethodHandlerAdapter class that ships with Spring is a HandlerAdapter implementation that is specifically designated to handle request intended for controllers based on the Annotation model. One of the primary responsibilities of this handler adapter is to 'resolve' all the handler method arguments prior to invoking the handler method.

While the AnnotationMethodHandlerAdapter knows how to resolve all the standard argument types, it consults the configured WebArgumentResolver-s for resolving the arguments that are not supported by default.

So next let's configure the custom WebArgumentResolver that we just developed -

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">     
   <property name="customArgumentResolver" ref="sessionArgResolver"/>
</bean>  
    
<bean id="sessionArgResolver" class=quot;com.springmvc.SessionParamArgumentResolver"/>
          

With the preceeding Spring bean configuration in place, you could rewrite your controller like this -

  @Controller
class MyController{
@RequestMapping("/test")
public void test(@SessionParam(value="user") String user){
doSomething(user);
}
}

Hopefully this article gives you a good idea of how to customize Spring MVC @Controller to suit your needs.

@HandlerInterceptor for Spring MVC

Posted in Java, Spring by karthik on October 12, 2009

I have been doing quite a bit of Spring MVC at work these days. I like the @Controller model and with the bunch of
enhancements that have gone into Spring 3.0, Spring MVC has the potential to become the defacto Action based framework.

While Spring MVC has excellent support for Controller Annotations to cut down on amount of the XML
configuration, you are still expected to configure HandlerInterceptor -s in the Spring configuration file.
Sometimes it helps to keep all the related code together in one artifact.

So let me quickly summarize what I did –

I defined a custom annotation @Interceptors that lets you specify the interceptors that you would like to apply on
the controller.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Interceptors {
      /** HandlerInterceptor**/  
     Class[] value();
}

The custom @Interceptors annotation is exactly same as the EJB 3.0 Interceptors annotation definition. I kind of liked the fact that I could navigate to the interceptor implementation class using the IDE.

Next you define a couple of interceptors like so –

class MyInterceptor1 implements HandlerInterceptor{
        //..
} 

class MyInterceptor2 extends HandlerInterceptorAdapter{
        //..
}

and annotate the @Controller-s –

@Controller
@Interceptors({MyInterceptor1.class,MyInterceptor2.class})    
class TestController{

      @RequestMapping("/test")
      void test(ModelMap mm){

      }
}

Next let’s look at the implementation –

Spring’s HandlerMapping is responsible for resolving the HandlerExectionChain that maps to a given request URL. The HandlerExecutionChain encapuslates the Controller and the associated interceptors for a given request URL.

public interface HandlerMapping {      
      HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;    
}

 

Spring’s DefaultAnnotationHandlerMapping class maps handlers/@Controllers based on HTTP paths expressed through the RequestMapping annotation at the type or method level.

So we will enhance DefaultAnnotationHandlerMapping and add the ability to detect @Interceptors on the controllers

   
   package com.springmvc.extensions;

   import java.util.ArrayList;
   import java.util.List;
   import javax.servlet.http.HttpServletRequest;
   import org.springframework.beans.BeanUtils;
   import org.springframework.core.annotation.AnnotationUtils;
   import org.springframework.web.servlet.HandlerExecutionChain;
   import org.springframework.web.servlet.HandlerInterceptor;
   import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping;

   public class HandlerInterceptorAnnotationAwareHandlerMapping extends  DefaultAnnotationHandlerMapping {

    @Override
    protected HandlerExecutionChain getHandlerExecutionChain(Object handler,HttpServletRequest request){ #1
        HandlerExecutionChain chain = super.getHandlerExecutionChain(handler,request);  #2
        HandlerInterceptor[] interceptors = detectInterceptors(chain.getHandler().getClass());
        chain.addInterceptors(interceptors);
        return chain;
    }

    protected HandlerInterceptor[] detectInterceptors(Class handlerClass) {

        Interceptors interceptorAnnot = AnnotationUtils.findAnnotation(handlerClass, Interceptors.class);    #3
        List interceptors = new ArrayList();
        if (interceptorAnnot != null) {
          Class[] interceptorClasses = interceptorAnnot.value();
          if (interceptorClasses != null) {
            for (Class interceptorClass : interceptorClasses) {     #4   
              if (!HandlerInterceptor.class.isAssignableFrom(interceptorClass)) {
                raiseIllegalInterceptorValue(handlerClass,interceptorClass);      #5 
              }
              interceptors.add((HandlerInterceptor) BeanUtils.instantiateClass(interceptorClass));   #6 
            }
          }
        }
        return interceptors.toArray(new HandlerInterceptor[0]);
    }


    protected void raiseIllegalInterceptorValue(Class handlerClass,Class interceptorClass) {          
        throw new IllegalArgumentException(interceptorClass + " specified on "
            + handlerClass + " does not implement " + HandlerInterceptor.class.getName());
            
    }

 }

 

#1 Override the getHandlerExecutionChain() method defined in DefaultAnnotationHandlerMapping
#2 Call ‘super()’ so that you don’t change the base class behavior . This way you can aggregate the interceptors configured
in the Spring context file against the property ‘interceptors’ as well (it at all configured) .
#3 Look for @Interceptors annotation for a given Controller class.
#4 #5 Run through all the specified HandlerInterceptor implementations , making sure that they indeed implement the Spring’s
HandlerInterceptor interface.
#6 Instantiate the HandlerInterceptor instance (This will look for a default no-arg contructor). You could look up the
Spring ApplicationContext and check if an Interceptor has been configured as well.

As a final step, you need to declare the HandlerInterceptorAnnotationAwareHandlerMapping class in the Spring Application Context

  <bean class="com.springmvc.extensions.HandlerInterceptorAnnotationAwareHandlerMapping "/>

 

Let me know what you think.

Update: Nice to see keith link to this blog on spring jira

Tagged with: , ,

Developing a custom Apache Wicket component

Posted in Wicket by karthik on January 24, 2008

Introduction

This article walks the reader through the process of building a custom component using Apache Wicket framework. As the first step, let’s discuss the functionality that we will be packaging with our custom component. An ‘Accordion’ is not pre-packaged as a Wicket core/extensions component the way Oracle ADF Faces does for example.If you want to take a look at a demo of an Accordion, Rico has one here. This component is known by different names on the web. We will call it a TogglePane. There are probably several ways to buid one. We will look at one such approach that employs a popular javascript library scriptaculous that in turn is built on top of another gem – prototype. If you are looking for a thorough discussion on prototype, read the excellent book Prototype and Scriptaculous in Action

Through this example, readers will learn how to –

  1. Integrate an existing popular javascript framework with Wicket
  2. Make header contributions from your custom component
  3. Ajax-enable your custom component and
  4. In general, get an insight into how components are typically
    modelled on the server or wicket-side.

A custom Wicket component that has a visual representation typically ship with the following artifacts:

  1. An HTML template
  2. Optional Javascript or stylesheet or Images
  3. A corresponding java component class that extends from one of the
    several component base classes that ship with the standard Wicket
    distribution.

Note : Wicket does not mandate having an HTML template or javascript
file or stylesheet for a custom component. Custom validators, converters are examples of
components that do not have a corresponding markup. You could also reuse an existing
component’s markup and customize its behavior by extending the corresponding Java
class. The existing component should provide for extension-hooks though. Standard
wicket/wicket-extensions distribution ship with several components that allow for
customization. Now that we have some idea of what we want to build, let’s design the
HTML markup.

The Template

Here is the barebone layout for the TogglePane Component:

File:TogglePane.html


  <html>
  <body>

    <div class="pane_border">
      <div class="pane">
        <div class="pane_header">
          <span>[[Header goes here]]</span>
        </div>
        <div class="pane_content">
          <span>[[Content goes here]]</span>
        </div>
      </div>
    </div>

  </body>
  </html>

– Just a bunch of divs that can be made to look prettier by adding a little bit of CSS style later.

Adding Client-side behavior to the Template

Next, let’s take a look at the javascript that adds the scriptaculous ‘toggle’ effect.

//File: toggle.js


  var TogglePane = Class.create();

  TogglePane.prototype = {

    //constructor as expected by the prototype library

    initialize:function(divId){

      //get all elements under div with id 'divId' having style .pane'
      var searchString = 'div#'+divId+' .pane';
      this.panes = $A($$(searchString));

      this._initClientOnlyToggle();
    },

    //Initiate the toggle effect
    //when the 'header' is clicked, the content needs
    //to toggle

    _initClientOnlyToggle:function(){
      var obj = this;

      //For every pane, get the 'content' to toggle
      //'onclick' of 'header'

      this.panes.each(
        function(pane){
            var header = obj._getContainedElement(pane,'pane_header');
            var content = obj._getContainedElement
                                  (pane,'pane_content');
            header.onclick = function(){
              Effect.toggle(content);
            };
        }
     );
    } ,

    //A Helper function
    //fetch an element with class 'className' and is
    //a child of 'pane' element

    _getContainedElement:function(pane,className){
      var elem = $A(pane.childNodes).find(
          function(child){
            return child.className == className;
          }
      );
      return elem;
    },

  }

Add some style to the template

Now that we have the client-side behavior ready, lets add a
stylesheet to the template ( I know that the majority of the readers
will probably do a far better job at this):

File: toggle.css


  .pane_border {
      position: relative;
      width: 300px;
      padding: 0px;
      background-color:  #508acd;
      border: 2px ;
      text-align: left;
   }

  .pane_header {
      border: 1px #ffffff outset;
      background-color: #2b5286;
      padding: 1px 50px 1px 50px;
      font-size: 0.7em;
      color:#FFFFFF;
      font-family:'Trebuchet MS';
      font-weight:bold;
   }

  .pane_content {
      border: 1px #ffffff outset;
      padding:10px 10px 10px 10px;
      background-color: #508acd;
      color:#FFFFFF;
      font-family:Verdana, Arial, Helvetica, sans-serif;
      font-size:10px;
      font-weight:bold;
   }

Let’s add some ‘previewable’ HTML content to the markup and see how it would look like in the real-world.

Adding Previewable Content to the template

//File: TogglePane.html


	<html>

      <!-- Add references to the javascript lib-->

      <head>
        <script type="text/javascript" src="prototype.js"></script>
        <script type="text/javascript" src="scriptaculous.js"></script>
        <script type="text/javascript" src="effects.js"></script>
        <script type="text/javascript" src="toggle.js"></script>
        <link rel="stylesheet" type="text/css" href="toggle.css"/>
      </head>

      <!-- Initialize the toggle effect on 'onload' event -->

      <body onload="new TogglePane('togglepane_id')">

        <div id="togglepane_id" class="pane_border">

          <!-- template content -->

          <div class="pane">
            <div class="pane_header">
              <span>[[Header goes here]]</span>
            </div>
            <div class="pane_content">
              <span>[[Content goes here]]</span>
            </div>
          </div>

          <!-- Dummy content-1 -->

          <div class="pane">
            <div class="pane_header">
              <span><b>User Details</b></span>
            </div>
            <div class="pane_content">
              <div>
                  <table>
                    <tr><td>Login Id</td><td><input type="text" value=""/></td></tr>
                    <tr><td>First Name</td><td><input type="text" value=""/></td></tr>
                    <tr><td>Last Name</td><td><input type="text" value=""/></td></tr>
                    <tr><td><input type="button" value="Save"/></td></tr>
                  </table>
              </div>
            </div>
          </div>

          <!-- Dummy content-2 -->

          <div class="pane">
            <div class="pane_header">
              <span><b>Address</b></span>
            </div>
            <div class="pane_content">
              <div>
                  <table>
                    <tr><td>Addr1</td><td><input type="text" value="Addr1"/></td></tr>
                    <tr><td>Addr2</td><td><input type="text" value=""/></td></tr>
                    <tr><td>City</td><td><input type="text" value="Dallas"/></td></tr>
                    <tr><td><input type="button" value="Save"/></td></tr>
                  </table>
              </div>
            </div>
          </div>

        </div>
      </body>
    </html>

Here is a sample screenshot –

Accordian

What we have at this stage is a working prototype. We are probably half way through the article and interestingly, we still haven’t discussed Wicket or fired up our Java ide yet. That’s pretty much “Wicket way” of developing web components : Develop the plain HTML UI mock-ups without bothering about the wicket-side side of the component and once you have something working, sprinkle a few wicket tags at appropriate places in the template and add “dynamic” “behavior” in the backing Java classes.

Next, lets see what it takes to convert this HTML/Javascript/CSS to a server-side Wicket component that can eventually be dropped into a Page or nested within other components that make up a page

Getting the component Wicket-ready

In Wicket, the HTML artifacts and the server-side representation of a component (a Java class) typically reside in the same package. So create a package – org.wicketstuff.funwithcomponents and copy the HTML artifacts (css, js e.t.c) under the package. Let’s add wicket:id and some minimal wicket tags to the dynamic sections of the template. This is how the template looks like now:


  <html>

     <wicket:head>
        <wicket:link>
           <script src="prototype.js" type="text/javascript"></script>
           <script src="scriptaculous.js" type="text/javascript">
           </script>
           <script src="effects.js" type="text/javascript"></script>
           <script src="toggle.js" type="text/javascript"></script>
           <link rel="stylesheet" href="toggle.css" type="text/css">
           </link>
         </wicket:link>
     </wicket:head>

     <body>

        <wicket:panel>

            <div wicket:id="outerDiv" class="pane_border">

                <div class="pane" wicket:id="panes">
                  <div class="pane_header">
                    <span wicket:id="header">[[Header]]</span>
                  </div>
                  <div class="pane_content">
                    <span wicket:id="content">[[Content]]</span>
                  </div>
                </div>

                <!--
                  Dummy content excluded for the same of brievity.
                  Feel free to copy the contents here
                 -->

            </div>

        </wicket:panel>

      </body>
    </html>

The table below summarize the wicket tags and wicket:id’s that we added to the template



  wicket:id     Purpose
  ----------   ----------
  outerDiv   |   We need to generate a unique HTML 'id' attribute for
                 this div and access it in our JS
  panes      |   We need to attach a repeater on the Wicket-side
                 corresponding to this element
  header     |   Place holder for the 'header' element
  content    |   Place holder for the 'content' element

  Wicket tags       Tag description
  -------------    -----------------
  <wicket:head>  |  Used by the component to 'contribute' header
                    artifacts
  <wicket:link>  |  Used to provide auto-linking to the artifacts
  <wicket:panel> |  To indicate that the component is essentially a
                    'Panel' that can be dropped on to a page or in
                    fact nested within our panels

Remember to remove the reference to the stylesheet ‘toggle.css’ from
within the component template : the users of the component typically
customize the look and feel of the component

Now on to the Java side of the Component

Just to reiterate, in Wicket, you typically employ HTML and/or CSS to specify the visual layout of a component while the dynamic behavior is added to the server-side representation of the Component.

The TogglePane is essentially composed of a set of ‘panes’ and every ‘pane’ consists of a ‘header’ and ‘content’ component. The ‘header’ could be simple text ( represented as a Label component in Wicket). It could also be a Link for e.g. Now that we are not sure what the ‘header’ / ‘content’ Component is likely to be, let’s represent it in a generic fashion as an org.apache.wicket.Component instance.


    package org.wicketstuff.funwithcomponents;
    import org.apache.wicket.Component;

    public interface Pane extends Serializable{
       public Component getHeader(String headerWicketId);
       public Component getContent(String contentWicketId);
    }

The fact that we are passing the ‘wicketid’ to the implementations of the Pane interface might seem a little strange to Wicket beginners at first. The client of the toggle component could probably look up the Toggle pane template and figure the wicket:id. But that would amount to leaking the implementation details of the Toggle Pane. The header and content wicket:id is internal to the component and should remain that way. So what we do instead is pass the wicket id to the ‘clients’ of the component. The TogglePane is composed of a collection of panes –


  package org.wicketstuff.funwithcomponents;

  import java.util.List;

  import org.apache.wicket.markup.html.IHeaderContributor;
  import org.apache.wicket.markup.html.IHeaderResponse;
  import org.apache.wicket.markup.html.WebMarkupContainer;
  import org.apache.wicket.markup.html.list.ListItem;
  import org.apache.wicket.markup.html.list.ListView;
  import org.apache.wicket.markup.html.panel.Panel;

  public class TogglePane extends Panel implements IHeaderContributor {

    private WebMarkupContainer outerDiv;

    public TogglePane(String id, List panes) {
      super(id);

      outerDiv = new WebMarkupContainer("outerDiv");
      //make sure that it emits 'id' attribute
      outerDiv.setOutputMarkupId(true);

      outerDiv.add(new Panes("panes", panes));
      add(outerDiv);
    }

    //ListView is a repeater component that allows you
    //to render the markup in a loop.

    class Panes extends ListView {

      public Panes(String id, List list) {
        super(id, list);
        // TODO Auto-generated constructor stub
      }

      @Override
      protected void populateItem(final ListItem item) {
        Pane pane = (Pane) item.getModelObject();
        item.add(pane.getHeader("header"));
        item.add(pane.getContent("content"));
      }

    }

    public void renderHead(IHeaderResponse response) {
        response.renderOnLoadJavascript("new TogglePane('"
            + outerDiv.getMarkupId() + "')");
    }
  }

The javascript class that we wrote requires the ‘outerDiv’ HTML ‘id’ as an initialization parameter – you do that by implementing wicket’s IHeaderContributor interface – it allows you to make header contributions at runtime – in our case, the div HTML ‘id’ is not known until render time.
Note that you still get to keep the ‘pane’-s that you added for the sake of previewability in the TogglePane template. Just make sure that you place them within tags as shown below –


  <wicket:remove>
      [[your dummy 'pane'-s goes here]]
  </wicket:remove>

Wicket ignores the content placed within tags when rendering.
That’s it – a functioning Wicket-Accordian is ready to be put to use. But this still doesn’t sound too exciting in the sense that the component is pretty much static – it just toggles the content that exists at the time of page render. Sometimes, you might want the ability to refresh the content from the server every time the header is clicked. Still better, you might want to repaint the content asynchronously through Ajax. We can also let the user decide if he/she would like to see the content panes initially expanded or collapsed. In the next section, we will see how we can make this behavior configurable at the Component level.

Adding ability to “configure” the Accordian

In Wicket, they are usually modelled through a Config class as shown below.


  package org.wicketstuff.funwithcomponents;

  import java.io.Serializable;

  public class Config implements Serializable {

    boolean panesCollapsedIntially;

    boolean withAjaxEnabled;

    public Config() {}

    public Config panesCollapsedIntially() {
      this.panesCollapsedIntially = true;
      return this;
    }

    public Config withAjaxEnabled() {
      this.withAjaxEnabled = true;
      return this;
    }

    public Config panesExplodedInitially() {
      this.panesCollapsedIntially = false;
      return this;
    }

    public Config withAjaxDisabled() {
      this.withAjaxEnabled = false;
      return this;
    }

    public boolean isAjaxEnabled(){
      return withAjaxEnabled;
    }

    public boolean arePanesCollapsedInitially(){
      return panesCollapsedIntially;
    }

  }

You can specify the Configuration at the time of construction of the TogglePane as shown below:

//MyPage is a Page that uses the TogglePane component


  import java.util.Collections;
  import java.util.List;

  import org.apache.wicket.markup.html.WebPage;

  public class MyPage extends WebPage {
    public MyPage() {
      add(new TogglePane("pane", createPanes(),
        new Config()
          .withAjaxDisabled()
            .panesCollapsedIntially()));
    }
    //snip

    public List createPanes(){
      // TODO return your panes from here
      return Collections.EMPTY_LIST;
    }
  }

//File:MyPage.html


  <html>
    <body>
      <span wicket:id="pane">[[Our Accordian will be placed here]]</span>
    </body>
  </html>

The template now requires the ‘header’ to be marked as a Wicket component as well (wicket:id=”h” – we need to be firing an Ajax request ‘onclick’ of the header)


  <wicket:panel>

    <div wicket:id="outerDiv" class="pane_border">
	  <div class="pane" wicket:id="panes">

		<! -- header identified as a wicket Component -->
		<div class="pane_header" wicket:id="h">
		    <span wicket:id="header">[[Header]]</span>
		</div>
		<div class="pane_content">
		    <span wicket:id="content">[[Content]]</span>
		</div>
	</div>
     </div>

  </wicket:panel>

Let’s add the Ajax behavior to the component next.


  public class TogglePane extends Panel implements IHeaderContributor {

    private WebMarkupContainer outerDiv;

    private Config config;

    public TogglePane(String id, List panes, Config config) {
      super(id);
      this.config = config;
      outerDiv = new WebMarkupContainer("outerDiv");
      outerDiv.setOutputMarkupId(true);

      outerDiv.add(new Panes("panes", panes));
      add(outerDiv);
    }

    class Panes extends ListView {

      public Panes(String id, List list) {
        super(id, list);
      }

      @Override
      protected void populateItem(final ListItem item) {
        final Pane pane = (Pane) item.getModelObject();
        WebMarkupContainer h = new WebMarkupContainer("h");

        //The component with wicket id 'content' needs to be added to
        //the one with wicket:id 'h' and not 'item' - Wicket expects us to
        //respect the component hierachy

        h.add(pane.getHeader("header"));

        //If Ajax, attach Ajax behavior to the 'onclick' event

        if (config.isAjaxEnabled()) {
          h.add(new AjaxEventBehavior("onclick") {

            @Override
            protected void onEvent(AjaxRequestTarget target) {

              Component newContent = pane.getContent("content");
              newContent.setOutputMarkupId(true);
              item.replace(newContent);
              target.addComponent(newContent);

            }

          });
        }
        item.add(h);
        Component content = pane.getContent("content")
        content.setOutputMarkupId(true);
        item.add(content);
      }

    }

    //Configure the togglepane.js based on the Config
    //object supplied by the user. We will modify the client-side
    //javascript to support this configuration in the next
    //section.

    public void renderHead(IHeaderResponse response) {
      String outerDivId = outerDiv.getMarkupId();
      StringBuilder jsTogglePane = new StringBuilder("new TogglePane('"
          + outerDivId + "',");

      if (config.isAjaxEnabled()) {
        jsTogglePane.append("true,");
      } else {
        jsTogglePane.append("false,");
      }
      if (config.arePanesCollapsedIntially()) {
        jsTogglePane.append("true)");
      } else {
        jsTogglePane.append("false)");
      }

      response.renderOnLoadJavascript(jsTogglePane.toString());

    }
  }

As promised earlier, let’s modify the initial version of our JavaScript class definition in order to support these configuration options.

Modify the client-side Javascript to support the configuration of component behaviour

If you are a Wicket beginner, you probably noticed that you didn’t have to write any javascript to trigger the Ajax request – Wicket enables this by computing the URL required to trigger the Ajax behavior and attaches it to the ‘onclick’ event of the header element transparently (you can click ‘view source’ on your browser’s menu and verify that the rendered HTML indeed ). But remember, we also need to attach the scriptaculous ‘toggle’ behavior on the ‘onclick’ event? Now the question is how do we combine both the ‘toggle’ and ‘wicket-introduced-ajax’ behavior? We also need to make sure that the Ajax request isn’t fired when the user collapses the header as this could be viewed as being inefficient.

The code snippet shown below addresses these issues:



   TogglePane.prototype = {

      //modified constructor to support configuration

      initialize:function(divId,enableAjax,collapsePanes){

        this.divId = divId;
        //get all elements under div with id 'divId' having style
        //'.pane'
        var searchString = 'div#'+divId+' .pane';
        this.panes = $A($$(searchString));

        if(enableAjax == true){
          this._initAjaxToggle();
        }else{

          //_initClientOnlyToggle() is same as what we discussed at the
          //beginning of the article

          this._initClientOnlyToggle();
        }

        if(collapsePanes == true){
          //get all child elements of div with id 'divId'
          //having style .pane_content
          searchString = 'div#'+divId+' .pane_content';
          this.contents = $A($$(searchString));
          this._collapseContent();
        }
      },

     //A new helper method that initialized the Ajax behavior

     _initAjaxToggle:function(){
        var obj = this;
        this.panes.each(
            function(pane){

              var header = obj._getContainedElement
                       (pane,'pane_header');
              var content = obj._getContainedElement
                       (pane,'pane_content');

              //wicket-introduced 'onclick' handler that triggers the
              //ajax request

              var existingCall = header.onclick;

              header.onclick = function(){

                Effect.toggle(content);

                //Conditionally invoke the Ajax request
                //based on the current visibility of the 'content'

                if (! $(content).visible() ){
                  existingCall();
                }

              };

           }
        );
      },

      //A new helper method that 'collapses' the panes initially if
      //configured

      _collapseContent:function(){
       this.contents.each(
        function(content){
            content.hide();
        }
       );
      }
   }

Getting our Accordion to behave like Rico’s Accordion

The Accordion component that we developed differs from Rico’s equivalent in the following ways:

  1. Our component allows “all” content panes to show up expanded
    initally.
  2. When the currently ‘active’ content pane is expanded, others
    don’t collapse automatically.

“1” can be addressed by simply ‘hardcoding’ the configuration and showing all the panes collapsed initially. “2” can be addressed by adding a simple method to the client side javascript that allows you to collapse all content panes except the one that is currently ‘active’ (the one that the user clicks)



  TogglePane.prototype = {

    //A new helper method that 'collapses' all panes except the one
    //that is passed as an argument to the method

    _collapseAllContentExcept:function(ignore){
      //get all child elements of div with id 'divId' having
      //style .pane_content
      searchString = 'div#'+this.divId+' .pane_content';
      this.contents = $A($$(searchString));
      this.contents.each(
        function(content){
           if( content != ignore){
            content.hide();
           }
        }
      );
    }

  }

Make sure that you call the _collapseAllContentExcept method from within the onclick handler as depicted below:


    header.onclick = function(){

       Effect.toggle(content);
       //Conditionally invoke the Ajax request
       //based on the current visibility of the 'content'

       if (! $(content).visible() ){
         existingCall();
         //collapse all other content panes except
         //the current one
         this._collapseAllContentExcept(content);
       }

    };

Conclusion:

Component based web frameworks are gaining a lot of traction in the Java World. Apache Wicket is one of the leading implementations in this area. But many development teams are still faced with the problem of picking a web framework given the plethora of open source options available in the java world. My observation has been that development teams, while zeroing in on a component based framework, typically tend to cite the built-in component library as one of the major criteria for choosing one. Wicket ships with quite a few built-in components. If you find that a Component, a competing framework ships with, doesn’t already exist in “standard” Wicket distribution , look it up in the component wiki – may be someone has already built it for you or better, subscribe to the mailing list. The possiblity of getting the question answered on the mailing-list within a couple of minutes or worse, within an hour is very high. But if you still fail to find a convincing reply, its not the end of the road. On the contrary, its probably the beginning of a fun-filled web development journey – you can feel assured that the Wicket framework developers have more than made up for the absence of the custom component you are looking for by providing a Component/programming model that makes developing custom components a breeze. It is also important to note that every project has its own unique requirement and very few components are designed with that kind of flexibility in mind.

On a side-note, “the ease of component development” is also the primary reason why Apache Wicket committers refrain from shipping all possible custom components with the standard distribution.

Follow

Get every new post delivered to your Inbox.