JavaScript

Formulare und Navigation mit dem Vue Router

Navigation in der SPA: Vue Router vorgestellt
Keine Kommentare

Dieser Teil der Artikelserie zu Vue widmet sich der Navigation innerhalb einer Applikation. In einer Single Page Application haben Sie ja grundsätzlich das Problem, dass ein Neuladen der Seite beziehungsweise das Navigieren von einer Unterseite zu einer anderen den lokalen State der Applikation im Browser zurücksetzt. Um dies zu vermeiden, existieren für alle modernen Frontend Frameworks Erweiterungen, die eine Navigation zwischen verschiedenen Ansichten und damit unterschiedlichen Komponentenbäumen ermöglichen.

Zur Demonstration der Implementierung einer Navigation innerhalb von Single Page Applications erweitern wir zunächst die To-do-Applikation aus den ersten beiden Teilen der Serie (siehe Entwickler Magazin 4.18 und 1.19) um ein Formular zum Erstellen und Modifizieren von To-dos und steuern dieses anschließend mit dem Vue Router an. Bevor Sie das Routing für Ihre Applikation implementieren, muss eine Möglichkeit zur Erzeugung von Datensätzen in die Anwendung eingefügt werden. Dazu generieren Sie eine neue Komponente, die Sie unterhalb der To-do-Liste anzeigen. Hierbei handelt es sich um ein einfaches Formular, über das Sie den Titel und den Status einer Aufgabe eingeben können. Wie das in der Applikation aussehen soll, sehen Sie in Abbildung 1.

Abb. 1: Inline-Formular zur Erzeugung neuer Aufgaben

Abb. 1: Inline-Formular zur Erzeugung neuer Aufgaben

Formulare werden von Vue nativ unterstützt. Das bedeutet, dass Sie keinerlei zusätzliche Pakete installieren müssen. Die wichtigste Funktionalität einer Frontend-Bibliothek wie Vue im Zusammenhang mit Formularen ist die Synchronisierung der Formulareingaben mit den Datenstrukturen der Komponenten. Diese Aufgabe erfüllt die v-model-Direktive. Sie übernimmt das Two-Way-Databinding, über das die Eigenschaften der Komponente mit dem Formular synchronisiert werden.

Neue Aufgaben erzeugen

Der erste Schritt der Implementierung besteht aus der Erzeugung der Formularkomponente. Zunächst legen Sie eine Datei mit dem Namen Form.vue im components-Verzeichnis der Applikation an. Auch diese folgt der Konvention der Single File Components mit der Dreiteilung in Template, JavaScript-Code und Styling. Den Quellcode der Komponente finden Sie in Listing 1.

<template>
  <form @submit.prevent="handleSubmit">
    <input
      autofocus
      id="title"
      name="title"
      type="text"
      v-model="title"
      placeholder="Neue Aufgabe erstellen"
    >
    <select id="done" name="done" v-model="done">
      <option :value="false">offen</option>>
      <option :value="true">erledigt</option>>
    </select>
    <button type="submit">OK</button>
  </form>
</template>

<script>
import { createNamespacedHelpers } from "vuex";

const { mapActions } = createNamespacedHelpers("todo");
import { CREATE_TODO } from "../todo";

export default {
  methods: {
    ...mapActions({ createTodo: CREATE_TODO }),
    handleSubmit() {
      this.createTodo({
        title: this.title,
        done: this.done
      });
      this.title = "";
      this.done = false;
    }
  },
  data() {
    return {
      title: "",
      done: false
    };
  }
};
</script>

<style scoped>
...
</style>

Das Template der Komponente besteht im Kern aus einem Formularelement. An dessen Submit-Event wird die handleSubmit-Methode gebunden. Durch die Erweiterung .prevent des Event-Attributs wird verhindert, dass die Standardaktion, also das Absenden des Formulars ausgeführt wird. Das Formular besteht aus drei Elementen: einem Eingabefeld für den Titel der Aufgabe, einem Dropdown für den Status, also ob die Aufgabe noch offen oder bereits erledigt ist, und schließlich einem Button zum Abschicken des Formulars.

IT Security Summit 2019

Sichere Logins sind doch ganz einfach!

mit Arne Blankerts (thePHP.cc)

Hands-on workshop – Hansel & Gretel do TLS

mit Marcus Bointon (Synchromedia Limited)

