Skip to content

Commit ac70ed3

Browse files
committed
feat: add integration support for Laravel pagination API
Integrates with Laravel pagination API and Spatie's Larevel Query Builder
1 parent 25a12ba commit ac70ed3

File tree

3 files changed

+137
-24
lines changed

3 files changed

+137
-24
lines changed

src/components/DataTable.ts

Lines changed: 131 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import VdtExportData from "./ExportData/ExportData.vue"
33
import VdtPagination from "./Pagination/Pagination.vue"
44
import VdtPerPage from "./PerPage/PerPage.vue"
55
import VdtSearchFilter from "./SearchFilter/SearchFilter.vue"
6-
import VdtSortingIcon from "./SortableColumn/SortingIcon.vue"
7-
import VdtSortingIndex from "./SortableColumn/SortingIndex.vue"
86
import VdtTable from "./Table/Table.vue"
97

108
import {
@@ -46,7 +44,7 @@ export default defineComponent({
4644
},
4745
data: {
4846
type: Array,
49-
required: true,
47+
required: false,
5048
},
5149
defaultColumn: {
5250
type: Object,
@@ -61,6 +59,14 @@ export default defineComponent({
6159
type: String,
6260
default: "download",
6361
},
62+
fetchUrl: {
63+
type: String,
64+
required: false,
65+
},
66+
fetchCallback: {
67+
type: Function,
68+
required: false,
69+
},
6470
footerComponent: {
6571
type: [Object, String],
6672
default: null
@@ -109,16 +115,12 @@ export default defineComponent({
109115
}
110116
},
111117
sortingIndexComponent: {
112-
type: Object,
113-
default: function() {
114-
return VdtSortingIndex
115-
}
118+
type: [Object, String],
119+
default: "vdt-sorting-index"
116120
},
117121
sortingIconComponent: {
118-
type: Object,
119-
default: function() {
120-
return VdtSortingIcon
121-
}
122+
type: [Object, String],
123+
default: "vdt-sorting-icon"
122124
},
123125
tableClass: {
124126
type: String,
@@ -136,6 +138,8 @@ export default defineComponent({
136138

137139
data: () => {
138140
return reactive({
141+
dataFetched: [] as Column[],
142+
dataFetchedLinks: [] as any[],
139143
currentPage: 1,
140144
currentPerPage: 10,
141145
parsedColumns: [] as Column[],
@@ -153,11 +157,16 @@ export default defineComponent({
153157
paginationSearchText: "",
154158
paginationSearchButtonText: "",
155159
search: "",
156-
searchText: ""
160+
searchText: "",
161+
totalRecords: 0,
157162
})
158163
},
159164

160165
computed: {
166+
actualData() {
167+
return (this.data != null) ? this.data : this.dataFetched
168+
},
169+
161170
/**
162171
* Get the total number of columns
163172
*/
@@ -201,7 +210,7 @@ export default defineComponent({
201210

202211
// assign key to track row
203212
const key = this.vKey;
204-
const data = this.data.map((value: any, index) => {
213+
const data = this.actualData.map((value: any, index) => {
205214
if (key !== "" && value[key]) {
206215
index = value[key];
207216
}
@@ -240,7 +249,10 @@ export default defineComponent({
240249
* Indicates if there are no rows to shown
241250
*/
242251
isEmpty() {
243-
return this.dataDisplayed.length === 0
252+
if (! this.data)
253+
return this.dataFetched.length === 0
254+
else
255+
return this.dataDisplayed.length === 0
244256
},
245257

246258
//
@@ -273,13 +285,18 @@ export default defineComponent({
273285
* Get the number of records
274286
*/
275287
totalEntries() {
276-
return this.data.length
288+
if (this.data == null)
289+
return this.totalRecords
290+
else
291+
return this.actualData.length
277292
},
278293

279294
/**
280295
* Get the number of records filtered
281296
*/
282297
filteredEntries() {
298+
if (this.data == null)
299+
return this.totalRecords
283300
return this.dataFiltered.length
284301
},
285302

@@ -435,11 +452,15 @@ export default defineComponent({
435452
* The props for the Table component
436453
*/
437454
propsTable() {
455+
const dataNotNull = this.data != null
456+
const data = (dataNotNull) ? this.data : this.dataFetched
457+
const dataDisplayed = (dataNotNull) ? this.dataDisplayed : this.dataFetched
458+
const dataFiltered = (dataNotNull) ? this.dataFiltered : this.dataFetched
438459
return {
439460
columns: this.parsedColumns,
440-
data: this.data,
441-
dataDisplayed: this.dataDisplayed,
442-
dataFiltered: this.dataFiltered,
461+
data: data,
462+
dataDisplayed: dataDisplayed,
463+
dataFiltered: dataFiltered,
443464
emptyTableText: this.emptyTableText,
444465
footerComponent: this.footerComponent,
445466
isEmpty: this.isEmpty,
@@ -496,9 +517,50 @@ export default defineComponent({
496517

497518
mounted() {
498519
this.setDefaults()
520+
this.updateData()
499521
},
500522

501523
methods: {
524+
/**
525+
* Update data, fetching it if needed.
526+
* If all data was previously fetched, it is stored in state variables,
527+
* therefore nothing is done in that case.
528+
*/
529+
async updateData() {
530+
if (this.data === null || this.data === undefined)
531+
this.fetchData()
532+
},
533+
534+
async fetchData(url = "") {
535+
if (this.fetchUrl == null || this.fetchCallback == null)
536+
throw Error("Fetch parameters are null");
537+
538+
// empty URL but we have the URL stored
539+
if (url === "" && this.dataFetchedLinks.length > 1) {
540+
url = this.dataFetchedLinks[this.currentPage].url
541+
+ this.getSearchQuery() + this.getSortQuery();
542+
}
543+
544+
// initial URL
545+
if (url === "") {
546+
url = this.fetchUrl
547+
}
548+
549+
this.fetchCallback(url).then((responseData: any) => {
550+
// Laravel API.
551+
// If response is from ResourceCollection,
552+
// then the metadata is in a nested object called meta.
553+
// Otherwise, the metadata is directly in the JSON response.
554+
const { data } = responseData
555+
const meta = responseData.meta ?? responseData;
556+
this.dataFetched = data
557+
this.dataFetchedLinks = meta.links
558+
this.currentPage = meta.current_page
559+
this.currentPerPage = meta.per_page
560+
this.totalRecords = meta.total
561+
})
562+
},
563+
502564
/**
503565
* Propagate upwards an event from user's custom component
504566
*/
@@ -594,8 +656,8 @@ export default defineComponent({
594656

595657
// column is being sorted in ascending mode
596658
// so, mark it as sorted in descending mode
597-
if (column.sortingMode === "asc") {
598-
column.sortingMode = "desc"
659+
if (column.sortingMode === SORTING_MODE.ASC) {
660+
column.sortingMode = SORTING_MODE.DESC
599661
this.columnsBeingSorted.splice(
600662
column.sortingIndex - 1,
601663
1,
@@ -631,9 +693,11 @@ export default defineComponent({
631693
* Set the current page being displayed
632694
*/
633695
setPage(value: any) {
634-
if (this.isValidPage(value)) {
635-
this.currentPage = value
696+
if (! this.isValidPage(value)) {
697+
return
636698
}
699+
this.currentPage = value
700+
this.updateData()
637701
},
638702

639703
/**
@@ -684,6 +748,46 @@ export default defineComponent({
684748
const value = getEventTargetValue() || ""
685749
this.search = value.trim()
686750
this.currentPage = 1
751+
this.updateData();
752+
},
753+
754+
/**
755+
* Get search query URI for fetching data.
756+
*
757+
* @returns string
758+
*/
759+
getSearchQuery() {
760+
const encodedSearch = encodeURIComponent(this.search);
761+
let searchQueryUri = ""
762+
this.searchableColumns.forEach((col: Column) => {
763+
if (col.key) {
764+
searchQueryUri += `&filter[${col.key}]=${encodedSearch}`
765+
}
766+
})
767+
return searchQueryUri
768+
},
769+
770+
/**
771+
* Return the sort query URI for fetching data.
772+
*
773+
* @returns string
774+
*/
775+
getSortQuery() {
776+
let { columnsBeingSorted } = this
777+
778+
// nothing being sorted
779+
if (columnsBeingSorted.length == 0)
780+
return ""
781+
782+
let searchQueryUri = "&sort="
783+
const descPrefix = "-"
784+
const sep = ","
785+
columnsBeingSorted.forEach((col: Column) => {
786+
if (col.sortingMode == SORTING_MODE.DESC)
787+
searchQueryUri += descPrefix
788+
searchQueryUri += col.key + sep
789+
})
790+
return searchQueryUri
687791
}
688792
},
689793

@@ -698,6 +802,11 @@ export default defineComponent({
698802
deep: true,
699803
immediate: true
700804
},
805+
columnsBeingSorted: {
806+
handler: "updateData",
807+
deep: false,
808+
immediate: false,
809+
},
701810
text: {
702811
handler: "parseTextProps",
703812
deep: true,

src/components/Table/Table.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default defineComponent({
2020
isLoading: Boolean,
2121
loadingComponent: [Object, String],
2222
numberOfColumns: Number,
23-
sortingIconComponent: Object,
24-
sortingIndexComponent: Object,
23+
sortingIconComponent: [Object, String],
24+
sortingIndexComponent: [Object, String],
2525
},
2626
})

src/main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import VdtTableCell from './components/Table/TableCell.vue'
33
import VdtTableCellEditable from './components/Table/TableCellEditable.vue'
44
import VdtTableCellSelectable from './components/Table/TableCellSelectable.vue'
55
import VdtActionButtons from './components/ActionButtons/ActionButtons.vue'
6+
import VdtSortingIcon from "./components/SortableColumn/SortingIcon.vue"
7+
import VdtSortingIndex from "./components/SortableColumn/SortingIndex.vue"
68

79
const components : { [key: string] : any } = {
810
'vdt': VueDataTable,
@@ -12,6 +14,8 @@ const components : { [key: string] : any } = {
1214
'vdt-actions': VdtActionButtons,
1315
'vdt-action-buttons': VdtActionButtons,
1416
'vue-data-table': VueDataTable,
17+
'vdt-sorting-icon': VdtSortingIcon,
18+
'vdt-sorting-index': VdtSortingIndex,
1519
}
1620

1721
function install(app: any) {

0 commit comments

Comments
 (0)