Forcing a file name when downloading a dynamically generated csv file

Over the last couple of weeks I've been working on a customisation project using IQJam as a starting point and then making it better fit a particular customer's requirements. It's worth mentioning as I finally had the justification to spend a little time investigating a problem which has been bugging me.

One of the features I added to IQJam was the ability to export data to Excel, a common enough feature that you've been able to do in Domino for ever. Simply print data out to the browser in simple HTML format and change the content-type of the page to "application/vnd.ms-excel". That's not the point of this posting really.

The problem I've been trying to work around is that if your user is using Excel 2007 or later (I'm only on the beta of Office 2010 but it still seems to be a problem for me anyway) and you use the printing HTML technique, Excel raises an error for the user when they load the page, something like:

The file you are trying to open, 'name.ext', is in a different format than specified by the file extension. Verify that the file is not corrupted and is from a trusted source before opening the file. Do you want to open the file now?

According to various technotes and blog posts there is no work around for this, it is a deliberate security feature of Excel. Fair enough I can live with this, though it's not acceptable to the end user, so instead of generating an Excel file I reverted to generated CSV formatted data.

The thing I learned from my investigations into the Excel problem was that there is this header which you can add to the data being generated which tells the browser that the page being opened is actually a file attachment and what the name of the file you want to download should be. So in my example, I'm able to create a unique file name every time the page is generated and also specify that it should be saved as a CSV file, not .XSP which is what it would be thanks to the page being loaded being an XPage. See this example:

try{
    var exCon = facesContext.getExternalContext();
    var writer = facesContext.getResponseWriter();
    var response = exCon.getResponse();
    response.setContentType("text/csv");
    response.setHeader("Content-disposition", "attachment; filename=export_" + DateConverter.dateToString(@Now(), "yyyyMMddhhmm") + ".csv");
    response.setHeader("Cache-Control", "no-cache");
    writer.write(getCSVBody());
    writer.endDocument();
    facesContext.responseComplete();
    writer.close();
}catch(e){
    _dump(e);
}

So this server side javascript sits in the afterRenderResponse of my XPage and is using the "web agents XPages style" technique which Stephan first documented to generate non HTML content from an XPage. The key line for this blog post is where the "Content-disposition" header is set, hopefully you can see where the filename is being created (I'm also using Tommy Valand's DateConverter SSJS code to get the current date / time formatted into a nice string).

Anyway, not a new technique looking at the dates on some of the internet postings out there, but a new one on me and worth passing on I thought.

Be very careful with your content type for Chrome Ajax requests

We released the first beta for IdeaJam 1.7 today. There are *tons* of changes under the covers including the new MooTools 1.2 framework, on which you can expect more later this week.

But for the moment, I thought I'd pass along one little tip which we ran into. Inevitably during our own testing we had missed this bug. When you're in an idea you can "Click to show votes" and what happens behind the scenes is that an Ajax request is fired off to our votes database, an agent runs and send back a list of the votes related to that idea, when they were cast, who by and so on. That agent had been set up with the following code at the start:

Print |Content-Type:text/javascript; charset=utf-8|
Print |Cache-Control: no-cache|
But what we are actually doing is pre-building some HTML and then just inserting it into a div, it saves a little bit of browser processing time this way. But in Chrome we were getting an error:
Uncaught SyntaxError: Unexpected token <
It took a while to track down the problem, but now I found it, it's obvious. The content-type of the agent needs to actually be set to text/html, not text/javascript. All of the other browsers worked fine with this oversight, even Safari which is a fellow Webkit browser, but Chrome seems to be a lot more picky. So this is just a reminder to make sure you're very precise with what is being sent to Chrome from your server.

 

XPages: Displaying a view inside a Dojo Dialog

This is my first SnTT post for absolutely ages, but, hopefully I'll be able to do quite a few more over the next few months as I am just starting a brand new XPages project for a customer that runs through until August.

One of the first things I have been bashing my head against the development wall over is how to get a view to act as a picklist in a dialog. In theory, it should be pretty easy, you just add the "dijit.Dialog" to your XPage resources like so:



Don't forget to also enable the dojoParseOnLoad and dojoTheme properties at the same time.Then inside your XPage you'll want to actually create the dialog HTML:



