# Formulario Bone
# Introducción
Próximamente será desarrollado en otra sección
# Obtención de los campos

# Código fuente
<script>
const v1ExternalDBEndpoint = '/v1/icl/exec/externalDB/'
const propsLocal = {
campos: { type: Array, default: () => [] },
sections: { type: Array, default: () => [] },
readonly: { type: Boolean, default: false },
flat: { type: Boolean, default: false },
filter: {
type: Object,
default: () => {
return {}
}
},
id_resolver: {
type: String
},
campos_fcn: {
type: String
},
args_campos_fcn: {
type: Array,
default: () => []
},
forceSubmitRefresh: {
type: Boolean,
default: false
},
comportamientos: {
type: Array,
default: () => []
},
campos_fcn_refresh: {
type: String,
required: false
},
args_campos_fcn_refresh: {
type: Array,
required: false
},
refresh_timer: {
type: Object,
required: false
},
sectionsAttrs: {
type: Object,
default: () => ({})
},
sectionsFcn: {
type: String,
default: ''
},
argsSectionsFcn: {
type: Array,
default: () => []
}
}
import Vue from 'vue'
import {
ref,
onMounted,
computed,
watch,
onBeforeUnmount,
h,
getCurrentInstance
} from 'vue'
import {
normalizeProps,
assignCamelToSnake
} from '../../../helpers/propsGenerator'
const mergedProps = normalizeProps(propsLocal)
import resolveMixin from '../../../mixins/resolveIdResolver'
import {
form_navigator,
form_modal,
form_fcn_executer,
unavailableTypes,
formComponentName,
externalDBEndpoint,
getCamposEndpoint,
fcnExecuterEndpoint
} from '../../../constants'
import fcnResponseHandler from '../../../mixins/fcnResponseHandler'
export default {
name: formComponentName + 'Bone',
mixins: [fcnResponseHandler, resolveMixin],
props: mergedProps,
setup(props, { slots, emit, expose }) {
const vueInstance = getCurrentInstance()
const vuetify = vueInstance.proxy.$vuetify
const iclAxios = Vue.prototype.$iclAxios
const iclstore = Vue.prototype.$iclstore
const iclRouter = Vue.prototype.$iclRouter
const logErrorComponent = Vue.prototype.$logErrorComponent
const detectResponseStructErrors = Vue.prototype.$detectResponseStructErrors
const propError = Vue.prototype.$propError
const { handleError, handleSuccess, handleSuccessResponse } =
fcnResponseHandler.methods
const { resolve } = resolveMixin.methods
const camposData = ref([])
const datosResp = ref({})
const camposDataAux = ref([])
const invalids = ref([])
const loadingRefresh = ref(false)
const loadingUpdate = ref(false)
const hasError = ref(false)
const refreshed = ref(false)
const dialogData = ref({
id_resolver_dlg: '',
titulo: '',
show: false,
dialog_args: {}
})
const comportamientosLocal = ref([])
const actionToConfirm = ref({})
const structureError = ref(false)
const firstTime = ref(true)
const loopInterval = ref(undefined)
const sectionsLocal = ref([])
const sectionsAttrsDefault = ref({
flat: true,
accordion: true,
hover: true,
multiple: true
})
const sectionsComputed = computed(() => {
if (props.sectionsFcn) return sectionsLocal.value
return props.sections
})
const sectionsAttrsComputed = computed(() => {
let openSections = sectionsComputed.value.map((sec, ind) => {
if (sec['is-default-open']) return ind
})
return {
...sectionsAttrsDefault.value,
...props.sectionsAttrs,
value: openSections
}
})
const columnasControl = computed(() => {
let mayor = 1
for (let i in camposData.value) {
let props = camposData.value[i].props
let widthByBreakpoint = {
xs: props['width-xs'],
sm: props['width-sm'],
md: props['width-md'],
lg: props['width-lg'],
xl: props['width-xl'],
default: props.width
}
let breakpoint = vuetify.breakpoint.name
let selectedBreakpoint = widthByBreakpoint[breakpoint]
? widthByBreakpoint[breakpoint]
: widthByBreakpoint.default
if (!selectedBreakpoint) selectedBreakpoint = 1
if (selectedBreakpoint > mayor) mayor = selectedBreakpoint
}
return mayor
})
const generatedIdResolver = computed(() => {
return props.id_resolver ? props.id_resolver : iclRouter.currentRoute.path
})
watch(
() => props.readonly,
(isReadonly) => {
if (camposDataAux.value.length === 0 && camposData.value.length !== 0)
camposDataAux.value = JSON.parse(JSON.stringify(camposData.value))
if (isReadonly) {
camposData.value.forEach((campo) => {
campo.props.readonly = true
})
} else {
if (camposDataAux.value.length !== 0)
camposData.value = JSON.parse(JSON.stringify(camposDataAux.value))
}
}
)
watch(
() => props.campos,
(isCampos) => {
assignCampos(isCampos)
}
)
watch(
() => camposData.value,
(newCampos) => {
emit('changeCamposValues', newCampos)
},
{ deep: true }
)
const changeFieldProps = (isCampo, props) => {
camposData.value = camposData.value.map((wasCampo) => {
return wasCampo.props.id === isCampo.props.id
? { ...wasCampo, props: { ...wasCampo.props, ...props } }
: wasCampo
})
}
const changeLinkedValue = (fieldData) => {
const { action, campo } = fieldData || {}
refreshed.value = true
if (action === 'update') {
changeFieldProps(campo, { disabled: true, loading: true })
createdFunctions(
props.campos_fcn_refresh,
props.args_campos_fcn_refresh,
{ campo }
)
} else {
createdFunctions(
props.campos_fcn_refresh,
props.args_campos_fcn_refresh
)
}
}
const muestraCampo = (campo) => {
if (
Object.keys(props.filter).length === 0 &&
(typeof campo.visible === 'undefined' || campo.visible)
) {
return true
}
if (typeof campo.visible === 'undefined' || campo.visible) {
if (
props.filter.hasOwnProperty('nombre') &&
campo[props.filter.nombre] &&
Object.keys(props.filter).length !== 1
) {
return campo[props.filter.nombre] === props.filter.valor
}
return false
}
return false
}
const isIrGpsMarkerValida = (campo) => {
if (campo.value.lat === '' || campo.value.long === '') {
invalids.value.push('latitud y longitud de ' + campo.label)
return false
}
return true
}
const typesValidator = (campos) => {
let aux = true
for (let campo in campos) {
let campoProps = campos[campo]
if (
typeof campoProps === 'object' &&
(typeof campoProps.props.visible === 'undefined' ||
campoProps.props.visible) &&
(campoProps.props.required || campoProps.type === 'IrRepeater')
) {
if (campoProps.type === 'IrGpsMarker') {
aux = isIrGpsMarkerValida(campoProps.props)
} else if (campoProps.type === 'IrRepeater') {
aux = isIrRepeaterValida(campoProps.props)
} else if (campoProps.type === 'IrWysiwyg') {
aux = isIrWysiwygValida(campoProps.props)
} else {
if (campoProps.props.value === '') {
aux = false
invalids.value.push(campoProps.props.label)
break
}
}
}
}
return aux
}
const isIrRepeaterValida = (campo) => {
let aux = true
for (let arr of campo.items) {
let auxrepeater = true
auxrepeater = typesValidator(arr)
if (!auxrepeater) {
invalids.value.push(campo.label)
aux = false
break
}
}
return aux
}
const isIrWysiwygValida = (campo) => {
let valid = campo.value !== '<p></p>'
if (!valid) {
invalids.value.push(campo.label)
}
return valid
}
const validaForm = () => {
let aux = true
invalids.value = []
aux = typesValidator(camposData.value)
if (!aux) {
let invalidos = ''
for (let i = 0; i < invalids.value.length; i++) {
if (i === invalids.value.length - 1) {
invalidos = invalidos + invalids.value[i]
} else {
invalidos = invalidos + invalids.value[i] + ' - '
}
}
iclstore.commit('updateSnack', {
active: true,
text: 'Complete correctamente los campos ' + invalidos,
color: 'error'
})
}
return aux
}
const makeQuery = async (fcn, args) => {
let { pre_api, post_api } = args
if (pre_api && pre_api.config && pre_api.config.loading_message) {
iclstore.dispatch('cancelSnack')
iclstore.commit('updateSnack', {
text: pre_api.config.loading_message,
active: true,
color: 'success',
time: 999999,
is_closing_allowed: false
})
} else if (
post_api &&
post_api.config &&
post_api.config.loading_message
) {
iclstore.dispatch('cancelSnack')
iclstore.commit('updateSnack', {
text: post_api.config.loading_message,
active: true,
color: 'success',
time: 999999,
is_closing_allowed: false
})
}
emit('toggle-loading-overlay', true)
let localArgs = [
JSON.stringify({
...args,
id_usuario: iclstore.state.id_usuario,
id_resolver: generatedIdResolver.value
})
]
iclAxios
.post(fcnExecuterEndpoint + fcn, localArgs, { timeout: 0 })
.then((rsp) => {
handleSuccess(rsp, manageCreatedFunctions, {
iclRouter,
iclstore,
emit,
handleSuccessResponse,
assignDialogData,
resolve
})
})
.catch((error) => {
handleError(error, iclstore)
console.error(error)
})
.finally(() => {
if (
(pre_api && pre_api.config && pre_api.config.loading_message) ||
(post_api && post_api.config && post_api.config.loading_message)
) {
iclstore.dispatch('showNextSnack')
}
emit('toggle-loading-overlay', false)
})
}
const preApiModal = async (
fcn,
args,
titulo,
id_resolver_aux,
position,
dialogAttrs
) => {
let { pre_api } = args
if (pre_api && pre_api.config && pre_api.config.loading_message) {
iclstore.commit('updateSnack', {
text: pre_api.config.loading_message,
active: true,
color: 'success',
time: 999999
})
}
emit('toggle-loading-overlay', true)
let localArgs = [
JSON.stringify({
...args,
id_usuario: iclstore.state.id_usuario,
id_resolver: generatedIdResolver.value
})
]
iclAxios
.post( v1ExternalDBEndpoint + fcn, localArgs)
.catch((error) => {
handleError(error, iclstore)
console.error(error)
})
.finally(() => {
iclstore.dispatch('cancelSnack')
assignDialogData({ titulo, id_resolver: id_resolver_aux, position, dialogAttrs })
emit('toggle-loading-overlay', false)
})
}
const preApiNavigator = async (fcn, args, id_resolver, found) => {
let { pre_api } = args
if (pre_api && pre_api.config && pre_api.config.loading_message) {
iclstore.commit('updateSnack', {
text: pre_api.config.loading_message,
active: true,
color: 'success',
time: 999999
})
}
emit('toggle-loading-overlay', true)
let localArgs = [
JSON.stringify({
...args,
id_usuario: iclstore.state.id_usuario,
id_resolver: generatedIdResolver.value
})
]
iclAxios
.post(v1ExternalDBEndpoint + fcn, localArgs)
.catch((error) => {
handleError(error, iclstore)
console.error(error)
})
.finally(() => {
iclstore.dispatch('cancelSnack')
emit('toggle-loading-overlay', false)
redirect(id_resolver, found)
})
}
const manageCreatedFunctions = async () => {
emit('refresh') // para customizaciones de componente
if (
refreshed.value ||
(props.campos_fcn_refresh && props.forceSubmitRefresh)
) {
await createdFunctions(
props.campos_fcn_refresh,
props.args_campos_fcn_refresh
)
} else {
await createdFunctions(props.campos_fcn, props.args_campos_fcn)
}
}
const consultaSections = () => {
if (props.sectionsFcn) {
let argsAux = {
args: props.argsSectionsFcn,
id_usuario: iclstore.state.id_usuario,
id_resolver: generatedIdResolver.value
}
loadingUpdate.value = true
iclAxios
.post(externalDBEndpoint + props.sectionsFcn, [
JSON.stringify(argsAux)
])
.then((res) => {
sectionsLocal.value = res
})
.catch((err) => {
console.log(err)
iclstore.commit('updateSnack', {
text: 'Ha ocurrido un error al cargar algunos campos del formulario',
active: true,
color: 'warning'
})
})
.finally(() => {
loadingUpdate.value = false
})
}
}
const checkCompIdField = () => {
if (camposData.value.length > 0) {
comportamientosLocal.value = Object.assign([])
props.comportamientos.forEach((comp) => {
if (typeof comp.field_id !== 'undefined') {
let arrFieldId = Array.isArray(comp.field_id)
? comp.field_id
: [comp.field_id]
let found = findFields(
comp.field_id,
'Se envió el "field_id" "#id#" que no cerresponde a un id de campo existente. No se renderizará el comportamiento de tipo "' +
comp.type +
'".',
' No se renderizará el comportamiento de tipo "' +
comp.type +
'".'
)
if (typeof found === 'undefined') return
else if (found.length === arrFieldId.length)
comportamientosLocal.value.push(comp)
} else comportamientosLocal.value.push(comp)
})
}
}
const replaceCamposById = (campos) => {
let camposAux = Object.assign(camposData.value)
campos.forEach(({ props: { id } }, index) => {
if (id) {
const campoIndex = camposAux.findIndex(({ props: { id: idAux } }) => {
return idAux === id
})
if (campoIndex !== -1) camposAux[campoIndex] = { ...campos[index] }
else camposAux[camposAux.length] = { ...campos[index] }
}
})
assignCampos(camposAux)
}
const handleFormClick = (action) => {
let {
type,
fcn_click,
field_id,
id_resolver,
args_fcn_click,
titulo,
confirmation,
validation,
pre_api,
post_api,
position,
dialogAttrs
} = action
let modalPosition = action['modal-position']
if (typeof confirmation === 'undefined' || confirmation === '') {
let found = []
let id_resolver_aux = ''
let argsLocal = {}
let isFieldValid = typeof field_id !== 'undefined'
if (isFieldValid && camposData.value.length > 0) {
let foundAux = findFields(
field_id,
'Ocurrió un error y no se encontró el campo correspondiente con id: "#id#".',
''
)
if (typeof foundAux === 'undefined') {
emit('cerrarDialog')
return
}
found = foundAux
}
let validValidation = typeof validation !== 'undefined'
if (
type === form_fcn_executer &&
((validValidation && validation && validaForm()) ||
(validValidation && !validation) ||
(!validValidation && validaForm()))
) {
argsLocal = {
args: typeof args_fcn_click !== 'undefined' ? args_fcn_click : []
}
let foundAux = found.map((field) => field.props.value)
argsLocal = { ...argsLocal, fields: foundAux } // El valor del campo correspondiente se agrega a argsLocal.field
makeQuery(fcn_click, {
...argsLocal,
campos: camposData.value,
pre_api,
post_api
})
emit('cerrarDialog')
} else if (type === form_modal) {
id_resolver_aux = resolve(id_resolver, found)
if (pre_api)
preApiModal(
'fcn_modal',
{ campos: camposData.value, pre_api },
titulo,
id_resolver_aux,
modalPosition,
dialogAttrs
)
else
assignDialogData({
titulo,
id_resolver: id_resolver_aux,
position: modalPosition,
dialogAttrs
})
} else if (type === form_navigator) {
emit('cerrarDialog')
if (pre_api)
preApiNavigator(
'fcn_navigator',
{ campos: camposData.value, pre_api },
id_resolver,
found
)
else redirect(id_resolver, found)
}
} else {
actionToConfirm.value = Object.assign({}, action)
assignDialogData({ message: confirmation })
}
}
const findFields = (ids, msg, unavailableTypesErrorMsg) => {
let arrFieldId = Array.isArray(ids) ? ids : [ids]
let found = []
for (let i = 0; i < arrFieldId.length; i++) {
let id = arrFieldId[i]
let foundedField = camposData.value.find(
(campo) => campo.props.id === id
)
if (typeof foundedField === 'undefined') {
logErrorComponent(
formComponentName,
msg.replace('#id#', id),
iclRouter
)
return
} else if (
typeof unavailableTypes.find((type) => foundedField.type === type) !==
'undefined'
) {
logErrorComponent(
formComponentName,
'Se envió un "field_id" correspondiente a un campo de tipo "' +
foundedField.type +
'" y el mismo no es válido.' +
unavailableTypesErrorMsg,
iclRouter
)
return
} else found.push(foundedField)
}
return found
}
const assignCampos = (campos) => {
camposData.value = Object.assign(campos)
emit('updateCampos', camposData.value) // para customizaciones de componente
checkCompIdField()
}
const createdFunctions = async (
consultaCampos,
argsConsultaCampos,
updateFieldData
) => {
let { campo, isFieldUpdate = false } = updateFieldData
? updateFieldData
: {}
if (
typeof consultaCampos !== 'undefined' &&
consultaCampos !== null &&
iclAxios
) {
let argsAux = {
...argsConsultaCampos,
id_usuario: iclstore.state.id_usuario,
id_resolver: generatedIdResolver.value
}
const isUpdatingField = typeof campo !== 'undefined'
if (!isUpdatingField && !isFieldUpdate)
// Cuando se actualiza el formulario por campo por id el loading lo tiene el componente que genera la actualización, no el formulario
loadingUpdate.value = true
if (refreshed.value) {
if (isUpdatingField) argsAux = { ...argsAux, campo }
else argsAux = { ...argsAux, campos: camposData.value }
}
let endpoint
if (!refreshed.value) endpoint = getCamposEndpoint
else endpoint = externalDBEndpoint
let paramsDetectErrors = [
consultaCampos,
['props', 'type'],
formComponentName,
'{type: String, props: Object}',
'https://icl.iridiumrobotics.com.ar/components/formularios.html#uso'
]
return await iclAxios
.post(endpoint + consultaCampos, [JSON.stringify(argsAux)])
.then((rsp) => {
rsp = rsp.data ?? rsp
if (isUpdatingField || isFieldUpdate) {
replaceCamposById(rsp)
if (isUpdatingField)
changeFieldProps(campo, { disabled: false, loading: false })
} else assignCampos(rsp)
detectResponseStructErrors(rsp, ...paramsDetectErrors)
})
.catch((error) => {
console.log(error)
logErrorComponent(
formComponentName,
'Ocurrió un error en la consulta de los campos.',
iclRouter
)
assignCampos(props.campos)
hasError.value = true
})
.finally(() => {
loadingUpdate.value = false
if (firstTime.value) {
firstTime.value = false
manageRefreshRepeater()
}
})
} else assignCampos(props.campos)
if (firstTime.value && props.campos_fcn_refresh) {
firstTime.value = false
manageRefreshRepeater()
}
}
const manageRefreshRepeater = () => {
const { repeat_time, loops, type, freeze_screen } = props.refresh_timer
? props.refresh_timer
: {}
let isInfiniteLoop = loops === -1
let countingDown = loops
let isFieldUpdate = type === 'update'
const isLoopAvailable =
typeof repeat_time !== 'undefined' &&
typeof loops !== 'undefined' &&
(countingDown > 0 || isInfiniteLoop)
if (isLoopAvailable) {
refreshed.value = true
loopInterval.value = setInterval(async () => {
console.log('in loop')
if (!isInfiniteLoop) countingDown = countingDown - 1
if (isFieldUpdate && freeze_screen)
emit('toggle-loading-overlay', true)
await createdFunctions(
props.campos_fcn_refresh,
props.args_campos_fcn_refresh,
{ isFieldUpdate }
).finally(() => {
emit('toggle-loading-overlay', false)
})
if (!isInfiniteLoop && countingDown <= 0)
clearInterval(loopInterval.value)
}, repeat_time)
}
}
const continueAction = (confirmation) => {
if (confirmation) {
delete actionToConfirm.value.confirmation
handleFormClick(actionToConfirm.value)
} else emit('cerrarDialog')
actionToConfirm.value = Object.assign({})
}
const redirect = (path, query) => {
let pathAux = resolve(path, query)
if (pathAux.indexOf('https://') === 0 || pathAux.indexOf('http://') === 0)
window.location.href = pathAux
else iclRouter.push({ path: pathAux })
}
const assignDialogData = ({ titulo, id_resolver, message, position, dialogAttrs }) => {
dialogData.value = Object.assign({})
dialogData.value.id_resolver_dlg = id_resolver
dialogData.value.titulo = titulo
dialogData.value.message = message
dialogData.value.position = position
dialogData.value = {...dialogData.value, ...dialogAttrs}
emit('abrirDialog')
}
const emitDisparadorUpdate = (key) => {
let varkey = key
emit('disparador', varkey)
}
assignCamelToSnake(mergedProps, props)
consultaSections()
createdFunctions(props.campos_fcn, props.args_campos_fcn)
onMounted(() => {
// if (process.client) {
document.body.id = 'address'
document.body.classList =
'lang-es country-ar currency-ars layout-full-width page-address tax-display-enabled page-customer-account'
// }
structureError.value = propError(propsLocal, props, formComponentName)
if (structureError.value) {
iclstore.commit('updateSnack', {
text: 'Ha ocurrido un error al cargar algunos campos del formulario',
active: true,
color: 'warning'
})
}
})
onBeforeUnmount(() => {
if (loopInterval.value) {
clearInterval(loopInterval.value)
}
})
expose({
createdFunctions,
camposData,
comportamientosLocal,
checkCompIdField,
muestraCampo
})
return () => {
if (structureError.value) {
return
}
return h(
'div', // Contenedor como nodo raíz
[
slots.default({
loadingRefresh: loadingRefresh.value,
loadingUpdate: loadingUpdate.value,
toggleLoading: () => {
loadingRefresh.value = !loadingRefresh.value
},
buttonsForm: comportamientosLocal.value,
columnasControl: columnasControl.value,
form: camposData.value,
muestraCampo: muestraCampo,
conditionalSlot: !props.readonly,
flat: props.flat,
id_resolver_dlg: dialogData.value.id_resolver_dlg,
dialogAttrs: dialogData.value,
handleFormClick: handleFormClick,
continueAction: continueAction,
hasError: hasError.value,
changeLinkedValue: changeLinkedValue,
id_resolver: props.id_resolver,
refreshParent: manageCreatedFunctions,
saveDraftFcn: (draftData) => {
let { args, fcn } = draftData
if (!fcn) {
logErrorComponent(
formComponentName,
'No se ha definido correctamente la propiedad save-draft. Es necesario que se envíe la key fcn',
iclRouter
)
return
}
if (args)
makeQuery(fcn, { ...{ args: args }, campos: camposData.value })
else makeQuery(fcn, { args: {}, campos: camposData.value })
},
sections: sectionsComputed.value,
sectionsAttrs: sectionsAttrsComputed.value
})
]
)
}
}
}
</script>