quasar
Version:
Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
230 lines (191 loc) • 5.75 kB
JavaScript
import {
h,
ref,
onActivated,
onDeactivated,
onMounted,
getCurrentInstance,
nextTick,
provide
} from 'vue'
import { createComponent } from '../../utils/private.create/create.js'
import { stopAndPrevent } from '../../utils/event/event.js'
import { addFocusFn } from '../../utils/private.focus/focus-manager.js'
import { hSlot } from '../../utils/private.render/render.js'
import { formKey } from '../../utils/private.symbols/symbols.js'
import { vmIsDestroyed } from '../../utils/private.vm/vm.js'
export default createComponent({
name: 'QForm',
props: {
autofocus: Boolean,
noErrorFocus: Boolean,
noResetFocus: Boolean,
greedy: Boolean,
onSubmit: Function
},
emits: ['reset', 'validationSuccess', 'validationError'],
setup(props, { slots, emit }) {
const vm = getCurrentInstance()
const rootRef = ref(null)
let validateIndex = 0
const registeredComponents = []
function validate(shouldFocus) {
const localFocus =
typeof shouldFocus === 'boolean'
? shouldFocus
: props.noErrorFocus !== true
const index = ++validateIndex
const emitEvent = (res, compRef) => {
emit(`validation${res === true ? 'Success' : 'Error'}`, compRef)
}
const validateComponent = comp => {
const valid = comp.validate()
return typeof valid.then === 'function'
? valid.then(
isValid => ({ valid: isValid, comp }),
err => ({ valid: false, comp, err })
)
: Promise.resolve({ valid, comp })
}
const errorsPromise =
props.greedy === true
? Promise.all(registeredComponents.map(validateComponent)).then(res =>
res.filter(r => r.valid !== true)
)
: registeredComponents
.reduce(
(acc, comp) =>
acc.then(() =>
validateComponent(comp).then(r => {
if (r.valid === false) {
return Promise.reject(r)
}
})
),
Promise.resolve()
)
.catch(error => [error])
return errorsPromise.then(errors => {
if (errors === void 0 || errors.length === 0) {
if (index === validateIndex) emitEvent(true)
return true
}
// if not outdated already
if (index === validateIndex) {
const { comp, err } = errors[0]
if (err !== void 0) console.error(err)
emitEvent(false, comp)
if (localFocus === true) {
// Try to focus first mounted and active component
const activeError = errors.find(
({ comp: compRef }) =>
typeof compRef.focus === 'function' &&
vmIsDestroyed(compRef.$) === false
)
if (activeError !== void 0) {
activeError.comp.focus()
}
}
}
return false
})
}
function resetValidation() {
validateIndex++
registeredComponents.forEach(comp => {
if (typeof comp.resetValidation === 'function') {
comp.resetValidation()
}
})
}
function submit(evt) {
if (evt !== void 0) stopAndPrevent(evt)
const index = validateIndex + 1
validate().then(val => {
// if not outdated && validation succeeded
if (index === validateIndex && val === true) {
if (props.onSubmit !== void 0) {
emit('submit', evt)
} else if (
evt?.target !== void 0 &&
typeof evt.target.submit === 'function'
) {
evt.target.submit()
}
}
})
}
function reset(evt) {
if (evt !== void 0) stopAndPrevent(evt)
emit('reset')
nextTick(() => {
// allow userland to reset values before
resetValidation()
if (props.autofocus === true && props.noResetFocus !== true) {
focus()
}
})
}
function focus() {
addFocusFn(() => {
if (rootRef.value === null) return
const target =
rootRef.value.querySelector(
'[autofocus][tabindex], [data-autofocus][tabindex]'
) ||
rootRef.value.querySelector(
'[autofocus] [tabindex], [data-autofocus] [tabindex]'
) ||
rootRef.value.querySelector('[autofocus], [data-autofocus]') ||
Array.prototype.find.call(
rootRef.value.querySelectorAll('[tabindex]'),
el => el.tabIndex !== -1
)
target?.focus({ preventScroll: true })
})
}
provide(formKey, {
bindComponent(vmProxy) {
registeredComponents.push(vmProxy)
},
unbindComponent(vmProxy) {
const index = registeredComponents.indexOf(vmProxy)
if (index !== -1) {
registeredComponents.splice(index, 1)
}
}
})
let shouldActivate = false
onDeactivated(() => {
shouldActivate = true
})
onActivated(() => {
if (shouldActivate === true && props.autofocus === true) {
focus()
}
})
onMounted(() => {
if (props.autofocus === true) focus()
})
// expose public methods
Object.assign(vm.proxy, {
validate,
resetValidation,
submit,
reset,
focus,
getValidationComponents: () => registeredComponents
})
return () =>
h(
'form',
{
class: 'q-form',
ref: rootRef,
onSubmit: submit,
onReset: reset
},
hSlot(slots.default)
)
}
})