PluginProbe
The Innovative Form Builder – IvyForms / 0.8.1
The Innovative Form Builder – IvyForms v0.8.1
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / composables / useTableStateSync.ts

useTableStateSync.ts in The Innovative Form Builder – IvyForms 0.8.1, at frontend/src/composables/useTableStateSync.ts

143 lines 4.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { ref, watch } from 'vue'
2 import { useRoute, useRouter } from 'vue-router'
3 import type { LocationQueryValue } from 'vue-router'
4 import { useLabels } from '@/composables/useLabels.ts'
5
6 export interface TableState {
7 page: number
8 perPage: number
9 search: string
10 sortBy: string
11 sortOrder: 'asc' | 'desc'
12 dateRange: string[]
13 filters: Record<string, string | number | null>
14 }
15
16 /**
17 * Composable to synchronize table state with URL query parameters
18 * Enables table state persistence across navigation and browser back/forward
19 */
20 export function useTableStateSync(defaultState: Partial<TableState> = {}) {
21 const route = useRoute()
22 const router = useRouter()
23 const { getLabel } = useLabels()
24
25 // Default values
26 const defaults: TableState = {
27 page: 1,
28 perPage: 10,
29 search: '',
30 sortBy: '',
31 sortOrder: 'desc',
32 dateRange: [],
33 filters: {},
34 ...defaultState,
35 }
36
37 // Parse query params to state
38 const parseQueryToState = (): TableState => {
39 const query = route.query
40
41 // Safely parse filters with try-catch to handle malformed JSON
42 let filters = defaults.filters
43 if (query.filters) {
44 try {
45 filters = JSON.parse(query.filters as string)
46 } catch (error) {
47 console.warn(getLabel('failed_to_parse_filter'), error)
48 // Use default filters on error
49 }
50 }
51
52 return {
53 page: parseInt((query.page as string) || String(defaults.page)),
54 perPage: parseInt((query.perPage as string) || String(defaults.perPage)),
55 search: (query.search as string) || defaults.search,
56 sortBy: (query.sortBy as string) || defaults.sortBy,
57 sortOrder: ((query.sortOrder as string) || defaults.sortOrder) as 'asc' | 'desc',
58 dateRange: query.dateRange
59 ? Array.isArray(query.dateRange)
60 ? query.dateRange
61 : [query.dateRange]
62 : defaults.dateRange,
63 filters,
64 }
65 }
66
67 // Initialize state from URL
68 const tableState = ref<TableState>(parseQueryToState())
69
70 // Convert state to query params
71 const stateToQuery = (
72 state: TableState,
73 ): Record<string, LocationQueryValue | LocationQueryValue[]> => {
74 const query: Record<string, LocationQueryValue | LocationQueryValue[]> = {}
75
76 // Only include non-default values to keep URL clean
77 if (state.page !== defaults.page) query.page = String(state.page)
78 if (state.perPage !== defaults.perPage) query.perPage = String(state.perPage)
79 if (state.search) query.search = state.search
80 if (state.sortBy) query.sortBy = state.sortBy
81 if (state.sortOrder !== defaults.sortOrder) query.sortOrder = state.sortOrder
82 if (state.dateRange && state.dateRange.length > 0) query.dateRange = state.dateRange
83 if (Object.keys(state.filters).length > 0) query.filters = JSON.stringify(state.filters)
84
85 return query
86 }
87
88 // Update URL when state changes
89 const syncStateToUrl = (state: TableState, replace = false) => {
90 const query = stateToQuery(state)
91 const method = replace ? router.replace : router.push
92
93 method({
94 query: {
95 ...route.query,
96 ...query,
97 },
98 }).catch((err) => {
99 // Ignore navigation duplicated errors
100 if (err.name !== 'NavigationDuplicated') {
101 console.error('Navigation error:', err)
102 }
103 })
104 }
105
106 // Watch route query changes and update state
107 watch(
108 () => route.query,
109 () => {
110 tableState.value = parseQueryToState()
111 },
112 { deep: true },
113 )
114
115 // Update state and sync to URL
116 const updateState = (updates: Partial<TableState>, replace = false) => {
117 tableState.value = {
118 ...tableState.value,
119 ...updates,
120 }
121 syncStateToUrl(tableState.value, replace)
122 }
123
124 // Reset to default state
125 const resetState = () => {
126 tableState.value = { ...defaults }
127 syncStateToUrl(defaults, true)
128 }
129
130 // Get current state as plain object
131 const getState = (): TableState => {
132 return { ...tableState.value }
133 }
134
135 return {
136 tableState,
137 updateState,
138 resetState,
139 getState,
140 syncStateToUrl,
141 }
142 }
143