1
0
mirror of synced 2025-03-03 11:33:17 +03:00

Fixes issues with upload field re-hydration and requried validation rule for FileUpload fields.

This commit is contained in:
Justin Schroeder 2020-03-02 00:35:56 -05:00
commit c6eae3493b
14 changed files with 176 additions and 20 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3
dist/snow.css vendored
View File

@ -397,7 +397,8 @@
z-index: 2; }
.formulate-input[data-classification="file"] .formulate-files .formualte-file-name {
padding-left: 1.5em;
padding-right: 2em; }
padding-right: 2em;
max-width: 100%; }
.formulate-input[data-classification="file"] .formulate-files .formualte-file-name::before {
position: absolute;
left: .7em;

2
dist/snow.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,5 @@
import nanoid from 'nanoid/non-secure'
import mimes from './libs/mimes'
/**
* The file upload class holds and represents a files upload state durring
@ -15,9 +16,35 @@ class FileUpload {
this.fileList = input.files
this.files = []
this.options = options
this.addFileList(this.fileList)
this.context = context
this.results = false
this.context = context
if (Array.isArray(this.fileList)) {
this.rehydrateFileList(this.fileList)
} else {
this.addFileList(this.fileList)
}
}
/**
* Given a pre-existing array of files, create a faux FileList.
* @param {array} items expects an array of objects [{ url: '/uploads/file.pdf' }]
* @param {string} pathKey the object-key to access the url (defaults to "url")
*/
rehydrateFileList (items) {
const fauxFileList = items.reduce((fileList, item) => {
const key = this.options ? this.options.fileUrlKey : 'url'
const url = item[key]
const ext = (url && url.lastIndexOf('.') !== -1) ? url.substr(url.lastIndexOf('.') + 1) : false
const mime = mimes[ext] || false
fileList.push(Object.assign({}, item, url ? {
name: url.substr((url.lastIndexOf('/') + 1) || 0),
type: item.type ? item.type : mime,
previewData: url
} : {}))
return fileList
}, [])
this.results = items
this.addFileList(fauxFileList)
}
/**
@ -41,7 +68,7 @@ class FileUpload {
uuid,
path: false,
removeFile: removeFile.bind(this),
previewData: false
previewData: file.previewData || false
})
}
}
@ -54,15 +81,18 @@ class FileUpload {
}
/**
* Check if the given uploader is axios instance.
* Check if the given uploader is axios instance. This isn't a great way of
* testing if it is or not, but AFIK there isn't a better way right now:
*
* https://github.com/axios/axios/issues/737
*/
uploaderIsAxios () {
if (
this.hasUploader &&
typeof this.hasUploader.request === 'function' &&
typeof this.hasUploader.get === 'function' &&
typeof this.hasUploader.delete === 'function' &&
typeof this.hasUploader.post === 'function'
typeof this.context.uploader.request === 'function' &&
typeof this.context.uploader.get === 'function' &&
typeof this.context.uploader.delete === 'function' &&
typeof this.context.uploader.post === 'function'
) {
return true
}
@ -76,14 +106,19 @@ class FileUpload {
if (this.uploaderIsAxios()) {
const formData = new FormData()
formData.append(this.context.name || 'file', args[0])
return this.uploader.post(this.context.uploadUrl, formData, {
if (this.context.uploadUrl === false) {
throw new Error('No uploadURL specified: https://vueformulate.com/guide/inputs/file/#props')
}
return this.context.uploader.post(this.context.uploadUrl, formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: progressEvent => {
// args[1] here is the upload progress handler function
args[1](Math.round((progressEvent.loaded * 100) / progressEvent.total))
}
})
.then(res => res.data)
.catch(err => args[2](err))
}
return this.context.uploader(...args)
@ -135,7 +170,8 @@ class FileUpload {
*/
removeFile (uuid) {
this.files = this.files.filter(file => file.uuid !== uuid)
if (window) {
this.context.performValidation()
if (window && this.fileList instanceof FileList) {
const transfer = new DataTransfer()
this.files.map(file => transfer.items.add(file.file))
this.fileList = transfer.files

View File

@ -43,6 +43,8 @@ class Formulate {
rules,
locale: 'en',
uploader: fauxUploader,
uploadUrl: false,
fileUrlKey: 'url',
uploadJustCompleteDuration: 1000,
plugins: [],
locales: {
@ -162,6 +164,21 @@ class Formulate {
return this.options.uploader || false
}
/**
* Get the global upload url.
*/
getUploadUrl () {
return this.options.uploadUrl || false
}
/**
* When re-hydrating a file uploader with an array, get the sub-object key to
* access the url of the file. Usually this is just "url".
*/
getFileUrlKey () {
return this.options.fileUrlKey || 'url'
}
/**
* Create a new instance of an upload.
*/

View File

@ -36,6 +36,7 @@ export default {
attributes: { id, ...groupApplicableAttributes },
classification,
blurHandler,
performValidation,
hasValidationErrors,
component,
hasLabel,

View File

@ -52,6 +52,15 @@ export default {
return !!(this.context.model instanceof FileUpload && this.context.model.files.length)
}
},
created () {
if (Array.isArray(this.context.model)) {
if (typeof this.context.model[0][this.$formulate.getFileUrlKey()] === 'string') {
this.context.model = this.$formulate.createUpload({
files: this.context.model
}, this.context)
}
}
},
mounted () {
// Add a listener to the window to prevent drag/drops that miss the dropzone
// from opening the file and navigating the user away from the page.

View File

@ -19,9 +19,10 @@ export default {
label: this.label,
labelPosition: this.logicalLabelPosition,
attributes: this.elementAttributes,
performValidation: this.performValidation.bind(this),
blurHandler: blurHandler.bind(this),
imageBehavior: this.imageBehavior,
uploadUrl: this.uploadUrl,
uploadUrl: this.mergedUploadUrl,
uploader: this.uploader || this.$formulate.getUploader(),
uploadBehavior: this.uploadBehavior,
preventWindowDrops: this.preventWindowDrops,
@ -37,7 +38,8 @@ export default {
mergedErrors,
hasErrors,
showFieldErrors,
mergedValidationName
mergedValidationName,
mergedUploadUrl
}
/**
@ -111,6 +113,14 @@ function mergedValidationName () {
return this.type
}
/**
* Use the uploadURL on the input if it exists, otherwise use the uploadURL
* that is defined as a plugin option.
*/
function mergedUploadUrl () {
return this.uploadUrl || this.$formulate.getUploadUrl()
}
/**
* Determines if the field should show it's error (if it has one)
* @return {boolean}

74
src/libs/mimes.js Normal file
View File

@ -0,0 +1,74 @@
export default {
'aac': 'audio/aac',
'abw': 'application/x-abiword',
'arc': 'application/x-freearc',
'avi': 'video/x-msvideo',
'azw': 'application/vnd.amazon.ebook',
'bin': 'application/octet-stream',
'bmp': 'image/bmp',
'bz': 'application/x-bzip',
'bz2': 'application/x-bzip2',
'csh': 'application/x-csh',
'css': 'text/css',
'csv': 'text/csv',
'doc': 'application/msword',
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'eot': 'application/vnd.ms-fontobject',
'epub': 'application/epub+zip',
'gz': 'application/gzip',
'gif': 'image/gif',
'htm': 'text/html',
'html': 'text/html',
'ico': 'image/vnd.microsoft.icon',
'ics': 'text/calendar',
'jar': 'application/java-archive',
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'js': 'text/javascript',
'json': 'application/json',
'jsonld': 'application/ld+json',
'midi': 'audio/x-midi',
'mid': 'audio/midi',
'mjs': 'text/javascript',
'mp3': 'audio/mpeg',
'mpeg': 'video/mpeg',
'mpkg': 'application/vnd.apple.installer+xml',
'odp': 'application/vnd.oasis.opendocument.presentation',
'ods': 'application/vnd.oasis.opendocument.spreadsheet',
'odt': 'application/vnd.oasis.opendocument.text',
'oga': 'audio/ogg',
'ogv': 'video/ogg',
'ogx': 'application/ogg',
'opus': 'audio/opus',
'otf': 'font/otf',
'png': 'image/png',
'pdf': 'application/pdf',
'php': 'application/php',
'ppt': 'application/vnd.ms-powerpoint',
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'rar': 'application/vnd.rar',
'rtf': 'application/rtf',
'sh': 'application/x-sh',
'svg': 'image/svg+xml',
'swf': 'application/x-shockwave-flash',
'tar': 'application/x-tar',
'tif': 'image/tiff',
'tiff': 'image/tiff',
'ts': 'video/mp2t',
'ttf': 'font/ttf',
'txt': 'text/plain',
'vsd': 'application/vnd.visio',
'wav': 'audio/wav',
'weba': 'audio/webm',
'webm': 'video/webm',
'webp': 'image/webp',
'woff': 'font/woff',
'woff2': 'font/woff2',
'xhtml': 'application/xhtml+xml',
'xls': 'application/vnd.ms-excel',
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xml': 'text/xml',
'xul': 'application/vnd.mozilla.xul+xml',
'zip': 'application/zip',
'7z': 'application/x-7z-compressed'
}

View File

@ -164,9 +164,9 @@ export default {
mime: function ({ value }, ...types) {
return Promise.resolve((() => {
if (value instanceof FileUpload) {
const fileList = value.getFileList()
const fileList = value.getFiles()
for (let i = 0; i < fileList.length; i++) {
const file = fileList[i]
const file = fileList[i].file
if (!types.includes(file.type)) {
return false
}
@ -227,6 +227,9 @@ export default {
if (Array.isArray(value)) {
return !!value.length
}
if (value instanceof FileUpload) {
return value.getFiles().length > 0
}
if (typeof value === 'string') {
return !!value
}

View File

@ -24,6 +24,10 @@ describe('required', () => {
it('passes with empty value if second argument is false', async () => expect(await rules.required({ value: '' }, false)).toBe(true))
it('passes with empty value if second argument is false string', async () => expect(await rules.required({ value: '' }, 'false')).toBe(true))
it('passes with FileUpload', async () => expect(await rules.required({ value: new FileUpload({ files: [{ name: 'j.png' }] }) })).toBe(true))
it('fails with empty FileUpload', async () => expect(await rules.required({ value: new FileUpload({ files: [] }) })).toBe(false))
})

View File

@ -501,6 +501,7 @@
.formualte-file-name {
padding-left: 1.5em;
padding-right: 2em;
max-width: 100%;
&::before {
position: absolute;