mirror of
https://github.com/directus/directus.git
synced 2026-01-27 15:17:58 -05:00
Implements server sort in o2m table interface (#16897)
* implements server sort when dealing with multple relational pages * always use server sort * fixed unsetting sort * removed frontend sorting * make loading total count more accurate * remove removed prop Co-authored-by: Nitwel <mail@nitwel.de>
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
<table :summary="internalHeaders.map((header) => header.text).join(', ')">
|
||||
<table-header
|
||||
v-model:headers="internalHeaders"
|
||||
v-model:sort="internalSort"
|
||||
v-model:reordering="reordering"
|
||||
:sort="internalSort"
|
||||
:show-select="showSelect"
|
||||
:show-resize="showResize"
|
||||
:some-items-selected="someItemsSelected"
|
||||
@@ -16,6 +16,7 @@
|
||||
:manual-sort-key="manualSortKey"
|
||||
:allow-header-reorder="allowHeaderReorder"
|
||||
@toggle-select-all="onToggleSelectAll"
|
||||
@update:sort="updateSort"
|
||||
>
|
||||
<template v-for="header in internalHeaders" #[`header.${header.value}`]>
|
||||
<slot :header="header" :name="`header.${header.value}`" />
|
||||
@@ -95,7 +96,7 @@ import { ShowSelect } from '@directus/shared/types';
|
||||
import { Header, HeaderRaw, Item, ItemSelectEvent, Sort } from './types';
|
||||
import TableHeader from './table-header.vue';
|
||||
import TableRow from './table-row.vue';
|
||||
import { sortBy, clone, forEach, pick } from 'lodash';
|
||||
import { clone, forEach, pick } from 'lodash';
|
||||
import { i18n } from '@/lang/';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { hideDragImage } from '@/utils/hide-drag-image';
|
||||
@@ -124,7 +125,6 @@ interface Props {
|
||||
loading?: boolean;
|
||||
loadingText?: string;
|
||||
noItemsText?: string;
|
||||
serverSort?: boolean;
|
||||
rowHeight?: number;
|
||||
selectionUseKeys?: boolean;
|
||||
inline?: boolean;
|
||||
@@ -146,7 +146,6 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
loading: false,
|
||||
loadingText: i18n.global.t('loading'),
|
||||
noItemsText: i18n.global.t('no_items'),
|
||||
serverSort: false,
|
||||
rowHeight: 48,
|
||||
selectionUseKeys: false,
|
||||
inline: false,
|
||||
@@ -205,18 +204,13 @@ const internalHeaders = computed({
|
||||
|
||||
// In case the sort prop isn't used, we'll use this local sort state as a fallback.
|
||||
// This allows the table to allow inline sorting on column ootb without the need for
|
||||
const internalLocalSort = ref<Sort>({
|
||||
by: null,
|
||||
desc: false,
|
||||
});
|
||||
|
||||
const internalSort = computed({
|
||||
get: () => props.sort || internalLocalSort.value,
|
||||
set: (newSort: Sort) => {
|
||||
emit('update:sort', newSort);
|
||||
internalLocalSort.value = newSort;
|
||||
},
|
||||
});
|
||||
const internalSort = computed<Sort>(
|
||||
() =>
|
||||
props.sort ?? {
|
||||
by: null,
|
||||
desc: false,
|
||||
}
|
||||
);
|
||||
|
||||
const reordering = ref<boolean>(false);
|
||||
|
||||
@@ -235,15 +229,7 @@ const fullColSpan = computed<string>(() => {
|
||||
|
||||
const internalItems = computed({
|
||||
get: () => {
|
||||
if (props.serverSort === true || internalSort.value.by === props.manualSortKey) {
|
||||
return props.items;
|
||||
}
|
||||
|
||||
if (internalSort.value.by === null) return props.items;
|
||||
|
||||
const itemsSorted = sortBy(props.items, [internalSort.value.by]);
|
||||
if (internalSort.value.desc === true) return itemsSorted.reverse();
|
||||
return itemsSorted;
|
||||
return props.items;
|
||||
},
|
||||
set: (value: Item[]) => {
|
||||
emit('update:items', value);
|
||||
@@ -345,6 +331,9 @@ function onSortChange(event: EndEvent) {
|
||||
|
||||
emit('manual-sort', { item, to });
|
||||
}
|
||||
function updateSort(newSort: Sort) {
|
||||
emit('update:sort', newSort?.by ? newSort : null);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -189,24 +189,6 @@ export function useRelationMultiple(
|
||||
|
||||
const { create, remove, select, update } = useActions(_value);
|
||||
|
||||
return {
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
select,
|
||||
displayItems,
|
||||
totalItemCount,
|
||||
loading,
|
||||
selected,
|
||||
fetchedSelectItems,
|
||||
fetchedItems,
|
||||
useActions,
|
||||
cleanItem,
|
||||
isItemSelected,
|
||||
localDelete,
|
||||
getItemEdits,
|
||||
};
|
||||
|
||||
function useActions(target: Ref<Item>) {
|
||||
return { create, update, remove, select };
|
||||
|
||||
@@ -311,20 +293,17 @@ export function useRelationMultiple(
|
||||
if (!relation.value) return;
|
||||
|
||||
if (!itemId.value || itemId.value === '+') {
|
||||
existingItemCount.value = 0;
|
||||
fetchedItems.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
let targetCollection: string;
|
||||
let targetPKField: string;
|
||||
let reverseJunctionField: string;
|
||||
const fields = new Set(previewQuery.value.fields);
|
||||
|
||||
switch (relation.value.type) {
|
||||
case 'm2a':
|
||||
targetCollection = relation.value.junctionCollection.collection;
|
||||
targetPKField = relation.value.junctionPrimaryKeyField.field;
|
||||
reverseJunctionField = relation.value.reverseJunctionField.field;
|
||||
fields.add(relation.value.junctionPrimaryKeyField.field);
|
||||
fields.add(relation.value.collectionField.field);
|
||||
@@ -335,14 +314,12 @@ export function useRelationMultiple(
|
||||
break;
|
||||
case 'm2m':
|
||||
targetCollection = relation.value.junctionCollection.collection;
|
||||
targetPKField = relation.value.junctionPrimaryKeyField.field;
|
||||
reverseJunctionField = relation.value.reverseJunctionField.field;
|
||||
fields.add(relation.value.junctionPrimaryKeyField.field);
|
||||
fields.add(`${relation.value.junctionField.field}.${relation.value.relatedPrimaryKeyField.field}`);
|
||||
break;
|
||||
case 'o2m':
|
||||
targetCollection = relation.value.relatedCollection.collection;
|
||||
targetPKField = relation.value.relatedPrimaryKeyField.field;
|
||||
reverseJunctionField = relation.value.reverseJunctionField.field;
|
||||
fields.add(relation.value.relatedPrimaryKeyField.field);
|
||||
break;
|
||||
@@ -353,8 +330,6 @@ export function useRelationMultiple(
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
await updateItemCount(targetCollection, targetPKField, reverseJunctionField);
|
||||
|
||||
if (itemId.value !== '+') {
|
||||
const filter: Filter = { _and: [{ [reverseJunctionField]: itemId.value } as Filter] };
|
||||
if (previewQuery.value.filter) {
|
||||
@@ -368,6 +343,7 @@ export function useRelationMultiple(
|
||||
filter,
|
||||
page: previewQuery.value.page,
|
||||
limit: previewQuery.value.limit,
|
||||
sort: previewQuery.value.sort,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -380,7 +356,55 @@ export function useRelationMultiple(
|
||||
}
|
||||
}
|
||||
|
||||
async function updateItemCount(targetCollection: string, targetPKField: string, reverseJunctionField: string) {
|
||||
watch(
|
||||
[previewQuery, itemId, relation],
|
||||
(newData, oldData) => {
|
||||
const [newPreviewQuery, newItemId, newRelation] = newData;
|
||||
const [oldPreviewQuery, oldItemId, oldRelation] = oldData;
|
||||
|
||||
if (
|
||||
isEqual(newRelation, oldRelation) &&
|
||||
newPreviewQuery.filter === oldPreviewQuery?.filter &&
|
||||
newPreviewQuery.search === oldPreviewQuery?.search &&
|
||||
newItemId === oldItemId
|
||||
)
|
||||
return;
|
||||
|
||||
updateItemCount();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
async function updateItemCount() {
|
||||
if (!relation.value) return;
|
||||
|
||||
if (!itemId.value || itemId.value === '+') {
|
||||
existingItemCount.value = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
let targetCollection: string;
|
||||
let targetPKField: string;
|
||||
let reverseJunctionField: string;
|
||||
|
||||
switch (relation.value.type) {
|
||||
case 'm2a':
|
||||
targetCollection = relation.value.junctionCollection.collection;
|
||||
targetPKField = relation.value.junctionPrimaryKeyField.field;
|
||||
reverseJunctionField = relation.value.reverseJunctionField.field;
|
||||
break;
|
||||
case 'm2m':
|
||||
targetCollection = relation.value.junctionCollection.collection;
|
||||
targetPKField = relation.value.junctionPrimaryKeyField.field;
|
||||
reverseJunctionField = relation.value.reverseJunctionField.field;
|
||||
break;
|
||||
case 'o2m':
|
||||
targetCollection = relation.value.relatedCollection.collection;
|
||||
targetPKField = relation.value.relatedPrimaryKeyField.field;
|
||||
reverseJunctionField = relation.value.reverseJunctionField.field;
|
||||
break;
|
||||
}
|
||||
|
||||
const filter: Filter = { _and: [{ [reverseJunctionField]: itemId.value } as Filter] };
|
||||
if (previewQuery.value.filter) {
|
||||
filter._and.push(previewQuery.value.filter);
|
||||
@@ -629,4 +653,22 @@ export function useRelationMultiple(
|
||||
|
||||
return { cleanItem, getPage, localDelete, getItemEdits, isEmpty };
|
||||
}
|
||||
|
||||
return {
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
select,
|
||||
displayItems,
|
||||
totalItemCount,
|
||||
loading,
|
||||
selected,
|
||||
fetchedSelectItems,
|
||||
fetchedItems,
|
||||
useActions,
|
||||
cleanItem,
|
||||
isItemSelected,
|
||||
localDelete,
|
||||
getItemEdits,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
:items="items"
|
||||
:loading="loading"
|
||||
:row-height="tableRowHeight"
|
||||
server-sort
|
||||
:item-key="primaryKeyField?.field"
|
||||
:show-manual-sort="sortField !== null"
|
||||
:manual-sort-key="sortField"
|
||||
|
||||
Reference in New Issue
Block a user