import { ref, watch } from 'vue' import { useRoute, useRouter } from 'vue-router' // Keep a list page's current page and search term in the URL query so the // browser Back button restores them. Without this a list keeps page in local // state and remounts at page 1 after visiting a detail page and going Back. // // Usage in a list view: // const { page, search, setPage, setSearch } = useListQuery({ onChange: loadRows }) // goToPage(p) -> setPage(p); loadRows() // debouncedSearch -> setSearch(search.value); loadRows() // filter reset -> setPage(1); loadRows() // // Options: // onChange callback run when the query changes from outside (Back/Forward, // deep link) so the list reloads at the restored page. // extraKeys optional extra query keys to persist (e.g. ['typeid']); each // gets a ref exposed under the returned `extras` object. export function useListQuery(options = {}) { const route = useRoute() const router = useRouter() const onChange = options.onChange || (() => {}) const extraKeys = options.extraKeys || [] const page = ref(parseInt(route.query.page, 10) || 1) const search = ref(route.query.q || '') const extras = {} for (const key of extraKeys) { extras[key] = ref(route.query[key] || '') } // Build the next query. Keep unrelated keys intact. Write page only when > 1 // and search only when non-empty so the clean state is a bare path. function buildQuery() { const query = { ...route.query } if (page.value > 1) query.page = String(page.value) else delete query.page if (search.value) query.q = search.value else delete query.q for (const key of extraKeys) { if (extras[key].value) query[key] = String(extras[key].value) else delete query[key] } return query } // replace (not push) so paging does not spam history; Back leaves the list. function syncUrl() { const query = buildQuery() const current = route.query const keys = new Set([...Object.keys(query), ...Object.keys(current)]) let same = true for (const key of keys) { if (String(query[key] ?? '') !== String(current[key] ?? '')) { same = false break } } if (!same) router.replace({ query }) } function setPage(newPage) { page.value = newPage syncUrl() } // Changing the search resets to page 1. function setSearch(term) { search.value = term page.value = 1 syncUrl() } // Changing an extra filter resets to page 1. function setExtra(key, value) { extras[key].value = value page.value = 1 syncUrl() } // Re-sync refs when the query changes from outside (Back/Forward, deep link). // Fire onChange only when page/search/extras actually changed so a self // syncUrl() call does not trigger a redundant reload. watch(() => route.query, (newQuery) => { const newPage = parseInt(newQuery.page, 10) || 1 const newSearch = newQuery.q || '' let changed = false if (newPage !== page.value) { page.value = newPage; changed = true } if (newSearch !== search.value) { search.value = newSearch; changed = true } for (const key of extraKeys) { const newValue = newQuery[key] || '' if (newValue !== String(extras[key].value || '')) { extras[key].value = newValue changed = true } } if (changed) onChange() }) return { page, search, extras, setPage, setSearch, setExtra } }