add support for relational sorting

This commit is contained in:
Nitwel
2020-10-16 17:33:03 +02:00
parent 9db874129e
commit e49a433ed3
7 changed files with 159 additions and 24 deletions

View File

@@ -1,5 +1,5 @@
<template>
<v-notice v-if="!collectionField" type="warning">
<v-notice v-if="!collectionField && !collection" type="warning">
{{ $t('collection_field_not_setup') }}
</v-notice>
<v-notice v-else-if="selectItems.length === 0" type="warning">
@@ -27,6 +27,10 @@ export default defineComponent({
type: String,
default: null,
},
collection: {
type: String,
default: null,
},
typeAllowList: {
type: Array as PropType<string[]>,
default: () => [],
@@ -54,8 +58,8 @@ export default defineComponent({
const values = inject('values', ref<Record<string, any>>({}));
const fields = computed(() => {
if (!props.collectionField) return [];
return fieldsStore.getFieldsForCollection(values.value[props.collectionField]);
if (!props.collectionField && !props.collection) return [];
return fieldsStore.getFieldsForCollection(props.collection || values.value[props.collectionField]);
});
const selectItems = computed(() =>
@@ -74,7 +78,7 @@ export default defineComponent({
})
);
return { selectItems };
return { selectItems, values };
},
});
</script>

View File

@@ -5,12 +5,16 @@
<div class="one-to-many" v-else>
<v-table
:loading="loading"
:items="items"
:items="sortedItems || items"
:headers.sync="tableHeaders"
show-resize
inline
:sort.sync="sort"
@update:items="sortItems($event)"
@click:row="editItem"
:disabled="disabled"
:show-manual-sort="sortField !== null"
:manual-sort-key="sortField"
>
<template v-for="header in tableHeaders" v-slot:[`item.${header.value}`]="{ item }">
<render-display
@@ -73,6 +77,7 @@ import useRelation from './use-relation';
import usePreview from './use-preview';
import useEdit from './use-edit';
import useSelection from './use-selection';
import useSort from './use-sort';
export default defineComponent({
components: { ModalItem, ModalCollection },
@@ -93,6 +98,10 @@ export default defineComponent({
type: String,
required: true,
},
sortField: {
type: String,
default: null,
},
fields: {
type: Array as PropType<string[]>,
default: () => [],
@@ -103,7 +112,7 @@ export default defineComponent({
},
},
setup(props, { emit }) {
const { value, collection, field, fields } = toRefs(props);
const { value, collection, field, fields, sortField } = toRefs(props);
function emitter(newVal: any[] | null) {
emit('input', newVal);
@@ -127,6 +136,7 @@ export default defineComponent({
const { tableHeaders, items, loading, error } = usePreview(
value,
fields,
sortField,
relationInfo,
getNewSelectedItems,
getUpdatedItems,
@@ -151,6 +161,8 @@ export default defineComponent({
emitter
);
const { sort, sortItems, sortedItems } = useSort(sortField, fields, items, emitter);
return {
junction,
relation,
@@ -172,6 +184,9 @@ export default defineComponent({
relatedPrimaryKey,
get,
editModalActive,
sort,
sortItems,
sortedItems,
};
},
});

View File

@@ -13,6 +13,15 @@
"
/>
</div>
<div class="field half">
<p class="type-label">{{ $t('sort_field') }}</p>
<interface-field
v-model="sortField"
:collection="junctionCollection"
:type-allow-list="['bigInteger', 'integer']"
allowNone
></interface-field>
</div>
</div>
</template>
@@ -52,6 +61,19 @@ export default defineComponent({
setup(props, { emit }) {
const collectionsStore = useCollectionsStore();
const relationsStore = useRelationsStore();
const sortField = computed({
get() {
return props.value?.sortField;
},
set(newFields: string) {
emit('input', {
...(props.value || {}),
sortField: newFields,
});
},
});
const fields = computed({
get() {
return props.value?.fields;
@@ -63,6 +85,7 @@ export default defineComponent({
});
},
});
const junctionCollection = computed(() => {
if (!props.fieldData || !props.relations || props.relations.length === 0) return null;
const { field } = props.fieldData;
@@ -71,12 +94,14 @@ export default defineComponent({
);
return junctionRelation?.many_collection || null;
});
const junctionCollectionExists = computed(() => {
return !!collectionsStore.state.collections.find(
(collection) => collection.collection === junctionCollection.value
);
});
return { fields, junctionCollection, junctionCollectionExists };
return { fields, sortField, junctionCollection, junctionCollectionExists };
},
});
</script>

View File

@@ -9,6 +9,7 @@ import { cloneDeep, get } from 'lodash';
export default function usePreview(
value: Ref<(string | number | Record<string, any>)[] | null>,
fields: Ref<string[]>,
sortField: Ref<string | null>,
relation: Ref<RelationInfo>,
getNewSelectedItems: () => Record<string, any>[],
getUpdatedItems: () => Record<string, any>[],
@@ -124,6 +125,9 @@ export default function usePreview(
if (filteredFields.includes(junctionPkField) === false) filteredFields.push(junctionPkField);
if (filteredFields.includes(junctionField) === false) filteredFields.push(junctionField);
if (sortField.value !== null && filteredFields.includes(sortField.value) === false)
filteredFields.push(sortField.value);
data = await request(junctionCollection, filteredFields, junctionPkField, primaryKeys);
}

View File

@@ -0,0 +1,35 @@
import { Ref, ref, computed } from '@vue/composition-api';
import { Sort } from '@/components/v-table/types';
import { sortBy } from 'lodash';
export default function useSort(
sortField: Ref<string | null>,
fields: Ref<string[]>,
items: Ref<Record<string, any>[]>,
emit: (newVal: any[] | null) => void
) {
const sort = ref<Sort>({ by: sortField.value || fields.value[0], desc: false });
function sortItems(newItems: Record<string, any>[]) {
const sField = sortField.value;
if (sField === null) return;
const itemsSorted = newItems.map((item, i) => {
item[sField] = i;
return item;
});
emit(itemsSorted);
}
const sortedItems = computed(() => {
const sField = sortField.value;
if (sField === null || sort.value.by !== sField) return null;
const desc = sort.value.desc;
const sorted = sortBy(items.value, [sField]);
return desc ? sorted.reverse() : sorted;
});
return { sort, sortItems, sortedItems };
}

View File

@@ -5,10 +5,12 @@
<div class="one-to-many" v-else>
<v-table
:loading="loading"
:items.sync="displayItems"
:items="sortedItems || items"
:headers.sync="tableHeaders"
show-resize
inline
:sort.sync="sort"
@update:items="sortItems($event)"
@click:row="editItem"
:disabled="disabled"
:show-manual-sort="sortField !== null"
@@ -69,9 +71,10 @@ import useCollection from '@/composables/use-collection';
import { useCollectionsStore, useRelationsStore, useFieldsStore } from '@/stores/';
import ModalItem from '@/views/private/components/modal-item';
import ModalCollection from '@/views/private/components/modal-collection';
import { Sort } from '@/components/v-table/types';
import { Filter, Field } from '@/types';
import { Header } from '@/components/v-table/types';
import { isEqual } from 'lodash';
import { isEqual, sortBy } from 'lodash';
export default defineComponent({
components: { ModalItem, ModalCollection },
@@ -96,6 +99,10 @@ export default defineComponent({
type: Array as PropType<string[]>,
default: () => [],
},
sortField: {
type: String,
default: null,
},
disabled: {
type: Boolean,
default: false,
@@ -106,14 +113,11 @@ export default defineComponent({
const collectionsStore = useCollectionsStore();
const fieldsStore = useFieldsStore();
const sortField = computed(() => {
return relatedCollection.value.meta?.sort_field || null;
});
const { relation, relatedCollection, relatedPrimaryKeyField } = useRelation();
const { tableHeaders, displayItems, loading, error } = useTable();
const { tableHeaders, items, loading, error } = useTable();
const { currentlyEditing, editItem, editsAtStart, stageEdits, cancelEdit } = useEdits();
const { stageSelection, selectModalActive, selectionFilters } = useSelection();
const { sort, sortItems, sortedItems } = useSort();
return {
relation,
@@ -128,9 +132,11 @@ export default defineComponent({
stageSelection,
selectModalActive,
deleteItem,
displayItems,
items,
sortItems,
selectionFilters,
sortField,
sort,
sortedItems,
};
function getItem(id: string | number) {
@@ -207,6 +213,31 @@ export default defineComponent({
);
}
function useSort() {
const sort = ref<Sort>({ by: props.sortField || props.fields[0], desc: false });
function sortItems(newItems: Record<string, any>[]) {
if (props.sortField === null) return;
const itemsSorted = newItems.map((item, i) => {
item[props.sortField] = i;
return item;
});
emit('input', itemsSorted);
}
const sortedItems = computed(() => {
if (props.sortField === null || sort.value.by !== props.sortField) return null;
const desc = sort.value.desc;
const sorted = sortBy(items.value, [props.sortField]);
return desc ? sorted.reverse() : sorted;
});
return { sort, sortItems, sortedItems };
}
/**
* Holds info about the current relationship, like related collection, primary key field
* of the other collection etc
@@ -230,7 +261,7 @@ export default defineComponent({
// values if it needs to. This allows the user to manually resize the columns for example
const tableHeaders = ref<Header[]>([]);
const loading = ref(false);
const displayItems = ref<Record<string, any>[]>([]);
const items = ref<Record<string, any>[]>([]);
const error = ref(null);
watch(
@@ -245,8 +276,8 @@ export default defineComponent({
fields.push(pkField);
}
if (sortField.value !== null && fields.includes(sortField.value) === false)
fields.push(sortField.value);
if (props.sortField !== null && fields.includes(props.sortField) === false)
fields.push(props.sortField);
try {
const endpoint = relatedCollection.value.collection.startsWith('directus_')
@@ -271,7 +302,7 @@ export default defineComponent({
const updatedItems = getUpdatedItems();
const newItems = getNewItems();
displayItems.value = existingItems
items.value = existingItems
.map((item) => {
const updatedItem = updatedItems.find((updated) => updated[pkField] === item[pkField]);
if (updatedItem !== undefined) return updatedItem;
@@ -321,7 +352,7 @@ export default defineComponent({
{ immediate: true }
);
return { tableHeaders, displayItems, loading, error };
return { tableHeaders, items, loading, error };
}
function useEdits() {
@@ -386,11 +417,11 @@ export default defineComponent({
const selectModalActive = ref(false);
const selectedPrimaryKeys = computed<(number | string)[]>(() => {
if (displayItems.value === null) return [];
if (items.value === null) return [];
const pkField = relatedPrimaryKeyField.value.field;
return displayItems.value
return items.value
.filter((currentItem) => pkField in currentItem)
.map((currentItem) => currentItem[pkField]);
});

View File

@@ -11,6 +11,15 @@
:inject="relatedCollectionExists ? null : { fields: newFields, collections: newCollections, relations }"
/>
</div>
<div class="field half">
<p class="type-label">{{ $t('sort_field') }}</p>
<interface-field
v-model="sortField"
:collection="relatedCollection"
:type-allow-list="['bigInteger', 'integer']"
allowNone
></interface-field>
</div>
</div>
</template>
@@ -62,6 +71,18 @@ export default defineComponent({
},
});
const sortField = computed({
get() {
return props.value?.sortField;
},
set(newFields: string) {
emit('input', {
...(props.value || {}),
sortField: newFields,
});
},
});
const relatedCollection = computed(() => {
if (!props.fieldData || !props.relations || props.relations.length === 0) return null;
const { field } = props.fieldData;
@@ -77,7 +98,7 @@ export default defineComponent({
);
});
return { fields, relatedCollection, relatedCollectionExists };
return { fields, sortField, relatedCollection, relatedCollectionExists };
},
});
</script>