22 <div >
33 <table class =" table" :class =" tableClass" >
44 <TableHeader
5- :id =" props.id"
6- v-model:sort =" sortModel"
7- v-model:filters =" filtersModel"
8- v-model:checkbox =" checkboxModel"
9- :headers =" props.infos.headers"
10- :filters-class =" props.filtersConfig?.class"
11- :checkbox-config =" props.checkboxConfig"
5+ :id =" id"
6+ :headers =" headers"
7+ :filters-class =" filtersConfig?.class"
8+ :checkbox-config =" checkboxConfig"
129 :table-data =" tableData"
10+ :sort =" sortValue"
11+ :filters =" filtersValue"
12+ :checkbox =" checkboxValue"
13+ @update:sort =" setSort"
14+ @update:filters =" setFilters"
15+ @update:checkbox =" setCheckbox"
1316 >
1417 <template
15- v-for =" header in props . infos . headers "
18+ v-for =" header in headersWithHeaderSlot "
1619 :key =" ` ${id }-header-${header .id } ` "
20+ #[headerSlotName (header .id ) ]=" slotProps "
1721 >
18- <slot :name =" ` header-${header .id}` " :data = " header " />
22+ <slot :name =" headerSlotName( header.id) " v-bind = " slotProps " />
1923 </template >
2024 </TableHeader >
21- <tbody v-if =" tableData && tableData .length > 0" >
25+ <tbody v-if =" tableData.length > 0" >
2226 <tr
2327 v-for =" (data, index) in tableData"
2428 :key =" `${id}-tr-${index}`"
2529 class =" creat-datatable-row"
2630 >
27- <td v-if =" props.checkboxConfig" >
28- <input
29- type =" checkbox"
30- :class =" props.checkboxConfig.class"
31- :checked =" checkboxModel.includes(data)"
32- @click =" updateCheckbox(data)"
33- />
31+ <td v-if =" checkboxConfig" >
32+ <slot name =" checkbox-cell" :row =" data" :checked =" checkboxValue.includes(data)" :toggle-checkbox =" () => toggleCheckbox(data)" >
33+ <input
34+ type =" checkbox"
35+ :class =" checkboxConfig.class"
36+ :checked =" checkboxValue.includes(data)"
37+ @click =" toggleCheckbox(data)"
38+ />
39+ </slot >
3440 </td >
3541 <td
36- v-for =" header in props.infos. headers"
42+ v-for =" header in headers"
3743 :key =" `${id}-td-${header.id}`"
38- :class ="
39- props.infos.content?.find((content) => content.id === header.id)
40- ?.tdClass
41- "
44+ :class =" contentClassMap.get(header.id)"
4245 >
4346 <slot v-if =" slots[header.id]" :name =" header.id" :data =" data" />
4447 <span v-else >{{ data[header.id] }}</span >
4548 </td >
4649 </tr >
4750 </tbody >
48- <TableEmpty v-else :headers-nb =" props.infos. headers.length" >
51+ <TableEmpty v-else :headers-nb =" headers.length" >
4952 <template #empty-state >
5053 <slot name =" empty-state" />
5154 </template >
5255 </TableEmpty >
5356 </table >
5457 <TablePagination
55- v-if =" props. paginationConfig"
58+ v-if =" paginationConfig"
5659 :current-page =" paginationCurrentPage"
57- :max-page =" paginationMaxPage "
58- :pagination-config =" props. paginationConfig"
60+ :max-page =" maxPage "
61+ :pagination-config =" paginationConfig"
5962 @change-page =" changePage"
6063 />
6164 </div >
@@ -73,7 +76,9 @@ import {
7376import TablePagination from " ./TablePagination.vue" ;
7477import TableEmpty from " ./TableEmpty.vue" ;
7578import TableHeader from " ./TableHeader.vue" ;
76- import { computed , ref , watch , useSlots } from " vue" ;
79+ import { computed , ref , watch , useSlots , toRef , toRefs } from " vue" ;
80+ import { useTableState } from " ../composables/useTableState" ;
81+ import { useTableFiltering } from " ../composables/useTableFiltering" ;
7782
7883const slots = useSlots ();
7984
@@ -88,55 +93,66 @@ const props = defineProps<{
8893 filtersConfig? : FiltersConfig ;
8994 paginationConfig? : PaginationConfig ;
9095 onPageChange? : (page : number ) => void ;
91- checkboxConfig? : CheckboxConfig ;
96+ checkboxConfig? : CheckboxConfig < T > ;
9297 tableClass? : string ;
9398}>();
9499
95- const emit = defineEmits ([" update:sort" , " update:filters" , " update:checkbox" ]);
100+ const emit = defineEmits <{
101+ " update:sort" : [[string , SortDirection ] | undefined ];
102+ " update:filters" : [{ [key : string ]: string }];
103+ " update:checkbox" : [T []];
104+ }>();
96105
97- // Sorting
98- const sortModel = computed ({
99- get : () => props .sort ,
100- set : (value ) => emit (" update:sort" , value ),
101- });
106+ const { id, infos, type, filtersConfig, paginationConfig, checkboxConfig } =
107+ toRefs (props );
102108
103- // Filtering
104- const filtersModel = computed ({
105- get : () => props .filters ?? {},
106- set : (value ) => emit (" update:filters" , value ),
107- });
108-
109- const filteredData = computed (() => {
110- if (props .type === " remote" ) {
111- return props .infos .data ;
112- }
109+ const headers = computed (() => infos .value .headers );
110+ const rows = computed (() => infos .value .data ?? []);
111+ const content = computed (() => infos .value .content ?? []);
112+ const isRemote = computed (() => type .value === " remote" );
113113
114- return props .infos .data .filter ((data : T ) => {
115- return props .infos .headers .every ((header ) => {
116- if (! filtersModel .value [header .id ]) {
117- return true ;
118- }
114+ const headersWithHeaderSlot = computed (() => {
115+ const slotNames = new Set (Object .keys (slots ));
116+ return headers .value .filter ((header ) =>
117+ slotNames .has (` header-${header .id } ` )
118+ );
119+ });
119120
120- const value = data [header .id ];
121+ const { sortValue, filtersValue, checkboxValue, setSort, setFilters, setCheckbox } =
122+ useTableState ({
123+ sort: toRef (props , ' sort' ),
124+ filters: toRef (props , ' filters' ),
125+ checkbox: toRef (props , ' checkbox' ),
126+ onSortUpdate : (v ) => emit (" update:sort" , v ),
127+ onFiltersUpdate : (v ) => emit (" update:filters" , v ),
128+ onCheckboxUpdate : (v ) => emit (" update:checkbox" , v ),
129+ });
121130
122- if (value == null || value .toString == null ) {
123- return false ;
124- }
131+ const { filteredData } = useTableFiltering (
132+ () => rows .value ,
133+ () => headers .value ,
134+ () => filtersValue .value ,
135+ type .value
136+ );
125137
126- return normalizeString (value .toString ()).includes (
127- normalizeString (filtersModel .value [header .id ])
128- );
129- });
138+ const contentClassMap = computed (() => {
139+ const map = new Map <string , string | undefined >();
140+ content .value .forEach ((c ) => {
141+ if (c .tdClass ) {
142+ map .set (c .id , c .tdClass );
143+ }
130144 });
145+ return map ;
131146});
132147
133148// Pagination
134- const ITEMS_PER_PAGE = props .paginationConfig ?.itemsPerPage ?? 5 ;
149+ const INITIAL_PAGE = 1 ;
150+ const itemsPerPage = computed (() => paginationConfig .value ?.itemsPerPage ?? 5 );
135151
136- const paginationCurrentPage = ref (1 );
152+ const paginationCurrentPage = ref (INITIAL_PAGE );
137153
138154watch (
139- () => props . paginationConfig ?.currentPage ,
155+ () => paginationConfig . value ?.currentPage ,
140156 (newCurrentPage ) => {
141157 if (newCurrentPage ) {
142158 paginationCurrentPage .value = newCurrentPage ;
@@ -145,73 +161,52 @@ watch(
145161);
146162
147163const maxPage = computed (() => {
148- if (props .paginationConfig ?.nbItems ) {
149- return Math .ceil (props .paginationConfig .nbItems / ITEMS_PER_PAGE );
150- } else {
151- return Math .ceil (filteredData .value .length / ITEMS_PER_PAGE );
152- }
164+ const total = paginationConfig .value ?.nbItems ?? filteredData .value .length ;
165+ return Math .ceil (total / itemsPerPage .value ) || 1 ;
153166});
154167
155- const paginationMaxPage = ref <number >(maxPage .value );
156-
157- watch (
158- () => props .paginationConfig ?.nbItems ,
159- (newNbItems ) => {
160- if (newNbItems ) {
161- paginationMaxPage .value = Math .ceil (newNbItems / ITEMS_PER_PAGE );
162- }
168+ watch ([filteredData , () => paginationConfig .value ?.nbItems ], () => {
169+ if (paginationCurrentPage .value > maxPage .value ) {
170+ paginationCurrentPage .value = INITIAL_PAGE ;
163171 }
164- );
165-
166- watch (filteredData , () => {
167- paginationMaxPage .value = maxPage .value ;
168172});
169173
170174function changePage(page : number ) {
171- if (props . type === " remote " ) {
175+ if (isRemote . value ) {
172176 props .onPageChange ?.(page );
173177 } else {
174178 paginationCurrentPage .value = page ;
175179 }
176180}
177181
178- // Checkbox
179- const checkboxModel = computed ({
180- get : () => props . checkbox ?? [],
181- set : ( value ) => emit ( " update:checkbox " , value ),
182- } );
182+ function toggleCheckbox( row : T ) {
183+ const idKey = checkboxConfig . value ?. idKey as keyof T | undefined ;
184+ const isSelected = idKey
185+ ? checkboxValue . value . some (( r ) => r [ idKey ] === row [ idKey ])
186+ : checkboxValue . value . includes ( row );
183187
184- function updateCheckbox(data : T ) {
185- if (! checkboxModel .value .includes (data )) {
186- checkboxModel .value .push (data );
188+ if (! isSelected ) {
189+ setCheckbox ([... checkboxValue .value , row ]);
187190 } else {
188- const index = checkboxModel .value .indexOf (data );
189- if (index !== - 1 ) {
190- checkboxModel .value .splice (index , 1 );
191- }
191+ setCheckbox (
192+ idKey
193+ ? checkboxValue .value .filter ((r ) => r [idKey ] !== row [idKey ])
194+ : checkboxValue .value .filter ((r ) => r !== row )
195+ );
192196 }
193197}
194198
195- // Table data
196199const tableData = computed (() => {
197- let data = filteredData .value ;
200+ let pageRows = filteredData .value ;
198201
199- if (props .type !== " remote" && props .paginationConfig ) {
200- const start = (paginationCurrentPage .value - 1 ) * ITEMS_PER_PAGE ;
201- const end = start + ITEMS_PER_PAGE ;
202-
203- data = data .slice (start , end );
202+ if (! isRemote .value && paginationConfig .value ) {
203+ const start = (paginationCurrentPage .value - 1 ) * itemsPerPage .value ;
204+ pageRows = pageRows .slice (start , start + itemsPerPage .value );
204205 }
205206
206- return data ;
207+ return pageRows ;
207208});
208209
209- function normalizeString(string : string ) {
210- return string
211- .normalize (" NFD" )
212- .replace (/ [\u0300 -\u036f ] / g , " " )
213- .toLowerCase ();
214- }
210+ const headerSlotName = (headerId : string ) => ` header-${headerId } ` ;
215211 </script >
216212
217- <style scoped></style >
0 commit comments