Das wichtigste Element des Eingabefelds ist die v-model-Direktive. Der Wert title verbindet das Eingabefeld mit der title-Eigenschaft der Komponenteninstanz. Sobald Sie den Wert im Formularelement ändern, wird auch automatisch der Wert in der Komponenteninstanz synchronisiert. Die Synchronisierung erfolgt bei jedem Input-Event. Dieses Verhalten können Sie mit Hilfe der .lazy-Erweiterung von v-model ändern, sodass die Synchronisierung erst beim Change-Event erfolgt. Die übrigen Attribute entsprechen denen eines gewöhnlichen HTML-Input-Elements. Das autofocus-Attribut sorgt dafür, dass der Fokus standardmäßig auf diesem Element liegt und der Benutzer direkt mit der Eingabe starten kann. Der Placeholder informiert den Benutzer über den Inhalt des Eingabefelds. Ähnlich wie das Eingabefeld funktioniert auch die Verarbeitung des Dropdowns. Allerdings müssen Sie hier darauf achten, dass die done-Eigenschaft einen boolschen Wert annehmen soll. Mit Hilfe der Angabe von v-bind:value oder mit der Kurzform :value können true oder false an die jeweiligen Optionen gebunden werden.

Die Applikation benutzt für das Statemanagement Vuex, also müssen Sie das Formular mit dem Store der Applikation verbinden. Das Hinzufügen einer neuen Aufgabe erfolgt, indem die Formularkomponente eine neue Action auslöst, die im Store abgefangen und verarbeitet wird. Mit Hilfe der mapActions-Funktion aus dem vuex-Paket kann eine solche Action, die im Beispiel die Bezeichnung CREATE_TODO trägt, über eine lokale Methode der Komponente getriggert werden. Die Konstante, die für die Action steht, wird mit dem Alias createTodo versehen, der den Methodennamen innerhalb der Komponente darstellt. Die bereits erwähnte handleSubmit-Methode der Komponente erzeugt ein Objekt, das für die zu erstellende Aufgabe steht und übergibt dieses an die createTodo-Methode. Nach dem Aufruf dieser Methode weisen Sie den Eigenschaften title und done Standardwerte zu, die dazu führen, dass das Formular in seinen Ursprungszustand zurückgesetzt wird, sodass der Benutzer wieder eine neue Aufgabe anlegen kann. Mit diesen Anpassungen und noch ein wenig Styling ist die Formularkomponente fertiggestellt. Allerdings ist diese aus zwei Gründen noch nicht funktionsfähig: Noch ist sie nicht in die Applikation eingefügt und die Gegenseite im Vuex-Store ist ebenfalls noch nicht umgesetzt.

Einbindung der Formularkomponente in die Applikation

Die Integration der Formularkomponente in die Applikation erfolgt in der TodoList-Komponente, da das Formular als letztes Element in der Liste angezeigt werden soll. Hierfür müssen Sie die Komponente zunächst importieren und anschließend in der Eigenschaft components registrieren. Im letzten Schritt fügen Sie das Form-Tag, das für die Komponente steht, in das Template ein (Listing 2). Eine Übergabe von Props ist hier nicht erforderlich, da die form-Komponente direkt mit dem Store verbunden und somit unabhängig von der Elternkomponente ist.

<template>
  <div>
    <Button v-on:click="toggleFilter()">Filter</Button>
    <ul>
      <TodoListItem v-for="todo in getTodos()" v-bind:todo="todo" v-bind:key="todo.id"></TodoListItem>
    </ul>
    <Form/>
  </div>
</template>

<script>
import TodoListItem from "./TodoListItem.vue";
import Form from "./Form.vue";
import { createNamespacedHelpers } from "vuex";

import { GET_TODOS } from "../todo";

const { mapState, mapGetters, mapActions } = createNamespacedHelpers("todo");

export default {
  name: "TodoList",
  components: { TodoListItem, Form },
  data() {…},
  methods: {…},
  computed: {…},
  mounted() {…}
};
</script>

Einbindung in den Vuex-Store

Der letzte Schritt, mit dem Sie die Funktionsfähigkeit der form-Komponente sicherstellen, ist die Einbindung in den Vuex-Store. Zunächst benötigen Sie hier zwei Konstanten, die für die Actions stehen. Die erste Action wird aus den Komponenten der Applikation heraus ausgelöst, um eine neue Aufgabe zu erzeugen. Die zweite Action, CREATE_TODO_SUCCESS, steht für eine erfolgreiche Antwort des Webservers, nachdem die Aufgabe erzeugt wurde. Diese nutzen Sie, um den Store zu aktualisieren. Beide Action-Konstanten werden exportiert, sodass Sie aus anderen Komponenten, wie beispielsweise der form-Komponente, darauf zugreifen können.

Die bestehenden Actions ergänzen Sie um einen Eintrag für CREATE_TODO. In dieser Methode haben Sie Zugriff auf die commit-Funktion und die Informationen der übergebenen Aufgabe. Innerhalb dieser Methode kommunizieren Sie mit dem Server und schicken die Daten dorthin. Sobald die Antwort des Servers vorliegt, lösen Sie die CREATE_TODO_SUCCESS-Action aus. Um die neu erzeugte Aufgabe anzuzeigen, müssen Sie noch die Mutations erweitern, um mit der CREATE_TODO_SUCCESS-Action umzugehen. An dieser Stelle pushen Sie die neue Aufgabe. Daraufhin wird die Anzeige aktualisiert und die Aufgabe erscheint in der Anzeige (Listing 3).

