From 524ebca53323a6b31dfd9e19947dd7d92ecbbe48 Mon Sep 17 00:00:00 2001 From: EshaanAgg <96648934+EshaanAgg@users.noreply.github.com> Date: Wed, 13 Nov 2024 22:59:08 +0530 Subject: [PATCH 01/17] refac: change handler for default sorting, add more tests and introduce new props for KTable --- lib/KTable/index.vue | 61 ++++++++--- lib/KTable/useSorting/__tests__/index.spec.js | 102 ++++++++++++++---- lib/KTable/useSorting/index.js | 17 ++- 3 files changed, 143 insertions(+), 37 deletions(-) diff --git a/lib/KTable/index.vue b/lib/KTable/index.vue index a8acb8814..121a96eb5 100644 --- a/lib/KTable/index.vue +++ b/lib/KTable/index.vue @@ -175,16 +175,22 @@ /* eslint-disable kolibri/vue-no-unused-properties */ props: { /** - * An array of objects `{ label, dataType, minWidth, width }`representing the headers of the table. The `dataType` can be one of `'string'`, `'number'`, `'date'`, or `'undefined'`. `label` and `dataType` are required. `minWidth` and `width` are optional. + * An array of objects `{ label, dataType, minWidth, width, columnId }`representing the headers of the table. The `dataType` can be one of `'string'`, `'number'`, `'date'`, or `'undefined'`. `label` and `dataType` are required. `minWidth` and `width` are optional. `columnId` is an unique identifier for the column, and can be a `number` or a `string`. */ headers: { type: Array, required: true, validator: function(value) { - return value.every( - header => - ['label', 'dataType'].every(key => key in header) && - ['string', 'number', 'date', 'undefined'].includes(header.dataType) + const uniqueColumnIds = new Set(value.map(h => h.columnId)); + + return ( + uniqueColumnIds.length == value.length && + value.every( + header => + ['label', 'dataType', 'columnId'].every(key => key in header) && + ['string', 'number', 'date', 'undefined'].includes(header.dataType) && + ['string', 'number'].includes(typeof header.columnId) + ) ); }, }, @@ -202,14 +208,6 @@ type: String, required: true, }, - - /** - * Disables the default sorting when sortable is true. Facilitates integration with externally sorted data. - */ - disableDefaultSorting: { - type: Boolean, - default: false, - }, /** * Enables or disables sorting functionality for the table headers. */ @@ -231,6 +229,26 @@ type: Boolean, default: false, }, + /** + * Indicates whether the table is to be sorted by default by any header or not. By default it is an empty object which means no default sorting is to be used. It accepts a configuration object `{ columnId, direction }`. `columnId` references a `columnId` defined for a header in `headers`. This specifies a column by which the table should be sorted when initially loaded. `direction` can be `'asc'` for ascending or `'desc'` for descending sort direction. + */ + defaultSort: { + type: Object, + required: false, + default: () => ({}), + validator: { + columnId: { + required: true, + validator: value => value !== null && ['string', 'number'].includes(typeof value), + }, + direction: { + type: String, + required: false, + default: 'asc', + validator: value => ['asc', 'desc'].includes(value), + }, + }, + }, }, data() { return { @@ -283,6 +301,23 @@ return colIndex => this.sortable && this.headers[colIndex].dataType !== DATA_TYPE_OTHERS; }, }, + watch: { + // Use a watcher on props to perform validation on props. + // This is required as we need access to multiple props simultaneously in some validations. + $props: { + immediate: true, + handler() { + if (this.defaultSort != {}) { + const allHeaderColumnIds = this.headers.map(h => h.columnId); + if (!allHeaderColumnIds.includes(this.defaultSort.columnId)) { + console.error( + `The columnId used for default sorting is ${this.defaultSort.columnId}, but the same was not found to be defined in any headers.` + ); + } + } + }, + }, + }, methods: { /** * Takes care of diff --git a/lib/KTable/useSorting/__tests__/index.spec.js b/lib/KTable/useSorting/__tests__/index.spec.js index bbaebe33a..829400b8f 100644 --- a/lib/KTable/useSorting/__tests__/index.spec.js +++ b/lib/KTable/useSorting/__tests__/index.spec.js @@ -9,14 +9,14 @@ import useSorting, { } from '../'; describe('useSorting', () => { - let headers, rows, useLocalSorting; + let headers, rows, useDefaultSorting; beforeEach(() => { headers = ref([ - { label: 'Name', dataType: DATA_TYPE_STRING }, - { label: 'Age', dataType: DATA_TYPE_NUMERIC }, - { label: 'Birthdate', dataType: DATA_TYPE_DATE }, - { label: 'Other', dataType: DATA_TYPE_OTHERS }, + { label: 'Name', dataType: DATA_TYPE_STRING, columnId: 'name' }, + { label: 'Age', dataType: DATA_TYPE_NUMERIC, columnId: 'age' }, + { label: 'Birthdate', dataType: DATA_TYPE_DATE, columnId: 'birthdate' }, + { label: 'Other', dataType: DATA_TYPE_OTHERS, columnId: 'other' }, ]); rows = ref([ @@ -25,21 +25,84 @@ describe('useSorting', () => { ['Alice', 28, new Date(1992, 8, 10)], ]); - useLocalSorting = ref(true); + // Disable default sorting by default + useDefaultSorting = ref({ + index: -1, + }); }); - it('should return rows unsorted when useLocalSorting is false', () => { - useLocalSorting.value = false; - - const { sortedRows } = useSorting(headers, rows, useLocalSorting); - expect(sortedRows.value).toEqual(rows.value); + describe('default sorting', () => { + it('should return rows unsorted by default', () => { + useDefaultSorting.value = { + index: -1, + }; + + const { sortedRows } = useSorting(headers, rows, useDefaultSorting); + expect(sortedRows.value).toEqual(rows.value); + }); + + it('should sort the rows in ascending correctly for string values', () => { + useDefaultSorting.value = { + index: 0, + direction: 'asc', + }; + + const { sortedRows } = useSorting(headers, rows, useDefaultSorting); + expect(sortedRows.value).toEqual([ + ['Alice', 28, new Date(1992, 8, 10)], + ['Jane', 25, new Date(1995, 10, 20)], + ['John', 30, new Date(1990, 5, 15)], + ]); + }); + + it('should sort the rows in descending order correctly for string values', () => { + useDefaultSorting.value = { + index: 0, + direction: 'desc', + }; + + const { sortedRows } = useSorting(headers, rows, useDefaultSorting); + expect(sortedRows.value).toEqual([ + ['John', 30, new Date(1990, 5, 15)], + ['Jane', 25, new Date(1995, 10, 20)], + ['Alice', 28, new Date(1992, 8, 10)], + ]); + }); + + it("should sort the rows correctly for numeric values", () => { + useDefaultSorting.value = { + index: 1, + direction: 'asc', + }; + + const { sortedRows } = useSorting(headers, rows, useDefaultSorting); + expect(sortedRows.value).toEqual([ + ['Jane', 25, new Date(1995, 10, 20)], + ['Alice', 28, new Date(1992, 8, 10)], + ['John', 30, new Date(1990, 5, 15)], + ]); + }) + + + it("should sort the rows correctly for date values", () => { + useDefaultSorting.value = { + index: 2, + direction: 'asc', + }; + + const { sortedRows } = useSorting(headers, rows, useDefaultSorting); + expect(sortedRows.value).toEqual([ + ['John', 30, new Date(1990, 5, 15)], + ['Alice', 28, new Date(1992, 8, 10)], + ['Jane', 25, new Date(1995, 10, 20)], + ]); + }) }); it('should sort rows by string column in ascending order', () => { - const { handleSort, sortedRows } = useSorting(headers, rows, useLocalSorting); + const { handleSort, sortedRows } = useSorting(headers, rows, useDefaultSorting); handleSort(0); // Sort by 'Name' - expect(sortedRows.value).toEqual([ ['Alice', 28, new Date(1992, 8, 10)], ['Jane', 25, new Date(1995, 10, 20)], @@ -48,7 +111,7 @@ describe('useSorting', () => { }); it('should sort rows by numeric column in ascending then descending order then back to default order', () => { - const { handleSort, sortedRows, sortOrder } = useSorting(headers, rows, useLocalSorting); + const { handleSort, sortedRows, sortOrder } = useSorting(headers, rows, useDefaultSorting); handleSort(1); // Sort by 'Age' expect(sortedRows.value).toEqual([ @@ -66,8 +129,7 @@ describe('useSorting', () => { ]); expect(sortOrder.value).toBe(SORT_ORDER_DESC); - handleSort(1); //Sort by 'Age' again to default order - + handleSort(1); // Sort by 'Age' again to default order expect(sortedRows.value).toEqual([ ['John', 30, new Date(1990, 5, 15)], ['Jane', 25, new Date(1995, 10, 20)], @@ -77,7 +139,7 @@ describe('useSorting', () => { }); it('should sort rows by date column in ascending order', () => { - const { handleSort, sortedRows } = useSorting(headers, rows, useLocalSorting); + const { handleSort, sortedRows } = useSorting(headers, rows, useDefaultSorting); handleSort(2); // Sort by 'Birthdate' expect(sortedRows.value).toEqual([ @@ -88,7 +150,7 @@ describe('useSorting', () => { }); it('should not sort rows when sorting by a column with dataType "undefined"', () => { - const { handleSort, sortedRows, sortKey } = useSorting(headers, rows, useLocalSorting); + const { handleSort, sortedRows, sortKey } = useSorting(headers, rows, useDefaultSorting); handleSort(3); // Attempt to sort by 'Other' expect(sortedRows.value).toEqual(rows.value); @@ -96,7 +158,7 @@ describe('useSorting', () => { }); it('should return correct aria-sort attribute based on current sorting', () => { - const { handleSort, getAriaSort } = useSorting(headers, rows, useLocalSorting); + const { handleSort, getAriaSort } = useSorting(headers, rows, useDefaultSorting); expect(getAriaSort(0)).toBe('none'); @@ -108,7 +170,7 @@ describe('useSorting', () => { }); it('should reset sortKey and sortOrder when a new column is sorted', () => { - const { handleSort, sortKey, sortOrder } = useSorting(headers, rows, useLocalSorting); + const { handleSort, sortKey, sortOrder } = useSorting(headers, rows, useDefaultSorting); handleSort(0); // Sort by 'Name' expect(sortKey.value).toBe(0); diff --git a/lib/KTable/useSorting/index.js b/lib/KTable/useSorting/index.js index 4b819b996..e7384fa9d 100644 --- a/lib/KTable/useSorting/index.js +++ b/lib/KTable/useSorting/index.js @@ -13,10 +13,11 @@ export const DATA_TYPE_OTHERS = 'undefined'; * * @param {Ref} headers - Reactive reference to the table headers. * @param {Ref} rows - Reactive reference to the table rows. - * @param {Ref} useLocalSorting - Reactive reference to a boolean indicating if local sorting should be used. + * @param {Ref} useDefaultSorting - Reactive reference to a string indicating if default sorting should be used. + * It has the properties as 'index' denoting the row index to be used for default sorting, and 'direction' denoting the sort order. * @returns {Object} - An object containing reactive references and methods for sorting. */ -export default function useSorting(headers, rows, useLocalSorting) { +export default function useSorting(headers, rows, useDefaultSorting) { const sortKey = ref(null); const sortOrder = ref(null); /** @@ -24,11 +25,19 @@ export default function useSorting(headers, rows, useLocalSorting) { * If local sorting is disabled or no sort key is set, it returns the original rows. */ const sortedRows = computed(() => { - if (!useLocalSorting.value || sortKey.value === null || sortOrder.value === null) - return rows.value; + // No sort key or value has been explicity set till now + if (sortKey.value === null || sortOrder.value === null) { + if (useDefaultSorting.value.index === -1) return rows.value; + return _.orderBy( + rows.value, + [row => row[useDefaultSorting.value.index]], + [useDefaultSorting.value.direction] + ); + } return _.orderBy(rows.value, [row => row[sortKey.value]], [sortOrder.value]); }); + /** * Method to handle sorting when a column header is clicked. * From 6d847a546989f91ee662ed3aa37288f363874d27 Mon Sep 17 00:00:00 2001 From: EshaanAgg <96648934+EshaanAgg@users.noreply.github.com> Date: Wed, 13 Nov 2024 23:33:21 +0530 Subject: [PATCH 02/17] update documentation --- docs/pages/ktable.vue | 86 ++++++++++++------- lib/KTable/index.vue | 18 +++- lib/KTable/useSorting/__tests__/index.spec.js | 9 +- 3 files changed, 73 insertions(+), 40 deletions(-) diff --git a/docs/pages/ktable.vue b/docs/pages/ktable.vue index 38878bea8..e20a6257f 100644 --- a/docs/pages/ktable.vue +++ b/docs/pages/ktable.vue @@ -33,9 +33,9 @@ data() { return { headers: [ - { label: 'Name', dataType: 'string' }, - { label: 'Age', dataType: 'number' }, - { label: 'City', dataType: 'string' }, + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, ], rows: [ ['John Doe', 28, 'New York'], @@ -58,9 +58,9 @@ -

Table with Default Sorting

+

Table with Sorting

- The KTable can be used with default sorting functionality, allowing you to sort data on the client side without the need for server requests. There are 4 permissible data types - string,number,date and undefined. Columns declared with undefined data type are not sortable. This example demonstrates a table with default sorting enabled. + The KTable can be used with sorting functionality, allowing you to sort data on the client side without the need for server requests. There are 4 permissible data types - string,number,date and undefined. Columns declared with undefined data type are not sortable. This example demonstrates a table with sorting enabled.

@@ -77,9 +77,9 @@ data() { return { headers: [ - { label: 'Name', dataType: 'string' }, - { label: 'Age', dataType: 'number' }, - { label: 'City', dataType: 'string' }, + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, ], rows: [ ['John Doe', 28, 'New York'], @@ -130,11 +130,11 @@ data() { return { slotHeaders: [ - { label: 'Name', dataType: 'string' }, - { label: 'Age', dataType: 'number' }, - { label: 'City', dataType: 'string' }, - { label: 'Joined', dataType: 'date' }, - { label: 'Misc', dataType: 'undefined' }, + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, + { label: 'Joined', dataType: 'date', columnId: 'joined' }, + { label: 'Misc', dataType: 'undefined', columnId: 'misc' }, ], slotRows: [ ['John Doe', 28, 'New York', '2022-01-15T00:00:00Z', 'N/A'], @@ -184,11 +184,23 @@ data() { return { headersWithCustomWidths: [ - { label: 'Name', dataType: 'string', minWidth: '20px', width: '2%' }, - { label: 'Age', dataType: 'number', minWidth: '100px', width: '33%' }, - { label: 'City', dataType: 'string', minWidth: '200px', width: '25%' }, - { label: 'Joined', dataType: 'date', minWidth: '150px', width: '20%' }, - { label: 'Misc', dataType: 'undefined', minWidth: '100px', width: '20%' }, + { label: 'Name', dataType: 'string', minWidth: '20px', width: '2%', columnId: 'name' }, + { label: 'Age', dataType: 'number', minWidth: '100px', width: '33%', columnId: 'age' }, + { label: 'City', dataType: 'string', minWidth: '200px', width: '25%', columnId: 'city' }, + { + label: 'Joined', + dataType: 'date', + minWidth: '150px', + width: '20%', + columnId: 'joined', + }, + { + label: 'Misc', + dataType: 'undefined', + minWidth: '100px', + width: '20%', + columnId: 'misc', + }, ], customRows: [ ['John Doe', 28, 'New York', '2022-01-15T00:00:00Z', 'N/A'], @@ -225,9 +237,9 @@ data() { return { headers: [ - { label: 'Name', dataType: 'string' }, - { label: 'Age', dataType: 'number' }, - { label: 'City', dataType: 'string' }, + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, ], rows: [ ['John Doe', 28, 'New York'], @@ -238,11 +250,11 @@ ['Emily Davis', 27, 'Philadelphia'], ], slotHeaders: [ - { label: 'Name', dataType: 'string' }, - { label: 'Age', dataType: 'number' }, - { label: 'City', dataType: 'string' }, - { label: 'Joined', dataType: 'date' }, - { label: 'Misc', dataType: 'undefined' }, + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, + { label: 'Joined', dataType: 'date', columnId: 'joined' }, + { label: 'Misc', dataType: 'undefined', columnId: 'misc' }, ], slotRows: [ ['John Doe', 28, 'New York', '2022-01-15T00:00:00Z', 'N/A'], @@ -251,11 +263,23 @@ ['Alice Johnson', 30, 'Houston', '2020-07-18T00:00:00Z', 'N/A'], ], headersWithCustomWidths: [ - { label: 'Name', dataType: 'string', minWidth: '20px', width: '2%' }, - { label: 'Age', dataType: 'number', minWidth: '100px', width: '33%' }, - { label: 'City', dataType: 'string', minWidth: '200px', width: '25%' }, - { label: 'Joined', dataType: 'date', minWidth: '150px', width: '20%' }, - { label: 'Misc', dataType: 'undefined', minWidth: '100px', width: '20%' }, + { label: 'Name', dataType: 'string', minWidth: '20px', width: '2%', columnId: 'name' }, + { label: 'Age', dataType: 'number', minWidth: '100px', width: '33%', columnId: 'age' }, + { label: 'City', dataType: 'string', minWidth: '200px', width: '25%', columnId: 'city' }, + { + label: 'Joined', + dataType: 'date', + minWidth: '150px', + width: '20%', + columnId: 'joined', + }, + { + label: 'Misc', + dataType: 'undefined', + minWidth: '100px', + width: '20%', + columnId: 'misc', + }, ], customRows: [ ['John Doe', 28, 'New York', '2022-01-15T00:00:00Z', 'N/A'], diff --git a/lib/KTable/index.vue b/lib/KTable/index.vue index 121a96eb5..17ed838f3 100644 --- a/lib/KTable/index.vue +++ b/lib/KTable/index.vue @@ -112,18 +112,28 @@ setup(props, { emit }) { const headers = ref(props.headers); const rows = ref(props.rows); - const useLocalSorting = ref(props.sortable && !props.disableDefaultSorting); + const useDefaultSorting = ref( + props.defaultSort === {} + ? { + index: -1, + } + : { + index: props.headers.map(h => h.columnId).indexOf(props.defaultSort.columnId), + direction: props.defaultSort.direction, + } + ); + const { sortKey, sortOrder, sortedRows, handleSort: localHandleSort, getAriaSort, - } = useSorting(headers, rows, useLocalSorting); + } = useSorting(headers, rows, useDefaultSorting); const finalRows = computed(() => { if (props.sortable) { - return useLocalSorting.value ? sortedRows.value : rows.value; + return sortedRows.value; } else { return rows.value; } @@ -142,7 +152,7 @@ if (headers.value[index].dataType === DATA_TYPE_OTHERS) { return; } - if (useLocalSorting.value) { + if (props.sortable) { localHandleSort(index); } else { emit( diff --git a/lib/KTable/useSorting/__tests__/index.spec.js b/lib/KTable/useSorting/__tests__/index.spec.js index 829400b8f..031084c54 100644 --- a/lib/KTable/useSorting/__tests__/index.spec.js +++ b/lib/KTable/useSorting/__tests__/index.spec.js @@ -69,7 +69,7 @@ describe('useSorting', () => { ]); }); - it("should sort the rows correctly for numeric values", () => { + it('should sort the rows correctly for numeric values', () => { useDefaultSorting.value = { index: 1, direction: 'asc', @@ -81,10 +81,9 @@ describe('useSorting', () => { ['Alice', 28, new Date(1992, 8, 10)], ['John', 30, new Date(1990, 5, 15)], ]); - }) - + }); - it("should sort the rows correctly for date values", () => { + it('should sort the rows correctly for date values', () => { useDefaultSorting.value = { index: 2, direction: 'asc', @@ -96,7 +95,7 @@ describe('useSorting', () => { ['Alice', 28, new Date(1992, 8, 10)], ['Jane', 25, new Date(1995, 10, 20)], ]); - }) + }); }); it('should sort rows by string column in ascending order', () => { From bfc7106dc329f4525670131f19169be2ebc1d547 Mon Sep 17 00:00:00 2001 From: EshaanAgg <96648934+EshaanAgg@users.noreply.github.com> Date: Wed, 13 Nov 2024 23:52:44 +0530 Subject: [PATCH 03/17] fix tests --- lib/KTable/index.vue | 48 +++++++++++------------------------- lib/__tests__/KTable.spec.js | 47 +++++++++-------------------------- 2 files changed, 25 insertions(+), 70 deletions(-) diff --git a/lib/KTable/index.vue b/lib/KTable/index.vue index 17ed838f3..e204c37d7 100644 --- a/lib/KTable/index.vue +++ b/lib/KTable/index.vue @@ -109,7 +109,7 @@ components: { KTableGridItem, }, - setup(props, { emit }) { + setup(props) { const headers = ref(props.headers); const rows = ref(props.rows); const useDefaultSorting = ref( @@ -123,13 +123,11 @@ } ); - const { - sortKey, - sortOrder, - sortedRows, - handleSort: localHandleSort, - getAriaSort, - } = useSorting(headers, rows, useDefaultSorting); + const { sortKey, sortOrder, sortedRows, handleSort, getAriaSort } = useSorting( + headers, + rows, + useDefaultSorting + ); const finalRows = computed(() => { if (props.sortable) { @@ -148,21 +146,6 @@ } ); - const handleSort = index => { - if (headers.value[index].dataType === DATA_TYPE_OTHERS) { - return; - } - if (props.sortable) { - localHandleSort(index); - } else { - emit( - 'changeSort', - index, - sortOrder.value === SORT_ORDER_ASC ? SORT_ORDER_DESC : SORT_ORDER_ASC - ); - } - }; - const getHeaderStyle = header => { const style = {}; if (header.minWidth) style.minWidth = header.minWidth; @@ -246,17 +229,14 @@ type: Object, required: false, default: () => ({}), - validator: { - columnId: { - required: true, - validator: value => value !== null && ['string', 'number'].includes(typeof value), - }, - direction: { - type: String, - required: false, - default: 'asc', - validator: value => ['asc', 'desc'].includes(value), - }, + validator: function(value) { + if (value === {}) return true; + + return ( + ['columnId', 'direction'].every(key => key in value) && + ['asc', 'desc'].includes(value.direction) && + ['string', 'number'].includes(typeof value.columnId) + ); }, }, }, diff --git a/lib/__tests__/KTable.spec.js b/lib/__tests__/KTable.spec.js index 7b0bcc59d..81257c769 100644 --- a/lib/__tests__/KTable.spec.js +++ b/lib/__tests__/KTable.spec.js @@ -4,9 +4,9 @@ import KTable from '../KTable'; describe('KTable.vue', () => { it('should mount the component', () => { const headers = [ - { label: 'Name', dataType: 'string' }, - { label: 'Age', dataType: 'number' }, - { label: 'City', dataType: 'string' }, + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, ]; const rows = [ ['Alice', 25, 'New York'], @@ -23,11 +23,12 @@ describe('KTable.vue', () => { const thElements = wrapper.findAll('th'); expect(thElements.length).toBe(headers.length); }); + it('renders the correct content in rows and columns', async () => { const headers = [ - { label: 'Name', dataType: 'string' }, - { label: 'Age', dataType: 'number' }, - { label: 'Date', dataType: 'date' }, + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, ]; const rows = [ ['John', 30, '2023-01-01'], @@ -52,37 +53,11 @@ describe('KTable.vue', () => { }); }); }); - it('should emit changeSort event on header click when disableDefaultSorting is true', async () => { - const headers = [ - { label: 'Name', dataType: 'string' }, - { label: 'Age', dataType: 'number' }, - { label: 'City', dataType: 'string' }, - ]; - const items = [ - ['Alice', 25, 'New York'], - ['Bob', 30, 'Los Angeles'], - ['Charlie', 35, 'San Francisco'], - ]; - const wrapper = mount(KTable, { - propsData: { - headers, - items, - sortable: true, - disableDefaultSorting: true, - }, - computed: { - isTableEmpty: () => false, - }, - }); - const thElements = wrapper.findAll('th'); - await thElements.at(0).trigger('click'); - expect(wrapper.emitted().changeSort).toBeTruthy(); - }); it('should handle sticky headers and columns', async () => { const headers = [ - { label: 'Name', dataType: 'string' }, - { label: 'Age', dataType: 'number' }, + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, ]; const rows = [ ['John', 30], @@ -116,8 +91,8 @@ describe('KTable.vue', () => { it('should handle keyboard navigation within the table', async () => { const headers = [ - { label: 'Name', dataType: 'string' }, - { label: 'Age', dataType: 'number' }, + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, ]; const rows = [ ['John', 30], From fc560a0ac68adfd80220edb7a79158e2bc36c76e Mon Sep 17 00:00:00 2001 From: EshaanAgg <96648934+EshaanAgg@users.noreply.github.com> Date: Thu, 14 Nov 2024 01:41:55 +0530 Subject: [PATCH 04/17] add tests --- docs/pages/ktable.vue | 86 ++++++---- lib/KTable/index.vue | 17 +- lib/KTable/useSorting/index.js | 3 +- lib/__tests__/KTable.spec.js | 284 ++++++++++++++++++++------------- 4 files changed, 235 insertions(+), 155 deletions(-) diff --git a/docs/pages/ktable.vue b/docs/pages/ktable.vue index e20a6257f..bc1960bf5 100644 --- a/docs/pages/ktable.vue +++ b/docs/pages/ktable.vue @@ -180,47 +180,65 @@ - - data() { - return { - headersWithCustomWidths: [ - { label: 'Name', dataType: 'string', minWidth: '20px', width: '2%', columnId: 'name' }, - { label: 'Age', dataType: 'number', minWidth: '100px', width: '33%', columnId: 'age' }, - { label: 'City', dataType: 'string', minWidth: '200px', width: '25%', columnId: 'city' }, - { - label: 'Joined', - dataType: 'date', - minWidth: '150px', - width: '20%', - columnId: 'joined', - }, - { - label: 'Misc', - dataType: 'undefined', - minWidth: '100px', - width: '20%', - columnId: 'misc', - }, - ], - customRows: [ - ['John Doe', 28, 'New York', '2022-01-15T00:00:00Z', 'N/A'], - ['Jane Smith', 34, 'Los Angeles', '2021-12-22T00:00:00Z', 'N/A'], - ['Samuel Green', 22, 'Chicago', '2023-03-10T00:00:00Z', 'N/A'], - ['Alice Johnson', 30, 'Houston', '2020-07-18T00:00:00Z', 'N/A'], - ], - }; - }, + +

Table with Default Sort

+

+ This is an example to show how KTable can be used with the defaultSort attribute to sort the table based on a particular column. This is useful if you are getting unsorted data from an API and want to display it in a sorted manner. The defaultSort attribute can be used irrespective of the sortable attribute. +

+ + + + + + + + + + data() { + return { + headers: [ + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, + ], + rows: [ + ['John Doe', 28, 'New York'], + ['Jane Smith', 34, 'Los Angeles'], + ['Samuel Green', 22, 'Chicago'], + ['Alice Johnson', 30, 'Houston'], + ['Michael Brown', 45, 'Phoenix'], + ['Emily Davis', 27, 'Philadelphia'], + ] + }; + }, + - diff --git a/lib/KTable/index.vue b/lib/KTable/index.vue index e204c37d7..a07a19a74 100644 --- a/lib/KTable/index.vue +++ b/lib/KTable/index.vue @@ -112,16 +112,11 @@ setup(props) { const headers = ref(props.headers); const rows = ref(props.rows); - const useDefaultSorting = ref( - props.defaultSort === {} - ? { - index: -1, - } - : { - index: props.headers.map(h => h.columnId).indexOf(props.defaultSort.columnId), - direction: props.defaultSort.direction, - } - ); + + const useDefaultSorting = computed(() => ({ + index: props.headers.findIndex(h => h.columnId === props.defaultSort.columnId), + direction: props.defaultSort.direction, + })); const { sortKey, sortOrder, sortedRows, handleSort, getAriaSort } = useSorting( headers, @@ -297,7 +292,7 @@ $props: { immediate: true, handler() { - if (this.defaultSort != {}) { + if (this.defaultSort.columnId) { const allHeaderColumnIds = this.headers.map(h => h.columnId); if (!allHeaderColumnIds.includes(this.defaultSort.columnId)) { console.error( diff --git a/lib/KTable/useSorting/index.js b/lib/KTable/useSorting/index.js index e7384fa9d..5d07682c8 100644 --- a/lib/KTable/useSorting/index.js +++ b/lib/KTable/useSorting/index.js @@ -13,13 +13,14 @@ export const DATA_TYPE_OTHERS = 'undefined'; * * @param {Ref} headers - Reactive reference to the table headers. * @param {Ref} rows - Reactive reference to the table rows. - * @param {Ref} useDefaultSorting - Reactive reference to a string indicating if default sorting should be used. + * @param {Ref} useDefaultSorting - Computed reactive reference to object describing if default sorting should be used. * It has the properties as 'index' denoting the row index to be used for default sorting, and 'direction' denoting the sort order. * @returns {Object} - An object containing reactive references and methods for sorting. */ export default function useSorting(headers, rows, useDefaultSorting) { const sortKey = ref(null); const sortOrder = ref(null); + /** * Computed property that returns the sorted rows based on the current sort key and order. * If local sorting is disabled or no sort key is set, it returns the original rows. diff --git a/lib/__tests__/KTable.spec.js b/lib/__tests__/KTable.spec.js index 81257c769..86b8625fc 100644 --- a/lib/__tests__/KTable.spec.js +++ b/lib/__tests__/KTable.spec.js @@ -1,130 +1,196 @@ import { mount } from '@vue/test-utils'; import KTable from '../KTable'; -describe('KTable.vue', () => { - it('should mount the component', () => { - const headers = [ - { label: 'Name', dataType: 'string', columnId: 'name' }, - { label: 'Age', dataType: 'number', columnId: 'age' }, - { label: 'City', dataType: 'string', columnId: 'city' }, - ]; - const rows = [ - ['Alice', 25, 'New York'], - ['Bob', 30, 'Los Angeles'], - ['Charlie', 35, 'San Francisco'], - ]; - const wrapper = mount(KTable, { - propsData: { - headers, - rows, - sortable: true, - }, - }); - const thElements = wrapper.findAll('th'); - expect(thElements.length).toBe(headers.length); - }); - - it('renders the correct content in rows and columns', async () => { - const headers = [ - { label: 'Name', dataType: 'string', columnId: 'name' }, - { label: 'Age', dataType: 'number', columnId: 'age' }, - { label: 'City', dataType: 'string', columnId: 'city' }, - ]; - const rows = [ - ['John', 30, '2023-01-01'], - ['Jane', 25, '2023-02-01'], - ['Doe', 35, '2023-03-01'], - ]; +const assertTableContent = (wrapper, rows) => { + const tableRows = wrapper.findAll('tbody tr'); + expect(tableRows.length).toBe(rows.length); - const wrapper = mount(KTable, { - propsData: { headers, rows, caption: 'Test Table' }, - }); - - // Wait for the table to be fully rendered - await wrapper.vm.$nextTick(); - - const tableRows = wrapper.findAll('tbody tr'); - expect(tableRows.length).toBe(rows.length); + rows.forEach((row, rowIndex) => { + const cells = tableRows.at(rowIndex).findAll('td'); + expect(cells.length).toBe(row.length); - rows.forEach((row, rowIndex) => { - const cells = tableRows.at(rowIndex).findAll('td'); - row.forEach((cellContent, colIndex) => { - expect(cells.at(colIndex).text()).toBe(String(cellContent)); - }); + row.forEach((cellContent, colIndex) => { + expect(cells.at(colIndex).text()).toBe(String(cellContent)); }); }); +}; - it('should handle sticky headers and columns', async () => { +describe('KTable.vue', () => { + // it('should mount the component', () => { + // const headers = [ + // { label: 'Name', dataType: 'string', columnId: 'name' }, + // { label: 'Age', dataType: 'number', columnId: 'age' }, + // { label: 'City', dataType: 'string', columnId: 'city' }, + // ]; + // const rows = [ + // ['Alice', 25, 'New York'], + // ['Bob', 30, 'Los Angeles'], + // ['Charlie', 35, 'San Francisco'], + // ]; + // const wrapper = mount(KTable, { + // propsData: { + // headers, + // rows, + // sortable: true, + // }, + // }); + // const thElements = wrapper.findAll('th'); + // expect(thElements.length).toBe(headers.length); + // }); + + // it('renders the correct content in rows and columns', async () => { + // const headers = [ + // { label: 'Name', dataType: 'string', columnId: 'name' }, + // { label: 'Age', dataType: 'number', columnId: 'age' }, + // { label: 'City', dataType: 'string', columnId: 'city' }, + // ]; + // const rows = [ + // ['John', 30, '2023-01-01'], + // ['Jane', 25, '2023-02-01'], + // ['Doe', 35, '2023-03-01'], + // ]; + + // const wrapper = mount(KTable, { + // propsData: { headers, rows, caption: 'Test Table' }, + // }); + + // // Wait for the table to be fully rendered + // await wrapper.vm.$nextTick(); + // assertTableContent(wrapper, rows); + // }); + + describe('should respect the defaultSort attribute', () => { const headers = [ { label: 'Name', dataType: 'string', columnId: 'name' }, { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, ]; + const rows = [ - ['John', 30], - ['Jane', 25], + ['John', 30, 'New York'], + ['Alice', 25, 'Los Angeles'], + ['Bob', 35, 'San Francisco'], ]; - const wrapper = mount(KTable, { - propsData: { headers, rows, caption: 'Sticky Table' }, - }); - - // Wait for the table to be fully rendered - await wrapper.vm.$nextTick(); - - const headerCells = wrapper.findAll('thead th'); - headerCells.wrappers.forEach(headerCell => { - expect(headerCell.classes()).toContain('sticky-header'); - }); + // it('should not sort the rows by default', async () => { + // const wrapper = mount(KTable, { + // propsData: { headers, rows, caption: 'Test Table' }, + // }); + + // await wrapper.vm.$nextTick(); + // assertTableContent(wrapper, rows); + // }); + + it('should sort the rows correctly in ascending order', async () => { + const wrapper = mount(KTable, { + propsData: { + headers, + rows, + caption: 'Test Table', + defaultSort: { columnId: 'age', direction: 'asc' }, + }, + }); - const firstColumnCells = wrapper.findAll('tbody tr td:first-child'); - firstColumnCells.wrappers.forEach(cell => { - expect(cell.classes()).toContain('sticky-column'); + await wrapper.vm.$nextTick(); + const expectedRows = [ + ['Alice', 25, 'Los Angeles'], + ['Bob', 35, 'San Francisco'], + ['John', 30, 'New York'], + ]; + assertTableContent(wrapper, expectedRows); }); - }); - beforeEach(() => { - /*Since our primary concern in this test is checking focus management rather than actual scrolling behavior, - mocking scrollIntoView allows the test to focus on the relevant aspects without getting interrupted - by unsupported methods in the test environment.*/ - window.HTMLElement.prototype.scrollIntoView = jest.fn(); + // it('should sort the rows correctly in descending order', async () => { + // const wrapper = mount(KTable, { + // propsData: { + // headers, + // rows, + // caption: 'Test Table', + // defaultSort: { columnId: 'age', direction: 'desc' }, + // }, + // }); + + // await wrapper.vm.$nextTick(); + // const expectedRows = [ + // ['Bob', 35, 'San Francisco'], + // ['John', 30, 'New York'], + // ['Alice', 25, 'Los Angeles'], + // ]; + // assertTableContent(wrapper, expectedRows); + // }); }); - it('should handle keyboard navigation within the table', async () => { - const headers = [ - { label: 'Name', dataType: 'string', columnId: 'name' }, - { label: 'Age', dataType: 'number', columnId: 'age' }, - ]; - const rows = [ - ['John', 30], - ['Jane', 25], - ]; - - const wrapper = mount(KTable, { - propsData: { headers, rows, caption: 'Keyboard Navigation Table' }, - attachTo: document.body, // Attach to document body to properly manage focus - }); - - await wrapper.vm.$nextTick(); // Ensure the component is fully rendered - - const firstCell = wrapper.find('tbody tr:first-child td:first-child'); - await firstCell.element.focus(); // Focus the first cell directly - expect(document.activeElement).toBe(firstCell.element); // Check if the first cell is focused - - // Simulate ArrowRight key press - await firstCell.trigger('keydown', { key: 'ArrowRight' }); - - const secondCell = wrapper.find('tbody tr:first-child td:nth-child(2)'); - await secondCell.element.focus(); // Focus the second cell directly - expect(document.activeElement).toBe(secondCell.element); // Check if the second cell is focused - - // Simulate ArrowDown key press - await secondCell.trigger('keydown', { key: 'ArrowDown' }); - - const thirdCell = wrapper.find('tbody tr:nth-child(2) td:nth-child(2)'); - await thirdCell.element.focus(); // Focus the third cell directly - expect(document.activeElement).toBe(thirdCell.element); // Check if the third cell is focused - - // Cleanup: detach the wrapper from the document body after the test - wrapper.destroy(); - }); + // it('should handle sticky headers and columns', async () => { + // const headers = [ + // { label: 'Name', dataType: 'string', columnId: 'name' }, + // { label: 'Age', dataType: 'number', columnId: 'age' }, + // ]; + // const rows = [ + // ['John', 30], + // ['Jane', 25], + // ]; + + // const wrapper = mount(KTable, { + // propsData: { headers, rows, caption: 'Sticky Table' }, + // }); + + // // Wait for the table to be fully rendered + // await wrapper.vm.$nextTick(); + + // const headerCells = wrapper.findAll('thead th'); + // headerCells.wrappers.forEach(headerCell => { + // expect(headerCell.classes()).toContain('sticky-header'); + // }); + + // const firstColumnCells = wrapper.findAll('tbody tr td:first-child'); + // firstColumnCells.wrappers.forEach(cell => { + // expect(cell.classes()).toContain('sticky-column'); + // }); + // }); + + // beforeEach(() => { + // /*Since our primary concern in this test is checking focus management rather than actual scrolling behavior, + // mocking scrollIntoView allows the test to focus on the relevant aspects without getting interrupted + // by unsupported methods in the test environment.*/ + // window.HTMLElement.prototype.scrollIntoView = jest.fn(); + // }); + + // it('should handle keyboard navigation within the table', async () => { + // const headers = [ + // { label: 'Name', dataType: 'string', columnId: 'name' }, + // { label: 'Age', dataType: 'number', columnId: 'age' }, + // ]; + // const rows = [ + // ['John', 30], + // ['Jane', 25], + // ]; + + // const wrapper = mount(KTable, { + // propsData: { headers, rows, caption: 'Keyboard Navigation Table' }, + // attachTo: document.body, // Attach to document body to properly manage focus + // }); + + // await wrapper.vm.$nextTick(); // Ensure the component is fully rendered + + // const firstCell = wrapper.find('tbody tr:first-child td:first-child'); + // await firstCell.element.focus(); // Focus the first cell directly + // expect(document.activeElement).toBe(firstCell.element); // Check if the first cell is focused + + // // Simulate ArrowRight key press + // await firstCell.trigger('keydown', { key: 'ArrowRight' }); + + // const secondCell = wrapper.find('tbody tr:first-child td:nth-child(2)'); + // await secondCell.element.focus(); // Focus the second cell directly + // expect(document.activeElement).toBe(secondCell.element); // Check if the second cell is focused + + // // Simulate ArrowDown key press + // await secondCell.trigger('keydown', { key: 'ArrowDown' }); + + // const thirdCell = wrapper.find('tbody tr:nth-child(2) td:nth-child(2)'); + // await thirdCell.element.focus(); // Focus the third cell directly + // expect(document.activeElement).toBe(thirdCell.element); // Check if the third cell is focused + + // // Cleanup: detach the wrapper from the document body after the test + // wrapper.destroy(); + // }); }); From 64f0e015f1a3d7cc3015f11e21cf9a878ee56774 Mon Sep 17 00:00:00 2001 From: EshaanAgg <96648934+EshaanAgg@users.noreply.github.com> Date: Thu, 14 Nov 2024 01:42:36 +0530 Subject: [PATCH 05/17] revert changes to tests --- lib/__tests__/KTable.spec.js | 280 +++++++++++++++++------------------ 1 file changed, 140 insertions(+), 140 deletions(-) diff --git a/lib/__tests__/KTable.spec.js b/lib/__tests__/KTable.spec.js index 86b8625fc..e38208b5a 100644 --- a/lib/__tests__/KTable.spec.js +++ b/lib/__tests__/KTable.spec.js @@ -16,48 +16,48 @@ const assertTableContent = (wrapper, rows) => { }; describe('KTable.vue', () => { - // it('should mount the component', () => { - // const headers = [ - // { label: 'Name', dataType: 'string', columnId: 'name' }, - // { label: 'Age', dataType: 'number', columnId: 'age' }, - // { label: 'City', dataType: 'string', columnId: 'city' }, - // ]; - // const rows = [ - // ['Alice', 25, 'New York'], - // ['Bob', 30, 'Los Angeles'], - // ['Charlie', 35, 'San Francisco'], - // ]; - // const wrapper = mount(KTable, { - // propsData: { - // headers, - // rows, - // sortable: true, - // }, - // }); - // const thElements = wrapper.findAll('th'); - // expect(thElements.length).toBe(headers.length); - // }); - - // it('renders the correct content in rows and columns', async () => { - // const headers = [ - // { label: 'Name', dataType: 'string', columnId: 'name' }, - // { label: 'Age', dataType: 'number', columnId: 'age' }, - // { label: 'City', dataType: 'string', columnId: 'city' }, - // ]; - // const rows = [ - // ['John', 30, '2023-01-01'], - // ['Jane', 25, '2023-02-01'], - // ['Doe', 35, '2023-03-01'], - // ]; - - // const wrapper = mount(KTable, { - // propsData: { headers, rows, caption: 'Test Table' }, - // }); - - // // Wait for the table to be fully rendered - // await wrapper.vm.$nextTick(); - // assertTableContent(wrapper, rows); - // }); + it('should mount the component', () => { + const headers = [ + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, + ]; + const rows = [ + ['Alice', 25, 'New York'], + ['Bob', 30, 'Los Angeles'], + ['Charlie', 35, 'San Francisco'], + ]; + const wrapper = mount(KTable, { + propsData: { + headers, + rows, + sortable: true, + }, + }); + const thElements = wrapper.findAll('th'); + expect(thElements.length).toBe(headers.length); + }); + + it('renders the correct content in rows and columns', async () => { + const headers = [ + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, + ]; + const rows = [ + ['John', 30, '2023-01-01'], + ['Jane', 25, '2023-02-01'], + ['Doe', 35, '2023-03-01'], + ]; + + const wrapper = mount(KTable, { + propsData: { headers, rows, caption: 'Test Table' }, + }); + + // Wait for the table to be fully rendered + await wrapper.vm.$nextTick(); + assertTableContent(wrapper, rows); + }); describe('should respect the defaultSort attribute', () => { const headers = [ @@ -72,14 +72,14 @@ describe('KTable.vue', () => { ['Bob', 35, 'San Francisco'], ]; - // it('should not sort the rows by default', async () => { - // const wrapper = mount(KTable, { - // propsData: { headers, rows, caption: 'Test Table' }, - // }); + it('should not sort the rows by default', async () => { + const wrapper = mount(KTable, { + propsData: { headers, rows, caption: 'Test Table' }, + }); - // await wrapper.vm.$nextTick(); - // assertTableContent(wrapper, rows); - // }); + await wrapper.vm.$nextTick(); + assertTableContent(wrapper, rows); + }); it('should sort the rows correctly in ascending order', async () => { const wrapper = mount(KTable, { @@ -100,97 +100,97 @@ describe('KTable.vue', () => { assertTableContent(wrapper, expectedRows); }); - // it('should sort the rows correctly in descending order', async () => { - // const wrapper = mount(KTable, { - // propsData: { - // headers, - // rows, - // caption: 'Test Table', - // defaultSort: { columnId: 'age', direction: 'desc' }, - // }, - // }); - - // await wrapper.vm.$nextTick(); - // const expectedRows = [ - // ['Bob', 35, 'San Francisco'], - // ['John', 30, 'New York'], - // ['Alice', 25, 'Los Angeles'], - // ]; - // assertTableContent(wrapper, expectedRows); - // }); + it('should sort the rows correctly in descending order', async () => { + const wrapper = mount(KTable, { + propsData: { + headers, + rows, + caption: 'Test Table', + defaultSort: { columnId: 'age', direction: 'desc' }, + }, + }); + + await wrapper.vm.$nextTick(); + const expectedRows = [ + ['Bob', 35, 'San Francisco'], + ['John', 30, 'New York'], + ['Alice', 25, 'Los Angeles'], + ]; + assertTableContent(wrapper, expectedRows); + }); + }); + + it('should handle sticky headers and columns', async () => { + const headers = [ + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + ]; + const rows = [ + ['John', 30], + ['Jane', 25], + ]; + + const wrapper = mount(KTable, { + propsData: { headers, rows, caption: 'Sticky Table' }, + }); + + // Wait for the table to be fully rendered + await wrapper.vm.$nextTick(); + + const headerCells = wrapper.findAll('thead th'); + headerCells.wrappers.forEach(headerCell => { + expect(headerCell.classes()).toContain('sticky-header'); + }); + + const firstColumnCells = wrapper.findAll('tbody tr td:first-child'); + firstColumnCells.wrappers.forEach(cell => { + expect(cell.classes()).toContain('sticky-column'); + }); }); - // it('should handle sticky headers and columns', async () => { - // const headers = [ - // { label: 'Name', dataType: 'string', columnId: 'name' }, - // { label: 'Age', dataType: 'number', columnId: 'age' }, - // ]; - // const rows = [ - // ['John', 30], - // ['Jane', 25], - // ]; - - // const wrapper = mount(KTable, { - // propsData: { headers, rows, caption: 'Sticky Table' }, - // }); - - // // Wait for the table to be fully rendered - // await wrapper.vm.$nextTick(); - - // const headerCells = wrapper.findAll('thead th'); - // headerCells.wrappers.forEach(headerCell => { - // expect(headerCell.classes()).toContain('sticky-header'); - // }); - - // const firstColumnCells = wrapper.findAll('tbody tr td:first-child'); - // firstColumnCells.wrappers.forEach(cell => { - // expect(cell.classes()).toContain('sticky-column'); - // }); - // }); - - // beforeEach(() => { - // /*Since our primary concern in this test is checking focus management rather than actual scrolling behavior, - // mocking scrollIntoView allows the test to focus on the relevant aspects without getting interrupted - // by unsupported methods in the test environment.*/ - // window.HTMLElement.prototype.scrollIntoView = jest.fn(); - // }); - - // it('should handle keyboard navigation within the table', async () => { - // const headers = [ - // { label: 'Name', dataType: 'string', columnId: 'name' }, - // { label: 'Age', dataType: 'number', columnId: 'age' }, - // ]; - // const rows = [ - // ['John', 30], - // ['Jane', 25], - // ]; - - // const wrapper = mount(KTable, { - // propsData: { headers, rows, caption: 'Keyboard Navigation Table' }, - // attachTo: document.body, // Attach to document body to properly manage focus - // }); - - // await wrapper.vm.$nextTick(); // Ensure the component is fully rendered - - // const firstCell = wrapper.find('tbody tr:first-child td:first-child'); - // await firstCell.element.focus(); // Focus the first cell directly - // expect(document.activeElement).toBe(firstCell.element); // Check if the first cell is focused - - // // Simulate ArrowRight key press - // await firstCell.trigger('keydown', { key: 'ArrowRight' }); - - // const secondCell = wrapper.find('tbody tr:first-child td:nth-child(2)'); - // await secondCell.element.focus(); // Focus the second cell directly - // expect(document.activeElement).toBe(secondCell.element); // Check if the second cell is focused - - // // Simulate ArrowDown key press - // await secondCell.trigger('keydown', { key: 'ArrowDown' }); - - // const thirdCell = wrapper.find('tbody tr:nth-child(2) td:nth-child(2)'); - // await thirdCell.element.focus(); // Focus the third cell directly - // expect(document.activeElement).toBe(thirdCell.element); // Check if the third cell is focused - - // // Cleanup: detach the wrapper from the document body after the test - // wrapper.destroy(); - // }); + beforeEach(() => { + /*Since our primary concern in this test is checking focus management rather than actual scrolling behavior, + mocking scrollIntoView allows the test to focus on the relevant aspects without getting interrupted + by unsupported methods in the test environment.*/ + window.HTMLElement.prototype.scrollIntoView = jest.fn(); + }); + + it('should handle keyboard navigation within the table', async () => { + const headers = [ + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + ]; + const rows = [ + ['John', 30], + ['Jane', 25], + ]; + + const wrapper = mount(KTable, { + propsData: { headers, rows, caption: 'Keyboard Navigation Table' }, + attachTo: document.body, // Attach to document body to properly manage focus + }); + + await wrapper.vm.$nextTick(); // Ensure the component is fully rendered + + const firstCell = wrapper.find('tbody tr:first-child td:first-child'); + await firstCell.element.focus(); // Focus the first cell directly + expect(document.activeElement).toBe(firstCell.element); // Check if the first cell is focused + + // Simulate ArrowRight key press + await firstCell.trigger('keydown', { key: 'ArrowRight' }); + + const secondCell = wrapper.find('tbody tr:first-child td:nth-child(2)'); + await secondCell.element.focus(); // Focus the second cell directly + expect(document.activeElement).toBe(secondCell.element); // Check if the second cell is focused + + // Simulate ArrowDown key press + await secondCell.trigger('keydown', { key: 'ArrowDown' }); + + const thirdCell = wrapper.find('tbody tr:nth-child(2) td:nth-child(2)'); + await thirdCell.element.focus(); // Focus the third cell directly + expect(document.activeElement).toBe(thirdCell.element); // Check if the third cell is focused + + // Cleanup: detach the wrapper from the document body after the test + wrapper.destroy(); + }); }); From 661ff12e3ba74e91d5fd7fd263dd74c25aafec94 Mon Sep 17 00:00:00 2001 From: EshaanAgg <96648934+EshaanAgg@users.noreply.github.com> Date: Thu, 14 Nov 2024 01:50:09 +0530 Subject: [PATCH 06/17] lint fix --- lib/__tests__/KTable.spec.js | 76 ++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/lib/__tests__/KTable.spec.js b/lib/__tests__/KTable.spec.js index e38208b5a..9b8332114 100644 --- a/lib/__tests__/KTable.spec.js +++ b/lib/__tests__/KTable.spec.js @@ -16,49 +16,49 @@ const assertTableContent = (wrapper, rows) => { }; describe('KTable.vue', () => { - it('should mount the component', () => { - const headers = [ - { label: 'Name', dataType: 'string', columnId: 'name' }, - { label: 'Age', dataType: 'number', columnId: 'age' }, - { label: 'City', dataType: 'string', columnId: 'city' }, - ]; - const rows = [ - ['Alice', 25, 'New York'], - ['Bob', 30, 'Los Angeles'], - ['Charlie', 35, 'San Francisco'], - ]; - const wrapper = mount(KTable, { - propsData: { - headers, - rows, - sortable: true, - }, - }); - const thElements = wrapper.findAll('th'); - expect(thElements.length).toBe(headers.length); + it('should mount the component', () => { + const headers = [ + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, + ]; + const rows = [ + ['Alice', 25, 'New York'], + ['Bob', 30, 'Los Angeles'], + ['Charlie', 35, 'San Francisco'], + ]; + const wrapper = mount(KTable, { + propsData: { + headers, + rows, + sortable: true, + }, }); + const thElements = wrapper.findAll('th'); + expect(thElements.length).toBe(headers.length); + }); - it('renders the correct content in rows and columns', async () => { - const headers = [ - { label: 'Name', dataType: 'string', columnId: 'name' }, - { label: 'Age', dataType: 'number', columnId: 'age' }, - { label: 'City', dataType: 'string', columnId: 'city' }, - ]; - const rows = [ - ['John', 30, '2023-01-01'], - ['Jane', 25, '2023-02-01'], - ['Doe', 35, '2023-03-01'], - ]; - - const wrapper = mount(KTable, { - propsData: { headers, rows, caption: 'Test Table' }, - }); + it('renders the correct content in rows and columns', async () => { + const headers = [ + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, + ]; + const rows = [ + ['John', 30, '2023-01-01'], + ['Jane', 25, '2023-02-01'], + ['Doe', 35, '2023-03-01'], + ]; - // Wait for the table to be fully rendered - await wrapper.vm.$nextTick(); - assertTableContent(wrapper, rows); + const wrapper = mount(KTable, { + propsData: { headers, rows, caption: 'Test Table' }, }); + // Wait for the table to be fully rendered + await wrapper.vm.$nextTick(); + assertTableContent(wrapper, rows); + }); + describe('should respect the defaultSort attribute', () => { const headers = [ { label: 'Name', dataType: 'string', columnId: 'name' }, From f76fc51821516e62a155280ff3afdc3e0bdefee8 Mon Sep 17 00:00:00 2001 From: EshaanAgg <96648934+EshaanAgg@users.noreply.github.com> Date: Tue, 19 Nov 2024 09:36:07 +0530 Subject: [PATCH 07/17] rewrite the useSorting hook, refactor the code and reintroduce disable builtin sorting --- docs/pages/ktable.vue | 7 +- lib/KTable/index.vue | 58 +++++++++---- lib/KTable/useSorting/__tests__/index.spec.js | 83 ++++++++++++++----- lib/KTable/useSorting/index.js | 15 ++-- 4 files changed, 120 insertions(+), 43 deletions(-) diff --git a/docs/pages/ktable.vue b/docs/pages/ktable.vue index bc1960bf5..0dba2e1d9 100644 --- a/docs/pages/ktable.vue +++ b/docs/pages/ktable.vue @@ -187,6 +187,7 @@

+

Sortable Table with Rows Sorted by 'Age' Column

- + +

Unsortable Table with Rows Sorted by 'Age' Column

+

Sortable Table with Rows Sorted by 'Age' Column

+ +

Unsortable Table with Rows Sorted by 'Age' Column

- import { ref, computed, watch } from '@vue/composition-api'; + import { ref, computed, watch, emit } from '@vue/composition-api'; import useSorting, { SORT_ORDER_ASC, SORT_ORDER_DESC, @@ -113,26 +113,21 @@ const headers = ref(props.headers); const rows = ref(props.rows); - const useDefaultSorting = computed(() => ({ + const defaultSort = ref({ index: props.headers.findIndex(h => h.columnId === props.defaultSort.columnId), direction: props.defaultSort.direction, - })); - - const { sortKey, sortOrder, sortedRows, handleSort, getAriaSort } = useSorting( - headers, - rows, - useDefaultSorting - ); - - const finalRows = computed(() => { - if (props.sortable) { - return sortedRows.value; - } else { - return rows.value; - } }); + const useLocalSorting = ref(props.sortable && !props.disableBuiltinSorting); - const isTableEmpty = computed(() => finalRows.value.length === 0); + const { + sortKey, + sortOrder, + sortedRows, + handleSort: localHandleSort, + getAriaSort, + } = useSorting(headers, rows, defaultSort, useLocalSorting); + + const isTableEmpty = computed(() => sortedRows.value.length === 0); watch( () => props.rows, @@ -141,16 +136,35 @@ } ); + const handleSort = index => { + if (headers.value[index].dataType === DATA_TYPE_OTHERS) { + return; + } + + // If we are using local sorting, then we need to sort the rows locally + if (useLocalSorting.value) { + localHandleSort(index); + } else { + // Emit the event to the parent component to provide the sorting logic + emit( + 'changeSort', + index, + sortOrder.value === SORT_ORDER_ASC ? SORT_ORDER_DESC : SORT_ORDER_ASC + ); + } + }; + const getHeaderStyle = header => { const style = {}; if (header.minWidth) style.minWidth = header.minWidth; if (header.width) style.width = header.width; return style; }; + return { sortKey, sortOrder, - finalRows, + finalRows: sortedRows, handleSort, getAriaSort, SORT_ORDER_ASC, @@ -234,6 +248,14 @@ ); }, }, + /* + * Disables all the sorting functionality provided by the component. This is useful when you want to define you own sorting logic. Refer to the documentation for more details. + */ + disableBuiltinSorting: { + type: Boolean, + default: false, + required: false, + }, }, data() { return { diff --git a/lib/KTable/useSorting/__tests__/index.spec.js b/lib/KTable/useSorting/__tests__/index.spec.js index 031084c54..816d2be85 100644 --- a/lib/KTable/useSorting/__tests__/index.spec.js +++ b/lib/KTable/useSorting/__tests__/index.spec.js @@ -9,7 +9,7 @@ import useSorting, { } from '../'; describe('useSorting', () => { - let headers, rows, useDefaultSorting; + let headers, rows, defaultSort, useLocalSorting; beforeEach(() => { headers = ref([ @@ -25,29 +25,31 @@ describe('useSorting', () => { ['Alice', 28, new Date(1992, 8, 10)], ]); - // Disable default sorting by default - useDefaultSorting = ref({ + // Disable default sorting + defaultSort = ref({ index: -1, }); + + useLocalSorting = ref(true); }); describe('default sorting', () => { it('should return rows unsorted by default', () => { - useDefaultSorting.value = { + defaultSort.value = { index: -1, }; - const { sortedRows } = useSorting(headers, rows, useDefaultSorting); + const { sortedRows } = useSorting(headers, rows, defaultSort, useLocalSorting); expect(sortedRows.value).toEqual(rows.value); }); it('should sort the rows in ascending correctly for string values', () => { - useDefaultSorting.value = { + defaultSort.value = { index: 0, direction: 'asc', }; - const { sortedRows } = useSorting(headers, rows, useDefaultSorting); + const { sortedRows } = useSorting(headers, rows, defaultSort, useLocalSorting); expect(sortedRows.value).toEqual([ ['Alice', 28, new Date(1992, 8, 10)], ['Jane', 25, new Date(1995, 10, 20)], @@ -56,12 +58,12 @@ describe('useSorting', () => { }); it('should sort the rows in descending order correctly for string values', () => { - useDefaultSorting.value = { + defaultSort.value = { index: 0, direction: 'desc', }; - const { sortedRows } = useSorting(headers, rows, useDefaultSorting); + const { sortedRows } = useSorting(headers, rows, defaultSort, useLocalSorting); expect(sortedRows.value).toEqual([ ['John', 30, new Date(1990, 5, 15)], ['Jane', 25, new Date(1995, 10, 20)], @@ -70,12 +72,12 @@ describe('useSorting', () => { }); it('should sort the rows correctly for numeric values', () => { - useDefaultSorting.value = { + defaultSort.value = { index: 1, direction: 'asc', }; - const { sortedRows } = useSorting(headers, rows, useDefaultSorting); + const { sortedRows } = useSorting(headers, rows, defaultSort, useLocalSorting); expect(sortedRows.value).toEqual([ ['Jane', 25, new Date(1995, 10, 20)], ['Alice', 28, new Date(1992, 8, 10)], @@ -84,12 +86,12 @@ describe('useSorting', () => { }); it('should sort the rows correctly for date values', () => { - useDefaultSorting.value = { + defaultSort.value = { index: 2, direction: 'asc', }; - const { sortedRows } = useSorting(headers, rows, useDefaultSorting); + const { sortedRows } = useSorting(headers, rows, defaultSort, useLocalSorting); expect(sortedRows.value).toEqual([ ['John', 30, new Date(1990, 5, 15)], ['Alice', 28, new Date(1992, 8, 10)], @@ -98,8 +100,36 @@ describe('useSorting', () => { }); }); + describe('useLocalSorting is set to false', () => { + it('should return rows unsorted when useLocalSorting is false', () => { + useLocalSorting.value = false; + + const { sortedRows } = useSorting(headers, rows, defaultSort, useLocalSorting); + expect(sortedRows.value).toEqual(rows.value); + }); + + it('should return the rows unsorted even when default sorting is enabled', () => { + useLocalSorting.value = false; + defaultSort.value = { + index: 0, + direction: 'asc', + }; + + const { sortedRows } = useSorting(headers, rows, defaultSort, useLocalSorting); + expect(sortedRows.value).toEqual(rows.value); + }); + + it('should not sort rows even when a column is clicked', () => { + useLocalSorting.value = false; + + const { handleSort, sortedRows } = useSorting(headers, rows, defaultSort, useLocalSorting); + handleSort(0); // Sort by 'Name' + expect(sortedRows.value).toEqual(rows.value); + }); + }); + it('should sort rows by string column in ascending order', () => { - const { handleSort, sortedRows } = useSorting(headers, rows, useDefaultSorting); + const { handleSort, sortedRows } = useSorting(headers, rows, defaultSort, useLocalSorting); handleSort(0); // Sort by 'Name' expect(sortedRows.value).toEqual([ @@ -110,7 +140,12 @@ describe('useSorting', () => { }); it('should sort rows by numeric column in ascending then descending order then back to default order', () => { - const { handleSort, sortedRows, sortOrder } = useSorting(headers, rows, useDefaultSorting); + const { handleSort, sortedRows, sortOrder } = useSorting( + headers, + rows, + defaultSort, + useLocalSorting + ); handleSort(1); // Sort by 'Age' expect(sortedRows.value).toEqual([ @@ -138,7 +173,7 @@ describe('useSorting', () => { }); it('should sort rows by date column in ascending order', () => { - const { handleSort, sortedRows } = useSorting(headers, rows, useDefaultSorting); + const { handleSort, sortedRows } = useSorting(headers, rows, defaultSort, useLocalSorting); handleSort(2); // Sort by 'Birthdate' expect(sortedRows.value).toEqual([ @@ -149,7 +184,12 @@ describe('useSorting', () => { }); it('should not sort rows when sorting by a column with dataType "undefined"', () => { - const { handleSort, sortedRows, sortKey } = useSorting(headers, rows, useDefaultSorting); + const { handleSort, sortedRows, sortKey } = useSorting( + headers, + rows, + defaultSort, + useLocalSorting + ); handleSort(3); // Attempt to sort by 'Other' expect(sortedRows.value).toEqual(rows.value); @@ -157,7 +197,7 @@ describe('useSorting', () => { }); it('should return correct aria-sort attribute based on current sorting', () => { - const { handleSort, getAriaSort } = useSorting(headers, rows, useDefaultSorting); + const { handleSort, getAriaSort } = useSorting(headers, rows, defaultSort, useLocalSorting); expect(getAriaSort(0)).toBe('none'); @@ -169,7 +209,12 @@ describe('useSorting', () => { }); it('should reset sortKey and sortOrder when a new column is sorted', () => { - const { handleSort, sortKey, sortOrder } = useSorting(headers, rows, useDefaultSorting); + const { handleSort, sortKey, sortOrder } = useSorting( + headers, + rows, + defaultSort, + useLocalSorting + ); handleSort(0); // Sort by 'Name' expect(sortKey.value).toBe(0); diff --git a/lib/KTable/useSorting/index.js b/lib/KTable/useSorting/index.js index 5d07682c8..b1b5406b1 100644 --- a/lib/KTable/useSorting/index.js +++ b/lib/KTable/useSorting/index.js @@ -13,11 +13,12 @@ export const DATA_TYPE_OTHERS = 'undefined'; * * @param {Ref} headers - Reactive reference to the table headers. * @param {Ref} rows - Reactive reference to the table rows. - * @param {Ref} useDefaultSorting - Computed reactive reference to object describing if default sorting should be used. + * @param {Ref} defaultSort - Computed reactive reference to object describing if default sorting should be used. * It has the properties as 'index' denoting the row index to be used for default sorting, and 'direction' denoting the sort order. + * @param {Ref} useLocalSorting - Reactive reference to disable all forms of sorting by the hook. * @returns {Object} - An object containing reactive references and methods for sorting. */ -export default function useSorting(headers, rows, useDefaultSorting) { +export default function useSorting(headers, rows, defaultSort, useLocalSorting) { const sortKey = ref(null); const sortOrder = ref(null); @@ -26,13 +27,17 @@ export default function useSorting(headers, rows, useDefaultSorting) { * If local sorting is disabled or no sort key is set, it returns the original rows. */ const sortedRows = computed(() => { + // If sorting is disabled, return the original rows + if (!useLocalSorting.value) return rows.value; + // No sort key or value has been explicity set till now if (sortKey.value === null || sortOrder.value === null) { - if (useDefaultSorting.value.index === -1) return rows.value; + if (defaultSort.value.index === -1) return rows.value; + return _.orderBy( rows.value, - [row => row[useDefaultSorting.value.index]], - [useDefaultSorting.value.direction] + [row => row[defaultSort.value.index]], + [defaultSort.value.direction] ); } From 71af1e1b32a8e8167b0105b747ace073a38f1400 Mon Sep 17 00:00:00 2001 From: EshaanAgg <96648934+EshaanAgg@users.noreply.github.com> Date: Tue, 19 Nov 2024 13:09:20 +0530 Subject: [PATCH 08/17] rewrite docs and examples --- docs/pages/ktable.vue | 292 ++++++++++++++++++++++++--------- lib/KTable/index.vue | 25 ++- lib/KTable/useSorting/index.js | 7 +- 3 files changed, 230 insertions(+), 94 deletions(-) diff --git a/docs/pages/ktable.vue b/docs/pages/ktable.vue index 0dba2e1d9..95c76aa37 100644 --- a/docs/pages/ktable.vue +++ b/docs/pages/ktable.vue @@ -13,13 +13,14 @@

+

Table without sorting functionality

This is an example to show how KTable can be used without any sorting functionality, as a simple table.

- + - - data() { - return { - headers: [ - { label: 'Name', dataType: 'string', columnId: 'name' }, - { label: 'Age', dataType: 'number', columnId: 'age' }, - { label: 'City', dataType: 'string', columnId: 'city' }, - ], - rows: [ - ['John Doe', 28, 'New York'], - ['Jane Smith', 34, 'Los Angeles'], - ['Samuel Green', 22, 'Chicago'], - ['Alice Johnson', 30, 'Houston'], - ['Michael Brown', 45, 'Phoenix'], - ['Emily Davis', 27, 'Philadelphia'], - ] - }; - }, - + + + data() { + return { + headers: [ + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, + ], + rows: [ + ['John Doe', 28, 'New York'], + ['Jane Smith', 34, 'Los Angeles'], + ['Samuel Green', 22, 'Chicago'], + ['Alice Johnson', 30, 'Houston'], + ['Michael Brown', 45, 'Phoenix'], + ['Emily Davis', 27, 'Philadelphia'], + ] + }; + }, + +

Table with Sorting

- The KTable can be used with sorting functionality, allowing you to sort data on the client side without the need for server requests. There are 4 permissible data types - string,number,date and undefined. Columns declared with undefined data type are not sortable. This example demonstrates a table with sorting enabled. + The KTable can be used with sorting functionality, allowing you to sort data on the client side without the need for server requests. There are 4 permissible data types - string,number,date and undefined. Columns declared with undefined data type are not sortable. This example demonstrates a table with sorting enabled. To allow client side sorting, set the sortable attribute to true. +

+

+ You can also notice that clicking the same header multiple times toggles the sort direction cyclically in the order of asc, desc and none. The none state is the default state when the table is loaded by default.

@@ -73,25 +79,28 @@ - - data() { - return { - headers: [ - { label: 'Name', dataType: 'string', columnId: 'name' }, - { label: 'Age', dataType: 'number', columnId: 'age' }, - { label: 'City', dataType: 'string', columnId: 'city' }, - ], - rows: [ - ['John Doe', 28, 'New York'], - ['Jane Smith', 34, 'Los Angeles'], - ['Samuel Green', 22, 'Chicago'], - ['Alice Johnson', 30, 'Houston'], - ['Michael Brown', 45, 'Phoenix'], - ['Emily Davis', 27, 'Philadelphia'], - ] - }; - }, - + + + data() { + return { + headers: [ + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, + ], + rows: [ + ['John Doe', 28, 'New York'], + ['Jane Smith', 34, 'Los Angeles'], + ['Samuel Green', 22, 'Chicago'], + ['Alice Johnson', 30, 'Houston'], + ['Michael Brown', 45, 'Phoenix'], + ['Emily Davis', 27, 'Philadelphia'], + ] + }; + }, + + + - +

Table showing use of slots

This is an example to show how slots can be used in KTable. The table currently provides slots for header and cell which can be used to customize the table header and cell content respectively.

+ - - - - + + +
- - data() { - return { - slotHeaders: [ - { label: 'Name', dataType: 'string', columnId: 'name' }, - { label: 'Age', dataType: 'number', columnId: 'age' }, - { label: 'City', dataType: 'string', columnId: 'city' }, - { label: 'Joined', dataType: 'date', columnId: 'joined' }, - { label: 'Misc', dataType: 'undefined', columnId: 'misc' }, - ], - slotRows: [ - ['John Doe', 28, 'New York', '2022-01-15T00:00:00Z', 'N/A'], - ['Jane Smith', 34, 'Los Angeles', '2021-12-22T00:00:00Z', 'N/A'], - ['Samuel Green', 22, 'Chicago', '2023-03-10T00:00:00Z', 'N/A'], - ['Alice Johnson', 30, 'Houston', '2020-07-18T00:00:00Z', 'N/A'], - ], - }; - }, + + data() { + return { + slotHeaders: [ + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, + { label: 'Joined', dataType: 'date', columnId: 'joined' }, + { label: 'Misc', dataType: 'undefined', columnId: 'misc' }, + ], + slotRows: [ + ['John Doe', 28, 'New York', '2022-01-15T00:00:00Z', 'N/A'], + ['Jane Smith', 34, 'Los Angeles', '2021-12-22T00:00:00Z', 'N/A'], + ['Samuel Green', 22, 'Chicago', '2023-03-10T00:00:00Z', 'N/A'], + ['Alice Johnson', 30, 'Houston', '2020-07-18T00:00:00Z', 'N/A'], + ], + }; + }, - + +

Table with custom column widths

This is an example to show how KTable can be used with custom column widths. The column widths are defined in the headers prop. The width property is used to define the overall width of the column. The minWidth defines the minimum width of column, below which the column will not shrink.

+ + + + data() { + return { + headersWithCustomWidths: [ + { label: 'Name', dataType: 'string', minWidth: '20px', width: '2%', columnId: 'name' }, + { label: 'Age', dataType: 'number', minWidth: '100px', width: '33%', columnId: 'age' }, + { label: 'City', dataType: 'string', minWidth: '200px', width: '25%', columnId: 'city' }, + { + label: 'Joined', + dataType: 'date', + minWidth: '150px', + width: '20%', + columnId: 'joined', + }, + { + label: 'Misc', + dataType: 'undefined', + minWidth: '100px', + width: '20%', + columnId: 'misc', + }, + ], + customRows: [ + ['John Doe', 28, 'New York', '2022-01-15T00:00:00Z', 'N/A'], + ['Jane Smith', 34, 'Los Angeles', '2021-12-22T00:00:00Z', 'N/A'], + ['Samuel Green', 22, 'Chicago', '2023-03-10T00:00:00Z', 'N/A'], + ['Alice Johnson', 30, 'Houston', '2020-07-18T00:00:00Z', 'N/A'], + ], + }; + }, + + + + +

Table with Default Sort

- This is an example to show how KTable can be used with the defaultSort attribute to sort the table based on a particular column. This is useful if you are getting unsorted data from an API and want to display it in a sorted manner. The defaultSort attribute can be used irrespective of the sortable attribute. + This is an example to show how KTable can be used with the defaultSort attribute to sort the table based on a particular column. This is useful if you are getting unsorted data from the backend and want to display it in a sorted manner on load. The defaultSort attribute can be used irrespective of the sortable attribute (as sortable is used to configure whether the client has sorting capabilities or not). +

+ +

+ The defaultSort attribute takes an object with two properties - columnId and direction. The columnId is the unique identifier of the column based on which the table should be sorted. The direction can be either asc or desc. +

+ +

+ To make use of defaultSort, please ensure that the disableBuiltinSorting attribute is not set to true as it will disable all sorting functionality.

-

Sortable Table with Rows Sorted by 'Age' Column

+

Sortable Table with Rows Sorted by 'Age' Column

- -

Unsortable Table with Rows Sorted by 'Age' Column

+ +

Unsortable Table with Rows Sorted by 'Age' Column

+ data() { return { @@ -225,9 +286,10 @@ }; }, + -

Sortable Table with Rows Sorted by 'Age' Column

+

Sortable Table with Rows Sorted by 'Age' Column

-

Unsortable Table with Rows Sorted by 'Age' Column

+

Unsortable Table with Rows Sorted by 'Age' Column

-
+ +

Disable Builtin Sorting & Custom Sorting Logic

+

+ You can make use of the disableBuiltinSorting attribute to disable all sorting functionality in the table. This is useful when you want to display the data in a particular order or want to define a custom sorting mechanism. +

+ +

+ You should not use this attribute if the sortable attribute is set to false, as in that case the table headers for sorting will not be displayed at all. If the same is set to true, then table emits a changeSort event (whose implementation can be defined by the user) which needs to return a sortOrder value for the UI headers to toggle. +

+ + + + + + + data() { + return { + headers: [ + { label: 'Name', dataType: 'string', columnId: 'name' }, + { label: 'Age', dataType: 'number', columnId: 'age' }, + { label: 'City', dataType: 'string', columnId: 'city' }, + ], + changeSortRows: [ + ['John Doe', 28, 'New York'], + ['Jane Smith', 34, 'Los Angeles'], + ['Samuel Green', 22, 'Chicago'], + ['Alice Johnson', 30, 'Houston'], + ['Michael Brown', 45, 'Phoenix'], + ['Emily Davis', 27, 'Philadelphia'], + ] + }; + }, + methods: { + changeSortHandler(index, sortOrder) { + // Demo Implementation: Reverse the order of the rows and return the opposite sort order + console.log('Index:', index, 'Sort Order:', sortOrder); + this.changeSortRows = [...this.changeSortRows.reverse()]; + return sortOrder === 'asc' ? 'desc' : 'asc'; + } + } + + + + + + + + @@ -310,8 +433,23 @@ ['Samuel Green', 22, 'Chicago', '2023-03-10T00:00:00Z', 'N/A'], ['Alice Johnson', 30, 'Houston', '2020-07-18T00:00:00Z', 'N/A'], ], + changeSortRows: [ + ['John Doe', 28, 'New York'], + ['Jane Smith', 34, 'Los Angeles'], + ['Samuel Green', 22, 'Chicago'], + ['Alice Johnson', 30, 'Houston'], + ['Michael Brown', 45, 'Phoenix'], + ], }; }, + methods: { + changeSortHandler(index, sortOrder) { + // Demo Implementation: Reverse the order of the rows and return the opposite sort order + console.log('Index:', index, 'Sort Order:', sortOrder); + this.changeSortRows = [...this.changeSortRows.reverse()]; + return sortOrder === 'asc' ? 'desc' : 'asc'; + }, + }, }; diff --git a/lib/KTable/index.vue b/lib/KTable/index.vue index debac3d2a..8df0779ba 100644 --- a/lib/KTable/index.vue +++ b/lib/KTable/index.vue @@ -95,7 +95,7 @@