4

Briefly: I can make a file, save it in the file system and then make a page with a link to that file, but what if I want a page with links to many files which may not all need to be generated?

So my user clicks a link on the list page like:

<g:link action="gimmeAFile" id="${myThingieInstance.id}">${fieldValue(bean: myThingieInstance, field: "id")}</g:link>

Right now I have a controller that looks like this:

def gimmeAFile = {
  def lotsaLines = []
  //Do a ton of stuff that has lotsaLines.add(resultStrings) all over

  def fileName = "blahblah-${dateOrSomething}.csv"
  def dumbFile = new File('web-app/tmpfiles/'+fileName).withWriter {out ->
    lotsaLines.each{
      out.println it
    }
  }
  [fileName:fileName]
}

And then they go to gimmeAFile.gsp which has the link to actually download the file:

<a href="${resource(dir:'tmpfiles',file:fileName)}">Download Report</a>

How do I make a link on the list viewer that will create and download the file without dragging the user to an extra screen. NOTE: I cannot have the files pre-generated, so I need to figure out how to link to a file that isnt there yet. I'm thinking something like render() at the end of the controller. Can I make the gimmeAFile controller just give the file instead of making a page with a link to the file?

OK so to clarify this is what I finally figured out based on Kaleb's answer. Thankyou SO!!

def gimmeAFile = {
  def lotsaLines = []
  //Do a ton of stuff that has lotsaLines.add(resultStrings) all over

  def fileName = "blahblah-${dateOrSomething}.csv"
  def dumbFile = new File('web-app/tmpfiles/'+fileName).withWriter {out ->
    lotsaLines.each{
      out.println it
    }
  }
  def openAgain = new File('web-app/tmpfiles/'+fileName)
  response.setContentType("text/csv")
  response.setHeader("Content-disposition", "filename=${fileName}")
  response.outputStream << openAgain.getBytes()
  response.outputStream.flush()
  return
}
Mikey
  • 4,692
  • 10
  • 45
  • 73

2 Answers2

3

Another option which is a bit nicer, you can just render the file, straight from your controller's action:

render(file: theFile, contentType: 'your/contentType')

See also: http://grails.org/doc/latest/ref/Controllers/render.html

(I've found that if you add the fileName option, it prompts the user to download the file.)

Nick Grealy
  • 24,216
  • 9
  • 104
  • 119
  • Is it possible to use this but still have the action to update the page with other changes? I guess, can you do two renders in one action? One for the file, one for the page? – Joseph Nelson Dec 11 '15 at 19:01
3

You can create a view that just gets the bytes of the file and writes out to the response:

response.contentType  = 'image/jpeg' // or whatever content type your resources are
response.outputStream << file.getBytes()
response.outputStream.flush()

Is that what you're trying to do?

Kaleb Brasee
  • 51,193
  • 8
  • 108
  • 113
  • This has to be on the right track, but if I use .getBytes() I get the error: groovy.lang.MissingMethodException: No signature of method: java.util.ArrayList.getBytes() is applicable for argument types: () values: [] and if I use .bytes the file is garbage. – Mikey Jun 07 '11 at 23:35
  • 1
    Ok. I have to reopen the file as a different object from the one I used .withWriter in order to do .getBytes() – Mikey Jun 07 '11 at 23:45
  • 2
    `file.bytes` is more groovy! – rodvlopes Jul 23 '15 at 17:44