import Vue from 'vue';

const initialState = { todos: [] };

export const TOGGLE_DONE = 'TOGGLE_DONE';
export const TOGGLE_DONE_SUCCESS = 'TOGGLE_DONE_SUCCESS';
export const GET_TODOS = 'GET_TODOS';
export const GET_TODOS_SUCCESS = 'GET_TODOS_SUCCESS';
export const CREATE_TODO = 'CREATE_TODO';
export const CREATE_TODO_SUCCESS = 'CREATE_TODO_SUCCESS';

export default {
  namespaced: true,
  state: initialState,
  getters: {…},
  mutations: {
    [TOGGLE_DONE_SUCCESS](state, modifiedTodo) {…},
    [GET_TODOS_SUCCESS](state, todos) {…},
    [CREATE_TODO_SUCCESS](state, todo) {
      state.todos.push(todo);
    },
  },
  actions: {
    async [GET_TODOS]({ commit }) {…},
    async [TOGGLE_DONE]({ commit }, todo) {…},
    async [CREATE_TODO]({ commit }, todo) {
      const result = await fetch('/todos', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify(todo),
      });
      commit(CREATE_TODO_SUCCESS, await result.json());
    },
  },
};

Bis hierhin haben Sie lediglich den Erfolgsfall abgedeckt und auch die Eingaben des Benutzers nicht weiter überprüft.

Behandlung von serverseitigen Fehlern

Mögliche Fehlerquellen beim Speichern sind Probleme bei der Kommunikation mit dem Server, weil beispielsweise keine Verbindung besteht oder der Server selbst Probleme bei der Verarbeitung der Anfrage hat.

Der erste Fall, also das Fehlen einer Serververbindung, führt dazu, dass das Promise-Objekt des fetch API abgewiesen wird. Innerhalb einer async-Funktion führt dies zu einer Exception, die Sie mit Hilfe eines try-catch-Statements fangen können.

Sollte der Server selbst ein Problem bei der Verarbeitung haben, resultiert dies in einem HTTP-Statuscode in der 400er- oder 500er-Spanne. In Listing 4 finden Sie den angepassten Quellcode des Vuex-Stores.

import Vue from 'vue';

const initialState = { todos: [], error: null };

export const TOGGLE_DONE = 'TOGGLE_DONE';
export const TOGGLE_DONE_SUCCESS = 'TOGGLE_DONE_SUCCESS';
export const GET_TODOS = 'GET_TODOS';
export const GET_TODOS_SUCCESS = 'GET_TODOS_SUCCESS';
export const CREATE_TODO = 'CREATE_TODO';
export const CREATE_TODO_SUCCESS = 'CREATE_TODO_SUCCESS';
export const CREATE_TODO_ERROR = 'CREATE_TODO_ERROR';

export default {
  namespaced: true,
  state: initialState,
  getters: {…},
  mutations: {
    [TOGGLE_DONE_SUCCESS](state, modifiedTodo) {…},
    [GET_TODOS_SUCCESS](state, todos) {…},
    [CREATE_TODO_SUCCESS](state, todo) {…},
    [CREATE_TODO_ERROR](state, error) {
      Vue.set(state, 'error', error);
    },
  },
  actions: {
    async [GET_TODOS]({ commit }) {…},
    async [TOGGLE_DONE]({ commit }, todo) {…},
    async [CREATE_TODO]({ commit }, todo) {
      try {
        const result = await fetch('/todos', {
          method: 'POST',
          headers: { 'content-type': 'application/json' },
          body: JSON.stringify(todo),
        });
        if (result.status > 299) {
          commit(CREATE_TODO_ERROR, result.statusText);
        } else {
          commit(CREATE_TODO_SUCCESS, await result.json());
        }
      } catch (e) {
        commit(CREATE_TODO_ERROR, e);
      }
    },
  },
};

Zur Behandlung der Fehler nutzen Sie den CREATE_TODO_ERROR-Action-Typ. Innerhalb der CREATE_TODO-Action-Methode fangen Sie die Kommunikationsfehler mit dem Server mit einem try-catch-Konstrukt ab und committen die CREATE_TODO_ERROR Action mit den Details zum Fehler. Das Objekt, das vom fetch API zurückgegeben wird, weist unter anderem die Eigenschaft status auf. Diese enthält den Statuscode. Ist dieser größer als 299, deutet dies auf eine Umleitung oder einen Fehler hin. In diesem Fall committen Sie ebenfalls die CREATE_TODO_ERROR-Action und übergeben den Statustext, der vom Server stammt. Diese Action behandeln Sie in einer separaten mutations-Methode. Initial setzen Sie die error-Eigenschaft des States auf den Wert null. Im Falle eines Fehlers weisen Sie dieser Eigenschaft die Fehlermeldung zu. Wurde eine Aufgabe erfolgreich gespeichert, sollten Sie den Wert wieder zurück auf null setzen.

