# Data Table Bone
# Introducción
Éste componente no está encargado de renderizar, es de uso interno de la tabla. Guía de uso parte lógica
# Código fuente
<script>
const propsLocal = {
search: {
type: undefined //Undefined para evitar error al setear como array [String, Object] ya que rompe la función propError
},
row_classes: {
type: Object
},
items: {
type: Array,
default: () => []
},
comportamientos: {
type: Array,
default: () => []
},
cache_items: {
type: Boolean,
default: true
},
items_fcn: {
type: String,
required: false
},
args_items_fcn: {
type: Array,
default: () => []
},
filtros: {
type: Array,
default: () => []
},
filtros_fcn: {
type: String,
required: false
},
args_filtros_fcn: {
type: Array,
default: () => []
},
headers: {
type: Array,
required: false
},
rowsPerPage: {
type: Number,
default: 5
},
groupColor: {
type: String,
required: false,
default: '#FF0000'
},
downloadable: {
type: Boolean,
default: false
},
show_select: {
type: Boolean,
default: false
},
value: {
type: Array,
default: () => []
},
headersCSV: {
// tiene un array con los value de los headers para poner en el CSV
type: Array
},
noExpandedData: {
type: String,
default: ''
},
itemKey: {
type: String
},
expandedDataProp: {
type: String,
default: undefined
},
name_csv: {
type: String,
default: `table_${new Date().getTime()}`
},
id_resolver: {
type: String
},
'column-filters': {
type: Object,
required: false,
default: () => ({
tooltip: 'Column filter',
label: '',
'is-filter-visible-on-load': false
})
},
columnFiltersValues: {
type: Object,
required: false,
default: () => ({})
},
refreshConfig: {
type: Object,
default: () => ({
repeatTime: 0,
timeout: 0,
refreshingToolTip: 'Refreshing table'
})
},
'must-sort': {
type: Boolean,
default: false
},
'multi-sort': {
type: Boolean,
default: false
}
}
import Vue from 'vue'
import {
ref,
onMounted,
computed,
watch,
onBeforeUnmount,
h,
getCurrentInstance,
nextTick
} from 'vue'
import {
normalizeProps,
assignCamelToSnake
} from '../../helpers/propsGenerator'
const mergedProps = normalizeProps(propsLocal)
import resolveMixin from '../../mixins/resolveIdResolver'
import stringToFunction from '../../mixins/stringToFunction'
import {
fcn_executer,
iconComponent,
imageComponent,
tableComponentName,
navigator,
modal,
fcn_executer_row,
navigator_row,
modal_row,
image_row,
avatarComponent,
icon_row,
availableTableTypes,
availableRowTypes,
availableRowGroupsTypes,
availableFunctionlessRowTypes,
externalDBEndpoint,
getCamposEndpoint,
fcnExecuterEndpoint
} from '../../constants'
import fcnResponseHandler from '../../mixins/fcnResponseHandler'
export default {
name: tableComponentName + 'Bone',
mixins: [fcnResponseHandler, resolveMixin, stringToFunction],
props: mergedProps,
setup(props, { slots, emit, expose }) {
const vueInstance = getCurrentInstance()
const vuetify = vueInstance.proxy.$vuetify
const { handleError, handleSuccess, handleSuccessResponse } =
fcnResponseHandler.methods
const { resolve } = resolveMixin.methods
const { buildBooleanFcn } = stringToFunction.methods
const iclAxios = Vue.prototype.$iclAxios
const iclstore = Vue.prototype.$iclstore
const iclRouter = Vue.prototype.$iclRouter
const detectResponseStructErrors = Vue.prototype.$detectResponseStructErrors
const logErrorComponent = Vue.prototype.$logErrorComponent
const propError = Vue.prototype.$propError
const headersLocal = ref([])
const rowsClassesFunction = ref(false)
const itemsLocal = ref([])
const rowsPerPageLocal = ref(0)
const loading = ref(false)
const filtrosLocal = ref([])
const comportamientosTable = ref([])
const comportamientosRow = ref([])
const comportamientosRowGroups = ref([])
const isThereImageTypes = ref(false)
const showSelectLocal = ref(false)
const dialogData = ref({
id_resolver_dlg: '',
titulo: '',
show: false,
dialog_args: {}
})
const actionToConfirm = ref({})
const itemToConfirm = ref({})
const structureError = ref(false)
const intervalId = ref(null)
const repeatTime = ref(null)
const headerOver = ref(-1)
const sortSelected = ref([])
const currentPage = ref(1)
const isLoadingComportamiento = ref(false)
watch(
() => props.items,
(newItems) => {
setHeadersItems(newItems)
}
)
watch(
() => props.headers,
(newHeaders) => {
headersLocal.value = Object.assign([], newHeaders)
setHeadersItems(itemsLocal.value)
}
)
watch(
() => props.filtros,
(newFiltros) => {
filtrosLocal.value = newFiltros
}
)
const manageGroupalHeader = computed(() => {
return comportamientosRowGroups.value.length > 0
? [
...headersLocal.value,
{ name: 'comportamientos-grouped', sortable: false }
]
: headersLocal.value
})
const sortDesc = computed(() => {
return sortSelected.value.map((selected) => {
return selected.sortDesc
})
})
const selectedHeadersValues = computed(() => {
return sortSelected.value.map((selected) => {
return selected.value
})
})
const comportamientosTableOutside = computed(() => {
// Se hace en bone aunque sea parte de FE para recorrer una única vez el arreglo, en el FE es necesario recorrerlo dos veces
let comportamientosTableOutside = []
for (let i = 0; i < comportamientosTable.value.length; i++) {
let compAux = comportamientosTable.value[i]
if (compAux.outside && !vuetify.breakpoint.smAndDown)
comportamientosTableOutside.push(compAux)
}
return comportamientosTableOutside
})
const comportamientosTableInside = computed(() => {
// Se hace en bone aunque sea parte de FE para recorrer una única vez el arreglo, en el FE es necesario recorrerlo dos veces
let comportamientosTableInside = []
for (let i = 0; i < comportamientosTable.value.length; i++) {
let compAux = comportamientosTable.value[i]
if (compAux.inside || !compAux.outside || vuetify.breakpoint.smAndDown)
comportamientosTableInside.push(compAux)
}
return comportamientosTableInside
})
const filteredItems = computed(() => {
//Filtra los items según los filtros de columnas.
let filteredItems = itemsLocal.value
Object.keys(props.columnFiltersValues).forEach((key) => {
let findValue = props.columnFiltersValues[key]
if (findValue && findValue !== '') {
filteredItems = filteredItems.filter((item) => {
let value = item[key]
if (Array.isArray(value) || typeof value === 'object')
// Si el componente de filtro tiene como valor por ejemplo 'auto' y el valor de la celda es un objeto, se fija si los valores de las propiedades contienen ese valor
value = JSON.stringify(Object.values(value))
else value = value.toString().toLowerCase()
// range type validation
const isFindValueArray = Array.isArray(findValue)
const hasTwoValues = findValue.length === 2
const hasNumbers =
typeof findValue[0] === 'number' &&
typeof findValue[1] === 'number'
// range type validation
if (isFindValueArray && hasTwoValues && hasNumbers) {
// case range
return value >= findValue[0] && value <= findValue[1]
} else if (isFindValueArray) {
let findValueLocal = findValue.find((v) => {
const valueLower = v.toString().toLowerCase()
return value.includes(valueLower)
})
return typeof findValueLocal !== 'undefined'
} else {
let findValueLocal = findValue.toString().toLowerCase()
return value.includes(findValueLocal)
}
})
}
})
return filteredItems
})
const refreshingToolTip = computed(() => {
return props.refreshConfig && props.refreshConfig.refreshingToolTip
? props.refreshConfig.refreshingToolTip
: ''
})
/**
* Toggles the selection of an item in the given array and emits the updated array.
*
* @param {boolean} value - The selection status. If true, the item will be selected; if false, it will be deselected.
* @param {function} select - The function to invoke when selecting an item.
* @param {object} item - The item to toggle selection for.
*/
const toggleItemSelection = (value, select, item) => {
let foundedItemIndex = -1
props.value.forEach((row, index) => {
const hasId =
typeof row.id !== 'undefined' && typeof item.id !== 'undefined'
if (
(hasId && row.id === item.id) ||
(!hasId && JSON.stringify(row) === JSON.stringify(item))
) {
foundedItemIndex = index
}
})
const valueAux = [...props.value]
if (value) {
select(item)
if (foundedItemIndex === -1) {
valueAux.push(item)
emit('update:value', valueAux)
}
} else if (foundedItemIndex !== -1) {
valueAux.splice(foundedItemIndex, 1)
emit('update:value', valueAux)
}
}
/**
* Gets the checkbox value for the specified item.
*
* @param {object} item - The item for which to get the checkbox value.
* @returns {boolean} - The checkbox value. True if the item is selected, false otherwise.
*/
const getCheckboxValue = (item) => {
const itemFounded = props.value.find((row) => {
const hasId =
typeof row.id !== 'undefined' && typeof item.id !== 'undefined'
return (
(hasId && row.id === item.id) ||
(!hasId && JSON.stringify(row) === JSON.stringify(item))
)
})
return typeof itemFounded !== 'undefined'
}
/**
* Selects or deselects all items based on the specified isSelected flag.
*
* @param {boolean} isSelected - The flag indicating whether to select (true) or deselect (false) all items.
* @param {Array} items - The array of items to be selected or deselected.
*/
const selectAllItems = (isSelected, items) => {
if (isSelected) {
emit('update:value', [...items]) // Pueden haber items invisibles???? No
} else {
emit('update:value', [])
}
}
/**
* Generates an array representing the pages based on the specified number of rows per page
* and the total number of items.
*
* @param {number} rowsPerPage - The number of rows to display per page.
* @param {Array} items - The array of items to be paginated.
* @returns {Array} - An array representing the pages for pagination navigation.
*/
const getPages = (rowsPerPage, items) => {
const totalPages = Math.ceil(items.length / rowsPerPage)
if (totalPages <= 6) {
return Array.from({ length: totalPages }, (_, index) => index + 1)
}
const range = (start, end) =>
Array.from({ length: end - start + 1 }, (_, index) => start + index)
const getDisplayedPages = () => {
if (currentPage.value <= 4) {
return [...range(1, 5), '...', totalPages]
} else if (currentPage.value >= totalPages - 3) {
return [1, '...', ...range(totalPages - 4, totalPages)]
} else {
return [
1,
'...',
...range(currentPage.value - 2, currentPage.value + 2),
'...',
totalPages
]
}
}
return getDisplayedPages()
}
/**
* Sets a new page number if the provided value is a valid number.
*
* @param {number} page - The new page number to be set.
*/
const setNewPage = (page) => {
if (!isNaN(page)) {
currentPage.value = page
}
}
/**
* Finds the index of the selected item in the sortSelected array based on the specified header index.
*
* @param {number} headerIndex - The index of the header for which to find the selected item in sortSelected.
* @returns {number} - The index of the selected item in the sortSelected array. Returns -1 if not found.
*/
const indexSelected = (headerIndex) => {
return sortSelected.value.findIndex(
(selected) => selected.index === headerIndex
)
}
/**
* Toggles the sorting selection for a header based on the specified header index and header information.
*
* @param {number} headerIndex - The index of the header for which to toggle sorting selection.
* @param {object} header - The header information containing sorting details.
*/
const toggleSelectHeaderSort = (headerIndex, header) => {
const index = indexSelected(headerIndex)
if (index !== -1) {
if (!sortSelected.value[index].sortDesc) {
sortSelected.value[index].sortDesc = true
} else if (props.mustSort) {
sortSelected.value[index].sortDesc =
!sortSelected.value[index].sortDesc
} else {
sortSelected.value.splice(index, 1)
}
} else if (props.multiSort) {
sortSelected.value.push({
sortDesc: false,
index: headerIndex,
value: header.value
})
} else {
sortSelected.value = [
{ sortDesc: false, index: headerIndex, value: header.value }
]
}
}
/**
* Handles mouseover and mouseleave events on a header element.
*
* @param {Event} e - The mouse event object.
* @param {number} headerIndex - The index of the header being interacted with.
*/
const mouseOver = (e, headerIndex) => {
if (e.type === 'mouseenter') {
headerOver.value = headerIndex
} else {
headerOver.value = -1
}
}
const setHeadersItems = (itemsAux) => {
setHeaders(itemsAux)
setItems(itemsAux)
nextTick(() => {
checkShowSelectConsistency()
})
}
const checkShowSelectConsistency = () => {
if (props.show_select) {
let validIdItems = []
let invalidItemKey = true
itemsLocal.value.forEach((item) => {
if (item.id) {
validIdItems.push(item)
}
if (typeof item[props.itemKey] !== 'undefined' && invalidItemKey) {
invalidItemKey = false
}
})
let validItemsWithId = itemsLocal.value.length === validIdItems.length
if (comportamientosTable.value.length === 0) {
logErrorComponent(
tableComponentName,
'Se quiso mostrar la selección de filas pero no se enviaron comportamientos de tabla, o se enviaron desactivados.',
iclRouter
)
} else if (
typeof comportamientosTable.value.find(
(comp) => !!comp.id_columna
) === 'undefined'
) {
logErrorComponent(
tableComponentName,
'Se quiso mostrar la selección de filas pero no se envió la propiedad "id_columna" en ningún comportamiento de tabla.',
iclRouter
)
} else if (
(!validItemsWithId &&
(typeof props.itemKey === 'undefined' || invalidItemKey)) ||
(validItemsWithId &&
typeof props.itemKey !== 'undefined' &&
invalidItemKey)
) {
logErrorComponent(
tableComponentName,
'Se quiso mostrar la selección de filas pero no se encontraron identificadores válidos para los items.',
iclRouter
)
} else {
showSelectLocal.value = props.show_select
}
}
}
const manageFunctionlessRowTypes = (comp) => {
// controla los tipos de comportamientos pertenecientes a "availableFunctionlessRowTypes" (no tienen funcionalidad)
comp = { ...comp, disabled: true } // disabled determina si cumplirá alguna función al ser clickeado o no
if (comp.type === icon_row) {
return { ...comp, component: iconComponent }
}
return comp
}
const checkExistence = (arr, element, id = undefined) => {
// chequea que 'element' se encuentre dentro de 'arr'
return arr.some((el) => {
return id === undefined ? el === element : el[id] === element[id]
})
}
const makeQuery = async (fcn, args) => {
// hace la petición desde un slot de la tabla
const localArgs = [
JSON.stringify({
...args,
id_usuario: iclstore.state.id_usuario,
id_resolver: props.id_resolver
? props.id_resolver
: iclRouter.currentRoute.path
})
]
try {
const rsp = await iclAxios.post(fcnExecuterEndpoint + fcn, localArgs)
handleSuccess(rsp, createdFunctions, {
iclRouter,
iclstore,
emit, // En caso de composition api pasar emit
handleSuccessResponse
})
} catch (error) {
handleError(error, iclstore)
console.log(error)
}
}
const setItems = (itemsAux) => {
if (itemsAux.length > 0) {
//si hay items los asigna en itemsLocal y crea los headers y los asigna en headersLocal
if (headersLocal.value.length > 0) {
itemsLocal.value = []
nextTick(() => {
itemsLocal.value = itemsAux
})
}
} else {
itemsLocal.value = []
}
}
const assignComportamientos = () => {
headersLocal.value.map((header) => {
const comp = comportamientosRow.value.find(
(slot) => slot.columna === header.value
)
if (comp) {
header = Object.assign(header, { ...comp, modified: true })
} else {
header = Object.assign(header, { modified: false })
}
})
}
const setHeaders = (itemsAux) => {
if (itemsAux.length > 0) {
//si hay items los asigna en itemsLocal y crea los headers y los asigna en headersLocal
if (
typeof props.headers === 'undefined' ||
props.headers.length === 0
) {
// si no recibe headers se arman con los keys del item que más propiedades tenga
//const maxLength = Math.max(...itemsAux.map(item => Object.keys(item).length))
//const firstLongerRow = itemsAux.find(item => Object.keys(item).length === maxLength)
let max = 0
let firstLongerRow
itemsAux.forEach((item) => {
// se busca el item con más propiedades haciendo un solo for
let len = Object.keys(item).length // (mejor que método anteriormente comentado, en el cual se hace un map y un find)
if (len > max) {
// (aunque más rudimentario)
max = len
firstLongerRow = item
}
})
headersLocal.value = Object.keys(firstLongerRow).map((r) => ({
text: r.toUpperCase().replace(/_/g, ' '),
value: r
}))
} else {
let localHeaders = JSON.parse(JSON.stringify(props.headers)) // this.headersLocal = structuredClone(this.headers)
headersLocal.value = filterHeaders(localHeaders, itemsAux)
}
if (headersLocal.value.length > 0) {
assignComportamientos()
} else {
logErrorComponent(tableComponentName, 'No se encuentran headers.', iclRouter)
}
}
}
/**
* Filtra un array de objetos de headers basado en las propiedades de items.
*
* @param {Array<{ value: string }>} headers - Un array de objetos de headers, cada uno con una propiedad 'value'.
* @param {Array<Object>} itemsAux - Un array de items que se utilizarán para filtrar los headers.
* @returns {Array<{ value: string }>} - Un nuevo array de objetos de headers que incluye solo aquellos cuyas propiedades 'value' coinciden con las propiedades de los items.
*/
const filterHeaders = (headers, itemsAux) => {
const allItemsProperties = []
itemsAux.forEach((item) => {
const localItemKeys = Object.keys(item)
localItemKeys.forEach((key) => {
if (!allItemsProperties.includes(key)) {
allItemsProperties.push(key)
}
})
})
return headers.filter(({ value }) => allItemsProperties.includes(value))
}
const getItems = (filters, args) => {
loading.value = true
const params = {
filtros: filters,
args,
id_usuario: iclstore.state.id_usuario,
id_resolver: props.id_resolver
? props.id_resolver
: iclRouter.currentRoute.path
}
return iclAxios
.post(externalDBEndpoint + props.items_fcn, [JSON.stringify(params)])
.then((rsp) => {
handleSuccess(rsp, () => {}, {
iclRouter,
iclstore,
emit, // En caso de composition api pasar emit
handleSuccessResponse
})
detectResponseStructErrors(
rsp,
props.items_fcn,
[],
tableComponentName,
'{ <propName>: Any }',
'https://icl.iridiumrobotics.com.ar/components/ir-data-table.html#uso'
)
if (
rsp === null ||
rsp.length === 0 ||
(typeof rsp[0][props.items_fcn] !== 'undefined' &&
rsp[0][props.items_fcn] === null)
)
rsp = Object.assign([])
setHeadersItems(rsp)
})
.catch((error) => {
emit('upload-refreshing-time', null)
clearInterval(intervalId.value)
handleError(error, iclstore)
console.log(error)
})
.finally(() => {
loading.value = false
})
}
const handleTableClick = (action) => {
// se encarga de la ejecución de los comportamientos de tabla
let {
type,
fcn_click,
id_columna,
id_resolver,
args_fcn_click,
confirmation,
titulo,
position,
dialogAttrs
} = action
if (typeof confirmation === 'undefined' || confirmation === '') {
let found = []
let argsLocal = {}
let isColumnaValid =
typeof id_columna !== 'undefined' && id_columna !== ''
if (isColumnaValid && props.value.length > 0) {
for (let i = 0; i < props.value.length; i++) {
let sel = props.value[i]
if (sel[id_columna] !== null && sel[id_columna] !== undefined)
found.push(sel[id_columna])
// se pushea a found en caso de que sólo se necesiten estos datos encontrados y no "args_dialog"
else {
logErrorComponent(
tableComponentName,
'La propiedad "id_columna" con valor "' +
id_columna +
'" no coincide con algún header de la tabla.',
iclRouter
)
return
}
}
}
if (type === fcn_executer) {
argsLocal = Object.assign({}, args_fcn_click)
if (typeof found !== 'undefined' && found.length > 0)
argsLocal = { ...argsLocal, selected_rows: found } //Las filas seleccionadas se agregan a argsLocal.selected_rows
makeQuery(fcn_click, argsLocal)
} else if (type === modal) {
id_resolver = checkIndexedFields(
id_resolver,
itemsLocal.value,
id_columna
)
let id_resolver_aux = resolve(id_resolver, found)
assignDialogData({ id_resolver: id_resolver_aux, titulo, position, dialogAttrs })
} else if (type === navigator) {
id_resolver = checkIndexedFields(
id_resolver,
itemsLocal.value,
id_columna
)
redirect(id_resolver, found)
}
} else {
actionToConfirm.value = Object.assign({}, action)
assignDialogData({ message: confirmation })
}
}
/*
* id_resolver (String): might contain #n# values to replace
* items (Array): items from table
* id_columna (String): column to search for the values to replace
*
* Checks if id_resolver has #n# subtrings to replace with values from a certain column
* Returns: replaced id_resolver.
*/
const checkIndexedFields = (idResolver, items, idColumna) => {
let intervals = idResolver.match(/#\d+#/g)
if (intervals !== null) {
intervals.forEach((interval) => {
let index = interval.slice(1, interval.length - 1)
idResolver = idResolver.replace(
interval,
typeof items[index] !== 'undefined'
? items[index][idColumna]
: 'undefined'
)
})
}
return idResolver
}
const handleRowClick = (action, item) => {
// se encarga de la ejecución de los comportamientos de celda
let {
type,
fcn_click,
id_columna,
id_resolver,
args_fcn_click,
confirmation,
titulo,
position,
dialogAttrs
} = action // action es un objeto de tipo comportamiento
if (typeof confirmation === 'undefined' || confirmation === '') {
//si no existe confirmation en el comportamiento, se realiza la acción del mismo
let idSelected = item[id_columna]
let argsLocal, idResolverAux
if (type === fcn_executer_row) {
argsLocal = { ...args_fcn_click }
if (typeof idSelected !== 'undefined') {
argsLocal = {
...argsLocal,
selected_rows: Array.isArray(idSelected)
? idSelected
: [idSelected]
} //Las filas seleccionadas se agregan a argsLocal.selected_rows
}
isLoadingComportamiento.value = true
makeQuery(fcn_click, argsLocal).finally(() => {
isLoadingComportamiento.value = false
})
} else if (type === modal_row) {
id_resolver = checkIndexedFields(
id_resolver,
itemsLocal.value,
id_columna
)
if (typeof idSelected !== 'undefined') {
idResolverAux = resolve(
id_resolver,
Array.isArray(idSelected) ? idSelected : [idSelected]
)
}
assignDialogData({ id_resolver: idResolverAux, titulo, position, dialogAttrs })
} else if (type === navigator_row) {
id_resolver = checkIndexedFields(
id_resolver,
itemsLocal.value,
id_columna
)
redirect(
id_resolver,
Array.isArray(idSelected) ? idSelected : [idSelected]
)
}
} else {
//Si existe confirmation en comportamiento, se asigna a la variable actionToConfirm y el item a itemToConfirm, se asigna a dialog un mensaje de confirmación
actionToConfirm.value = { ...action }
itemToConfirm.value = { ...item }
assignDialogData({ message: confirmation }) //Si se acepta la confirmación irDataTable ejecuta continueAction
}
}
const assignDialogData = (dialogDataLocal) => {
dialogData.value = Object.assign({})
// asigna los valores correspondientes al dialog, puede abrirlo o cerrarlo (cerrarlo desde este componente todavía no es utilizado)
const { id_resolver, titulo, message, position, dialogAttrs } = dialogDataLocal
dialogData.value.id_resolver_dlg = id_resolver
dialogData.value.titulo = titulo
dialogData.value.message = message
dialogData.value.position = position
dialogData.value = { ... dialogData.value, ...dialogAttrs }
}
const continueAction = (confirmation) => {
//Confirmation = true si se acepta confirmación, false si cancela (evento emitido por IrDialog)
if (confirmation) {
//al aceptar se elimina confirmation ya que con este objeto se llamara nuevamente a los handle...Click, evita bucle de confirmación
delete actionToConfirm.value.confirmation
if (checkExistence(availableTableTypes, actionToConfirm.value.type)) {
handleTableClick(actionToConfirm.value)
} else {
handleRowClick(actionToConfirm.value, itemToConfirm.value)
}
} else {
//si se cancela la confirmación se limpian las variables de ToConfirm y se cierra el dialog
actionToConfirm.value = {}
itemToConfirm.value = {}
}
emit('cerrar-dialog')
}
const redirect = (path, query) => {
// redirecciona
let pathAux = resolve(path, query)
if (pathAux.startsWith('https://') || pathAux.startsWith('http://'))
window.open(pathAux, '_blank')
else iclRouter.push({ path: pathAux })
}
const manageFunctionalRowTypes = (comp) => {
// controla los tipos de comportamientos pertenecientes a "availableRowTypes" (tienen funcionalidad)
comp = {
...comp,
disabled: typeof comp.disabled !== 'undefined' ? comp.disabled : false
}
if (comp.type === image_row) {
isThereImageTypes.value = true
return {
...comp,
component: imageComponent,
attrs: typeof comp.attrs !== 'undefined' ? comp.attrs : { size: 45 }
}
}
if (comp.component === avatarComponent) {
return {
...comp,
attrs: typeof comp.attrs !== 'undefined' ? comp.attrs : { size: 45 }
}
}
return comp
}
const divideComportamientos = () => {
// divide los comportamientos entre "availableTableTypes" y "comportamientosRow", y loggea errores
props.comportamientos.map((comportamiento) => {
let tipo = comportamiento.type
if (tipo === '') {
logErrorComponent(
tableComponentName,
'La propiedad "type" del comportamiento se envió vacía.',
iclRouter
)
} else if (typeof tipo === 'undefined') {
logErrorComponent(
tableComponentName,
'La propiedad "type" del comportamiento no se envió.',
iclRouter
)
} else if (
typeof comportamiento.active === 'boolean' &&
!comportamiento.active
) {
return
} else if (checkExistence(availableTableTypes, tipo)) {
comportamientosTable.value.push(comportamiento)
} else if (
!comportamiento['is-grouped'] &&
checkExistence(availableRowTypes, tipo)
) {
comportamientosRow.value.push(
manageFunctionalRowTypes(comportamiento)
)
} else if (checkExistence(availableFunctionlessRowTypes, tipo)) {
comportamientosRow.value.push(
manageFunctionlessRowTypes(comportamiento)
)
} else if (
comportamiento['is-grouped'] &&
checkExistence(availableRowGroupsTypes, tipo)
) {
const isCompVisible =
typeof comportamiento.visible === 'undefined' ||
comportamiento.visible !== false
if (isCompVisible) {
comportamientosRowGroups.value.push(comportamiento)
}
} else {
logErrorComponent(
tableComponentName,
'La propiedad "type" con valor: "' +
tipo +
'" del comportamiento no coincide con un tipo de comportamiento válido.',
iclRouter
)
}
})
}
const getFiltros = async (fcn, args) => {
let argsAux = {
id_usuario: iclstore.state.id_usuario,
id_resolver: props.id_resolver
? props.id_resolver
: iclRouter.currentRoute.path,
...args
}
return iclAxios
.post(getCamposEndpoint + fcn, [JSON.stringify(argsAux)])
.then((rsp) => {
return rsp
})
.catch((error) => {
console.log(error)
logErrorComponent(
tableComponentName,
'Ocurrió un error en la consulta de los campos.',
iclRouter
)
return props.filtros
})
}
const createdFunctions = async () => {
// controla que se haya recibido una función para obtener los items
if (!!props.items_fcn && iclAxios) {
if (typeof props.filtros_fcn !== 'undefined') {
let datosFiltros = iclstore.getters.getDatosSitios({
sitio: iclRouter.app._route.path,
query: props.filtros_fcn,
method: 'post',
params: props.args_filtros_fcn
})
if (datosFiltros !== null) {
// los busca en el store, si los encuentra los guarda en filtrosLocal hasta que se termine de hacer la consulta por los filtros
datosFiltros = Object.assign({}, JSON.parse(datosFiltros.response))
filtrosLocal.value = datosFiltros[0].props.campos
}
filtrosLocal.value = await getFiltros(
props.filtros_fcn,
props.args_filtros_fcn
)
} else {
filtrosLocal.value = props.filtros
}
let params = { filtros: filtrosLocal.value }
params = { ...params, args: props.args_items_fcn }
let datosSitio = iclstore.getters.getDatosSitios({
sitio: iclRouter.app._route.path,
query: props.items_fcn,
method: 'get',
params: [JSON.stringify(params)]
})
if (
datosSitio !== null &&
props.cache_items &&
datosSitio.response !== null
) {
// los busca en el store, si los encuentra los guarda en itemsLocal hasta que se termine de hacer la consulta por los items
setHeadersItems([...datosSitio.response])
}
await getItems(filtrosLocal.value, props.args_items_fcn)
manageGetItemsRepeater()
} else {
// si no se envía items_fcn se asigna a itemsLocal la propiedad items
let isFiltrosValid =
typeof props.filtros_fcn !== 'undefined' || props.filtros.length > 0
let isItemsFcnValid =
typeof props.items_fcn !== 'undefined' ||
props.items_fcn !== null ||
props.items_fcn !== ''
if (isFiltrosValid && !isItemsFcnValid) {
logErrorComponent(
tableComponentName,
'Se quiso renderizar un formulario de filtros pero no se detalló alguna función en la propiedad "items_fcn".',
iclRouter
)
}
itemsLocal.value = props.items
setHeadersItems(itemsLocal.value)
}
}
const manageGetItemsRepeater = async () => {
const { repeatTime: newRepeatTime, timeout } = props.refreshConfig
let timeRemaining = timeout
let isInfiniteLoop = timeout === -1
let repeatTimeLocalAux = Math.floor(newRepeatTime / 1000)
let countingDown = repeatTimeLocalAux
while (
typeof newRepeatTime !== 'undefined' &&
typeof timeout !== 'undefined' &&
timeRemaining !== 0 &&
(timeRemaining >= newRepeatTime || isInfiniteLoop)
) {
repeatTime.value = newRepeatTime
intervalId.value = setInterval(() => {
if (countingDown === 0) {
countingDown = repeatTimeLocalAux
}
countingDown = countingDown - 1
emit('upload-refreshing-time', countingDown)
}, 1000)
await new Promise((r) => setTimeout(r, newRepeatTime))
if (!isInfiniteLoop) {
timeRemaining = timeRemaining - newRepeatTime
}
if (timeRemaining < newRepeatTime && !isInfiniteLoop) {
emit('upload-refreshing-time', null)
}
clearInterval(intervalId.value)
await getItems(filtrosLocal.value, props.args_items_fcn)
}
}
/**
* Valida que el valor de la celda no coincida con el valor de la propiedad falsy-value o que no sea un falsy value.
* @param {Object} item - Item de la tabla.
* @returns {Array} - Arreglo de comportamientos válidos.
*/
const checkComportamientosGrouped = (item) => {
let comps = []
comportamientosRowGroups.value.forEach((comp) => {
const hasFalsyValue = typeof comp['falsy-value'] !== 'undefined'
if (typeof comp.attached === 'undefined') {
comps.push(comp)
} else if (
hasFalsyValue &&
typeof item[comp.attached] !== 'undefined' &&
item[comp.attached] !== comp['falsy-value']
) {
comps.push(comp)
} else if (!hasFalsyValue && item[comp.attached]) {
comps.push(comp)
}
})
return comps
}
/*
* assignCSVItems
* Genera los items que se escribirán en el csv.
* @returns {void}
*/
const assignCSVItems = () => {
// genera la data para el CSV
let itemsForCsv = [] // se vuelve a generar por si se actualiza la data de la tabla
let headersCSVLocal
if (typeof props.headersCSV === 'undefined') {
headersCSVLocal = Object.assign(
[],
headersLocal.value.map((header) => header.value)
)
} else {
headersCSVLocal = Object.assign([], props.headersCSV)
}
for (let i = 0; i < filteredItems.value.length; i++) {
// Se hace con for para evitar errores de referencia,
const item = { ...filteredItems.value[i] } // antes se hacía con reduce y fallaba
for (const [key] of Object.entries(item)) {
if (!checkExistence(headersCSVLocal, key)) {
delete item[key]
}
}
itemsForCsv.push({ ...item })
}
return itemsForCsv
}
onBeforeUnmount(() => {
clearInterval(intervalId.value)
})
assignCamelToSnake(mergedProps, props)
rowsPerPageLocal.value = props.rowsPerPage
divideComportamientos()
createdFunctions()
onMounted(() => {
structureError.value = propError(propsLocal, props, tableComponentName)
if (structureError.value) {
iclstore.commit('updateSnack', {
text: 'Ha ocurrido un error al cargar la tabla',
active: true,
color: 'warning'
})
}
try {
if (props.row_classes) {
rowsClassesFunction.value = eval(buildBooleanFcn(props.row_classes))
}
} catch (e) {
logErrorComponent(
tableComponentName,
'Ha ocurrido un error evaluando la funcion definida row_classes -> conditional, revise la estructura de la propiedad. ' +
e,
iclRouter
)
console.error('Error en función de estilos de filas - ', e)
}
emit(
'set-show-column-filters',
props.columnFilters ? props.columnFilters['is-visible-on-load'] : false
)
})
expose({
filteredItems,
assignCSVItems,
getItems,
itemsLocal,
headersLocal,
dialogData,
handleRowClick,
comportamientosRowGroups,
filterHeaders
})
return () => {
if (structureError.value) {
return
}
return h(
'div', // Contenedor como nodo raíz
[
slots.default({
headerOver: headerOver.value,
sortSelected: sortSelected.value,
sortDesc: sortDesc.value,
selectedHeadersValues: selectedHeadersValues.value,
indexSelected: indexSelected,
toggleSelectHeaderSort: toggleSelectHeaderSort,
mouseOver: mouseOver,
setNewPage: setNewPage,
multiSort: props.multiSort,
currentPage: currentPage.value,
getPages: getPages,
selectAllItems: selectAllItems,
getCheckboxValue: getCheckboxValue,
toggleItemSelection: toggleItemSelection,
headers: headersLocal.value,
items: filteredItems.value,
id_resolver_dlg: dialogData.value.id_resolver_dlg,
dialogAttrs: dialogData.value,
loading: loading.value,
showSelect: showSelectLocal.value,
buttonsTableOutside: comportamientosTableOutside.value,
buttonsTableInside: comportamientosTableInside.value,
comportamientosRowGroups: comportamientosRowGroups.value,
checkComportamientosGrouped: checkComportamientosGrouped,
filtros: filtrosLocal.value,
nameCSV: props.name_csv,
generateCSV: assignCSVItems,
downloadable: props.downloadable,
rowsPerPage: rowsPerPageLocal.value,
availableTableTypes: availableTableTypes,
availableRowTypes: availableRowTypes,
isThereImageTypes: isThereImageTypes.value,
handleRowClick: handleRowClick,
handleTableClick: handleTableClick,
checkType: checkExistence,
manageGroupalHeader: manageGroupalHeader.value,
image_row: image_row,
repeatTime: repeatTime.value,
noExpandedData: props.noExpandedData,
refreshingToolTip: refreshingToolTip.value,
expandedDataProp: props.expandedDataProp,
submitForm: {
click: () => {
getItems(filtrosLocal.value, props.args_items_fcn)
}
},
continueAction: continueAction,
id_resolver: props.id_resolver
? props.id_resolver
: iclRouter.currentRoute.path,
row_classes: rowsClassesFunction.value
? rowsClassesFunction.value
: undefined,
groupColor: props.groupColor,
search: props.search,
refreshParent: createdFunctions,
columnFiltersTooltip: props.columnFilters
? props.columnFilters.tooltip
: undefined,
columnFiltersLabel: props.columnFilters
? props.columnFilters.label
: '',
columnFiltersIcon:
props.columnFilters && props.columnFilters.icon
? props.columnFilters.icon
: 'mdi-table-search',
columnFiltersColor:
props.columnFilters && props.columnFilters.color
? props.columnFilters.color
: 'fourth',
columnFiltersAttrs: props.columnFilters
? props.columnFilters['text-field-attrs']
: undefined,
columnFilters: props.columnFiltersValues,
isLoadingComportamiento: isLoadingComportamiento.value
})
]
)
}
}
}
</script>