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; }
|
z-index: 2; }
|
||||||
.formulate-input[data-classification="file"] .formulate-files .formualte-file-name {
|
.formulate-input[data-classification="file"] .formulate-files .formualte-file-name {
|
||||||
padding-left: 1.5em;
|
padding-left: 1.5em;
|
||||||
padding-right: 2em; }
|
padding-right: 2em;
|
||||||
|
max-width: 100%; }
|
||||||
.formulate-input[data-classification="file"] .formulate-files .formualte-file-name::before {
|
.formulate-input[data-classification="file"] .formulate-files .formualte-file-name::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: .7em;
|
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 nanoid from 'nanoid/non-secure'
|
||||||
|
import mimes from './libs/mimes'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The file upload class holds and represents a file’s upload state durring
|
* The file upload class holds and represents a file’s upload state durring
|
||||||
@ -15,9 +16,35 @@ class FileUpload {
|
|||||||
this.fileList = input.files
|
this.fileList = input.files
|
||||||
this.files = []
|
this.files = []
|
||||||
this.options = options
|
this.options = options
|
||||||
this.addFileList(this.fileList)
|
|
||||||
this.context = context
|
|
||||||
this.results = false
|
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,
|
uuid,
|
||||||
path: false,
|
path: false,
|
||||||
removeFile: removeFile.bind(this),
|
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 () {
|
uploaderIsAxios () {
|
||||||
if (
|
if (
|
||||||
this.hasUploader &&
|
this.hasUploader &&
|
||||||
typeof this.hasUploader.request === 'function' &&
|
typeof this.context.uploader.request === 'function' &&
|
||||||
typeof this.hasUploader.get === 'function' &&
|
typeof this.context.uploader.get === 'function' &&
|
||||||
typeof this.hasUploader.delete === 'function' &&
|
typeof this.context.uploader.delete === 'function' &&
|
||||||
typeof this.hasUploader.post === 'function'
|
typeof this.context.uploader.post === 'function'
|
||||||
) {
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -76,14 +106,19 @@ class FileUpload {
|
|||||||
if (this.uploaderIsAxios()) {
|
if (this.uploaderIsAxios()) {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append(this.context.name || 'file', args[0])
|
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: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data'
|
'Content-Type': 'multipart/form-data'
|
||||||
},
|
},
|
||||||
onUploadProgress: progressEvent => {
|
onUploadProgress: progressEvent => {
|
||||||
|
// args[1] here is the upload progress handler function
|
||||||
args[1](Math.round((progressEvent.loaded * 100) / progressEvent.total))
|
args[1](Math.round((progressEvent.loaded * 100) / progressEvent.total))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.then(res => res.data)
|
||||||
.catch(err => args[2](err))
|
.catch(err => args[2](err))
|
||||||
}
|
}
|
||||||
return this.context.uploader(...args)
|
return this.context.uploader(...args)
|
||||||
@ -135,7 +170,8 @@ class FileUpload {
|
|||||||
*/
|
*/
|
||||||
removeFile (uuid) {
|
removeFile (uuid) {
|
||||||
this.files = this.files.filter(file => file.uuid !== 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()
|
const transfer = new DataTransfer()
|
||||||
this.files.map(file => transfer.items.add(file.file))
|
this.files.map(file => transfer.items.add(file.file))
|
||||||
this.fileList = transfer.files
|
this.fileList = transfer.files
|
||||||
|
@ -43,6 +43,8 @@ class Formulate {
|
|||||||
rules,
|
rules,
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
uploader: fauxUploader,
|
uploader: fauxUploader,
|
||||||
|
uploadUrl: false,
|
||||||
|
fileUrlKey: 'url',
|
||||||
uploadJustCompleteDuration: 1000,
|
uploadJustCompleteDuration: 1000,
|
||||||
plugins: [],
|
plugins: [],
|
||||||
locales: {
|
locales: {
|
||||||
@ -162,6 +164,21 @@ class Formulate {
|
|||||||
return this.options.uploader || false
|
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.
|
* Create a new instance of an upload.
|
||||||
*/
|
*/
|
||||||
|
@ -36,6 +36,7 @@ export default {
|
|||||||
attributes: { id, ...groupApplicableAttributes },
|
attributes: { id, ...groupApplicableAttributes },
|
||||||
classification,
|
classification,
|
||||||
blurHandler,
|
blurHandler,
|
||||||
|
performValidation,
|
||||||
hasValidationErrors,
|
hasValidationErrors,
|
||||||
component,
|
component,
|
||||||
hasLabel,
|
hasLabel,
|
||||||
|
@ -52,6 +52,15 @@ export default {
|
|||||||
return !!(this.context.model instanceof FileUpload && this.context.model.files.length)
|
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 () {
|
mounted () {
|
||||||
// Add a listener to the window to prevent drag/drops that miss the dropzone
|
// 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.
|
// from opening the file and navigating the user away from the page.
|
||||||
|
@ -19,9 +19,10 @@ export default {
|
|||||||
label: this.label,
|
label: this.label,
|
||||||
labelPosition: this.logicalLabelPosition,
|
labelPosition: this.logicalLabelPosition,
|
||||||
attributes: this.elementAttributes,
|
attributes: this.elementAttributes,
|
||||||
|
performValidation: this.performValidation.bind(this),
|
||||||
blurHandler: blurHandler.bind(this),
|
blurHandler: blurHandler.bind(this),
|
||||||
imageBehavior: this.imageBehavior,
|
imageBehavior: this.imageBehavior,
|
||||||
uploadUrl: this.uploadUrl,
|
uploadUrl: this.mergedUploadUrl,
|
||||||
uploader: this.uploader || this.$formulate.getUploader(),
|
uploader: this.uploader || this.$formulate.getUploader(),
|
||||||
uploadBehavior: this.uploadBehavior,
|
uploadBehavior: this.uploadBehavior,
|
||||||
preventWindowDrops: this.preventWindowDrops,
|
preventWindowDrops: this.preventWindowDrops,
|
||||||
@ -37,7 +38,8 @@ export default {
|
|||||||
mergedErrors,
|
mergedErrors,
|
||||||
hasErrors,
|
hasErrors,
|
||||||
showFieldErrors,
|
showFieldErrors,
|
||||||
mergedValidationName
|
mergedValidationName,
|
||||||
|
mergedUploadUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,6 +113,14 @@ function mergedValidationName () {
|
|||||||
return this.type
|
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)
|
* Determines if the field should show it's error (if it has one)
|
||||||
* @return {boolean}
|
* @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) {
|
mime: function ({ value }, ...types) {
|
||||||
return Promise.resolve((() => {
|
return Promise.resolve((() => {
|
||||||
if (value instanceof FileUpload) {
|
if (value instanceof FileUpload) {
|
||||||
const fileList = value.getFileList()
|
const fileList = value.getFiles()
|
||||||
for (let i = 0; i < fileList.length; i++) {
|
for (let i = 0; i < fileList.length; i++) {
|
||||||
const file = fileList[i]
|
const file = fileList[i].file
|
||||||
if (!types.includes(file.type)) {
|
if (!types.includes(file.type)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -227,6 +227,9 @@ export default {
|
|||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return !!value.length
|
return !!value.length
|
||||||
}
|
}
|
||||||
|
if (value instanceof FileUpload) {
|
||||||
|
return value.getFiles().length > 0
|
||||||
|
}
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
return !!value
|
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', 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 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 {
|
.formualte-file-name {
|
||||||
padding-left: 1.5em;
|
padding-left: 1.5em;
|
||||||
padding-right: 2em;
|
padding-right: 2em;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user