Diesen Fehler müssen Sie natürlich auch dem Benutzer anzeigen, um ihn zu informieren. Zu diesem Zweck verbinden Sie die error-Eigenschaft des States mit der form-Komponente. In Listing 5 sehen Sie den angepassten Quellcode der form-Komponente.

<template>
  <form @submit.prevent="handleSubmit">
    <div class="formContainer">…</div>
    <div class="errorContainer" v-if="error !== null">Es ist ein Fehler aufgetreten: {{error}}</div>
  </form>
</template>

<script>
import { createNamespacedHelpers } from "vuex";

const { mapActions, mapState } = createNamespacedHelpers("todo");
import { CREATE_TODO } from "../todo";

export default {
  methods: {…},
  data() {…},
  computed: {
    ...mapState(["error"])
  }
};
</script>

Mit Hilfe der mapState-Funktion verbinden Sie die error-Eigenschaft des States mit der form-Komponente. Den Fehlercontainer zeigen Sie abhängig vom Wert der error-Eigenschaft an. Ist der Wert null, wird der Container nicht angezeigt. Besitzt die Eigenschaft einen anderen Wert, zeigen Sie den Container an und informieren den Benutzer über den aufgetretenen Fehler. Abbildung 2 zeigt ein Beispiel für ein Kommunikationsproblem.

Abb. 2: Fehlermeldung bei einem Kommunikationsproblem

Abb. 2: Fehlermeldung bei einem Kommunikationsproblem

Validierung

Im nächsten Schritt kümmern Sie sich darum, dass die Applikation nur gültige Werte akzeptiert. Die Regeln für eine gültige Eingabe lauten, dass es sich bei dem Titel um eine Pflichteingabe handelt und dieser maximal vierzig Zeichen lang sein darf. Außerdem handelt es sich beim Status ebenfalls um eine Pflichtangabe.

Die Validierung können Sie entweder selbst implementieren oder Sie greifen auf eine Bibliothek zurück, die Ihnen die Arbeit abnimmt. Entscheiden Sie sich für Ihre eigene Implementierung, können Sie diese entweder auf die Change Events der jeweiligen Formularelemente binden, um dem Benutzer bereits beim Ausfüllen des Formulars Rückmeldung zu geben, oder Sie führen die Überprüfung beim Absenden des Formulars aus.

Eine Alternative dazu stellen Bibliotheken wie Vuelidate dar. Die Installation von Vuelidate erfolgt über npm mit dem Kommando npm install vuelidate. Vuelidate ist als Vue-Plug-in umgesetzt. Als solches müssen Sie es vor seiner Verwendung zunächst einbinden. Dies geschieht analog zu Vuex mit der use-Methode von Vue. Ein passender Platz hierfür ist die main.js-Datei im src-Verzeichnis der Applikation. Die Einbindung finden Sie in Listing 6.

import Vue from 'vue';
import Vuelidate from 'vuelidate';

import App from './App.vue';
import store from './store';

Vue.config.productionTip = false;

Vue.use(Vuelidate);

new Vue({
  store,
  render: h => h(App),
}).$mount('#app');

In der form-Komponente können Sie nun auf eine Reihe von Validatoren zurückgreifen und sie an die einzelnen Formularelemente binden. Über die $v-Eigenschaft der Komponente können Sie den Status der Validatoren überprüfen. Listing 7 zeigt Ihnen den angepassten Quellcode der form-Komponente.

<template>
  <form @submit.prevent="handleSubmit">
    <div class="formContainer">
      <div>
        <input
          autofocus
          id="title"
          name="title"
          type="text"
          v-model="title"
          placeholder="Neue Aufgabe erstellen"
        >
        <div v-if="$v.title.$invalid" class="errorContainer">
          <span v-if="$v.title.$dirty && !$v.title.required">Der Titel ist ein Pflichtfeld</span>
          <span v-if="!$v.title.maxLength">Maximal 40 Zeichen</span>
        </div>
      </div>
      <div>
        <select id="done" name="done" v-model="done">
          <option :value="false">offen</option>>
          <option :value="true">erledigt</option>>
        </select>
        <div v-if="$v.done.$invalid" class="errorContainer">
          <span v-if="$v.done.$dirty && !$v.done.required">Der Status ist ein Pflichtfeld</span>
        </div>
      </div>
      <button type="submit">OK</button>
    </div>
    <div class="errorContainer" v-if="error !== null">…</div>
  </form>
</template>

<script>
import { createNamespacedHelpers } from "vuex";
import { required, maxLength } from "vuelidate/lib/validators";

const { mapActions, mapState } = createNamespacedHelpers("todo");
import { CREATE_TODO } from "../todo";

export default {
  methods: {
    ...mapActions({ createTodo: CREATE_TODO }),
    handleSubmit() {
      this.$v.$touch();
      if (!this.$v.$invalid) {
        this.createTodo({
          title: this.title,
          done: this.done
        });
        this.title = "";
        this.done = false;
        this.$v.$reset();
      }
    }
  },
  data() {…},
  computed: {
    ...mapState(["error"])
  },
  validations: {
    title: {
      required,
      maxLength: maxLength(40)
    },
    done: {
      required
    }
  }
};
</script>

Der Kern der Validatoren von Vuelidate ist die Eigenschaft validations in der Komponente. Diese Eigenschaft erhält ein Objekt als Wert. Dessen Schlüssel entsprechen den Namen der v-models der Komponente. Diesen Schlüsseln weisen Sie wiederum ein Objekt zu, das die eigentliche Definition der Validatoren enthält. Die Validatoren sind Funktionen, die Sie aus vuelidate/lib/validators importieren können. In der Applikation kommen die Validatoren required und maxLength zum Einsatz.

Ist die Formulareingabe des Benutzers ungültig, soll es nicht möglich sein, das Formular abzusenden. Um dies umzusetzen, nutzen Sie die $invalid-Eigenschaft des $v-Objekts. Vue stellt Ihnen eine Reihe von Eigenschaften zur Verfügung, mit denen Sie den Status des Formulars prüfen können. In der handleSubmit-Methode führen Sie zunächst die $touch-Methode aus, um die Validatoren zu aktivieren, auch wenn der Benutzer noch keine Eingabe gemacht hat. Anschließend können Sie mit Hilfe der $invalid-Eigenschaft prüfen, ob das Formular gültig ist und damit die Daten zum Server senden. Nach erfolgter Speicherung setzen Sie die Validatoren mit Hilfe der $reset-Methode zurück und bereiten sie so für die nächste Eingabe vor.

Im Template der form-Komponente fügen Sie zusätzliche Container zur Anzeige der Fehlermeldungen ein. Diese werden abhängig von den Fehlern angezeigt. Die $dirty-Eigenschaft nimmt erst den Wert true an, sobald der Benutzer mit diesem Formularelement interagiert hat. required und maxLength haben den Wert false, sobald das Formularelement die Regel nicht erfüllt.

Mit diesen Anpassungen ist das Formular zum Anlegen neuer Aufgaben über seine komplette Funktionalität fertiggestellt. Im nächsten Schritt fügen Sie den Vue Router in Ihre Applikation ein, um in Ihrer Applikation zu navigieren.

Der Vue Router zur Navigation in der Applikation

Der Vue Router erlaubt die Navigation innerhalb einer Applikation. Diese basiert wahlweise auf der Hash-Navigation des Browsers oder dem HTML5 History API. Bei der Hash-Navigation macht sich der Vue Router den Umstand zunutze, dass HTML-Sprungmarken über den Hash-Teil eines URLs besucht werden können, ohne dass die Seite neu geladen wird. Die Aktivierung einer solchen Sprungmarke führt dazu, dass das hashChange-Event ausgelöst wird. An dieses Event bindet sich der Router, um die entsprechende Ansicht zu rendern.

Die Verwendung des History API ist die modernere und auch elegantere Lösung der Navigation in einer Single Page Application. Das History API ist Bestandteil des HTML5-Standards. Diese Schnittstelle wird von allen gängigen Browsern unterstützt. Lediglich der Internet Explorer in der Version 9 unterstützt dieses Feature nicht. Sollten Sie diese Browserversion also in Ihrer Applikation unterstützen wollen, müssen Sie auf die Hash-Navigation ausweichen.

Ähnlich wie die Hash-Navigation basiert auch das History API auf JavaScript Events – nur dass dieses Event in diesem Fall pushState lautet. Beim History API wird der URL-Pfad direkt angepasst, ohne dass die Seite neu geladen wird.

Installation und Setup des Routers

Vue verfolgt die Strategie, die Kernbibliothek so leichtgewichtig wie möglich zu halten. Aus diesem Grund ist der Router auch als separates Plug-in umgesetzt und muss separat installiert werden. Die Installation erfolgt über das npm-Paket mit den Namen vue-router. Nach erfolgter Installation muss der Router als Plug-in eingefügt und damit aktiviert werden. Anschließend können Sie die Routen konfigurieren.

Damit die Einstiegsdatei in Ihre Applikation nicht zu unübersichtlich wird, lagern Sie die Einbindung und Konfiguration des Routers in eine separate Datei mit dem Namen router.js aus. Den Inhalt dieser Datei finden Sie in Listing 8.

import Vue from 'vue';
import VueRouter from 'vue-router';

import TodoList from './components/TodoList';

Vue.use(VueRouter);

const routes = [
  { path: '/list', component: TodoList },
  { path: '*', redirect: '/list' },
];

const router = new VueRouter({
  routes,
});

export default router;

