diff --git a/.gitignore b/.gitignore index 1acc0456f..0a8d77212 100755 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ npm-debug.log public/ sass-cache settings.json +tmp-uploads # ignore everything in the package directory, but not govuk-prototype-kit.config.json, package.json and README.md package/* diff --git a/app/routes/upload.js b/app/routes/upload.js new file mode 100644 index 000000000..699548217 --- /dev/null +++ b/app/routes/upload.js @@ -0,0 +1,205 @@ +const express = require('express'); +const router = express.Router(); +const multer = require('multer'); +const cache = require( '../../lib/cache' ); + +function getErrorMessage(item) { + var message = ''; + if(item.error.code == 'FILE_TYPE') { + message += item.file.originalname + ' must be a png or gif'; + } else if(item.error.code == 'LIMIT_FILE_SIZE') { + message += item.file.originalname + ' must be smaller than 2mb'; + } + return message; +} + +function getUploadedFiles( req, res, next ){ + + if( !req.session.uploadId ){ + req.session.uploadId = req.sessionID; + } + + let files = cache.get( req.session.uploadId ); + + if( !files ){ + files = []; + cache.set( req.session.uploadId, files ); + } + + req.uploadedFiles = files; + next(); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// NO JAVASCRIPT +//////////////////////////////////////////////////////////////////////////////////////// + +const upload = multer( { + dest: './public/uploads', + limits: { fileSize: 2000000 }, + fileFilter: function( req, file, cb ){ + let ok = false; + + if(!req.rejectedFiles) { + req.rejectedFiles = []; + } + + if( file.mimetype !== 'image/png' && file.mimetype !== 'image/gif' && file.mimetype !== 'image/jpg' && file.mimetype !== 'image/jpeg') { + cb(null, false); + req.rejectedFiles.push({ + file: file, + error: { + code: 'FILE_TYPE' + } + }); + } else { + cb(null, true); + } + } +} ).array('documents', 10); + + + +router.get('/components/multi-file-upload', getUploadedFiles, function( req, res ){ + + const { uploadedFiles } = req; + + var pageObject = { + uploadedFiles: [], + errorMessage: null, + errorSummary: { + items: [] + } + }; + + // 1. UPLOADED FILES + + if(uploadedFiles.length) { + uploadedFiles.forEach(function(file) { + var o = file; + o.message = { + html: ` ${file.originalname} has been uploaded` + }; + + o.filePath = file.path; + o.originalFileName = file.originalname; + o.fileName = file.filename; + o.deleteButton = { + text: 'Delete' + }; + pageObject.uploadedFiles.push(o); + }); + } + + // 2. REJECTED FILES + + if(req.session.rejectedFiles && req.session.rejectedFiles.length) { + var errorMessage = ''; + req.session.rejectedFiles.forEach(function(item) { + errorMessage += getErrorMessage(item); + errorMessage += '
'; + }); + + req.session.rejectedFiles.forEach(function(item) { + pageObject.errorSummary.items.push({ + text: getErrorMessage(item), + href: '#documents' + }); + }); + + pageObject.errorMessage = { + html: errorMessage + }; + } + + req.session.rejectedFiles = null; + + res.render( 'components/multi-file-upload/index.html', pageObject ); +}); + +function removeFileFromFileList(fileList, filename) { + + const index = fileList.findIndex( (item ) => item.filename === filename ); + if( index >= 0 ){ + fileList.splice( index, 1 ); + } +} + +router.post('/components/multi-file-upload', getUploadedFiles, function( req, res ){ + upload(req, res, function(err) { + if(err) { + // console.log(err); + } + + req.uploadedFiles.push(...req.files); + + if(req.body.delete) { + removeFileFromFileList(req.uploadedFiles, req.body.delete); + } + + // no concat because errors are discarded after use anyway + req.session.rejectedFiles = req.rejectedFiles; + + res.redirect('/components/multi-file-upload'); + }); +} ); + +//////////////////////////////////////////////////////////////////////////////////////// +// AJAX +//////////////////////////////////////////////////////////////////////////////////////// + +const uploadAjax = multer( { + dest: './public/uploads', + limits: { fileSize: 2000000 }, + fileFilter: function( req, file, cb ){ + let ok = false; + if( file.mimetype !== 'image/png' && file.mimetype !== 'image/gif' && file.mimetype !== 'image/jpg' && file.mimetype !== 'image/jpeg'){ + return cb({ + code: 'FILE_TYPE', + field: 'documents', + file: file + }, false); + } else { + return cb(null, true); + } + } +} ).single('documents'); + +router.post('/ajax-upload', getUploadedFiles, function( req, res ){ + + uploadAjax(req, res, function(error, val1, val2) { + if(error) { + if(error.code == 'FILE_TYPE') { + error.message = error.file.originalname + ' must be a png or gif'; + } else if(error.code == 'LIMIT_FILE_SIZE') { + // error.message = error.file.originalname + ' must be smaller than 2mb'; + error.message = 'The file must be smaller than 2mb'; + } + + var response = { + error: error, + file: error.file || { filename: 'filename', originalname: 'originalname' } + }; + + res.json(response); + } else { + + req.uploadedFiles.push(req.file); + + res.json({ + file: req.file, + success: { + messageHtml: ` ${req.file.originalname} has been uploaded`, + messageText: `${req.file.originalname} has been uploaded` + } + }); + } + } ); +} ); + +router.post('/ajax-delete', getUploadedFiles, function( req, res ){ + removeFileFromFileList(req.uploadedFiles, req.body.delete); + res.json({}); +}); + +module.exports = router; \ No newline at end of file diff --git a/app/views/components/multi-file-upload/index.html b/app/views/components/multi-file-upload/index.html new file mode 100644 index 000000000..10ee8d924 --- /dev/null +++ b/app/views/components/multi-file-upload/index.html @@ -0,0 +1,80 @@ +{% extends "layouts/base.html" %} + +{% from "govuk/components/file-upload/macro.njk" import govukFileUpload %} + +{% block body %} +
+
+ + {{ govukBackLink({ + text: "Back", + href: "../" + }) }} + +

+ Components Multi file upload +

+ +
+
+ + {% if errorSummary.items.length %} + {{ govukErrorSummary({ + titleText: 'There is a problem', + errorList: errorSummary.items + }) }} + {% endif %} + +
+ {% set uploadHtml %} + {{ govukFileUpload({ + id: "documents", + name: "documents", + classes: 'moj-multi-file-upload__input', + label: { + text: "Upload a file", + classes: 'govuk-label--m' + }, + attributes: { multiple: '' }, + errorMessage: errorMessage + }) }} + + {{govukButton({ + text: 'Upload file', + classes: 'govuk-button--secondary moj-multi-file-upload__button' + })}} + {% endset %} + + {{ mojMultiFileUpload({ + uploadedFiles: { + heading: { text: 'Files added' }, + items: uploadedFiles + }, + uploadHtml: uploadHtml + }) }} + + {{govukButton({ + text: 'Continue' + })}} +
+
+
+
+
+ +{% endblock %} + +{% block pageScripts %} + + + +{% endblock %} + diff --git a/app/views/components/primary-navigation/index.html b/app/views/components/primary-navigation/index.html index aca4bb9da..354bafad6 100644 --- a/app/views/components/primary-navigation/index.html +++ b/app/views/components/primary-navigation/index.html @@ -74,7 +74,7 @@

Toggle search