Monday, 18 June 2012

Calling Controller from JSP via JS

A quick note on calling different methods from Javascript within a jsp.

Essentially I want to display a list of objects, and from that update, view or delete each of the objects. I want a separate button for each operation, ideally with a mouseover effect.There are a number of ways of doing this, in the end I settled for the following method, which allows the same Controller to be used for the REST webservices as well as the Web GUI, the latter coded using JSP and various tag libraries - Spring where possible since this is the underlying framework.

The jsp lists items, they can be any object or entity.  The listing shows key properties, and then has 3 buttons, allowing the user to view (read-only), update, or delete the item. Each item displays animage which changes on mouseover, and which calls the respective REST methods on Controller.  I've repeated this idea for each of the main objects in the registry.

<td><form:form method="get" name="vform${dataElement.dataElementId}" action="${pageContext.request.contextPath}/dataelement/${dataElement.dataElementId}/de" >
        <a href="#" onClick="javascript: return viewDataElement(vform${dataElement.dataElementId})" 
                           onMouseOver="javascript: return changeUpdateImage(vwButton${dataElement.dataElementId})"  
                           onMouseOut="javascript: return changeUpdateBack(vwButton${dataElement.dataElementId})">
          <img id="vwButton${dataElement.dataElementId}" src="${pageContext.request.contextPath}/images/updateS.png" />
   </a></form:form></td>
 
When the jsp is compiled the dataElementId's are added into the method calls. Hence to get the mouseover a javascript method is called passing in the name of the image to change, this will something like vwbutton#N wwhere #N is the id.
Likewise each button is surrounded by the spring form tag (form:form), which for the get method is straightforward. The onClick event calls the javascript method, passing in the form name, this then calls the form submit() from javascript, which calls the server using the correct http method - i.e. GET or POST. Originally I used the html href, however since I needed a mouseover effect it was easier and clearer to use javascript to make the calls.

 After some time trying I couldn't get the DELETE method to work, despite the call from javascript rather than directly from the browser. After a bit of googling I discovered that Spring provides a workaround that allows one to embed hidden variables in the method call, these are picked up using a servlet filter and the request is redirected to the correct method -  so for delete we have:

<td><form:form method="delete" name="form${dataElement.dataElementId}" action="${pageContext.request.contextPath}/dataelement/${dataElement.dataElementId}/de">
               <a href="#" onClick="javascript: return deleteDataElement(form${dataElement.dataElementId})" 
                                   onMouseOver="javascript: return changeImage(delButton${dataElement.dataElementId})"  
                                   onMouseOut="javascript: return changeBack(delButton${dataElement.dataElementId})" >
  <input type="hidden" name="_method" value="delete"/>
 <img id="delButton${dataElement.dataElementId}" src="${pageContext.request.contextPath}/images/deleteS.png" /></a></form:form></td>

In essence the servlet receives a post request with this hidden field, this is handled by configuring a servlet filter called HiddenHttpFilter in the web.xml file.

<filter>
        <filter-name>httpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
 </filter>

The Controller then picks up the delete request in the same method used for REST requests :

    @RequestMapping(method=RequestMethod.DELETE, value="/{id}/de")
    public ModelAndView delete(DataElement ude, BindingResult result, Model model, HttpServletRequest request) {
I've used a similar technique with my create forms, although in these I didn't use javascript to make the calls, I simply used the standard form action tags. :
 
<form:form method="put" commandName="createDataElement">
  <input type="hidden" name="_method" value="PUT"/>

No comments:

Post a Comment