Fixes issues with upload field re-hydration and requried validation rule for FileUpload fields.
This commit is contained in:
commit
c6eae3493b
2
dist/formulate.esm.js
vendored
2
dist/formulate.esm.js
vendored
File diff suppressed because one or more lines are too long
4
dist/formulate.min.js
vendored
4
dist/formulate.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/formulate.umd.js
vendored
2
dist/formulate.umd.js
vendored
File diff suppressed because one or more lines are too long
3
dist/snow.css
vendored
3
dist/snow.css
vendored
@ -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
2
dist/snow.min.css
vendored
File diff suppressed because one or more lines are too long
@ -1,4 +1,5 @@
|
||||
import nanoid from 'nanoid/non-secure'
|
||||
import mimes from './libs/mimes'
|
||||
|
||||
/**
|
||||
* The file upload class holds and represents a file’s 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
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -36,6 +36,7 @@ export default {
|
||||
attributes: { id, ...groupApplicableAttributes },
|
||||
classification,
|
||||
blurHandler,
|
||||
performValidation,
|
||||
hasValidationErrors,
|
||||
component,
|
||||
hasLabel,
|
||||
|
@ -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.
|
||||
|
@ -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
74
src/libs/mimes.js
Normal 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'
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
})
|
||||
|
||||
|
||||
|
@ -501,6 +501,7 @@
|
||||
.formualte-file-name {
|
||||
padding-left: 1.5em;
|
||||
padding-right: 2em;
|
||||
max-width: 100%;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
|
Loading…
x
Reference in New Issue
Block a user