Rough Book

random musings of just another computer nerd

Tag: iframe

Setting the content type to text/plain for a JSON response from a Spring controller

I was using a jQuery plugin called a ajaxfileupload to upload a file through AJAX. Technically what the plugin does isn’t AJAX. It creates a hidden form and an iframe, and then submits the form using the iframe as the target. The iframe will then end up with the response from the server. This response is then read-in by the plugin and handled appropriately. In my case I was using a controller action that would return JSON (using the .action extension). The action uses Spring’s MappingJacksonJSONView that returns JSON with a content type of application/json (as it should). This works perfectly in Chrome, however in both Firefox and IE, the user is presented with a dialog box that asks them to download the JSON response. This is obviously not what I wanted. The reason this is happening is because the response is being directly submitted to the iframe (and therefore, the page). That is, it’s not coming through via the XMLHttpRequest object. So IE and FF don’t know what to do with it and assume that it is something the user would want to download. The solution to this problem is to set the content-type to text/plain. This wasn’t as straightforward as I thought it would be.

Initially I was going to call the render(…) method of MappingJacksonJsonView but that didn’t work because the content-type had already been set to application/json. The solution I came up with was to duplicate some of the code (ugh) inside MappingJacksonJsonView to get the JSON as a string and to then write that to the response:

@RequestMapping
public void processFileUpload(HttpServletResponse response, Model model, ...) {

    ...

    //Set the content-type and charset of the response
    response.setContentType("text/plain");
    response.setCharacterEncoding("UTF-8");

    //I need to use another OutputStream here; I cannot use the response's OutputStream because that will cause errors
    //later on when the JSP needs to render its content (recall that getOutputStream() can only be called exactly once
    //on a response). Therefore I'm writing the data to a ByteArrayOutputStream and then writing the byte array from
    //the ByteArrayOutputStream to the response manually.

    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    ObjectMapper objectMapper = new ObjectMapper();
    JsonGenerator generator = objectMapper.getJsonFactory().createJsonGenerator(byteArrayOutputStream, JsonEncoding.UTF8);

    //Before I can convert the data into JSON, I will need to filter some attributes out of the model (namely BindingResult)
    Map<String, Object> result = new HashMap<String, Object>();

    for(Map.Entry<String, Object> entry : model.asMap().entrySet()) {
        if(!(entry.getValue() instanceof BindingResult)) {
            result.put(entry.getKey(), entry.getValue());
        }
    }

    objectMapper.writeValue(generator, result);
    response.getWriter().write(new String(byteArrayOutputStream.toByteArray(), "UTF8"));
}

This still seems a little hacky to me. A possible improvement is to annotate the action with @ResponseBody and return the JSON as a string without involving the response at all. If anyone has a better solution, I’m all ears!

Preview Button and no more Iframes

I have added a “Preview Comments” button to the comment submission form. So now, you’re able to preview your comments before submitting them. The preview will show you your comments exactly as they will appear (after they have been submitted).

The other big thing is the removal of iframes. I thought iframes were really freaking awesome when I first found out about them. And they are. There are a bunch of cool things you can do with them. I always used iframes to update the contents of a page without refreshing the entire page itself. This was accomplished by submitting to a hidden iframe. The server-side script in the iframe would generate Javascript, which would then manipulate the DOM on the parent page. Pretty nifty. I learnt that when I was working at College of Business at ASU. It was something that the bunch of us (Chris, Kelly, Gravey, Cameron, and I) came up with in the Fall of 2001. We thought it was pretty novel.

I used iframes extensively in the last incarnation of my website. When that crashed and burned, I needed to make a new website. So I went ahead and used iframes on this one too. Somewhere along the way, I was possessed by the urge to make this website XHTML compatible. But alas, iframes didn’t pass the compatibility test. I then tried to find a replacement for iframes. The object tag seemed to work, except there was no way to make it reload. Then I tried XMLHttpRequest. This also seemed promising, but I had to make a bunch of changes to the Javascript. I didn’t feel like doing that. I also then realized that I wanted content to show up on search engines, and having an iframe wasn’t going to do that. So I then decided to re-design the backend completely. Instead of using an iframe, I would just dump content directly into the page. Of course, now it wouldn’t load the content seamlessly when you clicked on a date, but what the heck. I still had one iframe though – the one that populates the calendar. I figured there simply wasn’t any other way to do it, so I left it as it is.

It stayed that way until Marc left me a comment talking about the need for the functionality to preview comments. I was going to use the iframe, before I realized that the comments could have line-breaks, and all other kinds of nasty stuff that could mess up Javascript. I could have accessed the HTML from the iframe, and dumped it into the page, but the first thing that came to my mind was the XMLHttpRequest object. I used it to replace the iframe. I think it actually works a whole lot better, and is definitely more flexible. Even though the object is used for XML, you can get HTML through it as well through the responseText attribute. This way, you can simulate the action of submitting to a form, and then access the data generated by the server-side script. You can then do whatever you want with the data. This is definitely much more flexible than having to deal with Javascript from the action iframe.

In the course of all this, I wrote some (I think) pretty nifty code. I wrote two functions; getXMLHttpRequestObject creates an XMLHttpRequest object and returns it. I needed this function because IE and Firefox create the object differently. The other function, sendXMLHttpRequest sends the request to the server. The cool part is that I also send in a custom handler. When sendXMLHttpRequest finishes receiving the data from the server, it calls the custom handler with that data. Maybe not all that fancy, but I think it’s cool :). Here are what the functions look like:

getXMLHttpRequestObject:

function getXMLHttpRequestObject()
 {
          if(Firefox)
          {
             return new XMLHttpRequest();
          }

          else
          {
             return new ActiveXObject('Microsoft.XMLHTTP');
          }
 }

sendXMLHttpRequest:

function sendXMLHttpRequest(Request, uri, POSTdata, responseHandler)
 {
          Request.open("POST", uri, true);
          Request.setRequestHeader(
                                   'Content-Type',
                                   'application/x-www-form-urlencoded'
                                  );

          Request.onreadystatechange = function()
                                       {
                                                if(Request.readyState == 4)
                                                {
                                                   if(Request.status == 200)
                                                   {
                                                      responseHandler(Request.responseText);
                                                   }

                                                   else
                                                   {
                                                      alert("There was a problem retrieving data: " + Request.statusText);
                                                   }
                                                }
                                       }

          Request.send(POSTdata);
 }

A sample call is as follows:

function doSomething(param1, param2)
 {
          var Request = getXMLHttpRequestObject();
          var FormData = "param1=" + param1 + "&amp;param2=" + param2;
          sendXMLHttpRequest(Request, "doit.php", FormData, myResponseTextHandler);
 }

 function myResponseTextHandler(text)
 {
          //do something with text
 }

Neat huh? Javascript is a whole lot more powerful than some people think. Ok, enough geek talk!

References:

All original content on these pages is fingerprinted and certified by Digiprove
%d bloggers like this: