Allow for displaying lists in render template (#15243)

* support arrays in render-template

* allow for rendering lists in render-template
This commit is contained in:
Nitwel
2022-08-24 21:22:10 +02:00
committed by GitHub
parent 61a8cca102
commit ba3494572a

View File

@@ -20,8 +20,8 @@
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed, ref } from 'vue';
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useFieldsStore } from '@/stores/fields';
import { get } from 'lodash';
import { Field } from '@directus/shared/types';
@@ -29,96 +29,123 @@ import { getDisplay } from '@/displays';
import { getDefaultDisplayForType } from '@/utils/get-default-display-for-type';
import { translate } from '@/utils/translate-object-values';
export default defineComponent({
props: {
collection: {
type: String,
default: null,
},
fields: {
type: Array as PropType<Field[]>,
default: null,
},
item: {
type: Object as PropType<Record<string, any>>,
default: null,
},
template: {
type: String,
required: true,
},
direction: {
type: String,
default: undefined,
},
},
setup(props) {
const fieldsStore = useFieldsStore();
interface Props {
template: string;
collection?: string;
fields?: Field[];
item?: Record<string, any>;
direction?: string;
}
const templateEl = ref<HTMLElement>();
const regex = /({{.*?}})/g;
const parts = computed(() =>
props.template
.split(regex)
.filter((p) => p)
.map((part) => {
if (part.startsWith('{{') === false) return part;
let fieldKey = part.replace(/{{/g, '').replace(/}}/g, '').trim();
const field: Field | undefined =
fieldsStore.getField(props.collection, fieldKey) || props.fields?.find((field) => field.field === fieldKey);
/**
* This is for cases where you are rendering a display template directly on
* directus_files. The $thumbnail fields doesn't exist, but instead renders a
* thumbnail based on the other fields in the file info. In that case, the value
* should be the whole related file object, not just the fake "thumbnail" field. By
* stripping out the thumbnail part in the field key path, the rest of the function
* will extract the value correctly.
*/
if (field && field.collection === 'directus_files' && field.field === '$thumbnail') {
fieldKey = fieldKey
.split('.')
.filter((part) => part !== '$thumbnail')
.join('.');
}
// Try getting the value from the item, return some question marks if it doesn't exist
const value = get(props.item, fieldKey);
if (value === undefined) return null;
if (!field) return value;
const display = field?.meta?.display || getDefaultDisplayForType(field.type);
// No need to render the empty display overhead in this case
if (display === 'raw') return value;
const displayInfo = getDisplay(field.meta?.display);
// If used display doesn't exist in the current project, return raw value
if (!displayInfo) return value;
return {
component: field.meta?.display,
options: field.meta?.display_options,
value: value,
interface: field.meta?.interface,
interfaceOptions: field.meta?.options,
type: field.type,
collection: field.collection,
field: field.field,
};
})
.map((p) => p ?? null)
);
return { parts, templateEl, translate };
},
const props = withDefaults(defineProps<Props>(), {
collection: undefined,
fields: () => [],
item: () => ({}),
direction: undefined,
});
const fieldsStore = useFieldsStore();
const templateEl = ref<HTMLElement>();
const regex = /({{.*?}})/g;
const parts = computed(() =>
props.template
.split(regex)
.filter((p) => p)
.map((part) => {
if (part.startsWith('{{') === false) return part;
let fieldKey = part.replace(/{{/g, '').replace(/}}/g, '').trim();
let fieldKeyBefore = fieldKey.split('.').slice(0, -1).join('.');
let fieldKeyAfter = fieldKey.split('.').slice(-1)[0];
// Try getting the value from the item, return some question marks if it doesn't exist
let value = get(props.item, fieldKeyBefore);
return Array.isArray(value) ? handleArray(fieldKeyBefore, fieldKeyAfter) : handleObject(fieldKey);
})
.map((p) => p ?? null)
);
function handleArray(fieldKeyBefore: string, fieldKeyAfter: string) {
const value = get(props.item, fieldKeyBefore);
const field =
fieldsStore.getField(props.collection, fieldKeyBefore) ||
props.fields?.find((field) => field.field === fieldKeyBefore);
if (value === undefined) return null;
if (!field) return value;
const displayInfo = getDisplay(field.meta?.display);
let component = field.meta?.display;
let options = field.meta?.display_options;
if (!displayInfo) {
component = 'related-values';
options = { template: `{{${fieldKeyAfter}}}` };
}
return {
component,
options,
value: value,
interface: field.meta?.interface,
interfaceOptions: field.meta?.options,
type: field.type,
collection: field.collection,
field: field.field,
};
}
function handleObject(fieldKey: string) {
const value = get(props.item, fieldKey);
const field =
fieldsStore.getField(props.collection, fieldKey) || props.fields?.find((field) => field.field === fieldKey);
if (value === undefined) return null;
/**
* This is for cases where you are rendering a display template directly on
* directus_files. The $thumbnail fields doesn't exist, but instead renders a
* thumbnail based on the other fields in the file info. In that case, the value
* should be the whole related file object, not just the fake "thumbnail" field. By
* stripping out the thumbnail part in the field key path, the rest of the function
* will extract the value correctly.
*/
if (field && field.collection === 'directus_files' && field.field === '$thumbnail') {
fieldKey = fieldKey
.split('.')
.filter((part) => part !== '$thumbnail')
.join('.');
}
if (!field) return value;
const display = field?.meta?.display || getDefaultDisplayForType(field.type);
// No need to render the empty display overhead in this case
if (display === 'raw') return value;
const displayInfo = getDisplay(field.meta?.display);
// If used display doesn't exist in the current project, return raw value
if (!displayInfo) return value;
return {
component: field.meta?.display,
options: field.meta?.display_options,
value: value,
interface: field.meta?.interface,
interfaceOptions: field.meta?.options,
type: field.type,
collection: field.collection,
field: field.field,
};
}
</script>
<style lang="scss" scoped>