Der Router wird, wie auch Vuelidate oder Vuex, als Plug-in mit der use-Methode eingebunden. Die Routenkonfiguration erfolgt in Form eines Arrays aus Konfigurationsobjekten. Die Routen verfügen über einen Pfad, der angibt, über welchen URL-Pfad sie angesprochen werden können. Eine Route zeigt auf eine bestimmte Komponente, die die Wurzel eines Komponentenbaums darstellt. Dieser Komponentenbaum wird gerendert, sobald die Route aktiviert wird. Mit der router-view-Komponente definieren Sie die Stelle, an der die Komponente eingehängt wird. Die Routenkonfiguration übergeben Sie dem VueRouter-Konstruktor für die Instanziierung des Routers. Diesen exportieren Sie in der Datei, um den Router schließlich in Ihre Applikation einzubinden.

Eine Besonderheit der Routenkonfiguration ist die Festlegung einer Standardroute. Diese wird aktiviert, falls keine passende Route zutrifft. Standardmäßig wird laut dieser Konfiguration die Aufgabenliste gerendert. Den somit konfigurierten Router binden Sie in der Einstiegsdatei, der main.js-Datei, Ihrer Applikation ein. Wie das funktioniert, sehen Sie in Listing 9.

import Vue from 'vue';
import Vuelidate from 'vuelidate';

import App from './App.vue';
import store from './store';
import router from './router';

Vue.config.productionTip = false;

Vue.use(Vuelidate);

new Vue({
  store,
  router,
  render: h => h(App),
}).$mount('#app');

Die Instanz des Routers, den Sie in der router.js-Datei exportiert haben, fügen Sie in das Konfigurationsobjekt des Vue-Konstruktors ein.

Zu guter Letzt passen Sie noch das Template der app-Komponente an (Listing 10). Hier fügen Sie statt des TodoList-Tags das routerview-Tag ein. An dieser Stelle wird die Komponente der jeweils aktiven Route eingefügt.

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "app"
};
</script>

Routingparameter

Bei der list-Route handelt es sich um eine statische Route. Diese können Sie nicht von außerhalb beeinflussen. Für einige Anwendungsfälle reicht diese allerdings nicht aus. Implementieren Sie beispielsweise eine Komponente, die die Details einer Aufgabe anzeigen soll, oder ein Formular zum Bearbeiten einer Aufgabe, benötigen Sie eine Möglichkeit anzugeben, um welchen Datensatz es sich handelt. Hierfür erweitern Sie die Routenkonfiguration um einen weiteren Eintrag. In Listing 11 finden Sie den erforderlichen Quellcode.

const routes = [
  { path: '/list', component: TodoList },
  { path: '/details/:id', component: Detail },
  { path: '*', redirect: '/list' },
];

Bei der Routendefinition geben Sie die dynamischen Routenparameter mit einem führenden Doppelpunkt an. Auf diese Parameter haben Sie Zugriff über die params-Eigenschaft der $route-Variable in Ihrer Komponente. Im nächsten Schritt implementieren Sie eine solche Detailansicht für die Aufgabenliste. Die Komponente für die Detailansicht speichern Sie in der Datei Detail.vue im components-Verzeichnis. Den Quellcode der Komponente sehen Sie in Listing 12.

<template>
  <div>
    <div v-if="this.todo !== null">{{this.todo.title}}</div>
    <router-link tag="button" to="/list">zurück zur Liste</router-link>
  </div>
</template>

<script>
import { createNamespacedHelpers } from "vuex";

import { GET_TODOS } from "../todo";

const { mapGetters, mapActions, mapState } = createNamespacedHelpers("todo");

export default {
  data() {
    return {
      todo: null
    };
  },
  methods: {
    ...mapActions({ fetchTodos: GET_TODOS })
  },

  async created() {
    if (this.todos.length === 0) {
      await this.fetchTodos();
    }

    this.todo = this.byId(parseInt(this.$route.params.id));
  },

  computed: {
    ...mapState(["todos"]),
    ...mapGetters(["byId"])
  }
};
</script>

<style scoped>
</style>

Die Detail-Komponente ist direkt mit dem Vuex-Store verknüpft. Dadurch erreichen Sie eine Unabhängigkeit der Komponente von der übrigen Applikation beziehungsweise vom restlichen Komponentenbaum. In der Methods-Eigenschaft verknüpfen Sie die GET_TODOS Action mit der Komponente und definieren den Namen fetchTodos damit, um die Aufgaben in den Store laden zu können. In den computed-Properties binden Sie die todos des Stores ein. Außerdem binden Sie eine neue Getter-Methode des Stores ein, mit deren Hilfe Sie eine Aufgabe anhand ihrer ID auslesen können. Im created Hook der Detail-Komponente prüfen Sie zunächst, ob bereits Aufgaben in den Store geladen wurden. Dies ist der Fall, wenn die Detail-Komponente nach der Listenkomponente angezeigt wurde. Damit können Sie unnötige Anfragen an den Server vermeiden, was die Anzeige der Details beschleunigt. Wurden die Daten noch nicht geladen, geschieht dies durch die fetchTodos-Methode. Die created-Methode wird als async-Methode implementiert, damit Sie auf die Antwort des Servers mit Hilfe des await-Schlüsselworts warten können. Auf der bestehenden Datenstruktur nutzen Sie den byId Getter, um die Aufgabe anhand des übergebenen Routingparameters auszulesen. An dieser Stelle ist Vorsicht geboten, da die übergebene ID als Zeichenkette vorliegt und diese zuvor in eine Zahl umgewandelt werden muss. Die ausgelesene Aufgabe weisen Sie der todo-Eigenschaft der Komponente zu, sodass die Informationen angezeigt werden können.

