Skip to content

Commit

Permalink
Merge pull request #65 from ministryofjustice/dropzone
Browse files Browse the repository at this point in the history
Dropzone (Do not merge)
  • Loading branch information
adamsilver authored Aug 9, 2019
2 parents 72b77df + 42d676d commit eb20bd2
Show file tree
Hide file tree
Showing 22 changed files with 861 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/*
Expand Down
205 changes: 205 additions & 0 deletions app/routes/upload.js
Original file line number Diff line number Diff line change
@@ -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: `<span class="moj-multi-file-upload__success"> <svg class="moj-banner__icon" fill="currentColor" role="presentation" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25" height="25" width="25"><path d="M25,6.2L8.7,23.2L0,14.1l4-4.2l4.7,4.9L21,2L25,6.2z"/></svg> <a href="/${file.path}">${file.originalname}</a> has been uploaded</span>`
};

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 += '<br>';
});

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: `<a href="${req.file.path}" class="govuk-link"> ${req.file.originalname}</a> 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;
80 changes: 80 additions & 0 deletions app/views/components/multi-file-upload/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{% extends "layouts/base.html" %}

{% from "govuk/components/file-upload/macro.njk" import govukFileUpload %}

{% block body %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-full">

{{ govukBackLink({
text: "Back",
href: "../"
}) }}

<h1 class="govuk-heading-xl">
<span class="govuk-caption-xl">Components</span> Multi file upload
</h1>

<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">

{% if errorSummary.items.length %}
{{ govukErrorSummary({
titleText: 'There is a problem',
errorList: errorSummary.items
}) }}
{% endif %}

<form action="/components/multi-file-upload" method="post" enctype="multipart/form-data">
{% 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'
})}}
</form>
</div>
</div>
</div>
</div>

{% endblock %}

{% block pageScripts %}

<script>
if(typeof MOJFrontend.MultiFileUpload !== 'undefined') {
new MOJFrontend.MultiFileUpload({
container: $('.moj-multi-file-upload'),
uploadUrl: '/ajax-upload',
deleteUrl: '/ajax-delete'
});
}
</script>

{% endblock %}

2 changes: 1 addition & 1 deletion app/views/components/primary-navigation/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ <h2 class="govuk-heading-m govuk-!-margin-top-9">Toggle search</h2>
<div class="moj-search-toggle__toggle"></div>
<div class="moj-search-toggle__search">
{{ mojSearch({
classes: 'moj-search--ondark moj-search--toggle moj-hidden',
classes: 'moj-search--ondark moj-search--toggle moj-js-hidden',
input: {
id: 'search2',
name: 'search2'
Expand Down
1 change: 1 addition & 0 deletions app/views/includes/scripts.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<script src="/public/javascripts/components/add-another/add-another.js"></script>
<script src="/public/javascripts/components/form-validator/form-validator.js"></script>
<script src="/public/javascripts/components/menu/menu.js"></script>
<script src="/public/javascripts/components/multi-file-upload/multi-file-upload.js"></script>
<script src="/public/javascripts/components/multi-select/multi-select.js"></script>
<script src="/public/javascripts/components/password-reveal/password-reveal.js"></script>
<script src="/public/javascripts/components/rich-text-editor/rich-text-editor.js"></script>
Expand Down
1 change: 1 addition & 0 deletions app/views/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ <h1 class="govuk-heading-l">MOJ Frontend</h1>
<li><a href="/components/form-validator">Form validator</a></li>
<li><a href="/components/menu">Menu</a></li>
<li><a href="/components/messages">Messages</a></li>
<li><a href="/components/multi-file-upload">Multi file upload</a></li>
<li><a href="/components/multi-select">Multi-select</a></li>
<li><a href="/components/header">Header</a></li>
<li><a href="/components/identity-bar">Identity bar</a></li>
Expand Down
1 change: 1 addition & 0 deletions app/views/layouts/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
{%- from "moj/components/identity-bar/macro.njk" import mojIdentityBar %}
{%- from "moj/components/menu/macro.njk" import mojMenu %}
{%- from "moj/components/messages/macro.njk" import mojMessages %}
{%- from "moj/components/multi-file-upload/macro.njk" import mojMultiFileUpload %}
{%- from "moj/components/notification-badge/macro.njk" import mojNotificationBadge %}
{%- from "moj/components/organisation-switcher/macro.njk" import mojOrganisationSwitcher %}
{%- from "moj/components/pagination/macro.njk" import mojPagination %}
Expand Down
9 changes: 9 additions & 0 deletions lib/cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const cache = {};

module.exports = {
get: ( key ) => cache[ key ],
set: ( key, value ) => {
cache[ key ] = value
},
delete: ( key ) => delete cache[ key ],
};
Loading

0 comments on commit eb20bd2

Please sign in to comment.