2020-10-10 22:45:28 +03:00
|
|
|
|
import { VueConstructor } from 'vue'
|
|
|
|
|
|
2020-05-22 14:22:56 +03:00
|
|
|
|
import library from './libs/library'
|
|
|
|
|
import rules from './libs/rules'
|
|
|
|
|
import mimes from './libs/mimes'
|
|
|
|
|
import FileUpload from './FileUpload'
|
2020-05-25 12:49:49 +03:00
|
|
|
|
import RuleValidationMessages from './RuleValidationMessages'
|
2020-10-10 22:45:28 +03:00
|
|
|
|
import { arrayify, has } from './libs/utils'
|
2020-05-22 14:22:56 +03:00
|
|
|
|
import isPlainObject from 'is-plain-object'
|
|
|
|
|
import fauxUploader from './libs/faux-uploader'
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
import FormularioForm from '@/FormularioForm.vue'
|
|
|
|
|
import FormularioInput from '@/FormularioInput.vue'
|
2020-05-22 14:22:56 +03:00
|
|
|
|
import FormularioGrouping from './FormularioGrouping.vue'
|
2020-10-10 22:45:28 +03:00
|
|
|
|
import { ObjectType } from '@/common.types'
|
|
|
|
|
import { ValidationContext } from '@/validation.types'
|
|
|
|
|
|
|
|
|
|
interface ErrorHandler {
|
|
|
|
|
(error: any, formName?: string): any
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface FormularioOptions {
|
|
|
|
|
components?: { [name: string]: VueConstructor }
|
|
|
|
|
plugins?: any[]
|
|
|
|
|
library?: any
|
|
|
|
|
rules?: any
|
|
|
|
|
mimes?: any
|
|
|
|
|
locale?: any
|
|
|
|
|
uploader?: any
|
|
|
|
|
uploadUrl?: any
|
|
|
|
|
fileUrlKey?: any
|
|
|
|
|
errorHandler?: ErrorHandler
|
|
|
|
|
uploadJustCompleteDuration?: any
|
|
|
|
|
validationMessages?: any
|
|
|
|
|
idPrefix?: string
|
|
|
|
|
}
|
2020-05-22 14:22:56 +03:00
|
|
|
|
|
2020-10-09 22:58:28 +03:00
|
|
|
|
// noinspection JSUnusedGlobalSymbols
|
2020-05-22 14:22:56 +03:00
|
|
|
|
/**
|
|
|
|
|
* The base formulario library.
|
|
|
|
|
*/
|
|
|
|
|
class Formulario {
|
2020-10-10 22:45:28 +03:00
|
|
|
|
public options: FormularioOptions
|
|
|
|
|
public defaults: FormularioOptions
|
|
|
|
|
public registry: Map<string, FormularioForm>
|
|
|
|
|
public idRegistry: { [name: string]: number }
|
|
|
|
|
|
2020-05-22 14:22:56 +03:00
|
|
|
|
/**
|
|
|
|
|
* Instantiate our base options.
|
|
|
|
|
*/
|
|
|
|
|
constructor () {
|
|
|
|
|
this.options = {}
|
|
|
|
|
this.defaults = {
|
|
|
|
|
components: {
|
|
|
|
|
FormularioForm,
|
|
|
|
|
FormularioInput,
|
2020-10-10 22:45:28 +03:00
|
|
|
|
FormularioGrouping,
|
2020-05-22 14:22:56 +03:00
|
|
|
|
},
|
|
|
|
|
library,
|
|
|
|
|
rules,
|
|
|
|
|
mimes,
|
|
|
|
|
locale: false,
|
|
|
|
|
uploader: fauxUploader,
|
|
|
|
|
uploadUrl: false,
|
|
|
|
|
fileUrlKey: 'url',
|
|
|
|
|
uploadJustCompleteDuration: 1000,
|
2020-10-10 22:45:28 +03:00
|
|
|
|
errorHandler: (error: any) => error,
|
2020-10-09 22:58:28 +03:00
|
|
|
|
plugins: [RuleValidationMessages],
|
2020-05-25 12:49:49 +03:00
|
|
|
|
validationMessages: {},
|
2020-05-22 14:22:56 +03:00
|
|
|
|
idPrefix: 'formulario-'
|
|
|
|
|
}
|
|
|
|
|
this.registry = new Map()
|
|
|
|
|
this.idRegistry = {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Install vue formulario, and register it’s components.
|
|
|
|
|
*/
|
2020-10-10 22:45:28 +03:00
|
|
|
|
install (Vue: VueConstructor, options?: FormularioOptions) {
|
2020-05-22 14:22:56 +03:00
|
|
|
|
Vue.prototype.$formulario = this
|
|
|
|
|
this.options = this.defaults
|
2020-10-10 22:45:28 +03:00
|
|
|
|
let plugins = this.defaults.plugins as any[]
|
2020-05-22 14:22:56 +03:00
|
|
|
|
if (options && Array.isArray(options.plugins) && options.plugins.length) {
|
2020-10-09 22:58:28 +03:00
|
|
|
|
plugins = plugins.concat(options.plugins)
|
2020-05-22 14:22:56 +03:00
|
|
|
|
}
|
|
|
|
|
plugins.forEach(plugin => (typeof plugin === 'function') ? plugin(this) : null)
|
|
|
|
|
this.extend(options || {})
|
2020-10-09 22:58:28 +03:00
|
|
|
|
for (const componentName in this.options.components) {
|
|
|
|
|
if (Object.prototype.hasOwnProperty.call(this.options.components, componentName)) {
|
|
|
|
|
Vue.component(componentName, this.options.components[componentName])
|
|
|
|
|
}
|
2020-05-22 14:22:56 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Produce a deterministically generated id based on the sequence by which it
|
|
|
|
|
* was requested. This should be *theoretically* the same SSR as client side.
|
|
|
|
|
* However, SSR and deterministic ids can be very challenging, so this
|
|
|
|
|
* implementation is open to community review.
|
|
|
|
|
*/
|
2020-10-10 22:45:28 +03:00
|
|
|
|
nextId (vm: Vue) {
|
|
|
|
|
const options = this.options as FormularioOptions
|
2020-05-22 14:22:56 +03:00
|
|
|
|
const path = vm.$route && vm.$route.path ? vm.$route.path : false
|
|
|
|
|
const pathPrefix = path ? vm.$route.path.replace(/[/\\.\s]/g, '-') : 'global'
|
|
|
|
|
if (!Object.prototype.hasOwnProperty.call(this.idRegistry, pathPrefix)) {
|
|
|
|
|
this.idRegistry[pathPrefix] = 0
|
|
|
|
|
}
|
2020-10-10 22:45:28 +03:00
|
|
|
|
return `${options.idPrefix}${pathPrefix}-${++this.idRegistry[pathPrefix]}`
|
2020-05-22 14:22:56 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Given a set of options, apply them to the pre-existing options.
|
|
|
|
|
*/
|
2020-10-10 22:45:28 +03:00
|
|
|
|
extend (extendWith: FormularioOptions) {
|
2020-05-22 14:22:56 +03:00
|
|
|
|
if (typeof extendWith === 'object') {
|
2020-10-10 22:45:28 +03:00
|
|
|
|
this.options = this.merge(this.options as FormularioOptions, extendWith)
|
2020-05-22 14:22:56 +03:00
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
throw new Error(`VueFormulario extend() should be passed an object (was ${typeof extendWith})`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a new object by copying properties of base and mergeWith.
|
|
|
|
|
* Note: arrays don't overwrite - they push
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} base
|
|
|
|
|
* @param {Object} mergeWith
|
|
|
|
|
* @param {boolean} concatArrays
|
|
|
|
|
*/
|
2020-10-10 22:45:28 +03:00
|
|
|
|
merge (base: ObjectType, mergeWith: ObjectType, concatArrays: boolean = true) {
|
|
|
|
|
const merged: ObjectType = {}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
|
|
|
|
for (const key in base) {
|
2020-10-10 22:45:28 +03:00
|
|
|
|
if (has(mergeWith, key)) {
|
2020-05-22 14:22:56 +03:00
|
|
|
|
if (isPlainObject(mergeWith[key]) && isPlainObject(base[key])) {
|
|
|
|
|
merged[key] = this.merge(base[key], mergeWith[key], concatArrays)
|
|
|
|
|
} else if (concatArrays && Array.isArray(base[key]) && Array.isArray(mergeWith[key])) {
|
|
|
|
|
merged[key] = base[key].concat(mergeWith[key])
|
|
|
|
|
} else {
|
|
|
|
|
merged[key] = mergeWith[key]
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
merged[key] = base[key]
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
|
|
|
|
for (const prop in mergeWith) {
|
2020-10-10 22:45:28 +03:00
|
|
|
|
if (!has(merged, prop)) {
|
2020-05-22 14:22:56 +03:00
|
|
|
|
merged[prop] = mergeWith[prop]
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-05-22 14:22:56 +03:00
|
|
|
|
return merged
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Determine what "class" of input this element is given the "type".
|
|
|
|
|
*/
|
2020-10-10 22:45:28 +03:00
|
|
|
|
classify (type: string) {
|
|
|
|
|
if (has(this.options.library, type)) {
|
2020-05-22 14:22:56 +03:00
|
|
|
|
return this.options.library[type].classification
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-05-22 14:22:56 +03:00
|
|
|
|
return 'unknown'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Determine what type of component to render given the "type".
|
|
|
|
|
*/
|
2020-10-10 22:45:28 +03:00
|
|
|
|
component (type: string) {
|
|
|
|
|
if (has(this.options.library, type)) {
|
2020-05-22 14:22:56 +03:00
|
|
|
|
return this.options.library[type].component
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-05-22 14:22:56 +03:00
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get validation rules by merging any passed in with global rules.
|
|
|
|
|
*/
|
2020-10-10 22:45:28 +03:00
|
|
|
|
rules (rules: Object = {}) {
|
2020-05-22 14:22:56 +03:00
|
|
|
|
return { ...this.options.rules, ...rules }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the validation message for a particular error.
|
|
|
|
|
*/
|
2020-10-10 22:45:28 +03:00
|
|
|
|
validationMessage (rule: string, context: ValidationContext, vm: Vue) {
|
|
|
|
|
if (has(this.options.validationMessages, rule)) {
|
|
|
|
|
return this.options.validationMessages[rule](vm, context)
|
2020-05-25 12:49:49 +03:00
|
|
|
|
} else {
|
2020-10-10 22:45:28 +03:00
|
|
|
|
return this.options.validationMessages.default(vm, context)
|
2020-05-25 12:49:49 +03:00
|
|
|
|
}
|
2020-05-22 14:22:56 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Given an instance of a FormularioForm register it.
|
|
|
|
|
*/
|
2020-10-10 22:45:28 +03:00
|
|
|
|
register (form: FormularioForm) {
|
|
|
|
|
// @ts-ignore
|
2020-05-22 14:22:56 +03:00
|
|
|
|
if (form.$options.name === 'FormularioForm' && form.name) {
|
2020-10-10 22:45:28 +03:00
|
|
|
|
// @ts-ignore
|
2020-05-22 14:22:56 +03:00
|
|
|
|
this.registry.set(form.name, form)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Given an instance of a form, remove it from the registry.
|
|
|
|
|
*/
|
2020-10-10 22:45:28 +03:00
|
|
|
|
deregister (form: FormularioForm) {
|
2020-05-22 14:22:56 +03:00
|
|
|
|
if (
|
|
|
|
|
form.$options.name === 'FormularioForm' &&
|
2020-10-10 22:45:28 +03:00
|
|
|
|
// @ts-ignore
|
2020-05-22 14:22:56 +03:00
|
|
|
|
form.name &&
|
2020-10-10 22:45:28 +03:00
|
|
|
|
// @ts-ignore
|
|
|
|
|
this.registry.has(form.name as string)
|
2020-05-22 14:22:56 +03:00
|
|
|
|
) {
|
2020-10-10 22:45:28 +03:00
|
|
|
|
// @ts-ignore
|
|
|
|
|
this.registry.delete(form.name as string)
|
2020-05-22 14:22:56 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Given an array, this function will attempt to make sense of the given error
|
|
|
|
|
* and hydrate a form with the resulting errors.
|
|
|
|
|
*/
|
2020-10-10 22:45:28 +03:00
|
|
|
|
handle (error: any, formName: string, skip: boolean = false) {
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
const e = skip ? error : this.options.errorHandler(error, formName)
|
2020-05-22 14:22:56 +03:00
|
|
|
|
if (formName && this.registry.has(formName)) {
|
2020-10-10 22:45:28 +03:00
|
|
|
|
const form = this.registry.get(formName) as FormularioForm
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
form.applyErrors({
|
2020-05-22 14:22:56 +03:00
|
|
|
|
formErrors: arrayify(e.formErrors),
|
|
|
|
|
inputErrors: e.inputErrors || {}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Reset a form.
|
|
|
|
|
*/
|
2020-10-10 22:45:28 +03:00
|
|
|
|
reset (formName: string, initialValue: Object = {}) {
|
2020-05-22 14:22:56 +03:00
|
|
|
|
this.resetValidation(formName)
|
|
|
|
|
this.setValues(formName, initialValue)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Reset the form's validation messages.
|
|
|
|
|
*/
|
2020-10-10 22:45:28 +03:00
|
|
|
|
resetValidation (formName: string) {
|
|
|
|
|
const form = this.registry.get(formName) as FormularioForm
|
|
|
|
|
// @ts-ignore
|
2020-05-22 14:22:56 +03:00
|
|
|
|
form.hideErrors(formName)
|
2020-10-10 22:45:28 +03:00
|
|
|
|
// @ts-ignore
|
2020-05-22 14:22:56 +03:00
|
|
|
|
form.namedErrors = []
|
2020-10-10 22:45:28 +03:00
|
|
|
|
// @ts-ignore
|
2020-05-22 14:22:56 +03:00
|
|
|
|
form.namedFieldErrors = {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set the form values.
|
|
|
|
|
*/
|
2020-10-10 22:45:28 +03:00
|
|
|
|
setValues (formName: string, values?: ObjectType) {
|
|
|
|
|
if (values) {
|
|
|
|
|
const form = this.registry.get(formName) as FormularioForm
|
|
|
|
|
// @ts-ignore
|
2020-05-22 14:22:56 +03:00
|
|
|
|
form.setValues({ ...values })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the file uploader.
|
|
|
|
|
*/
|
|
|
|
|
getUploader () {
|
|
|
|
|
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.
|
|
|
|
|
*/
|
2020-10-10 22:45:28 +03:00
|
|
|
|
createUpload (data: DataTransfer, context: ObjectType) {
|
|
|
|
|
return new FileUpload(data, context, this.options)
|
2020-05-22 14:22:56 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
export { Formulario }
|
|
|
|
|
|
2020-05-22 14:22:56 +03:00
|
|
|
|
export default new Formulario()
|