277 lines
7.9 KiB
JavaScript
277 lines
7.9 KiB
JavaScript
import library from './libs/library'
|
||
import rules from './libs/rules'
|
||
import mimes from './libs/mimes'
|
||
import FileUpload from './FileUpload'
|
||
import { arrayify, parseLocale, has } from './libs/utils'
|
||
import isPlainObject from 'is-plain-object'
|
||
import fauxUploader from './libs/faux-uploader'
|
||
import FormularioForm from './FormularioForm.vue'
|
||
import FormularioInput from './FormularioInput.vue'
|
||
import FormularioGrouping from './FormularioGrouping.vue'
|
||
|
||
/**
|
||
* The base formulario library.
|
||
*/
|
||
class Formulario {
|
||
/**
|
||
* Instantiate our base options.
|
||
*/
|
||
constructor () {
|
||
this.options = {}
|
||
this.defaults = {
|
||
components: {
|
||
FormularioForm,
|
||
FormularioInput,
|
||
FormularioGrouping,
|
||
},
|
||
library,
|
||
rules,
|
||
mimes,
|
||
locale: false,
|
||
uploader: fauxUploader,
|
||
uploadUrl: false,
|
||
fileUrlKey: 'url',
|
||
uploadJustCompleteDuration: 1000,
|
||
errorHandler: (err) => err,
|
||
plugins: [],
|
||
idPrefix: 'formulario-'
|
||
}
|
||
this.registry = new Map()
|
||
this.idRegistry = {}
|
||
}
|
||
|
||
/**
|
||
* Install vue formulario, and register it’s components.
|
||
*/
|
||
install (Vue, options) {
|
||
Vue.prototype.$formulario = this
|
||
this.options = this.defaults
|
||
var plugins = this.defaults.plugins
|
||
if (options && Array.isArray(options.plugins) && options.plugins.length) {
|
||
plugins = plugins.concat(options.plugins)
|
||
}
|
||
plugins.forEach(plugin => (typeof plugin === 'function') ? plugin(this) : null)
|
||
this.extend(options || {})
|
||
for (var componentName in this.options.components) {
|
||
Vue.component(componentName, this.options.components[componentName])
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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.
|
||
*/
|
||
nextId (vm) {
|
||
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
|
||
}
|
||
return `${this.options.idPrefix}${pathPrefix}-${++this.idRegistry[pathPrefix]}`
|
||
}
|
||
|
||
/**
|
||
* Given a set of options, apply them to the pre-existing options.
|
||
* @param {Object} extendWith
|
||
*/
|
||
extend (extendWith) {
|
||
if (typeof extendWith === 'object') {
|
||
this.options = this.merge(this.options, extendWith)
|
||
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
|
||
*/
|
||
merge (base, mergeWith, concatArrays = true) {
|
||
var merged = {}
|
||
for (var key in base) {
|
||
if (mergeWith.hasOwnProperty(key)) {
|
||
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]
|
||
}
|
||
}
|
||
for (var prop in mergeWith) {
|
||
if (!merged.hasOwnProperty(prop)) {
|
||
merged[prop] = mergeWith[prop]
|
||
}
|
||
}
|
||
return merged
|
||
}
|
||
|
||
/**
|
||
* Determine what "class" of input this element is given the "type".
|
||
* @param {string} type
|
||
*/
|
||
classify (type) {
|
||
if (this.options.library.hasOwnProperty(type)) {
|
||
return this.options.library[type].classification
|
||
}
|
||
return 'unknown'
|
||
}
|
||
|
||
/**
|
||
* Determine what type of component to render given the "type".
|
||
* @param {string} type
|
||
*/
|
||
component (type) {
|
||
if (this.options.library.hasOwnProperty(type)) {
|
||
return this.options.library[type].component
|
||
}
|
||
return false
|
||
}
|
||
|
||
/**
|
||
* Get validation rules by merging any passed in with global rules.
|
||
* @return {object} object of validation functions
|
||
*/
|
||
rules (rules = {}) {
|
||
return { ...this.options.rules, ...rules }
|
||
}
|
||
|
||
/**
|
||
* Attempt to get the vue-i18n configured locale.
|
||
*/
|
||
i18n (vm) {
|
||
if (vm.$i18n) {
|
||
switch (typeof vm.$i18n.locale) {
|
||
case 'string':
|
||
return vm.$i18n.locale
|
||
case 'function':
|
||
return vm.$i18n.locale()
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
/**
|
||
* Get the validation message for a particular error.
|
||
*/
|
||
validationMessage (rule, validationContext, vm) {
|
||
return rule
|
||
}
|
||
|
||
/**
|
||
* Given an instance of a FormularioForm register it.
|
||
* @param {vm} form
|
||
*/
|
||
register (form) {
|
||
if (form.$options.name === 'FormularioForm' && form.name) {
|
||
this.registry.set(form.name, form)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Given an instance of a form, remove it from the registry.
|
||
* @param {vm} form
|
||
*/
|
||
deregister (form) {
|
||
if (
|
||
form.$options.name === 'FormularioForm' &&
|
||
form.name &&
|
||
this.registry.has(form.name)
|
||
) {
|
||
this.registry.delete(form.name)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Given an array, this function will attempt to make sense of the given error
|
||
* and hydrate a form with the resulting errors.
|
||
*
|
||
* @param {error} err
|
||
* @param {string} formName
|
||
* @param {error}
|
||
*/
|
||
handle (err, formName, skip = false) {
|
||
const e = skip ? err : this.options.errorHandler(err, formName)
|
||
if (formName && this.registry.has(formName)) {
|
||
this.registry.get(formName).applyErrors({
|
||
formErrors: arrayify(e.formErrors),
|
||
inputErrors: e.inputErrors || {}
|
||
})
|
||
}
|
||
return e
|
||
}
|
||
|
||
/**
|
||
* Reset a form.
|
||
* @param {string} formName
|
||
* @param {object} initialValue
|
||
*/
|
||
reset (formName, initialValue = {}) {
|
||
this.resetValidation(formName)
|
||
this.setValues(formName, initialValue)
|
||
}
|
||
|
||
/**
|
||
* Reset the form's validation messages.
|
||
* @param {string} formName
|
||
*/
|
||
resetValidation (formName) {
|
||
const form = this.registry.get(formName)
|
||
form.hideErrors(formName)
|
||
form.namedErrors = []
|
||
form.namedFieldErrors = {}
|
||
}
|
||
|
||
/**
|
||
* Set the form values.
|
||
* @param {string} formName
|
||
* @param {object} values
|
||
*/
|
||
setValues (formName, values) {
|
||
if (values && !Array.isArray(values) && typeof values === 'object') {
|
||
const form = this.registry.get(formName)
|
||
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.
|
||
*/
|
||
createUpload (fileList, context) {
|
||
return new FileUpload(fileList, context, this.options)
|
||
}
|
||
}
|
||
|
||
export default new Formulario()
|