innoboxrr-vue-datatable 2.3.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -168
- package/index.js +13 -2
- package/package.json +6 -4
- package/src/DataTable.vue +249 -424
- package/src/components/DataTableComponent.vue +218 -174
- package/src/components/SelectPaginationComponent.vue +78 -93
- package/src/table.js +251 -0
- package/src/useDataTable.js +370 -0
- package/src/useTheme.js +20 -0
- package/src/components/DisabledLinkComponent.vue +0 -45
- package/src/components/IconLinkComponent.vue +0 -81
- package/src/components/IconRouteComponent.vue +0 -84
- package/src/components/NavDropdownComponent.vue +0 -118
- package/src/components/PaginationComponent.vue +0 -79
package/README.md
CHANGED
|
@@ -1,198 +1,160 @@
|
|
|
1
|
+
# innoboxrr-vue-datatable
|
|
1
2
|
|
|
2
|
-
|
|
3
|
+
Un listado de administración para Vue 3 sobre el contrato de modelo de
|
|
4
|
+
LaraPack: barra de acciones, filtros, orden y paginación en el servidor,
|
|
5
|
+
selección de filas con acciones masivas, permisos resueltos antes de abrir cada
|
|
6
|
+
menú, esqueletos mientras carga y errores que se ven.
|
|
3
7
|
|
|
4
|
-
|
|
8
|
+
Por debajo, [TanStack Table](https://tanstack.com/table) lleva el estado de la
|
|
9
|
+
tabla —columnas visibles, orden, selección— y los componentes de
|
|
10
|
+
`innoboxrr-form-elements` ponen el menú, los iconos y los esqueletos, con el
|
|
11
|
+
tema de `innoboxrr-form-core`.
|
|
5
12
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
## Índice
|
|
9
|
-
|
|
10
|
-
1. [Instalación](#instalación)
|
|
11
|
-
2. [Configuración en Vue](#configuración-en-vue)
|
|
12
|
-
3. [Archivo de Configuración](#archivo-de-configuración)
|
|
13
|
-
4. [Uso de DataTable](#uso-de-datatable)
|
|
14
|
-
5. [Personalización](#personalización)
|
|
15
|
-
6. [CRUD y Políticas](#crud-y-políticas)
|
|
16
|
-
7. [Ejemplo Completo](#ejemplo-completo)
|
|
17
|
-
|
|
18
|
-
---
|
|
13
|
+
Su gemelo es [`innoboxrr-react-datatable`](../react-datatable): los mismos
|
|
14
|
+
props, el mismo modelo, el mismo comportamiento.
|
|
19
15
|
|
|
20
16
|
## Instalación
|
|
21
17
|
|
|
22
|
-
### Paso 1: Instalar el paquete
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
npm install innoboxrr-vue-datatable innoboxrr-http-request
|
|
26
18
|
```
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## Configuración en Vue
|
|
31
|
-
|
|
32
|
-
1. **Registrar el paquete:**
|
|
33
|
-
|
|
34
|
-
```javascript
|
|
35
|
-
// main.js
|
|
36
|
-
import { createApp } from 'vue';
|
|
37
|
-
import App from './App.vue';
|
|
38
|
-
import DataTable from 'innoboxrr-vue-datatable';
|
|
39
|
-
|
|
40
|
-
const app = createApp(App);
|
|
41
|
-
app.component('DataTable', DataTable);
|
|
42
|
-
app.mount('#app');
|
|
19
|
+
npm i innoboxrr-vue-datatable innoboxrr-form-core innoboxrr-form-elements vue-router
|
|
43
20
|
```
|
|
44
21
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
```javascript
|
|
48
|
-
// main.js
|
|
49
|
-
import ClipboardInput from './components/ClipboardInput.vue';
|
|
50
|
-
|
|
51
|
-
app.component('ClipboardInput', ClipboardInput);
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## Archivo de Configuración
|
|
57
|
-
|
|
58
|
-
Crea un archivo de configuración, por ejemplo, `dataTableConfig.js`, para centralizar la lógica asociada a tus tablas y modelos.
|
|
59
|
-
|
|
60
|
-
```javascript
|
|
61
|
-
import makeHttpRequest from 'innoboxrr-http-request';
|
|
62
|
-
import ClipboardInput from '@components/ClipboardInput.vue';
|
|
63
|
-
|
|
64
|
-
export const API_ROUTE_PREFIX = 'api.example.';
|
|
65
|
-
export const CSRF_TOKEN = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
|
66
|
-
|
|
67
|
-
export let filters = {};
|
|
68
|
-
|
|
69
|
-
export const strings = {
|
|
70
|
-
crudActions: {
|
|
71
|
-
create: { name: 'Create', icon: 'fa-plus' },
|
|
72
|
-
export: { name: 'Export', icon: 'fa-download' },
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
export const setFilters = (newFilters = {}) => {
|
|
77
|
-
filters = { ...filters, ...newFilters };
|
|
78
|
-
return filters;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
export const getFilters = () => filters;
|
|
82
|
-
|
|
83
|
-
export const crudActions = () => [
|
|
84
|
-
{
|
|
85
|
-
id: 'create',
|
|
86
|
-
name: strings.crudActions.create.name,
|
|
87
|
-
callback: 'createModel',
|
|
88
|
-
icon: strings.crudActions.create.icon,
|
|
89
|
-
route: true,
|
|
90
|
-
params: { to: { name: 'CreateExample', params: {} } },
|
|
91
|
-
},
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
export const dataTableComponents = () => ({
|
|
95
|
-
ClipboardInput,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
export const dataTableHead = () => [
|
|
99
|
-
{ id: 'id', value: 'ID', sortable: true },
|
|
100
|
-
{ id: 'name', value: 'Name', sortable: true },
|
|
101
|
-
{ id: 'description', value: 'Description', sortable: false },
|
|
102
|
-
{ id: 'link', value: 'Link', component: 'ClipboardInput', parser: (value) => ({ value }) },
|
|
103
|
-
];
|
|
104
|
-
|
|
105
|
-
export const dataTableSort = () => ({
|
|
106
|
-
id: 'asc',
|
|
107
|
-
name: 'asc',
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
export const indexModel = (filters = {}) =>
|
|
111
|
-
makeHttpRequest('get', route(API_ROUTE_PREFIX + 'index'), { _token: CSRF_TOKEN, ...filters });
|
|
112
|
-
|
|
113
|
-
export const createModel = (data) =>
|
|
114
|
-
makeHttpRequest('post', route(API_ROUTE_PREFIX + 'create'), { _token: CSRF_TOKEN, ...data });
|
|
22
|
+
```js
|
|
23
|
+
import 'innoboxrr-form-core/styles'
|
|
115
24
|
```
|
|
116
25
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
## Uso de DataTable
|
|
26
|
+
## Uso
|
|
120
27
|
|
|
121
28
|
```vue
|
|
122
29
|
<template>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
30
|
+
<DataTable
|
|
31
|
+
ref="table"
|
|
32
|
+
:data-url="route('api.acme.shop.product.index')"
|
|
33
|
+
data-method="get"
|
|
34
|
+
:policy-url="route('api.acme.shop.product.policies')"
|
|
35
|
+
policy-method="get"
|
|
36
|
+
:model="model"
|
|
37
|
+
:form-filters="filters"
|
|
38
|
+
selectable>
|
|
39
|
+
<template #filterForm>
|
|
40
|
+
<FilterForm @submit="filters = $event" />
|
|
41
|
+
</template>
|
|
42
|
+
</DataTable>
|
|
43
|
+
|
|
44
|
+
<!-- Una vez en la aplicación, para los avisos y las confirmaciones. -->
|
|
45
|
+
<ToastRegionComponent />
|
|
46
|
+
<ConfirmHostComponent />
|
|
139
47
|
</template>
|
|
140
48
|
|
|
141
|
-
<script>
|
|
142
|
-
import
|
|
143
|
-
import
|
|
144
|
-
import * as model from './
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
dataUrl: '/api/example',
|
|
151
|
-
model,
|
|
152
|
-
externalFilters: {},
|
|
153
|
-
formFilters: {},
|
|
154
|
-
extraParams: {},
|
|
155
|
-
hideColumns: [],
|
|
156
|
-
};
|
|
157
|
-
},
|
|
158
|
-
methods: {
|
|
159
|
-
updateFormFilters(filters) {
|
|
160
|
-
this.formFilters = filters;
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
};
|
|
49
|
+
<script setup>
|
|
50
|
+
import { ref } from 'vue'
|
|
51
|
+
import DataTable from 'innoboxrr-vue-datatable'
|
|
52
|
+
import * as model from './models/product'
|
|
53
|
+
|
|
54
|
+
const table = ref(null)
|
|
55
|
+
const filters = ref({})
|
|
56
|
+
|
|
57
|
+
// Tras crear o editar en un drawer: table.value.refresh()
|
|
164
58
|
</script>
|
|
165
59
|
```
|
|
166
60
|
|
|
167
|
-
|
|
61
|
+
## Props
|
|
168
62
|
|
|
169
|
-
|
|
63
|
+
| Prop | Por defecto | |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `dataUrl`, `policyUrl` | — | Obligatorias. |
|
|
66
|
+
| `dataMethod`, `policyMethod` | `'post'` | `get` manda `params`; `post`, `data`. |
|
|
67
|
+
| `model` | — | El contrato del modelo (abajo). |
|
|
68
|
+
| `formFilters` | `{}` | Un cambio vuelve a la primera página. |
|
|
69
|
+
| `externalFilters` | `{}` | Un cambio recarga sin cambiar de página. |
|
|
70
|
+
| `extraParams`, `extraQuery` | `{}` | Se suman a las rutas de las acciones. |
|
|
71
|
+
| `hideColumns` | `[]` | Ids, como `'name'` o `{ id: 'name' }`. |
|
|
72
|
+
| `selectable` | `false` | Casillas y barra de acciones masivas. |
|
|
73
|
+
| `showTopbar`, `hasActions`, `hasFilter`, `showTableHeader`, `cardWrapper` | `true` | |
|
|
74
|
+
| `labels` | `{}` | Textos; se mezclan con `DEFAULT_LABELS`. |
|
|
170
75
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
2. **Filtros externos e internos:**
|
|
175
|
-
Usa `formFilters` para los filtros internos y `externalFilters` para filtros definidos fuera del componente.
|
|
76
|
+
Por `ref`: `refresh()`, `clearSelection()`, `selectedIds` y la instancia de
|
|
77
|
+
TanStack Table en `table`.
|
|
176
78
|
|
|
177
|
-
|
|
178
|
-
Registra componentes como `ClipboardInput` para columnas dinámicas.
|
|
79
|
+
## El contrato del modelo
|
|
179
80
|
|
|
180
|
-
|
|
81
|
+
Es **el mismo archivo** para Vue y para React: funciones puras y llamadas HTTP.
|
|
181
82
|
|
|
182
|
-
|
|
83
|
+
```js
|
|
84
|
+
export const crudActions = () => [
|
|
85
|
+
{ id: 'create', name: 'Crear', icon: 'plus', route: true, policy: false,
|
|
86
|
+
params: { to: { name: 'AdminCreateProduct', params: {} } } },
|
|
87
|
+
{ id: 'export', name: 'Exportar', icon: 'download', route: false, policy: false,
|
|
88
|
+
callback: 'exportModel', params: {}, success: 'Exportación en cola' },
|
|
89
|
+
]
|
|
183
90
|
|
|
184
|
-
|
|
185
|
-
|
|
91
|
+
export const dataTableHead = () => [
|
|
92
|
+
{ id: 'id', value: 'ID', sortable: true, numeric: true },
|
|
93
|
+
{ id: 'price', value: 'Precio', sortable: true, numeric: true, parser: (value, row) => `$${value}` },
|
|
94
|
+
{ id: 'status', value: 'Estado', html: true, parser: (value) => `<b>${value}</b>` },
|
|
95
|
+
{ id: 'link', value: 'Enlace', component: 'CopyLink', callback: (payload, row) => {} },
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
export const dataTableSort = () => ({ id: 'asc' })
|
|
99
|
+
export const setFilters = (filters) => filters
|
|
100
|
+
|
|
101
|
+
// Opcionales
|
|
102
|
+
export const dataTableComponents = () => ({ CopyLink })
|
|
103
|
+
export const bulkActions = () => [
|
|
104
|
+
{ id: 'delete', name: 'Borrar', icon: 'delete', callback: 'bulkDelete', danger: true },
|
|
105
|
+
]
|
|
106
|
+
export const bulkDelete = (ids, rows) => { /* … */ }
|
|
107
|
+
```
|
|
186
108
|
|
|
187
|
-
|
|
188
|
-
|
|
109
|
+
- **Acciones.** `route: true` navega a `params.to` con vue-router;
|
|
110
|
+
`route: true, link: true` abre `params.link`; `route: false` llama a
|
|
111
|
+
`model[callback](params)` y recarga. Cada fila trae las suyas en `actions`.
|
|
112
|
+
- **Permisos.** Antes de abrir un menú se pregunta a `policyUrl` con el `id` de
|
|
113
|
+
la fila (o `null` para la barra) y se espera la respuesta: un
|
|
114
|
+
`{ edit: true }` habilita esa acción. Lo que no está permitido se ve
|
|
115
|
+
deshabilitado y dice por qué. La respuesta se guarda hasta la próxima carga.
|
|
116
|
+
- **Parsers.** Reciben una copia de la fila: mutarla no cambia los datos.
|
|
117
|
+
- **Acciones masivas.** Reciben los ids seleccionados —también los de otras
|
|
118
|
+
páginas— y las filas cargadas que están entre ellos.
|
|
119
|
+
- **Confirmaciones.** Si un callback rechaza con un `RequestCancelledError`
|
|
120
|
+
(lo que lanza `innoboxrr-http-request` cuando se cancela la confirmación), la
|
|
121
|
+
tabla no avisa ni recarga.
|
|
122
|
+
|
|
123
|
+
## Qué se ve
|
|
124
|
+
|
|
125
|
+
- **Cargando.** La primera carga pinta la forma de las filas; las siguientes
|
|
126
|
+
atenúan las que hay (`aria-busy`).
|
|
127
|
+
- **Errores.** Sin filas, el motivo va en la tabla: «No tienes permiso» para un
|
|
128
|
+
403, y «Reintentar» para lo demás. Con filas en pantalla, un aviso. No hay
|
|
129
|
+
reintentos silenciosos.
|
|
130
|
+
- **Orden.** Un botón en cada cabecera ordenable, con `aria-sort`.
|
|
131
|
+
- **Una petición por cambio.** Un filtro nuevo en la página 3 es una sola
|
|
132
|
+
petición a la página 1, y una respuesta que llega tarde no pisa a la última.
|
|
133
|
+
|
|
134
|
+
## `useDataTable`
|
|
135
|
+
|
|
136
|
+
La lógica, sin la vista:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
import { useDataTable } from 'innoboxrr-vue-datatable'
|
|
140
|
+
|
|
141
|
+
const { table, rows, meta, loading, error, sortColumn, updatePage, refresh } = useDataTable(props, { navigate, labels })
|
|
142
|
+
```
|
|
189
143
|
|
|
190
|
-
3.
|
|
191
|
-
Integra validaciones de acceso por políticas usando rutas predefinidas.
|
|
144
|
+
## De 2.x a 3.0
|
|
192
145
|
|
|
193
|
-
|
|
146
|
+
- Ya no existen `NavDropdownComponent`, `IconRouteComponent`,
|
|
147
|
+
`IconLinkComponent`, `DisabledLinkComponent` ni `PaginationComponent`: el menú
|
|
148
|
+
es `MenuComponent` de `innoboxrr-form-elements`.
|
|
149
|
+
- Las acciones de ruta se ejecutan desde el menú con `router.push`, no con un
|
|
150
|
+
`<router-link>`.
|
|
151
|
+
- Los textos están en español y se cambian con `labels`.
|
|
152
|
+
- Un fallo ya no se reintenta tres veces: se ve, y se reintenta a mano.
|
|
153
|
+
- `innoboxrr-form-elements` pasa a ser dependencia par, y `innoboxrr-form-core`
|
|
154
|
+
sube a `^2.6`, que trae las clases del listado.
|
|
194
155
|
|
|
195
|
-
##
|
|
156
|
+
## Pruebas
|
|
196
157
|
|
|
197
|
-
|
|
198
|
-
|
|
158
|
+
```
|
|
159
|
+
npm test
|
|
160
|
+
```
|
package/index.js
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* El export por defecto es la tabla, igual que en innoboxrr-react-datatable.
|
|
3
|
+
* Se exponen además la lógica, por si alguien quiere pintarla de otra forma,
|
|
4
|
+
* y las piezas con las que está hecha.
|
|
5
|
+
*/
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
import DataTable from './src/DataTable.vue'
|
|
8
|
+
|
|
9
|
+
export default DataTable
|
|
10
|
+
|
|
11
|
+
export { default as useDataTable } from './src/useDataTable.js'
|
|
12
|
+
export { default as DataTableComponent } from './src/components/DataTableComponent.vue'
|
|
13
|
+
export { default as SelectPaginationComponent } from './src/components/SelectPaginationComponent.vue'
|
|
14
|
+
export { DEFAULT_LABELS } from './src/table.js'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "innoboxrr-vue-datatable",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Datatable para vue3",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -40,18 +40,20 @@
|
|
|
40
40
|
"*.vue"
|
|
41
41
|
],
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"innoboxrr-form-core": "^2.
|
|
43
|
+
"innoboxrr-form-core": "^2.6.0",
|
|
44
|
+
"innoboxrr-form-elements": "^6.4.0",
|
|
44
45
|
"vue": "^3.5.0",
|
|
45
46
|
"vue-router": "^4.5.0"
|
|
46
47
|
},
|
|
47
48
|
"dependencies": {
|
|
48
|
-
"@
|
|
49
|
-
"@iconify/vue": "^5.0.0",
|
|
49
|
+
"@tanstack/vue-table": "^9.2.4",
|
|
50
50
|
"axios": "^1.7.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@vitejs/plugin-vue": "^6.0.0",
|
|
54
54
|
"@vue/test-utils": "^2.4.6",
|
|
55
|
+
"innoboxrr-form-core": "^2.6.0",
|
|
56
|
+
"innoboxrr-form-elements": "^6.4.0",
|
|
55
57
|
"jsdom": "^25.0.0",
|
|
56
58
|
"vite": "^7.1.0",
|
|
57
59
|
"vitest": "^3.0.0",
|