Uploading files
<-Previous | ^UP^ | Next->
Uploading files
To upload a file we need to submit a form, containing the file, to the server. This is a traditional multipart form submission.
Gendl provides the with-form-string macro to create a form. When using this macro for file uploads we must specify the keyword argunemt enctype with a value of multipart/form-data to ensure the file is ransmited to the server correctly
Within the body of the form we need two input tags.
- The first is of type file which gives us a Browse button to enable a file to be selected. Within this tag we also need to assign a :name, the value of which must correspond with the name of a :settable :computed-slot (its symbol name, so preceeded witha : character). The default value of that slot shoud be an empty string
- The second is a standard submit button, which submits the form
(define-object file-upload-1 (base-html-page):computed-slots((uploaded-path " " :settable)(body(with-lhtml-string ()(str (the development-links))(str (with-form-string (:enctype "multipart/form-data ")(:table (:tr (:td (:input :type "file " :name :uploaded-path :value (the uploaded-path))))(:tr (:td (:input :type "submit " :name "upload " :value "Upload "))(:td (str (the uploaded-path)))))))))))
In the above example we also include an output of the value of uploaded-path The value of this slot is automatically set to the location of the uploaded file on the server when the form is submitted
Screenshot on opening the form
Once a file has been selected the no file selected text changes to the filename of the selected file.
Screenshot after selecting the file, but before hitting Upload
If Upload is then clicked the file is uploaded to the server and the text alongside the Browse button will revert to no file selected. As part of this upload, as well as transmitting the file to the server, the slot representing the server pathname to the file (in this case :uploaded-path) is set. One important thing to note: the default value for uploaded-path is set to an empty string, rather then nil. This is to ensure that when the uploaded-path value is set as part of the upload it is set to a string value and not a symbol - on Windows in particular with a drive letter followed by a : character this will cause problems as Lisp will believe its a package...
It is often useful to conditionalise the display of the Browse and Submit buttons depending on the value of the uploaded file slot and possibly present an alternative button which will reset the form to original values. The code below includes a Reset button to perform the reset. We use the after-set! function, check what values are in the query-plist and if the value of this button ("Reset Form") is present firstly delete the uploaded file if it exists and then run the restore-slot-default! function on upoaded-path
(define-object file-upload-2 (base-html-page):computed-slots((uploaded-path " " :settable)(body(with-lhtml-string ()(str (the development-links))(when (= (length (the uploaded-path)) 0)(htm (str (with-form-string (:enctype "multipart/form-data ")(:table (:tr (:td (:input :type "file " :name :uploaded-path :value (the uploaded-path))))(:tr (:td (:input :type "submit " :name "upload " :value "Upload "))))))))(when (> (length (the uploaded-path)) 0)(htm (str (fmt "The file has been uploaded to ~a" (the uploaded-path)))(str (with-form-string (:enctype "multipart/form-data")(:input :type "submit" :name "reset" :value "Reset Form"))))))):functions((after-set! ()(when (member "Reset Form" (the query-plist) :test 'equalp)(when (probe-file (the uploaded-path)) (delete-file (the uploaded-path)))(the (restore-slot-default! :uploaded-path ))))))
Screenshot after hitting Upload
Screenshot after hitting Reset Form
Once the file has been uploaded to the server, it will generally need processing to access the data it contains. In the example below the uploaded file is processed as the page demands (the file-content) to be evaluated, but for more complex data processing we may use the after-set! function, test for presence of the Upload Button value (Upload) in the query-plist and initiate processing based on that. (Note that the function read-file is a custom function included in the resources file)
(define-object file-upload-3 (base-html-page):computed-slots((uploaded-path " " :settable)(body(with-lhtml-string ()(str (the development-links))(when (= (length (the uploaded-path)) 0)(htm (str (with-form-string (:enctype "multipart/form-data ")(:table (:tr (:td (:input :type "file " :name :uploaded-path :value (the uploaded-path))))(:tr (:td (:input :type "submit " :name "upload " :value "Upload "))))))))(when (> (length (the uploaded-path)) 0)(htm (str (fmt "The file has been uploaded to ~a" (the uploaded-path)))(:p "The file contents are")(:table :border 1 (:tr (:td :colspan 2 (str (first (first (the file-content))))))(dolist (line (cdr (the file-content)))(htm (:tr (:td (str (first line)))(:td (str (second line)))))))(str (with-form-string (:enctype "multipart/form-data")(:input :type "submit" :name "reset" :value "Reset Form"))))))):functions((after-set! ()(when (member "Reset Form" (the query-plist) :test 'equalp)(when (probe-file (the uploaded-path)) (delete-file (the uploaded-path)))(the (restore-slot-default! :uploaded-path ))))))