6. How To - Anbindung des Frontends
Da nun alle Routes und die Backend-Logik implementiert sind, kann das Frontend daran angebunden werden.
6.1 Das Szenario
Durch eine sauber definierte Konfigurationsdatei config.json sollte die unser Übungsmodul Vokabeltrainer bereits in der UebungenLibrary zur Auswahl stehen, wenn man eine neue Übung erstellen wollte. Bei den anderen funktionalen Übungsmodulen öffnet sich hier ein neues Modal, das eine Vue-Komponente zur Übungserstellung enthält. Wir wollen die entsprechende Komponente für den Vokabeltrainer entwickeln. Diese soll aus fünf Schritten bestehen:
- Kurze Beschreibung der Übungsform (Subkomponente
UebungVokabeltrainerCreateIntro.vue) - Auswahl lateinischer Vokabeln aus dem Glossarium (Subkomponente
LemmaComposer) - Eingabe der nötigen Übungsdaten: Titel, Beschreibung, lateinische Vokabeln + Lösungen (Subkomponente
UebungVokabeltrainerCreateForm.vue) - Preview der Übung zur Kontrolle (Subkomponente
UebungVokabeltrainerPreview.vue) - Speichern der Übung (Subkomponente
UebungVokabeltrainerSubmit.vue) Im letzten Schritt werden die Übungsdaten dann an das Backend geschickt, bevor man zurUebungenLibraryzurückkehrt.
6.2 Wichtiges Vorwissen
6.2.1 Was wir an das Backend senden wollen
Das JSON-Objekt, das wir an das Backend senden wollen, sieht ungefähr so aus:
{
"uebung": {
"title": "Titel der Übung",
"description": "Beschreibung der Übung",
"content_data": [
{
"hint": "Numismatik ist die Wissenschaft von ...",
"quantifier": 1,
"value_given": "nummus",
"value_input": "",
"value_expected": "die Münze",
"value_given_info": ", -i"
},
{
"hint": "ital. il negozio",
"quantifier": 1,
"value_given": "negotium",
"value_input": "",
"value_expected": "das Geschäft, die Aufgabe",
"value_given_info": ", -i"
},
{
"hint": "tendere = spannen",
"quantifier": 1,
"value_given": "contendere",
"value_input": "",
"value_expected": "sich anstrengen, kämpfen, eilen, behaupten",
"value_given_info": ", contendo, contendi, contentum"
}
]
}
}In der Datenbank werden für die Übung drei Spalten gefüllt: Neben den selbsterklärenden Werten title und description wird ein JSON-Array names content_data gespeichert, das den linguistischen Inhalt der Übung enthält. Welche JSON-Keys hier vergeben werden, ist unerheblich, jedoch sollte jede Übung ein content_data-Array enthalten. Ich habe mich für folgende JSON-Keys entschieden:
{
"hint": "ital. il negozio", // Ein Hinweis, der nebe der Vokabel angezeigt wird
"quantifier": 1, // Quantifikator für die Auswertung
"value_given": "negotium", // Gegebene Vokabel, die der Nutzer übersetzen muss
"value_input": "", // Die vom Nutzer eingegebene Antwort (noch leer)
"value_expected": "das Geschäft, die Aufgabe", // Die korrekte Antwort
"value_given_info": ", -i" // Zusatzinformationen zu der Vokabel
}6.2.1 Vordefinierte Komponentennamen
Die CREATE-Komponente, die in dem Modal zum Erstellen einer neuen Übung verwendet wird, ist durch die Konfigurationsdatei config.json bereits vordefiniert:
"components": {
"create": {
// ... Pfad zu der Komponente im Modulverzeichnis
"path": "resources\/vue\/UebungVokabeltrainerCreateComponent.vue",
// Kebab-case-Alias zur Einbindung (<uebung-vokabeltrainer-create-component>)
"alias": "uebung-vokabeltrainer-create-component",
// Titel der Komponente, der im Modal zur Übungserstellung angezeigt wird
"title": "Vokabeltrainer: Übung erstellen"
},
// ..
}Alle anderen Optionen (edit, show, print, preview) haben ebenfalls vordefinierte Komponentenpfade.
Es ist möglich, hier andere Komponentennamen einzufügen, allerdings folgen wir hier dem Standard und erstellen im entsprechenden Verzeichnis eine Vue-Single-File-Component.
6.2.2 Ein Store erleichtert die Verwaltung von Zuständen und Daten
Während der Übungserstellung müssen allerhand Daten zwischengespeichert werden, die sich nochmals verändern können, z.B. Übungstitel, Beschreibung, lateinische Vokabeln + Lösungen, Reihenfolge, etc... Diese Daten in einer Komponente zu speichern ist nicht empfehlenswert, da sie in den ganzen Subkomponenten verwendet werden und die Kommunikation zwischen diesen zu managen, recht mühsam ist. Stattdessen verwenden wir einen Pinia-Store, um diese Daten zu verwalten. Im letzten Schritt werden die Daten dann gesammelt aus dem Store an das Backend geschickt.
6.3 Erstellung des Stores
In /modules/Uebungen/UebungVokabeltrainer/resources/vue/stores/uebungVokabeltrainerStore.js:
import { defineStore } from 'pinia'
export const useUebungVokabeltrainerStore = defineStore('uebungVokabeltrainer', {
state: () => ({
// Hier wird die Vokabelauswahl gespeichert
latin_lemma_collection: [],
// Hier werden die Übungsdaten gespeichert
uebung: {
title: "",
description: "",
content_data: []
}
}),
getters: {
getLatinLemmaCollection: (state) => state.latin_lemma_collection,
getUebung: (state) => state.uebung
},
actions: {
setLatinLemmaCollection(collection) {
this.latin_lemma_collection = collection
},
setUebung(uebung) {
this.uebung = uebung
},
updateContentData(contentData) {
this.uebung.content_data = contentData
}
}
})6.4 Erstellung der Create-Komponente
Es wäre natürlich möglich, alle drei Schritte in einer einzigen Vue-Single-File-Component zu implementieren. Im Sinne des Best Practice werden wir in unserer UebungVokabeltrainerCreateComponent.vue eine Stepper-Komponente verwenden, die drei weitere Unterkomponenten enthält.
Hierzu verwenden wir die Hermeneus-Stepper-Komponente HProgressor:
In /modules/Uebungen/UebungVokabeltrainer/resources/vue/UebungVokabeltrainerCreateComponent.vue:
Die HProgressor-Komponente erwartet ein Array von Objekten, die die Schritte für die Stepper-Komponente definiert. Die Komponenten UebungVokabeltrainerCreateIntro, UebungVokabeltrainerCreateForm und UebungVokabeltrainerPreview müssen wir selbst erstellen, während die LemmaComposer-Komponente bereits von Hermeneus zur Verfügung gestellt wird. Sie bietet eine praktische Möglichkeit, lateinische Vokabeln aus dem Glossarium auszuwählen.
<template>
<!-- Die einzelnen Schritte werden in `data` als Array von Objekten definiert -->
<h-progressor :steps="stepData"/>
</template>
<script>
import UebungVokabeltrainerCreateIntro from "./UebungVokabeltrainerCreateIntro.vue";
import UebungVokabeltrainerCreateForm from "./UebungVokabeltrainerCreateForm.vue";
import UebungVokabeltrainerPreview from "./UebungVokabeltrainerPreview.vue";
import UebungVokabeltrainerSubmit from "./UebungVokabeltrainerSubmit.vue";
import { markRaw } from "vue";
import { useUebungVokabeltrainerStore } from "../stores/uebungVokabeltrainerStore";
export default {
name: "uebung-vokabeltrainer-create-component",
store: useUebungVokabeltrainerStore(),
props: [],
components: {
UebungVokabeltrainerCreateIntro,
LemmaComposer,
UebungVokabeltrainerCreateForm,
UebungVokabeltrainerPreview,
UebungVokabeltrainerSubmit
},
data: function () {
return {
// Leere Datenstruktur für die neuen Übungsdaten, die später als `data` die API geschickt werden
uebungData: {
title: '',
description: '',
content_data: []
}
// Schritte für HProgressor
stepData: [
{
id: 1,
position: 0,
component: markRaw(UebungVokabeltrainerCreateIntro),
title: 'Einführung',
},
{
id: 2,
position: 1,
component: markRaw(LemmaComposer),
title: 'Wortschatzauswahl',
isOptional: false,
hasNextStepCondition: true
},
{
id: 3,
position: 2,
component: markRaw(UebungVokabeltrainerCreateForm),
title: 'Erstellen',
isOptional: false,
hasNextStepCondition: true
},
{
id: 4,
position: 3,
component: markRaw(UebungVokabeltrainerPreview),
title: 'Vorschau',
},
{
id: 5,
position: 4,
component: markRaw(UebungVokabeltrainerSubmit),
title: 'Speichern',
isOptional: false,
}
]
},
},
mounted() {
// Hier wird das Event aus dem LemmaComposer abgefangen und die Wortschatzauswahl über im Store aktualisiert.
EventBus.$on('update:latin-lemma-collection', (latin_lemma_collection) => {
this.store.setLatinLemmaCollection(latin_lemma_collection);
}); }
}
</script>
<style> </style>6.5 Beispiel einer Subkomponente
Da die Komponente UebungVokabeltrainerCreateIntro.vue eine reine Anzeigekomponente ist, soll am Beispiel von UebungVokabeltrainerCreateForm.vue gezeigt werden, wie die Daten in den Store geschrieben werden.
6.5.1 Integration von Richtzeit und Gravitas-Komponenten
Hermeneus bietet vorgefertigte Komponenten zur Festlegung der Richtzeit und der Gravitas-Stufe. Diese sollten in der Konfigurationsphase der Übungserstellung integriert werden.
Wichtig: Die Felder cfg_max_gravitas_level und cfg_avg_time_seconds sind bereits im Basis-Model definiert und werden in der Tabelle uebungen gespeichert. Der Entwickler muss hierfür keine eigenen Datenbankfelder anlegen!
<template>
<div>
<h2>Titel und Beschreibung</h2>
<input v-model="store.uebung.title" type="text" placeholder="Titel" />
<input v-model="store.uebung.description" type="text" placeholder="Beschreibung" />
<!-- Gravitas und Richtzeit Komponenten -->
<div>
<uebungen-library-max-gravitas-level-selector
v-model="store.uebung.cfg_max_gravitas_level"
/>
</div>
<div>
<uebungen-library-avg-time-seconds-selector
v-model="store.uebung.cfg_avg_time_seconds"
/>
</div>
<h2>Vokabelangaben</h2>
<div v-for="(word, index) in store.uebung.content_data">
<div v-text="word.value_given"></div>
<input v-model="word.value_given_info" type="text" placeholder="Zusatzinformationen">
<input v-model="word.quantifier" type="number" placeholder="Quantifikator">
<input v-model="word.hint" type="text" placeholder="Hinweis/Tipp">
<input v-model="word.value_expected" type="text" placeholder="Bedeutung" />
</div>
</div>
</template>
<script>
import { useUebungVokabeltrainerStore } from "../stores/uebungVokabeltrainerStore";
import UebungenLibraryMaxGravitasLevelSelector from '@js/uebungen-library/components/ScoringComponents/UebungenLibraryMaxGravitasLevelSelector.vue';
import UebungenLibraryAvgTimeSecondsSelector from '@js/uebungen-library/components/ScoringComponents/UebungenLibraryAvgTimeSecondsSelector.vue';
export default {
name: "UebungVokabeltrainerCreateForm",
components: {
UebungenLibraryMaxGravitasLevelSelector,
UebungenLibraryAvgTimeSecondsSelector
},
data() {
return {
store: useUebungVokabeltrainerStore()
}
},
methods: {
createContentData() {
haxiosAPIClient.post('/uebungen/uebung-vokabeltrainer/create-content-data', {
latin_lemma_collection: this.store.latin_lemma_collection
}).then((response) => {
this.store.uebung.content_data = response.data;
// Setze Standard-Werte für Gravitas und Richtzeit
this.store.uebung.cfg_max_gravitas_level = 3; // 3 Sterne = 30 Gravitas
this.store.uebung.cfg_avg_time_seconds = 0; // Keine Zeitmessung
this.store.setUebung(this.store.uebung);
});
},
},
mounted() {
this.createContentData();
}
}
</script>Die Gravitas-Komponente (UebungenLibraryMaxGravitasLevelSelector)
Diese Komponente zeigt 5 Sterne, wobei jeder Stern 10 Gravitas-Punkten entspricht:
- 0 Sterne = 0 Gravitas (keine Belohnung)
- 3 Sterne = 30 Gravitas (Standard)
- 5 Sterne = 50 Gravitas (Maximum)
Die Richtzeit-Komponente (UebungenLibraryAvgTimeSecondsSelector)
Diese Komponente bietet einen Slider zur Auswahl der Richtzeit:
- 0 Sekunden = Zeitmessung deaktiviert
- 20-600 Sekunden in 20er-Schritten
6.5.2 Store-Struktur erweitern
Der Store muss diese Felder enthalten:
// In uebungVokabeltrainerStore.js
export const useUebungVokabeltrainerStore = defineStore('uebungVokabeltrainer', {
state: () => ({
latin_lemma_collection: [],
uebung: {
title: "",
description: "",
cfg_max_gravitas_level: 3, // Gravitas-Level (0-5)
cfg_avg_time_seconds: 0, // Richtzeit in Sekunden
content_data: []
}
}),
// ... rest des Stores
})6.5.3 Erklärung der ursprünglichen Subkomponente
v-model: Doppelbindung zwischen Input-Feld und Store (für Übungstitel und Beschreibung)- Beim Mounting der Komponente werden die über die Methode
createContentData()ausgewählten Vokabeln in das gewünschte Format fürcontent_dataumgewandelt. Dies werden wir im Backend durchführen. - Der Ersteller kann in dieser Komponente die Vokabelangaben (Vokabel, Bedeutung, Zusatzinformationen, Quantifikator, Hinweis/Tipp) für die Übung hinterlegen.
- Das Vorgehen mit
v-modelsollte funktionieren. Andernfalls muss manuell einchange-Event von den Inputs ausgelöst und die Store-ActionupdateContentData(content_data)aufgerufen werden.
Warum müssen die Daten transformiert werden? Durch den LemmaComposer werden die Daten als LatinLemmaCollection-Array im Store gespeichert und haben folgendes Format:
[
{
lemma: 'nummus',
pos: 'nomen',
info: ', -i',
sense: 'die Münze'
},
{
lemma: 'negotium',
pos: 'nomen',
info: ', -i',
sense: 'das Geschäft'
},
//...
]Sie müssen aber in das oben angegebene Format von content_data umgewandelt werden.
Hierzu erstellen wir eine API-Route in modules/Uebungen/UebungVokabeltrainer/routes/uebung-vokabeltrainer.api.php:
Route::post('/create-content-data', [UebungVokabeltrainerAPIController::class, 'createContentData']);Der UebungVokabeltrainerAPIController-Controller wird in modules/Uebungen/UebungVokabeltrainer/app/Controllers/UebungVokabeltrainerAPIController.php definiert und enthält die createContentData-Methode:
public function createContentData(Request $request)
{
$ContentData = UebungVokabeltrainer::createContentData((new LatinLemmaCollection())->fromArray($request->input('latin_lemma_collection')));
return response()->json($ContentData);
}Die Umwandlung erfolgt in der bereits vorangelegten Methode createContentData des UebungVokabeltrainer-Models:
/**
* Erstellt die nötigen Daten für das content_data-Feld der Datenbank
* @param LatinLemmaCollection $LatinLemmaCollection
* @return LatinLemmaCollection
*/
public static function createContentData(LatinLemmaCollection $LatinLemmaCollection): LatinLemmaCollection
{
return $LatinLemmaCollection->map(function (LatinLemma $LatinLemma) {
return [
'hint' => '',
'quantifier' => 1,
'value_given' => $LatinLemma->getLemma(),
'value_input' => '',
'value_expected' => $LatinLemma->getSense(),
'value_given_info' => $LatinLemma->getInfo()
];
});
}6.6 Abschluss und Submit-Komponente
Hier wird lediglich die relevante Methode der Vue-Komponente dargestellt, die die Übung ans Backend sendet.
methods: {
saveAndReturn() {
// Bereits existierende Route der UebungenAPI
haxiosAPIClient.post('/uebungen/store/uebung-vokabeltrainer', {data: this.store.getUebung}).then(async response => {
// Event, das das Modal schließt und zur UebungenLibrary zurückkehrt
EventBus.$emit('close-create-uebung-modal');
// Event, das in der UebungenLibrary die Übungen neu lädt.
EventBus.$emit('requested-uebungen-fetch');
// Erfolgsnachricht
this.$IrisMessenger.bar({
type: 'success',
message: 'Die Übung wurde erfolgreich gespeichert und kann nun verwendet werden.'
})
})
},
}6.7 Löschfunktion?
Die Löschfunktion ist bereits automatisch über die UebungenAPI in der UebungenLibrary implementiert.
6.8 PRINT-Version der Übung aufrufen
Klickt man in der UebungenLibrary auf den Printbutton, wird per API-URL eine vordefinierte Route aufgerufen, die die PRINT-Version der Übung aufruft, indem es die vom Ersteller vordefinierte Print-Komponente in das UebungenLibraryPrintUebungModal.vue-Modal einbindet.
Man muss also lediglich in der config.json die Print-Komponente für die neue Übung definieren:
"print": {
"path": "resources\/vue\/UebungVokabeltrainerPrintComponent.vue",
"alias": "uebung-vokabeltrainer-print-component",
"title": "Vokabeltrainer: Übung drucken"
}In dieser Komponente können die Übungsdaten für die PRINT-Version aufbereitet und dargestellt werden. Es hat sich bewährt, in CSS die @media print-Query zu nutzen.
/* Im <style></style>-Tag: */
@media print {
/* CSS-Code für die PRINT-Version */
}Für die Druckoption implementiert man einfach eine Methode, die die Browserdruckfunktion aufruft:
// Im <script></script>-Tag unter methods:
print() {
window.print();
}6.9 Digitale Version der Übung aufrufen
Ebenso verfährt man mit der digitalen Version. Man muss lediglich in der config.json die Absolve-Komponente für die neue Übung definieren:
"absolve": {
"path": "resources\/vue\/UebungVokabeltrainerAbsolveComponent.vue",
"alias": "uebung-vokabeltrainer-absolve-component",
"title": "Vokabeltrainer: Übung durchführen"
}Auch diese wird per vordefinierter Route aus der UebungenLibrary aufgerufen, sobald man auf den entsprechenden Button klickt.
6.10 Feedback und Auswertung der Übung / Verteilung von Gravitas
Hier bieten sich zwei Möglichkeiten:
- Feedback nach Durchführung der Übung: Hier führt ein Nutzer die Übung durch, schickt seine Ergebnisse ab und bekommt ein Feedback über die gesamte Übung. Für den Entwickler ist dies ist die unkomplizierteste Variante, da hierzu die Nutzereingaben einfach aus der Absolve-Komponente einfach an die
evaluate-Route der UebungenAPI geschickt werden müssen:
// Im <script></script>-Tag unter methods:
submit() {
haxiosAPIClient.post(`/uebungen/evaluate/${this.uebung.id}`,
{data: this.uebung}).then(async response => {
//... Darstellung der Ergebnisse und Feedback
})
}Die Auswertung erfolgt im Backend über die entsprechende Methode des Models UebungVokabeltrainer:
public function evaluateUebung(Request $request)
{
$content_data = $request->input('content_data');
$feedback_data = $content_data->map(function ($word) {
if ($word['value_input'] === $word['value_expected']) {
$feedback = 'richtig';
$score = $word['quantifier'] * 1;
} else {
$feedback = 'falsch';
$score = $word['quantifier'] * 0;
}
return [
'value_given' => $word['value_given'],
'value_input' => $word['value_input'],
'value_expected' => $word['value_expected'],
'quantifier' => $word['quantifier'],
'value_given_info' => $word['value_given_info'],
'feedback' => $feedback,
'score' => $score
];
});
}Der Rückgabewert wird von der UebungenAPI automatisch in JSON verpackt und zurückgegeben.
Feedback während der Durchführung der Übung (z.B. beim Beenden einer einzelnen Eingabe): Diese Logik muss der Entwickler selbst implementieren. Im besten Fall findet die Auswertung ebenfalls im Backend statt, da auch hier Gravitas verteilt werden kann.
Hier ein grober Fahrplan zur Implementierung:
- Erstellen einer Methode in der Vue-Komponente, die die Feedback-Logik implementiert
- Erstellen einer modulspezifischen Route, in der z.B. die Richtigkeit eines einzelnen Wortes überprüft wird (unter:
/modules/Uebungen/UebungVokabeltrainer/routes/uebung-vokabeltrainer.api.php) - Erstellen eines modulspezifischen Controllers (unter:
/modules/Uebungen/UebungVokabeltrainer/app/Controllers/UebungVokabeltrainerEvaluationAPIController.php) - Implementieren der Feedback-Logik im Controller
Theoretisch ist auch eine Mischform möglich, z.B. indem der Nutzer unmittelbar Rückmeldung bekommt, aber bei falschen Antworten Punktabzug (nicht Gravitas) bekommt. Am Ende wird die Punktzahl dann in der zentralen evaluate-Route der UebungenAPI in Gravitas umgerechnet.
6.11 Die Übung bearbeiten
Die Bearbeitungsfunktion ermöglicht es, eine bereits erstellte Übung nachträglich zu modifizieren. Im Gegensatz zur Create-Funktion arbeiten wir hier mit bereits existierenden Daten.
6.11.1 Grundkonzept für Anfänger
Bei der Edit-Funktionalität müssen wir:
- Die bestehenden Übungsdaten vom Backend laden
- Diese in einem separaten Store speichern (um Konflikte mit dem Create-Store zu vermeiden)
- Die Daten bearbeiten lassen
- Die geänderten Daten ans Backend zurückschicken
6.11.2 Der Edit-Store
Erstellen Sie einen separaten Store für die Edit-Funktionalität:
// In /modules/Uebungen/UebungVokabeltrainer/resources/vue/stores/uebungVokabeltrainerEditStore.js
import { defineStore } from 'pinia'
export const useUebungVokabeltrainerEditStore = defineStore('uebungVokabeltrainerEdit', {
state: () => ({
// Kopie der originalen Daten (für "Änderungen verwerfen")
originalUebung: null,
// Die aktuell bearbeiteten Daten
uebung: {
title: "",
description: "",
cfg_max_gravitas_level: 0,
cfg_avg_time_seconds: 0,
content_data: [],
// Wichtig: Die base_uebung enthält die ID und andere Metadaten
base_uebung: null
}
}),
getters: {
// Prüft, ob Änderungen vorgenommen wurden
hasChanges: (state) => {
return JSON.stringify(state.uebung) !== JSON.stringify(state.originalUebung);
}
},
actions: {
// Lädt die Übungsdaten und speichert eine Kopie
setUebung(uebung) {
this.uebung = uebung;
this.originalUebung = JSON.parse(JSON.stringify(uebung)); // Deep copy
},
// Setzt auf Originalzustand zurück
resetChanges() {
this.uebung = JSON.parse(JSON.stringify(this.originalUebung));
},
updateContentData(contentData) {
this.uebung.content_data = contentData;
}
}
})6.11.3 Die Edit-Komponente
<template>
<div>
<h2>Übung bearbeiten</h2>
<!-- Titel und Beschreibung -->
<div>
<label>Titel:</label>
<input v-model="store.uebung.title" type="text" />
</div>
<div>
<label>Beschreibung:</label>
<textarea v-model="store.uebung.description"></textarea>
</div>
<!-- Gravitas und Richtzeit -->
<div>
<label>Gravitas-Stufe:</label>
<uebungen-library-max-gravitas-level-selector
v-model="store.uebung.cfg_max_gravitas_level"
/>
</div>
<div>
<label>Richtzeit:</label>
<uebungen-library-avg-time-seconds-selector
v-model="store.uebung.cfg_avg_time_seconds"
/>
</div>
<!-- Vokabelliste -->
<h3>Vokabeln</h3>
<div v-for="(item, index) in store.uebung.content_data" :key="index">
<span>{{ item.value_given }}</span>
<input v-model="item.hint" type="text" placeholder="Hinweis" />
<input v-model="item.value_expected" type="text" placeholder="Bedeutung" />
<button @click="removeItem(index)">Entfernen</button>
</div>
<!-- Speichern Button -->
<button @click="saveUebung">Speichern</button>
</div>
</template>
<script>
import { useUebungVokabeltrainerEditStore } from "./stores/uebungVokabeltrainerEditStore";
import { haxiosAPIClient } from "@js/haxios/haxios.js";
export default {
name: "uebung-vokabeltrainer-edit-component",
props: {
// Die Übungsdaten werden als Prop übergeben
data: {
type: Object,
required: true
}
},
data() {
return {
store: useUebungVokabeltrainerEditStore()
};
},
methods: {
saveUebung() {
// Wichtig: Die ID kommt aus base_uebung!
const uebungId = this.store.uebung.base_uebung.id;
haxiosAPIClient.post(`/uebungen/update/${uebungId}`, {
data: this.store.uebung
}).then(response => {
console.log('Übung gespeichert!');
// Events für Modal und Liste
EventBus.$emit('close-edit-uebung-modal');
EventBus.$emit('requested-uebungen-fetch');
});
},
removeItem(index) {
this.store.uebung.content_data.splice(index, 1);
}
},
mounted() {
// Lade die Übungsdaten in den Store
this.store.setUebung(this.data.uebung);
}
};
</script>6.11.4 Wichtige Punkte
Die base_uebung Struktur: Beim Editieren erhalten wir diese Datenstruktur:
{
title: "Meine Übung",
description: "Beschreibung",
cfg_max_gravitas_level: 3,
cfg_avg_time_seconds: 120,
content_data: [...],
base_uebung: {
id: 12345, // Diese ID brauchen wir für Updates!
// ... andere Metadaten
}
}Der Update-Prozess:
- Die Daten kommen über das
dataProp - Wir speichern sie im Store
- Bei Änderungen nutzen wir v-model
- Beim Speichern verwenden wir die ID aus
base_uebung
6.11 Die Übung bearbeiten
Die Bearbeitungsfunktion ermöglicht es, eine bereits erstellte Übung nachträglich zu modifizieren. Hierzu muss in der config.json die Edit-Komponente definiert werden:
"edit": {
"path": "resources/vue/UebungVokabeltrainerEditComponent.vue",
"alias": "uebung-vokabeltrainer-edit-component",
"title": "Vokabeltrainer: Übung bearbeiten"
}6.11.1 Grundkonzept
Bei der Edit-Funktionalität arbeiten wir mit bereits existierenden Daten. Der Ablauf ist:
- Die Übungsdaten werden geladen (als Prop übergeben)
- Der Nutzer kann sie bearbeiten
- Die geänderten Daten werden ans Backend geschickt
6.11.2 Einfacher Edit-Store
// In /modules/Uebungen/UebungVokabeltrainer/resources/vue/stores/uebungVokabeltrainerEditStore.js
import { defineStore } from 'pinia'
export const useUebungVokabeltrainerEditStore = defineStore('uebungVokabeltrainerEdit', {
state: () => ({
uebung: {
title: "",
description: "",
cfg_max_gravitas_level: 0,
cfg_avg_time_seconds: 0,
content_data: [],
base_uebung: null // Wichtig: Enthält die ID für Updates
}
}),
actions: {
setUebung(uebung) {
this.uebung = uebung;
},
updateContentData(contentData) {
this.uebung.content_data = contentData;
}
}
})6.11.3 Die Edit-Komponente (vereinfacht)
<template>
<div>
<h2>Übung bearbeiten</h2>
<!-- Titel und Beschreibung -->
<div>
<label>Titel:</label>
<input v-model="store.uebung.title" type="text" />
</div>
<div>
<label>Beschreibung:</label>
<textarea v-model="store.uebung.description"></textarea>
</div>
<!-- Gravitas und Richtzeit -->
<div>
<label>Gravitas-Stufe:</label>
<uebungen-library-max-gravitas-level-selector
v-model="store.uebung.cfg_max_gravitas_level"
/>
</div>
<div>
<label>Richtzeit:</label>
<uebungen-library-avg-time-seconds-selector
v-model="store.uebung.cfg_avg_time_seconds"
/>
</div>
<!-- Vokabelliste -->
<h3>Vokabeln</h3>
<div v-for="(item, index) in store.uebung.content_data" :key="index">
<span>{{ item.value_given }}</span>
<input v-model="item.hint" type="text" placeholder="Hinweis" />
<input v-model="item.value_expected" type="text" placeholder="Bedeutung" />
<button @click="removeItem(index)">Entfernen</button>
</div>
<!-- Speichern Button -->
<button @click="saveUebung">Speichern</button>
</div>
</template>
<script>
import { useUebungVokabeltrainerEditStore } from "./stores/uebungVokabeltrainerEditStore";
import { haxiosAPIClient } from "@js/haxios/haxios.js";
export default {
name: "uebung-vokabeltrainer-edit-component",
props: {
// Die Übungsdaten werden als Prop übergeben
data: {
type: Object,
required: true
}
},
data() {
return {
store: useUebungVokabeltrainerEditStore()
};
},
methods: {
saveUebung() {
// Wichtig: Die ID kommt aus base_uebung!
const uebungId = this.store.uebung.base_uebung.id;
haxiosAPIClient.post(`/uebungen/update/${uebungId}`, {
data: this.store.uebung
}).then(response => {
console.log('Übung gespeichert!');
// Events für Modal und Liste
EventBus.$emit('close-edit-uebung-modal');
EventBus.$emit('requested-uebungen-fetch');
});
},
removeItem(index) {
this.store.uebung.content_data.splice(index, 1);
}
},
mounted() {
// Lade die Übungsdaten in den Store
this.store.setUebung(this.data.uebung);
}
};
</script>6.11.4 Wichtige Punkte
Die base_uebung Struktur: Beim Editieren erhalten wir diese Datenstruktur:
{
title: "Meine Übung",
description: "Beschreibung",
cfg_max_gravitas_level: 3,
cfg_avg_time_seconds: 120,
content_data: [...],
base_uebung: {
id: 12345, // Diese ID brauchen wir für Updates!
// ... andere Metadaten
}
}Der Update-Prozess:
- Die Daten kommen über das
dataProp - Wir speichern sie im Store
- Bei Änderungen nutzen wir v-model
- Beim Speichern verwenden wir die ID aus
base_uebung