Das Template ist relativ einfach gehalten. In einem Container wird der Titel der Aufgabe angezeigt. Dies erfolgt nur, wenn die todo-Eigenschaft der Komponente nicht den Wert null aufweist. Unterhalb dieser Anzeige wird ein Button angezeigt, der den Benutzer zurück auf die Liste leitet. Zu diesem Zweck kommt die router-link-Komponente zum Einsatz. Die to-Eigenschaft gibt an, wohin der Benutzer weitergeleitet wird, in diesem Fall ist das Ziel die Liste. Außerdem können Sie mit dem tag-Attribut angeben, welche Gestalt der router-link annehmen soll. Geben Sie hier nichts an, wird ein a-Tag verwendet.

Sie können jedoch nicht nur mittels der router-link-Komponente in Ihrer Applikation navigieren, sondern auch aus dem Programmcode Ihrer Applikation heraus. Mit dem $router-Objekt einer Komponente haben Sie Zugriff auf die push-Methode (Listing 13). Dieser können Sie die gewünschte Route übergeben. Sie können jedoch nicht nur aus Komponenten heraus navigieren, sondern von jeder beliebigen Stelle aus Ihrer Applikation den Navigationsprozess anstoßen, also auch aus Vuex heraus. In diesem Fall können Sie jedoch nicht auf eine $router-Eigenschaft zurückgreifen. Außerhalb einer Vue-Komponente können Sie den Router nutzen, den Sie in der router.js-Datei exportieren. Dieses Objekt erfüllt denselben Zweck wie das $router-Objekt. Also können Sie auch hier die push-Methode verwenden. Anhand der TodoListItem-Komponente sehen Sie, wie die Verwendung des Routers funktioniert.

<script>
import { TOGGLE_DONE } from "../todo";
import { createNamespacedHelpers } from "vuex";
import router from "../router";

const { mapActions } = createNamespacedHelpers("todo");

export default {
  props: ["todo"],
  methods: {
    ...mapActions({ toggleDone: TOGGLE_DONE }),
    goToDetails() {
      router.push(`/details/${this.todo.id}`);
    }
  }
};
</script>

Routing-Guards

