Writing output for download
<-Previous | ^UP^ | Next->
Generating text output for download
There is often a requirement to generate output as a file which can then be downloaded. There are 4 basic steps to this process
- Generate the file content
- Write the content to either a static file or a html stream
- Publish the output
- Provide a link to the output on your web page
Generate the content
In general this will involve writing a formatted string
(in-package :gwl-user)(define-object simple-file-output (base-html-page)(:computed-slots((file-contents (let ((line (list 1 2 3 4 5 6 7)))(format nil "~{Line ~a of my file~^~%~}" line))))))
Write the static output
This is just standard file output, as described in the tutorial File I/0 and its associated topic Writing to a file
(in-package :gwl-user)(define-object simple-file-output (base-html-page):computed-slots((file-contents (let ((line (list 1 2 3 4 5 6 7)))(format nil "~{Line ~a of my file~^~%~}" line)))(text-physical-file (let ((file-path (make-pathname :defaults (glisp:temporary-file):type "txt")))(with-open-file (f file-path :direction :output :if-exists :supersede)(write-string (the file-contents) f))file-path)))
The slot text-physical-file returns the pathname of the physical file. We'll cover writing the content directly to a html stream in the next part as its done at the same time as publishing
Publish the output
Publishing the output is done by side affecting. For the static file we define the url, write the static file to disc, publish the static file to the url and then return the url. (Note that the slot has been renamed in this example). For the output direct to the html stream we define a function as part of the publish to write the content on the fly and return the url.
(in-package :gwl-user)(define-object simple-file-output (base-html-page):computed-slots((file-contents (let ((line (list 1 2 3 4 5 6 7)))(format nil "~{Line ~a of my file~^~%~}" line)))(text-physical-file-url (let ((url (format nil "/file-output-~a.txt" (get-current-date-time)))(file-path (make-pathname :defaults (glisp:temporary-file):type "txt")))(with-open-file (f file-path :direction :output :if-exists :supersede)(write-string (the file-contents) f))(publish-file :path url:content-type "text/plain":file file-path)url))(text-file-url (let ((url (format nil "/stream-output-~a.txt" (get-current-date-time))))(publish :path url:content-type "text/plain":function #'(lambda(req ent)(with-http-response (req ent)(with-http-body (req ent)(write-string (the file-contents) *html-stream*)))))url))))
Note that when publishing the stream output we have to provide a :function argument to publish function. We use a lambda function, and whilst it may appear somewhat complex it is a fairly standard boilerplate solution/syntax which may be used elsewhere
Provide a link for download
A page-section is defined comprising links to each of the download options, and then displayed on the page by including its div in the body slot. Note the use of the :download atribute for the links, this ensures that on clicking the link the file is downloaded and gives the download file a name which appears in the browsers download window
(in-package :gwl-user)(define-object simple-file-output (base-html-page):computed-slots(......(body (with-lhtml-string ()(str (the development-links))(str (the export-section div))))):objects(((export-section :type 'page-section:inner-html (with-lhtml-string ()(:p "Click "(:a :href (the text-file-url) :download "virtual-text-file.txt" "Here")" to download a virtual text file.")(:p "Click "(:a :href (the text-physical-file-url) :download "physical-text-file.txt" "Here")" to download a physical text file."))))))