# Componente
# Introducción
Este componente renderizará componentes en una grilla.
La misma se compone de dos partes, una de renderizado ir-iterator (nombre utilizado para renderizar el componente, se debe recibir desde BD) y otra de logica ir-iterator-bone, esta ultima será la encargada de el manejo de los datos así sean ingresados por propiedades o por consulta a la capa de datos.
# Uso
El componente espera recibir un arreglo de items con la siguiente estructura y un type para identificar el componente a renderizar, desde la capa de datos.
type: 'IrDataCard',
items: [
{
"title": "1",
"value": "180",
"subtitle": "Subtítulo 1"
},
{
"title": "3",
"value": "199%",
"subtitle": "Subtítulo 2"
}
]
# Ejemplo
| Propiedad | Tipo | Default | Requerido | Descripción |
| items | Array | [] | false | Arreglo de items. En caso de que uno de los items tenga la estructura: |
| items_fcn | String | undefined | false | Nombre de la función a ejecutarse en la DB, la respuesta se utilizará para reemplazar el arreglo de items. |
| args_items_fcn | Object | [] | false | Parámetros de la función que se ejecuta con el nombre proveniente de la propiedad "items_fcn" (ésta propiedad queda inutilizada si no se envía "items_fcn"). *IMPORTANTE* la función descripta en la propiedad "items_fcn" va a recibir los prámetros que incluya esta propiedad y además un parámetro extra "filtros" que va a tener como valor un array hecho string con los valores de los campos del formulario de filtro. En caso de no poseer filtros, el parámetro se envía como un string vacío. |
| filtros | Array | [] | false | Arreglo de campos del formulario de filtro (igual formato que la propiedad "campo" del componente "Formulario"). *IMPORTANTE* Los filtros no se mostrarán si la propiedad "items_fcn" no está setteada. Se explica más detalladamente aquí. |
| filtros_fcn | String | undefined | false | Nombre de la función a ejecutarse en la DB, la respuesta se utilizará para reemplazar el arreglo de filtros. |
| args_filtros_fcn | Object | {} | false | Parámetros de la función que se ejecuta con el nombre proveniente de la propiedad "filtros_fcn" (ésta propiedad queda inutilizada si no se envía "filtros_fcn") |
| rowsPerPage | Number | 5 | false | Determina la cantidad de filas por página que tendrá la tabla inicialmente (puede cambiarse desde los botones de navegación de la misma tabla). En caso de valer -1 se muestran todas las filas. |
| cache_items | Boolean | true | false | Activa un cache de los items de la tabla, el cual permitirá que al navegar por el sitio se almacenen en el equipo del usuario los items consultados por items_fcn, para mostrar temporalmente mientras se consulta la información actualizada a la DB. Esto dará una mejor performance a la tabla, ya que mostrará contenido mas rápido. Al desactivarse la tabla no mostrará items hasta obtener la información de la DB, solo mostrará un loading. |
| item-key | String |
| false | Nombre de la propiedad que va a utilizarse como identificador de cada item. |
| type | String | undefined | true | Nombre del componente que se va a renderizar. |
| columns | Number | 4 | false | Cantidad de columnas que tendrá la grilla que contendrá los registros. Se tomará por default en los breakpoints que no estén definidos. |
| columns-[breakpoint] | Number | undefined | false | Cantidad de columnas que tendrá la grilla que contendrá los registros. La variable breakpoint del nombre de la propiedad puede tomar los valores
En el caso de Los tamaños de los breakpoints son pueden verse aquí. |
| adaptPerPage | Boolean | false | false | Determinará si la cantidad de registros se adaptará al ancho del contenedor (en caso de ser |
| hideDefaultFooter | Boolean | false | false | Determinará si se muestra el footer con la paginación. |
| hideDefaultHeader | Boolean | true | false | Determinará si se muestra el header con el buscador y el filtrado. |
| colorHeader | String | '#ffffff' | false | Determinará el color del header. |
| fullHeight | Boolean | false | false | Determina si los componentes renderizados tomarán el alto máximo disponible. |
| value | undefined | undefined | false | Valor que actualiza el componente en caso de que sus items tengan una propiedad value. |
Código fuente
<template>
<ir-iterator-bone
v-bind="$attrs"
@close-dialog="cerrarDialog"
@update-value="updateSelectValue"
@open-dialog="openDialog"
>
<div
slot-scope="{
items: itemsBone,
itemsPerPage,
keys,
filtros,
submitForm,
id_resolver,
colorHeader,
type,
columns,
adaptPerPage,
hideDefaultFooter,
hideDefaultHeader,
handleItemClick,
comportamiento,
continueAction,
tituloDialog,
isValidIdResolver,
id_resolver_dlg,
message,
refreshParent,
fullHeight,
linked,
isLoading,
dialogAttrs
}"
>
<ir-dialog
v-model="showDialog"
:titulo="tituloDialog"
:json_resolver="{
id_resolver: id_resolver_dlg,
args_components_fcn: { filtros: filtros }
}"
:message="message"
v-bind="dialogAttrs"
@confirm="continueAction"
@refresh-parent="refreshParent"
/>
<v-data-iterator
ref="irIteratorRef"
:items="itemsBone"
:items-per-page="itemsPerPage"
:search="search"
:loading="isLoading"
:sort-by="sortBy"
v-bind="getFilteredProps"
:sort-desc="sortDesc"
:hide-default-footer="hideDefaultFooter"
:style="
!isLoading
? getIteratorStyle(adaptPerPage, itemsPerPage, columns)
: ''
"
>
<template
v-if="!hideDefaultHeader"
#header
>
<ir-form
v-if="filtros.length !== 0 && isFiltros"
:campos="filtros"
data-cy="form-filtro"
flat
:style="getFormStyle(adaptPerPage, itemsPerPage, columns)"
>
<template #footer>
<v-btn
dark
data-cy="aplicar-filtro"
v-on="submitForm"
>
Apply filters
</v-btn>
</template>
</ir-form>
<div
v-if="filtros.length !== 0 && visibleFields(filtros)"
:style="getFiltersStyle(adaptPerPage, itemsPerPage, columns)"
>
<div class="d-flex flex-row actionPosition">
<v-tooltip bottom>
<template #activator="{ on, attrs }">
<v-btn
data-cy="form-filtro-show"
class="d-inline-flex mx-2"
fab
small
dark
v-on="on"
@click="mostrarFiltros"
>
<v-icon dark>
{{ isFiltros ? 'mdi-filter-off' : 'mdi-filter' }}
</v-icon>
</v-btn>
</template>
<span>
{{ isFiltros ? 'Hide filters' : 'Show filters' }}
</span>
</v-tooltip>
</div>
</div>
<v-toolbar
:color="colorHeader"
class="mb-1"
:style="getHeaderStyle(adaptPerPage, itemsPerPage, columns)"
>
<v-text-field
v-model="search"
clearable
flat
solo-inverted
hide-details
prepend-inner-icon="mdi-magnify"
label="Search"
/>
<v-spacer />
<v-select
v-model="sortBy"
clearable
flat
solo-inverted
hide-details
:items="keys"
prepend-inner-icon="mdi-magnify"
label="Sort by"
/>
<v-spacer />
<v-btn-toggle
v-model="sortDesc"
mandatory
>
<v-btn
large
depressed
color="blue"
:value="false"
>
<v-icon>mdi-arrow-up</v-icon>
</v-btn>
<v-btn
large
depressed
color="blue"
:value="true"
>
<v-icon>mdi-arrow-down</v-icon>
</v-btn>
</v-btn-toggle>
</v-toolbar>
</template>
<template #item="{ item }">
<!--El estilo se agregó de esta forma porque con scoped en <style> no toma el estilo por ser un componente de Vuetify.-->
<style>
.v-data-footer {
grid-column: {{ getFooterStyle(adaptPerPage, itemsPerPage, columns) }};
}
</style>
<ir-component-resolver
class="mb-5"
style="grid-column: span 1 / span 1; min-height: 100%"
:full-height="fullHeight"
:class="
componentClasses(
item.value,
comportamiento.type && isValidIdResolver(item, comportamiento)
)
"
:json_resolver="{
id_usuario: $iclstore.state.id_usuario,
id_resolver: id_resolver
}"
:dev_components="[getItemsProps(type, item)]"
@click="
slotsClickManage(
item,
handleItemClick,
comportamiento,
linked,
typeof comportamiento.type !== 'undefined' &&
isValidIdResolver(item, comportamiento)
)
"
/>
</template>
<template #loading>
<v-progress-linear
style="width: 100%"
indeterminate
color="primary"
/>
<span v-if="$attrs['loading-text']">{{
$attrs['loading-text']
}}</span>
</template>
</v-data-iterator>
</div>
</ir-iterator-bone>
</template>
<script>
import { ref, computed, nextTick, onMounted, getCurrentInstance } from 'vue'
import { iteratorComponentName } from '../../constants'
import IrIteratorBone from './IrIteratorBone.vue'
import { filterProps } from '../../helpers/filterProps'
export default {
name: iteratorComponentName,
components: {
IrIteratorBone
},
inheritAttrs: false,
props: {
value: {
type: undefined,
required: false,
default: ''
}
},
setup(props, { emit, attrs }) {
const vueInstance = getCurrentInstance()
const vuetify = vueInstance.proxy.$vuetify
const irIteratorRef = ref(null)
const showDialog = ref(false)
const selectedRows = ref([])
const isFiltros = ref(false)
const search = ref('')
const sortBy = ref('')
const sortDesc = ref(false)
const otherProps = ref({})
const getFilteredProps = computed(() => {
return filterProps(irIteratorRef.value, otherProps.value, ['data-cy'])
})
const componentClasses = (itemsValue, isValidIdResolver) => {
let isValueSelected = props.value === itemsValue
let isSelectable =
isValidIdResolver ||
(typeof props.value !== 'undefined' &&
typeof itemsValue !== 'undefined')
return (
(isValueSelected ? 'active' : '') +
(isSelectable ? ' non-selected-comps' : ' unselectable')
)
}
const getItemsProps = (type, item) => {
return item.type && item.props ? item : { type, props: item }
}
const visibleFields = (filtros) => {
return filtros.filter((filtro) => filtro.props.visible).length > 0
}
const cerrarDialog = () => {
showDialog.value = false
}
const openDialog = () => {
showDialog.value = true
}
const slotsClickManage = (
item,
clickHandler,
comp,
linked,
isValidIdResolver
) => {
if (comp.type !== 'navigator') {
updateValue(item.value, linked)
}
if (typeof comp.type !== 'undefined' && isValidIdResolver) {
clickHandler(item)
}
}
const updateValue = (newValue, linked) => {
if (typeof newValue !== 'undefined') {
nextTick(() => emit('input', newValue))
if (linked) {
nextTick(() => emit('change-linked-value'))
}
}
}
const updateSelectValue = (value) => {
let localValue = value || props.value
updateValue(localValue, false)
}
const mostrarFiltros = () => {
isFiltros.value = !isFiltros.value
}
const calculateColumns = (adapt, perPage, columns) => {
let breakpoint = vuetify.breakpoint.name
return adapt ? perPage : columns[breakpoint] || columns.default || '4'
}
const getIteratorStyle = (adaptPerPage, itemsPerPage, columns) => ({
display: 'grid',
'grid-template-columns': `repeat(${calculateColumns(
adaptPerPage,
itemsPerPage,
columns
)}, minmax(0, 1fr))`,
gap: '10px'
})
const getFormStyle = (adaptPerPage, itemsPerPage, columns) => ({
'margin-bottom': '40px',
'grid-column': `span ${calculateColumns(
adaptPerPage,
itemsPerPage,
columns
)} / span ${calculateColumns(adaptPerPage, itemsPerPage, columns)}`
})
const getFiltersStyle = (adaptPerPage, itemsPerPage, columns) => ({
position: 'relative',
'grid-column': `span ${calculateColumns(
adaptPerPage,
itemsPerPage,
columns
)} / span ${calculateColumns(adaptPerPage, itemsPerPage, columns)}`,
width: '100%'
})
const getHeaderStyle = (adaptPerPage, itemsPerPage, columns) => ({
'grid-column': `span ${calculateColumns(
adaptPerPage,
itemsPerPage,
columns
)} / span ${calculateColumns(adaptPerPage, itemsPerPage, columns)}`
})
const getFooterStyle = (adaptPerPage, itemsPerPage, columns) =>
`span ${calculateColumns(
adaptPerPage,
itemsPerPage,
columns
)} / span ${calculateColumns(adaptPerPage, itemsPerPage, columns)}`
onMounted(() => {
let aux = { ...attrs }
updateSelectValue(props.value)
delete aux.items
delete aux.show_select
otherProps.value = aux
})
return {
irIteratorRef,
showDialog,
selectedRows,
isFiltros,
search,
sortBy,
sortDesc,
getFilteredProps,
componentClasses,
getItemsProps,
visibleFields,
openDialog,
cerrarDialog,
slotsClickManage,
updateValue,
updateSelectValue,
mostrarFiltros,
calculateColumns,
getIteratorStyle,
getFormStyle,
getFiltersStyle,
getHeaderStyle,
getFooterStyle
}
}
}
</script>
<style scoped>
.actionPosition {
position: absolute;
right: -27px;
top: -35px;
z-index: 10;
}
.active > :first-child {
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
transform: scale(1.02);
}
.unselectable > :first-child {
pointer-events: none;
}
.non-selected-comps {
cursor: pointer;
}
.non-selected-comps > :first-child {
transition-timing-function: linear;
transition-duration: 120ms;
transition-property: all;
}
.non-selected-comps:hover > :first-child {
transform: scale(1.02);
}
</style>