1
0
mirror of synced 2025-03-25 01:13:57 +03:00
vue-formulario/src/libs/context.js
2020-03-29 00:32:07 -04:00

279 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import nanoid from 'nanoid/non-secure'
import { map, arrayify, shallowEqualObjects } from './utils'
/**
* For a single instance of an input, export all of the context needed to fully
* render that element.
* @return {object}
*/
export default {
context () {
return defineModel.call(this, {
type: this.type,
value: this.value,
name: this.nameOrFallback,
classification: this.classification,
component: this.component,
id: this.id || this.defaultId,
hasLabel: (this.label && this.classification !== 'button'),
label: this.label,
labelPosition: this.logicalLabelPosition,
attributes: this.elementAttributes,
performValidation: this.performValidation.bind(this),
blurHandler: blurHandler.bind(this),
imageBehavior: this.imageBehavior,
uploadUrl: this.mergedUploadUrl,
uploader: this.uploader || this.$formulate.getUploader(),
uploadBehavior: this.uploadBehavior,
preventWindowDrops: this.preventWindowDrops,
hasValidationErrors: this.hasValidationErrors.bind(this),
getValidationErrors: this.getValidationErrors.bind(this),
validationErrors: this.validationErrors,
errors: this.explicitErrors,
setErrors: this.setErrors.bind(this),
showValidationErrors: this.showValidationErrors,
visibleValidationErrors: this.visibleValidationErrors,
...this.typeContext
})
},
// Used in sub-context
nameOrFallback,
typeContext,
elementAttributes,
logicalLabelPosition,
mergedUploadUrl,
// These items are not passed as context
isVmodeled,
mergedValidationName,
explicitErrors,
allErrors,
hasErrors,
hasVisibleErrors,
showValidationErrors,
visibleValidationErrors
}
/**
* Given (this.type), return an object to merge with the context
* @return {object}
* @return {object}
*/
function typeContext () {
switch (this.classification) {
case 'select':
return {
options: createOptionList.call(this, this.options),
optionGroups: this.optionGroups ? map(this.optionGroups, (k, v) => createOptionList.call(this, v)) : false,
placeholder: this.$attrs.placeholder || false
}
case 'slider':
return { showValue: !!this.showValue }
default:
if (this.options) {
return {
options: createOptionList.call(this, this.options)
}
}
return {}
}
}
/**
* Reducer for attributes that will be applied to each core input element.
* @return {object}
*/
function elementAttributes () {
const attrs = Object.assign({}, this.localAttributes)
if (this.id) {
attrs.id = this.id
} else {
attrs.id = this.defaultId
}
return attrs
}
/**
* Determine the a best-guess location for the label (before or after).
* @return {string} before|after
*/
function logicalLabelPosition () {
if (this.labelPosition) {
return this.labelPosition
}
switch (this.classification) {
case 'box':
return 'after'
default:
return 'before'
}
}
/**
* The validation label to use.
*/
function mergedValidationName () {
if (this.validationName) {
return this.validationName
}
if (typeof this.name === 'string') {
return this.name
}
if (this.label) {
return this.label
}
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}
*/
function showValidationErrors () {
if (this.showErrors || this.formShouldShowErrors) {
return true
}
if (this.classification === 'file' && this.uploadBehavior === 'live' && modelGetter.call(this)) {
return true
}
return this.behavioralErrorVisibility
}
/**
* All of the currently visible validation errors (does not include error handling)
* @return {array}
*/
function visibleValidationErrors () {
return (this.showValidationErrors && this.validationErrors.length) ? this.validationErrors : []
}
/**
* Return the elements name, or select a fallback.
*/
function nameOrFallback () {
if (this.name === true && this.classification !== 'button') {
return `${this.type}_${this.elementAttributes.id}`
}
if (this.name === false || (this.classification === 'button' && this.name === true)) {
return false
}
return this.name
}
/**
* Determines if this formulate element is v-modeled or not.
*/
function isVmodeled () {
return !!(this.$options.propsData.hasOwnProperty('formulateValue') &&
this._events &&
Array.isArray(this._events.input) &&
this._events.input.length)
}
/**
* Given an object or array of options, create an array of objects with label,
* value, and id.
* @param {array|object}
* @return {array}
*/
function createOptionList (options) {
if (!Array.isArray(options) && options && typeof options === 'object') {
const optionList = []
const that = this
for (const value in options) {
optionList.push({ value, label: options[value], id: `${that.elementAttributes.id}_${value}` })
}
return optionList
} else if (Array.isArray(options) && !options.length) {
return [{ value: this.value, label: (this.label || this.name), id: this.context.id || nanoid(9) }]
}
return options
}
/**
* These are errors we that have been explicity passed to us.
*/
function explicitErrors () {
return arrayify(this.errors)
.concat(this.localErrors)
.concat(arrayify(this.error))
}
/**
* The merged errors computed property.
*/
function allErrors () {
return this.explicitErrors
.concat(arrayify(this.validationErrors))
}
/**
* Does this computed property have errors
*/
function hasErrors () {
return !!this.allErrors.length
}
/**
* Returns if form has actively visible errors (of any kind)
*/
function hasVisibleErrors () {
return ((this.validationErrors && this.showValidationErrors) || !!this.explicitErrors.length)
}
/**
* Bound into the context object.
*/
function blurHandler () {
this.$emit('blur')
if (this.errorBehavior === 'blur') {
this.behavioralErrorVisibility = true
}
}
/**
* Defines the model used throughout the existing context.
* @param {object} context
*/
function defineModel (context) {
return Object.defineProperty(context, 'model', {
get: modelGetter.bind(this),
set: modelSetter.bind(this)
})
}
/**
* Get the value from a model.
**/
function modelGetter () {
const model = this.isVmodeled ? 'formulateValue' : 'internalModelProxy'
if (this.type === 'checkbox' && !Array.isArray(this[model]) && this.options) {
return []
}
if (!this[model]) {
return ''
}
return this[model]
}
/**
* Set the value from a model.
**/
function modelSetter (value) {
if (!shallowEqualObjects(value, this.internalModelProxy)) {
this.internalModelProxy = value
}
this.$emit('input', value)
if (this.context.name && typeof this.formulateFormSetter === 'function') {
this.formulateFormSetter(this.context.name, value)
}
}