Metadata Store Examples¶
What a screen can draw from the metadata alone, without a line written per model. Every example awaits the store: getMetadata and getMetadatas return promises.
Form fields from the metadata¶
The fields a form edits, with what an input needs to know about each:
import { useMetadataStore } from 'vue-fastedgy';
async function formFields(model) {
const metadata = await useMetadataStore().getMetadata(model);
return Object.values(metadata?.fields ?? {})
.filter((field) => !field.readonly && !field.target)
.map((field) => ({
name: field.name,
label: field.label,
type: field.type,
required: field.required,
choices: field.choices ? Object.entries(field.choices) : null,
default: field.default,
}));
}
The relations are left out here: an input for one picks a record of its target.
Required fields¶
The metadata says which fields a new record must give, not their lengths or their ranges: the server checks those when the record is written.
function missingFields(metadata, values) {
return Object.values(metadata.fields)
.filter((field) => field.required && (values[field.name] ?? '') === '')
.map((field) => field.label);
}
Table columns¶
The label and the type of a column, a path through the relations included:
import { resolveFieldPath, useMetadataStore } from 'vue-fastedgy';
async function tableColumns(model, keys) {
const metadatas = await useMetadataStore().getMetadatas();
return keys.map((key) => {
const field = resolveFieldPath(metadatas, model, key)?.field;
return { key, label: field?.label ?? key, type: field?.type ?? null };
});
}
await tableColumns('product', ['name', 'price', 'category.name']);
useDataTable takes the type of its columns from the metadata the same way.
Model catalogue¶
Every model the server describes, with its label and what it offers:
import { useMetadataStore } from 'vue-fastedgy';
async function modelCatalogue() {
const metadatas = await useMetadataStore().getMetadatas();
return Object.values(metadatas ?? {}).map((metadata) => ({
name: metadata.name,
label: metadata.label_plural,
searchable: metadata.searchable,
sortable: metadata.sortable,
fields: Object.keys(metadata.fields).length,
}));
}
A dynamic form component¶
A form for any model, saving a new record through useApiModel:
<template>
<form v-if="fields.length" @submit.prevent="save">
<div v-for="field in fields" :key="field.name">
<label :for="field.name">
{{ field.label }}
<span v-if="field.required">*</span>
</label>
<select v-if="field.choices" :id="field.name" v-model="values[field.name]" :required="field.required">
<option v-for="(label, value) in field.choices" :key="value" :value="value">{{ label }}</option>
</select>
<input v-else-if="field.type === 'boolean'" :id="field.name" v-model="values[field.name]" type="checkbox" />
<input
v-else-if="NUMBER_TYPES.includes(field.type)"
:id="field.name"
v-model.number="values[field.name]"
type="number"
:required="field.required"
/>
<input
v-else-if="field.type === 'date'"
:id="field.name"
v-model="values[field.name]"
type="date"
:required="field.required"
/>
<textarea
v-else-if="field.type === 'text'"
:id="field.name"
v-model="values[field.name]"
:required="field.required"
/>
<input v-else :id="field.name" v-model="values[field.name]" type="text" :required="field.required" />
</div>
<p v-if="failed" role="alert">The record could not be saved.</p>
<button type="submit" :disabled="saving">{{ saving ? 'Saving...' : 'Save' }}</button>
</form>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue';
import { useApiModel, useMetadataStore } from 'vue-fastedgy';
const NUMBER_TYPES = ['integer', 'small_integer', 'big_integer', 'float', 'decimal'];
const props = defineProps({ model: { type: String, required: true } });
const emit = defineEmits(['saved']);
const metadataStore = useMetadataStore();
const api = useApiModel(props.model);
const fields = ref([]);
const values = reactive({});
const saving = ref(false);
const failed = ref(false);
onMounted(async () => {
const metadata = await metadataStore.getMetadata(props.model);
fields.value = Object.values(metadata?.fields ?? {}).filter((field) => !field.readonly && !field.target);
for (const field of fields.value) {
values[field.name] = field.default ?? (field.type === 'boolean' ? false : null);
}
});
const save = async () => {
saving.value = true;
failed.value = false;
try {
const response = await api.create({ ...values });
emit('saved', response.data);
} catch {
failed.value = true;
} finally {
saving.value = false;
}
};
</script>
Model field inspector¶
A component listing what the metadata says of each field of a model:
<template>
<section>
<h3>{{ metadata?.label ?? model }}</h3>
<p v-if="metadataStore.loading">Loading the fields...</p>
<dl v-else-if="metadata">
<template v-for="field in metadata.fields" :key="field.name">
<dt>{{ field.label }} <code>{{ field.name }}</code></dt>
<dd>
{{ field.type }}
<span v-if="field.target">to {{ field.target }}</span>
<span v-if="field.required">, required</span>
<span v-if="field.readonly">, read only</span>
<span v-if="field.choices">: {{ Object.values(field.choices).join(', ') }}</span>
</dd>
</template>
</dl>
</section>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { useMetadataStore } from 'vue-fastedgy';
const props = defineProps({ model: { type: String, required: true } });
const metadataStore = useMetadataStore();
const metadata = ref(null);
onMounted(async () => {
metadata.value = await metadataStore.getMetadata(props.model);
});
</script>