Die bisherige Implementierung der Detail-Komponente geht davon aus, dass die Daten bei der Erstellung der Komponente geladen und anschließend angezeigt werden. Eine alternative Implementierung nutzt die Routing-Guards des Vue Routers, um die Daten bereits bei der Aktivierung der Route zu laden. Es existieren globale, routen- und komponentenspezifische Guards, mit denen bestimmte Funktionen ausgeführt werden können oder die Navigation abgebrochen oder umgeleitet werden kann. Die einzelnen Guards sind:

  • Globale Guards: Mit der beforeEach-Methode des Routers können Sie auf Navigations-Events reagieren. Die Funktionen werden in der Reihenfolge ihrer Erstellung ausgeführt. Die beforeResolve-Methode lässt Sie Guard-Funktionen implementieren, die nach allen komponentenspezifischen Guards, und nachdem die asynchronen Routenkomponenten aufgelöst wurden, ausgeführt werden. Der letzte der globalen Guards ist afterEach.
  • Routenspezifische Guards: Im Konfigurationsobjekt der jeweiligen Route können Sie eine beforeEnter-Methode definieren.
  • Komponentenspezifische Guards: Innerhalb einer Komponente können Sie auf weitere drei Guards zurückgreifen. Diese werden als Methoden der Komponente implementiert und haben die Möglichkeit, die Navigation zu beeinflussen und weitere Funktionen auszuführen. Die erste Methode ist beforeRouteEnter. Innerhalb dieser können Sie allerdings nicht auf die Komponenteninstanz zugreifen, außer Sie nutzen eine Callback-Funktion beim Aufruf des dritten Arguments der Guard-Funktion. Weitere Routing-Guards sind die beforeRouteUpdate-Methode, die ausgeführt wird, wenn die Komponente bereits existiert, und die beforeRouteLeave-Methode, die beim Verlassen der Komponente ausgeführt wird.Für das Laden der Daten vor der Navigation kommen die beforeRouteEnter– und beforeRouteUpdate-Methoden zum Einsatz. In Listing 14 finden Sie den angepassten Quellcode.
    <template>
      <div>
        <div v-if="this.todo !== null">{{this.todo.title}}</div>
        <router-link tag="button" to="/list">zurück zur Liste</router-link>
      </div>
    </template>
    
    <script>
    import { createNamespacedHelpers } from "vuex";
    
    import { GET_TODOS } from "../todo";
    
    const { mapGetters, mapActions, mapState } = createNamespacedHelpers("todo");
    
    export default {
      data() {
        return {
          todo: null
        };
      },
      methods: {
        ...mapActions({ fetchTodos: GET_TODOS })
      },
    
      beforeRouteEnter(to, from, next) {
        next(async vm => {
          if (vm.todos.length === 0) {
            await vm.fetchTodos();
          }
          vm.todo = vm.byId(parseInt(to.params.id, 10));
        });
      },
    
      async beforeRouteUpdate(to, from, next) {
        this.todo = null;
        if (this.todos.length === 0) {
          await this.fetchTodos();
        }
        this.todo = this.byId(parseInt(to.params.id, 10));
        next();
      },
    
      computed: {
        ...mapState(["todos"]),
        ...mapGetters(["byId"])
      }
    };
    </script>
    
    <style scoped>
    </style>

    In der beforeRouteEnter-Methode müssen Sie, wie bereits erwähnt, darauf achten, dass Sie keinen Zugriff auf this haben. Stattdessen können Sie über das Argument der next-Callback-Funktion auf die Komponenteninstanz zugreifen. Diese Callback-Funktion wird auch nur bei dieser Guard-Methode unterstützt. Dieser Umbau hat zur Folge, dass die Navigation zur Detailkomponente erst stattfindet, sobald die Daten geladen wurden. Der Benutzer sieht also keine Komponente ohne Daten.

    Transitions

    Die Übergänge zwischen den einzelnen Routen lassen sich mit den Transitionen von Vue animieren. So können Sie beispielsweise die neu darzustellende Komponente mit Fade-Effekt einblenden oder hereinsliden lassen. Wrappen Sie die router-view-Komponente in die transition-Komponente, gilt die konfigurierte Transition für sämtliche Übergänge. Wrappen Sie eine einzelne Komponente in die transition-Komponente, gilt die Transition nur für den Übergang zu dieser Komponente. Mit dem name-Attribut können Sie die Art der Transition angeben. Mögliche Werte sind hier beispielsweise slide oder fade. Außerdem können Sie mit dem mode-Attribut das Easing der Transition festlegen, indem Sie hier etwa out-in angeben.

    Routingmodus

    Wie bereits erwähnt, unterstützt der Vue Router sowohl Hash-Navigation als auch das HTML5 History API. Geben Sie nichts weiter an, wird die Hash-Navigation verwendet. Das führt zu URLs wie beispielsweise http://localhost:8080/#/list. An der Raute (#) in dem URL erkennen Sie, dass es sich dabei um die Hash-Navigation handelt. Bei Änderungen des URL hinter der Raute wird die Seite nicht neu geladen, sondern lediglich das hashChange-Event ausgelöst. Den Routingmodus können Sie ändern, indem Sie bei der Instanziierung des Routers zusätzlich zum routes-Array eine mode-Eigenschaft mit dem Wert history übergeben. Daraufhin lautet die Adresse der Liste http://localhost:8080/list, was deutlich angenehmer aussieht. Das bedeutet allerdings auch, dass der Server, der Ihre Applikation ausliefert, die Anfragen auf die Liste an die index.html-Datei weiterleitet. In der Dokumentation des Vue Routers finden Sie Beispiele für die Konfiguration von Apache, NGINX und auch Node.js.

    Fazit

    Die Formularimplementierung von Vue ermöglicht es Ihnen, schnell leichtgewichtige Formulare umzusetzen. Hinzu kommt, dass Sie durch das Plug-in-Konzept der Bibliothek die Funktionalität durch zusätzliche Pakete erweitern können. Ein klassisches Beispiel ist die Validierung von Eingaben durch den Einsatz von Paketen wie beispielsweise Vuelidate.

    Der Vue Router ist eins der wichtigsten Erweiterungspakete für Vue. Es erlaubt die Navigation in einer Single Page Application, ohne dass der Zustand der Applikation durch einen Reload der Seite zurückgesetzt würde. Neben einem einfachen Wechsel zwischen verschiedenen Komponentenbäumen unterstützt der Router auch weiterführende Features wie den animierten Übergang zwischen einzelnen Routen oder eine feine Kontrolle der Navigation mit Hilfe von Routing-Guards.

Entwickler Magazin

Entwickler Magazin abonnierenDieser Artikel ist im Entwickler Magazin erschienen.

Natürlich können Sie das Entwickler Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -