2019-10-07 10:24:30 -04:00
|
|
|
|
import library from './libs/library'
|
2019-11-07 17:03:34 -05:00
|
|
|
|
import rules from './libs/rules'
|
2020-03-06 16:10:25 -05:00
|
|
|
|
import mimes from './libs/mimes'
|
2019-11-15 14:44:01 -05:00
|
|
|
|
import FileUpload from './FileUpload'
|
2020-04-22 00:00:02 -04:00
|
|
|
|
import { arrayify, parseLocale, has } from './libs/utils'
|
2019-10-07 10:24:30 -04:00
|
|
|
|
import isPlainObject from 'is-plain-object'
|
2020-03-07 09:03:59 -05:00
|
|
|
|
import { en } from '@braid/vue-formulate-i18n'
|
2019-11-15 14:44:01 -05:00
|
|
|
|
import fauxUploader from './libs/faux-uploader'
|
2020-04-16 09:22:58 -04:00
|
|
|
|
import FormulateSlot from './FormulateSlot'
|
2019-10-07 10:24:30 -04:00
|
|
|
|
import FormulateForm from './FormulateForm.vue'
|
2020-04-18 00:39:25 -04:00
|
|
|
|
import FormulateInput from './FormulateInput.vue'
|
2020-03-06 16:10:25 -05:00
|
|
|
|
import FormulateErrors from './FormulateErrors.vue'
|
2020-04-06 09:29:04 -04:00
|
|
|
|
import FormulateHelp from './slots/FormulateHelp.vue'
|
2020-04-16 09:22:58 -04:00
|
|
|
|
import FormulateGrouping from './FormulateGrouping.vue'
|
2020-04-06 09:29:04 -04:00
|
|
|
|
import FormulateLabel from './slots/FormulateLabel.vue'
|
2020-04-18 00:39:25 -04:00
|
|
|
|
import FormulateAddMore from './slots/FormulateAddMore.vue'
|
2019-10-07 10:24:30 -04:00
|
|
|
|
import FormulateInputBox from './inputs/FormulateInputBox.vue'
|
|
|
|
|
import FormulateInputText from './inputs/FormulateInputText.vue'
|
2019-11-15 14:44:01 -05:00
|
|
|
|
import FormulateInputFile from './inputs/FormulateInputFile.vue'
|
2020-04-16 09:22:58 -04:00
|
|
|
|
import FormulateRepeatable from './slots/FormulateRepeatable.vue'
|
|
|
|
|
import FormulateInputGroup from './inputs/FormulateInputGroup.vue'
|
2020-01-28 12:12:08 -05:00
|
|
|
|
import FormulateInputButton from './inputs/FormulateInputButton.vue'
|
2019-10-07 10:24:30 -04:00
|
|
|
|
import FormulateInputSelect from './inputs/FormulateInputSelect.vue'
|
2019-11-14 01:00:56 -05:00
|
|
|
|
import FormulateInputSlider from './inputs/FormulateInputSlider.vue'
|
2019-10-07 10:24:30 -04:00
|
|
|
|
import FormulateInputTextArea from './inputs/FormulateInputTextArea.vue'
|
2020-05-08 17:25:52 -04:00
|
|
|
|
import FormulateRepeatableProvider from './FormulateRepeatableProvider.vue'
|
2019-11-07 17:03:34 -05:00
|
|
|
|
|
2019-10-07 10:24:30 -04:00
|
|
|
|
/**
|
|
|
|
|
* The base formulate library.
|
|
|
|
|
*/
|
2018-01-30 17:21:21 -05:00
|
|
|
|
class Formulate {
|
|
|
|
|
/**
|
2019-10-07 10:24:30 -04:00
|
|
|
|
* Instantiate our base options.
|
2018-01-30 17:21:21 -05:00
|
|
|
|
*/
|
|
|
|
|
constructor () {
|
2020-02-27 14:47:24 -05:00
|
|
|
|
this.options = {}
|
2019-10-07 10:24:30 -04:00
|
|
|
|
this.defaults = {
|
|
|
|
|
components: {
|
2020-04-16 09:22:58 -04:00
|
|
|
|
FormulateSlot,
|
2019-10-07 10:24:30 -04:00
|
|
|
|
FormulateForm,
|
2020-04-06 09:29:04 -04:00
|
|
|
|
FormulateHelp,
|
|
|
|
|
FormulateLabel,
|
2019-10-07 10:24:30 -04:00
|
|
|
|
FormulateInput,
|
2020-03-06 16:10:25 -05:00
|
|
|
|
FormulateErrors,
|
2020-04-18 00:39:25 -04:00
|
|
|
|
FormulateAddMore,
|
2020-04-16 09:22:58 -04:00
|
|
|
|
FormulateGrouping,
|
2019-10-07 10:24:30 -04:00
|
|
|
|
FormulateInputBox,
|
|
|
|
|
FormulateInputText,
|
2019-11-15 14:44:01 -05:00
|
|
|
|
FormulateInputFile,
|
2020-04-16 09:22:58 -04:00
|
|
|
|
FormulateRepeatable,
|
2019-10-07 10:24:30 -04:00
|
|
|
|
FormulateInputGroup,
|
2020-01-28 12:12:08 -05:00
|
|
|
|
FormulateInputButton,
|
2019-10-07 10:24:30 -04:00
|
|
|
|
FormulateInputSelect,
|
2019-11-14 01:00:56 -05:00
|
|
|
|
FormulateInputSlider,
|
2020-04-18 23:57:43 -04:00
|
|
|
|
FormulateInputTextArea,
|
|
|
|
|
FormulateRepeatableProvider
|
2018-01-30 17:21:21 -05:00
|
|
|
|
},
|
2020-04-06 09:29:04 -04:00
|
|
|
|
slotDefaults: {
|
|
|
|
|
label: 'FormulateLabel',
|
|
|
|
|
help: 'FormulateHelp',
|
2020-04-18 00:39:25 -04:00
|
|
|
|
errors: 'FormulateErrors',
|
|
|
|
|
repeatable: 'FormulateRepeatable',
|
|
|
|
|
addMore: 'FormulateAddMore'
|
2020-04-06 09:29:04 -04:00
|
|
|
|
},
|
2019-11-07 17:03:34 -05:00
|
|
|
|
library,
|
2019-11-13 16:10:17 -05:00
|
|
|
|
rules,
|
2020-03-06 16:10:25 -05:00
|
|
|
|
mimes,
|
2020-03-07 09:03:59 -05:00
|
|
|
|
locale: false,
|
2019-11-15 14:44:01 -05:00
|
|
|
|
uploader: fauxUploader,
|
2020-03-01 13:28:46 -05:00
|
|
|
|
uploadUrl: false,
|
2020-03-02 00:35:56 -05:00
|
|
|
|
fileUrlKey: 'url',
|
2019-11-19 07:15:13 -05:00
|
|
|
|
uploadJustCompleteDuration: 1000,
|
2020-03-06 16:10:25 -05:00
|
|
|
|
errorHandler: (err) => err,
|
2020-03-07 10:05:05 -05:00
|
|
|
|
plugins: [ en ],
|
|
|
|
|
locales: {}
|
2018-01-30 17:21:21 -05:00
|
|
|
|
}
|
2020-03-06 16:10:25 -05:00
|
|
|
|
this.registry = new Map()
|
2018-01-30 17:21:21 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2019-10-07 10:24:30 -04:00
|
|
|
|
* Install vue formulate, and register it’s components.
|
2018-01-30 17:21:21 -05:00
|
|
|
|
*/
|
2019-10-07 10:24:30 -04:00
|
|
|
|
install (Vue, options) {
|
2018-01-30 17:21:21 -05:00
|
|
|
|
Vue.prototype.$formulate = this
|
2020-03-07 10:27:51 -05:00
|
|
|
|
this.options = this.defaults
|
|
|
|
|
var plugins = this.defaults.plugins
|
|
|
|
|
if (options && Array.isArray(options.plugins) && options.plugins.length) {
|
|
|
|
|
plugins = plugins.concat(options.plugins)
|
2020-02-27 14:47:24 -05:00
|
|
|
|
}
|
2020-03-07 10:27:51 -05:00
|
|
|
|
plugins.forEach(plugin => (typeof plugin === 'function') ? plugin(this) : null)
|
|
|
|
|
this.extend(options || {})
|
2019-10-07 10:24:30 -04:00
|
|
|
|
for (var componentName in this.options.components) {
|
|
|
|
|
Vue.component(componentName, this.options.components[componentName])
|
2018-01-30 17:21:21 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-02-27 14:47:24 -05:00
|
|
|
|
* Given a set of options, apply them to the pre-existing options.
|
2019-10-07 10:24:30 -04:00
|
|
|
|
* @param {Object} extendWith
|
2018-01-30 17:21:21 -05:00
|
|
|
|
*/
|
2020-02-27 14:47:24 -05:00
|
|
|
|
extend (extendWith) {
|
|
|
|
|
if (typeof extendWith === 'object') {
|
|
|
|
|
this.options = this.merge(this.options, extendWith)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
throw new Error(`VueFormulate 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) {
|
2019-10-07 10:24:30 -04:00
|
|
|
|
var merged = {}
|
|
|
|
|
for (var key in base) {
|
2020-02-27 14:47:24 -05:00
|
|
|
|
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]
|
|
|
|
|
}
|
2019-10-07 10:24:30 -04:00
|
|
|
|
} else {
|
|
|
|
|
merged[key] = base[key]
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-27 14:47:24 -05:00
|
|
|
|
for (var prop in mergeWith) {
|
2019-10-07 10:24:30 -04:00
|
|
|
|
if (!merged.hasOwnProperty(prop)) {
|
2020-02-27 14:47:24 -05:00
|
|
|
|
merged[prop] = mergeWith[prop]
|
2019-10-07 10:24:30 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return merged
|
2018-01-30 17:21:21 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2019-10-07 10:24:30 -04:00
|
|
|
|
* Determine what "class" of input this element is given the "type".
|
|
|
|
|
* @param {string} type
|
2018-01-30 17:21:21 -05:00
|
|
|
|
*/
|
2019-10-07 10:24:30 -04:00
|
|
|
|
classify (type) {
|
|
|
|
|
if (this.options.library.hasOwnProperty(type)) {
|
|
|
|
|
return this.options.library[type].classification
|
|
|
|
|
}
|
|
|
|
|
return 'unknown'
|
2018-01-30 17:21:21 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2019-10-07 10:24:30 -04:00
|
|
|
|
* Determine what type of component to render given the "type".
|
|
|
|
|
* @param {string} type
|
2018-01-30 17:21:21 -05:00
|
|
|
|
*/
|
2019-10-07 10:24:30 -04:00
|
|
|
|
component (type) {
|
|
|
|
|
if (this.options.library.hasOwnProperty(type)) {
|
|
|
|
|
return this.options.library[type].component
|
2018-01-30 17:21:21 -05:00
|
|
|
|
}
|
2019-10-07 10:24:30 -04:00
|
|
|
|
return false
|
2018-01-30 17:21:21 -05:00
|
|
|
|
}
|
2019-11-07 17:03:34 -05:00
|
|
|
|
|
2020-04-06 09:29:04 -04:00
|
|
|
|
/**
|
|
|
|
|
* What component should be rendered for the given slot location and type.
|
|
|
|
|
* @param {string} type the type of component
|
|
|
|
|
* @param {string} slot the name of the slot
|
|
|
|
|
*/
|
|
|
|
|
slotComponent (type, slot) {
|
|
|
|
|
const def = this.options.library[type]
|
2020-04-18 00:39:25 -04:00
|
|
|
|
if (def.slotComponents && def.slotComponents[slot]) {
|
|
|
|
|
return def.slotComponents[slot]
|
2020-04-06 09:29:04 -04:00
|
|
|
|
}
|
|
|
|
|
return this.options.slotDefaults[slot]
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-07 17:03:34 -05:00
|
|
|
|
/**
|
2020-05-08 17:25:52 -04:00
|
|
|
|
* Get validation rules by merging any passed in with global rules.
|
2019-11-07 17:03:34 -05:00
|
|
|
|
* @return {object} object of validation functions
|
|
|
|
|
*/
|
2020-02-27 01:18:51 -05:00
|
|
|
|
rules (rules = {}) {
|
|
|
|
|
return { ...this.options.rules, ...rules }
|
2019-11-07 17:03:34 -05:00
|
|
|
|
}
|
2019-11-13 16:10:17 -05:00
|
|
|
|
|
2020-03-07 09:03:59 -05:00
|
|
|
|
/**
|
|
|
|
|
* Attempt to get the vue-i18n configured locale.
|
|
|
|
|
*/
|
|
|
|
|
i18n (vm) {
|
2020-03-24 23:44:19 -04:00
|
|
|
|
if (vm.$i18n) {
|
|
|
|
|
switch (typeof vm.$i18n.locale) {
|
|
|
|
|
case 'string':
|
|
|
|
|
return vm.$i18n.locale
|
|
|
|
|
case 'function':
|
|
|
|
|
return vm.$i18n.locale()
|
|
|
|
|
}
|
2020-03-07 09:03:59 -05:00
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Select the proper locale to use.
|
|
|
|
|
*/
|
|
|
|
|
getLocale (vm) {
|
|
|
|
|
if (!this.selectedLocale) {
|
|
|
|
|
this.selectedLocale = [
|
|
|
|
|
this.options.locale,
|
|
|
|
|
this.i18n(vm),
|
|
|
|
|
'en'
|
|
|
|
|
].reduce((selection, locale) => {
|
|
|
|
|
if (selection) {
|
|
|
|
|
return selection
|
|
|
|
|
}
|
|
|
|
|
if (locale) {
|
|
|
|
|
const option = parseLocale(locale)
|
2020-04-22 00:00:02 -04:00
|
|
|
|
.find(locale => has(this.options.locales, locale))
|
2020-03-07 09:03:59 -05:00
|
|
|
|
if (option) {
|
|
|
|
|
selection = option
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return selection
|
|
|
|
|
}, false)
|
|
|
|
|
}
|
|
|
|
|
return this.selectedLocale
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-13 16:10:17 -05:00
|
|
|
|
/**
|
|
|
|
|
* Get the validation message for a particular error.
|
|
|
|
|
*/
|
2020-03-07 09:03:59 -05:00
|
|
|
|
validationMessage (rule, validationContext, vm) {
|
|
|
|
|
const generators = this.options.locales[this.getLocale(vm)]
|
2019-11-13 16:10:17 -05:00
|
|
|
|
if (generators.hasOwnProperty(rule)) {
|
|
|
|
|
return generators[rule](validationContext)
|
2019-11-14 01:00:56 -05:00
|
|
|
|
} else if (rule[0] === '_' && generators.hasOwnProperty(rule.substr(1))) {
|
|
|
|
|
return generators[rule.substr(1)](validationContext)
|
2019-11-13 16:10:17 -05:00
|
|
|
|
}
|
|
|
|
|
if (generators.hasOwnProperty('default')) {
|
|
|
|
|
return generators.default(validationContext)
|
|
|
|
|
}
|
|
|
|
|
return 'This field does not have a valid value'
|
|
|
|
|
}
|
2019-11-15 14:44:01 -05:00
|
|
|
|
|
2020-03-06 16:10:25 -05:00
|
|
|
|
/**
|
|
|
|
|
* Given an instance of a FormulateForm register it.
|
|
|
|
|
* @param {vm} form
|
|
|
|
|
*/
|
|
|
|
|
register (form) {
|
|
|
|
|
if (form.$options.name === 'FormulateForm' && 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 === 'FormulateForm' &&
|
|
|
|
|
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) {
|
2020-04-13 11:59:54 -04:00
|
|
|
|
const e = skip ? err : this.options.errorHandler(err, formName)
|
2020-03-06 16:10:25 -05:00
|
|
|
|
if (formName && this.registry.has(formName)) {
|
|
|
|
|
this.registry.get(formName).applyErrors({
|
|
|
|
|
formErrors: arrayify(e.formErrors),
|
|
|
|
|
inputErrors: e.inputErrors || {}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-15 14:44:01 -05:00
|
|
|
|
/**
|
|
|
|
|
* Get the file uploader.
|
|
|
|
|
*/
|
|
|
|
|
getUploader () {
|
|
|
|
|
return this.options.uploader || false
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-01 13:28:46 -05:00
|
|
|
|
/**
|
|
|
|
|
* Get the global upload url.
|
|
|
|
|
*/
|
|
|
|
|
getUploadUrl () {
|
|
|
|
|
return this.options.uploadUrl || false
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-02 00:35:56 -05:00
|
|
|
|
/**
|
|
|
|
|
* 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'
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-15 14:44:01 -05:00
|
|
|
|
/**
|
|
|
|
|
* Create a new instance of an upload.
|
|
|
|
|
*/
|
|
|
|
|
createUpload (fileList, context) {
|
|
|
|
|
return new FileUpload(fileList, context, this.options)
|
|
|
|
|
}
|
2018-01-30 17:21:21 -05:00
|
|
|
|
}
|
2019-10-07 10:24:30 -04:00
|
|
|
|
|
|
|
|
|
export default new Formulate()
|