At this point I had assumed that I could just add my view control inside the div and Bob, as they say, would be my mother's brother. But I hadn't catered for Dojo taking control of the HTML in the XPage once it was rendered. Basically what happens is that after the page loads, Dojo moves the div I created out of the Domino generated form and makes it a child of the body element. This displays fine, until you try and navigate through the view using the pager control, which no longer works as the javascipt requires the HTML to beinside the Domino form. So as far I can tell you have a couple of options, either move the div back inside the form, but I'm assuming that Dojo moved it out for a reason so I wanted to avoid that.

In the end I moved the view control itself to be in a standalone XPage and then opened that via an iFrame that is embedded inside the dialog div. It's just a matter then of changing my javascript to refer to window.parent when I want to send values back from the dialog to the main XPage.

Tips for referencing elements in XPages JavaScript

I managed to hit a bit of a wall with my IdeaJam on XPages development this week. Up until now I haven't really needed to write any client side JavaScript which is impressive in itself compared to how much client side scripts we have in IdeaJam classic. But I have reached that point where I want to add some interactivity without round-tripping to the server.

In classic Domino it's very easy to write script that refers to specific elements of the HTML because you get to control the IDs of those elements. But with XPages it is not possible to predict what the name of an element will be when it reaches the browser. So you are provided with a couple of functions to help you out. When you're writing client side Javascript in an XPage client side event (like clicking a button) it's very easy, the syntax you use to reference an element is "#{id:myElement}" in place of the id. So for example you could use:

dojo.byId("#{id:myDiv}").style.display = "none";

But the thing that I didn't know about until John Mackey gave me some help was that there is a second way of getting the ID of an element. My specific problem was that I wanted to have a script run when the page finishes loading and that script needed to go and work on several different elements. It's easy enough to make a script run "onload". You can actually write JavaScript directly into the source XML of the XPage, so I have added, at the top of my XPage something like this:

<script language="Javascript">
XSP.addOnLoad(function () {
initLocal();
}
);
</script>

This will tell Dojo to call the function "initLocal()" when the page has finished loading, no problem there. But my initLocal function needs to reference an element on the page and I can't use the "#{id:myElement}" format as that only gets evaluated in events that are provided for you by the XPage, enter the "getClientId()" function. You can add a computed field to the XPage that has JavaScript embedded within it in the following format:

var out="<script language=\"Javascript\">\n";
out += "var myElementId = \"" + getClientId("myElement") + "\";\n";
out += "function initLocal(){\n";
out += "myFunction(myElementId);\n";
out += "}\n";
out += "</script>";
out

And by the time it makes it out the the browser it has been translated into:

<script language="Javascript">
var myElementId = "view:_id1:_id2:content:_id4:myElement";
function initLocal(){
myFunction(myElementId);
}
</script>

So in the initLocal function I call another function which is held in a Client Side JavaScript library and I pass in the element ID so that the function can reference the element on the page with proper separation between UI and business logic. So it's all a bit round the houses but this way of doing things does work well and hopefully you won't need to go through the pain that I have in working this stuff out.

Finally a big thanks to John Mackey for his help, this is when the Lotus community really proves it's worth

Character encoding in an Ajax LotusScript agent

I know it's Monday, but I thought I'd share this with you anyway.

So, most people know that you can control the content-type in an agent called by an Ajax request (or any type of agent for that matter) by using this line:

print |content-type: text/plain|

which will result in plain text being sento to the browser, of course you can also send HTML, XML, JSON or any other content type that you so choose.

What you may not have known is that you can also control the charset property of the response. Why would you want to do this? Well if you have an agent that returns high number Ascii characters such as é or ö then by default they will be returned to the Ajax request as non-printing characters. But if you use this line in your agent then they will be transferred to your browser correctly:

print |content-type: text/plain; charset=utf-8|

So my recommendation is that for every agent that you write where you would have just set the content-type, just take that extra few seconds to define the character set as well to save yourself trouble down the line.

You may well take from this, that I have just been burned by this very issue, and you may well be correct. Did I mention that version 1.3 of IdeaJam will be released very shortly ;-)