cheetah-framework
Version:
Cheetah Framework JS used in all our applications
194 lines (163 loc) • 4.37 kB
JavaScript
import FormErrorHelper from './FormErrorHelper'
import FormErrors from './errors'
import VueScrollTo from 'vue-scrollto'
import LaravelModel from '@cheetah/models/LaravelModel'
Vue.config.optionMergeStrategies.form = function (toVal, fromVal) {
return _.defaults(fromVal, toVal)
}
const formDefaultOptions = {
/**
* Override method used to save instance.
* By default, store and update method will be used.
*/
storeMethodName: 'store',
updateMethodName: 'update'
}
export default {
form: formDefaultOptions,
provide () {
const _this = this
return {
formErrors: _this.formErrors
}
},
mixins: [FormErrorHelper],
props: {
instance: {
type: LaravelModel,
required: true
}
},
data () {
return {
loading: true,
saving: false,
formErrors: new FormErrors(),
localInstance: null,
savePromise: Promise.resolve()
}
},
computed: {
canNotSave () {
return this.loading || this.saving
},
canNotCancel () {
return this.saving
},
isPersisted () {
return this.instance.isPersisted
}
},
methods: {
handleError (error) {
if (!error) {
return
}
if (error.response) {
this.formErrors.set(error.response.data.errors)
this.$nextTick(() => {
if (!this.scrollToFirstError()) {
// Fallback error reporting when there are errors, but they are not visible.
RemoteErrors(error)
}
})
} else {
RemoteErrors(error)
}
throw error
},
scrollToFirstError,
/**
* Use it to bind save action on a click button event. Prevent,
* el-button to react with Promise and throw a weird error. Use
* saveInstance if you support Promise.
*/
callSaveMethod () {
this.saveInstance()
},
saveInstance () {
this.saving = true
this.formErrors.forget()
const calledMethod = this.$options.form[this.localInstance.isPersisted ? 'updateMethodName' : 'storeMethodName']
return (this.savePromise = this.localInstance[calledMethod]().then(response => {
this.$emit('saved', response.data)
return response
}).catch(this.handleError).finally(_ => {
this.saving = false
}))
},
load () {
const Constructor = this.instance.constructor
let promises = this.promisesToWaitingFor()
if (promises instanceof Promise) {
promises = [promises]
}
this.loading = true
this.localInstance = null
if (this.instance.isPersisted) {
promises.push(Constructor.get(this.instance[Constructor.idKey]).then(response => {
this.localInstance = new Constructor(response.data)
this.$emit('loaded', this.localInstance)
}))
} else {
this.localInstance = new Constructor(this.instance)
}
Promise.all(promises).finally(_ => {
this.afterLoad()
this.loading = false
})
},
/**
* Use it to return Promises your need to wait for
* to display your form.
* Can be a Promise or an array of Promises
* @returns {Promise<void>[]|Promise<void>}
*/
promisesToWaitingFor () {
return []
},
/**
* Override this function to do something after everything is loaded
*/
afterLoad () { },
/**
* Override this function to do something after save success
*/
afterSave () { }
},
created () {
this.load()
}
}
function scrollToFirstError () {
let container, offset
if (this.$el) {
container = this.$el
offset = 0
} else {
container = document
offset = _.get(document.getElementsByClassName('c-header'), '0.offsetHeight') || 0
}
const firstError = container.querySelector('.is-error, .inlineError')
if (!firstError) {
return false
}
// @todo - If in modal, scroll in modal (scroll not supported yet in modal)
let errorInput = firstError.querySelector('input')
if (!errorInput) {
errorInput = firstError.querySelector('textarea')
}
if (errorInput) {
errorInput.focus()
}
if (offset !== null) {
VueScrollTo.scrollTo(firstError, 600, {
container,
easing: 'ease',
offset: (offset * -1) - 30
})
return true
}
return false
}
export { scrollToFirstError }