17. Voorbeeld [nuxt-20]: porting van het voorbeeld [vuejs-22]
17.1. Présentation
We stellen hier voor om het voorbeeld [vuejs-22], dat een toepassing [vue.js] van het type SPA was, te porten naar een context [nuxt] SSR. [vuejs-22] was een clienttoepassing van de belastingberekeningsserver die de volgende weergaven bevatte:
Het eerste scherm is het authenticatiescherm:

Het tweede scherm is dat voor de belastingberekening:

Het derde scherm toont de lijst met simulaties die door de gebruiker zijn uitgevoerd:

Het bovenstaande scherm laat zien dat simulatie nr. 1 kan worden verwijderd. We krijgen dan het volgende scherm te zien:

Als we nu de laatste simulatie verwijderen, krijgen we het volgende nieuwe scherm te zien:

We gaan de applicatie [vuejs-22] stapsgewijs omzetten naar de applicatie [nuxt-20]. We zullen de code van [vuejs-22] niet opnieuw uitleggen. De lezer wordt verzocht het document |Inleiding tot het VUE.JS-framework aan de hand van een voorbeeld| nog eens door te nemen. De verschillende stappen zouden de verschillen tussen een [vuejs]-toepassing en een [nuxt]-toepassing moeten laten zien.
17.2. stap 1
Het project [nuxt-20] wordt in eerste instantie verkregen door het project [nuxt-12] te kopiëren. Dit is namelijk een goed uitgangspunt:
- het kan communiceren met de server voor de belastingberekening;
- het verwerkt de fouten die de server terugstuurt correct;
- de client en server van [nuxt] kunnen communiceren via een sessie van [nuxt];
We beschikken dus over een goede basisinfrastructuur. Onze belangrijkste taak zou moeten zijn om de volgende zaken aan te passen:
- de pagina’s. We nemen die van het project [vuejs-22], die moeten worden aangepast aan de nieuwe omgeving;
- het beheer van de store. Er zou aanvullende informatie moeten verschijnen (lijst met simulaties) en andere informatie zou overbodig kunnen worden;
- het beheer van de routing van de client en de server [nuxt];
Dus allereerst maken we het project [nuxt-20] aan door het project [nuxt-12] te kopiëren:

Vervolgens verwijderen we de pagina’s en componenten die overbodig zijn geworden [2]:
- de component [components/navigation] verdwijnt;
- de lay-out [layout/default] verdwijnt;
- de pagina's [index, authentification, get-admindata, fin-session] verdwijnen;
Vervolgens worden elementen uit [vuejs-22] en [3] geïntegreerd in [nuxt-20]:
- de drie pagina’s [Authentification, CalculImpot, ListeSimulations] van de applicatie [vuejs-22] worden in de map [pages] geplaatst;
- de componenten [FormCalculImpot, Menu, Layout] van de applicatie [vuejs-22] gaan naar de map [components];
- de pagina [Main] van [vuejs-22], die als [layout] diende voor de toepassing [vuejs-22], wordt verplaatst naar de map [layouts];
De geïntegreerde elementen [4] worden hernoemd:

- in [layouts] is [Main] veranderd in [default], aangezien dit de standaardnaam is van de lay-out van een applicatie [nuxt];
- in [pages] is de pagina [Authentification] veranderd in [index], omdat [Authentification] deze rol vervulde in de applicatie [vuejs-22];
Op dit punt kunnen we het project compileren om de eerste fouten te zien. We wijzigen het bestand [nuxt.config] uit het voorbeeld [nuxt-12], zodat voortaan [nuxt-20] wordt uitgevoerd:
export default {
mode: 'universal',
/*
** Headers of the page
*/
head: {
title: 'Introduction à [nuxt.js]',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{
hid: 'description',
name: 'description',
content: 'ssr routing loading asyncdata middleware plugins store'
}
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
},
/*
** Customize the progress-bar color
*/
loading: false,
/*
** Global CSS
*/
css: [],
/*
** Plugins to load before mounting the App
*/
plugins: [
{ src: '@/plugins/client/plgSession', mode: 'client' },
{ src: '@/plugins/server/plgSession', mode: 'server' },
{ src: '@/plugins/client/plgDao', mode: 'client' },
{ src: '@/plugins/server/plgDao', mode: 'server' },
{ src: '@/plugins/client/plgEventBus', mode: 'client' }
],
/*
** Nuxt.js dev-modules
*/
buildModules: [
// Doc: https://github.com/nuxt-community/eslint-module
'@nuxtjs/eslint-module'
],
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://bootstrap-vue.js.org
'bootstrap-vue/nuxt',
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
// https://www.npmjs.com/package/cookie-universal-nuxt
'cookie-universal-nuxt'
],
/*
** Axios module configuration
** See https://axios.nuxtjs.org/options
*/
axios: {},
/*
** Build configuration
*/
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {}
},
// broncodemap
srcDir: 'nuxt-20',
// router
router: {
// hoofdmap van de applicatie URL
base: '/nuxt-20/',
// routeringsmiddleware
middleware: ['routing']
},
// server
server: {
// servicepoort, standaard 3000
port: 81,
// netwerkadressen waarop wordt geluisterd, standaard localhost: 127.0.0.1
// 0.0.0.0 = alle netwerkadressen van de machine
host: 'localhost'
},
// omgeving
env: {
// axios-configuratie
timeout: 2000,
withCredentials: true,
baseURL: 'http://'localhost/php7/scripts-web/impots/version-14',
// configuratie van de sessiecookie [nuxt]
maxAge: 60 * 5
}
}
Vervolgens maken we een [build] van het project:

De gemelde fouten zijn als volgt:
- de fout op regel 1 geeft aan dat er naar een niet-bestaande afbeelding wordt verwezen. Deze wordt opgehaald in [vuejs-22];
- de fout op regel 2 geeft aan dat de component [./FormCalculImpot] niet bestaat. Deze component bevindt zich inderdaad nu in [@/components/form-calcul-impot];
- de fouten in de regels van [3-5] geven aan dat de component [./Layout] niet bestaat. Deze component bevindt zich nu inderdaad in [@/components/layout];
- uit de foutmeldingen in de regels [6-7] blijkt dat de component [./Menu] niet bestaat. Deze heet nu inderdaad [@/components/menu];
We voegen de afbeelding [assets/logo.jpg] toe aan het project [nuxt-20]:

Bovendien gaan we op alle pagina’s het pad naar de componenten aanpassen. Laten we de pagina [calcul-impot] als voorbeeld nemen:
<!-- definitie HTML van de weergave -->
<template>
<div>
<Layout :left="true" :right="true">
<!-- formulier voor belastingberekening aan de rechterkant -->
<FormCalculImpot slot="right" @resultatObtenu="handleResultatObtenu" />
<!-- navigatiemenu aan de linkerkant -->
<Menu slot="left" :options="options" />
</Layout>
<!-- weergavegebied voor de resultaten van de belastingberekening onder het formulier -->
<b-row v-if="résultatObtenu" class="mt-3">
<!-- leeg gebied met drie kolommen -->
<b-col sm="3" />
<!-- gebied met negen kolommen -->
<b-col sm="9">
<b-alert show variant="success">
<span v-html="résultat"></span>
</b-alert>
</b-col>
</b-row>
</div>
</template>
<script>
// import
import FormCalculImpot from './FormCalculImpot'
import Menu from './Menu'
import Layout from './Layout'
export default {
// gebruikte componenten
components: {
Layout,
FormCalculImpot,
Menu
},
De drie [import] in de regels 26-28 worden:
// importen
import FormCalculImpot from '@/components/form-calcul-impot'
import Menu from '@/components/menu'
import Layout from '@/components/layout'
Op deze manier controleert en corrigeert men de [import] van alle componenten, lay-outs en pagina’s. Zodra deze correcties zijn doorgevoerd, kan men een nieuwe [build] uitvoeren. Normaal gesproken zijn er dan geen fouten meer.
Vervolgens kan men een uitvoering proberen:

Er verschijnen fouten:
We zien dat het commando [dev] in combinatie met de module [eslint] syntactisch strenger is dan het commando [build]. Hier wordt geëist dat de vergelijkingsoperator [!=] wordt geschreven als [!==], wat een strengere operator is (deze controleert ook het type van de operanden). Deze fouten treden op in de pagina [index.vue].
We corrigeren de bovenstaande fouten en starten de uitvoering van het project opnieuw. We krijgen dan een waarschuwing van de module [eslint]:

We verhelpen deze fout met de module [Quick fix] van de module [eslint] [2].
We starten het project opnieuw. Er is geen compilatiefout meer. Vervolgens roepen we URL en [http://localhost:81/nuxt-20/] op via een browser. We krijgen een uitvoeringsfout:

De fout zit in [index.vue] [2]. De fout [1] komt doordat in [vuejs-22], de laag [dao] beschikbaar was in [this.$dao], terwijl deze in [nuxt-12] – waarvan we de infrastructuur hebben overgenomen – beschikbaar is in de functie [this.$dao()].
De fout zit in de functie [created] van de levenscyclus van de pagina [index]:

Voorlopig volstaat het om [created] te hernoemen naar [created2], zodat de levenscyclusfunctie [created] niet wordt uitgevoerd [3].
We slaan de wijziging op en laden de pagina [index] opnieuw in de browser. Deze keer lukt het:

17.3. stap 2
De pagina’s van het project [vuejs-22] maakten gebruik van de volgende geïnjecteerde elementen:
- $dao: voor de laag [dao] van de client [vue.js];
- $session: voor een sessie die was opgeslagen in de [localStorage] van de browser;
Deze elementen bestaan niet meer in de infrastructuur van het project [nuxt-12] dat we hebben gekopieerd:
- er zijn nu twee lagen [dao], één voor de client [nuxt] en één voor de server [nuxt]. Beide zijn beschikbaar via een geïnjecteerde functie met de naam [$dao]. Dit betekent dat in de pagina’s van de applicatie [this.$dao] moet worden vervangen door [this.$dao()];
- de sessie [nuxt], die wordt beheerd door de applicatie [nuxt-20], heeft niets meer te maken met het object [$session] van deapplicatie [vuejs-22], waar het begrip sessiecookie nog niet bestond. Niettemin hebben ze een vergelijkbare functionaliteit: het opslaan van persistente informatie tijdens de acties van de gebruiker. De sessie [nuxt] slaat de informatie op in de store in plaats van rechtstreeks in de sessie. Op de pagina’s van de applicatie moet [this.$session] worden vervangen door [this.$store] wanneer het gaat om het opslaan van informatie in de sessie, en door [this.$session()] wanneer het gaat om het bewerken van de sessie zelf;
- om de status van een eigenschap P van de store te achterhalen, moet [this.$store.state.P] worden geschreven;
- om de eigenschap P van de store te wijzigen, moet u [this.$store.commit(‘replace’, {P:value}] invoeren;
We voeren deze wijzigingen door op de pagina [index]:
<!-- definitie HTML van de weergave -->
<template>
<Layout :left="false" :right="true">
<template slot="right">
<!-- formulier HTML – de waarden worden verzonden met de actie [authentifier-utilisateur] -->
<b-form @submit.prevent="login">
<!-- titel -->
<b-alert show variant="primary">
<h4>Bienvenue. Veuillez vous authentifier pour vous connecter</h4>
</b-alert>
<!-- 1e regel -->
<b-form-group label="Nom d'utilisateur" label-for="user" label-cols="3">
<!-- invoerveld gebruiker -->
<b-col cols="6">
<b-form-input id="user" v-model="user" type="text" placeholder="Nom d'utilisateur" />
</b-col>
</b-form-group>
<!-- 2e regel -->
<b-form-group label="Mot de passe" label-for="password" label-cols="3">
<!-- wachtwoordveld -->
<b-col cols="6">
<b-input id="password" v-model="password" type="password" placeholder="Mot de passe" />
</b-col>
</b-form-group>
<!-- derde regel -->
<b-alert v-if="showError" show variant="danger" class="mt-3">L'erreur suivante s'est produite : {{ message }}</b-alert>
<!-- knop van het type [submit] op een derde regel -->
<b-row>
<b-col cols="2">
<b-button :disabled="!valid" variant="primary" type="submit">Valider</b-button>
</b-col>
</b-row>
</b-form>
</template>
</Layout>
</template>
<!-- weergavedynamiek -->
<script>
/* eslint-disable no-console */
import Layout from '@/components/layout'
export default {
// gebruikte componenten
components: {
Layout
},
// status van de component
data() {
return {
// gebruiker
user: '',
// zijn wachtwoord
password: '',
// bepaalt of er een foutmelding wordt weergegeven
showError: false,
// de foutmelding
message: ''
}
},
// berekende eigenschappen
computed: {
// geldige invoer
valid() {
return this.user && this.password && this.$store.state.started
}
},
// levenscyclus: de component is zojuist aangemaakt
mounted() {
// eslint-disable-next-line
console.log("Authentification mounted");
// kan de gebruiker simulaties uitvoeren?
if (this.$store.state.started && this.$store.state.authenticated && this.$métier.taxAdminData) {
// dan kan de gebruiker simulaties uitvoeren
this.$router.push({ name: 'calculImpot' })
// terug naar de gebeurtenislus
return
}
// als de sessie jSON al is gestart, wordt deze niet opnieuw gestart
if (!this.$store.state.started) {
// begin van het wachten
this.$emit('loading', true)
// de sessie wordt geïnitialiseerd met de server – asynchrone aanvraag
// de belofte die door de methoden van de laag [dao] wordt geretourneerd, wordt gebruikt
this.$dao()
// een sessie wordt geïnitialiseerd jSON
.initSession()
// het antwoord is ontvangen
.then((response) => {
// wachten beëindigd
this.$emit('loading', false)
// analyse van het antwoord
if (response.état !== 700) {
// de fout wordt weergegeven
this.message = response.réponse
this.showError = true
// terug naar de gebeurtenislus
return
}
// de sessie is gestart
this.$store.commit('replace', { started: true })
console.log('[authentification], session=', this.$session())
})
// in geval van een fout
.catch((error) => {
// de fout wordt doorgegeven aan de weergave [Main]
this.$emit('error', error)
})
// in alle gevallen
.finally(() => {
// wordt de sessie opgeslagen
this.$session().save()
})
}
},
// gebeurtenisbeheerders
methods: {
// ----------- authenticatie
async login() {
try {
// begin wachttijd
this.$emit('loading', true)
// we zijn nog niet geauthenticeerd
this.$store.commit('replace', { authenticated: false })
// blokkerende authenticatie bij de server
const response = await this.$dao().authentifierUtilisateur(this.user, this.password)
// einde van het laden
this.$emit('loading', false)
// analyse van het antwoord van de server
if (response.état !== 200) {
// de fout wordt weergegeven
this.message = response.réponse
this.showError = true
// terug naar de gebeurtenislus
return
}
// geen fout
this.showError = false
// authentificatie geslaagd
this.$store.commit('replace', { authenticated: true })
// --------- nu worden de gegevens van de belastingdienst opgevraagd
// in eerste instantie geen gegevens
this.$métier.setTaxAdminData(null)
// wachten begint
this.$emit('loading', true)
// verzoek bij de server is geblokkeerd
const response2 = await this.$dao().getAdminData()
// einde van het laden
this.$emit('loading', false)
// analyse van het antwoord
if (response2.état !== 1000) {
// de fout wordt weergegeven
this.message = response2.réponse
this.showError = true
// terug naar de gebeurtenislus
return
}
// geen fout
this.showError = false
// de ontvangen gegevens worden opgeslagen in de laag [métier]
this.$métier.setTaxAdminData(response2.réponse)
// we kunnen doorgaan met de belastingberekening
this.$router.push({ name: 'calculImpot' })
} catch (error) {
// de fout wordt doorgegeven aan de hoofdcomponent
this.$emit('error', error)
} finally {
// update opslag
this.$store.commit('replace', { métier: this.$métier })
// de sessie wordt opgeslagen
this.$session().save()
}
}
}
}
</script>
Let op de volgende punten:
- regel 69: de functie [created2] is hernoemd naar [mounted], zodat de server [nuxt] deze niet uitvoert (deze voert noch [beforeMount], noch [mounted] uit). Alleen de client [nuxt] zal het uitvoeren, net zoals dat het geval was met het voorbeeld [vuejs-22];
- regel 73: er wordt verwezen naar [this.$métier], dat op dit moment nog niet bestaat;
- regel 75: we hebben deze methode nog nooit gebruikt in een [nuxt]-toepassing. We zullen moeten kijken of het werkt in een [nuxt]-context;
- regel 112, 172: in [vuejs-22] werd de projectsessie op deze manier opgeslagen. Bij het project [nuxt-20] moet de methode [save] de huidige context ontvangen. We weten dat in een pagina [nuxt] het object [context] beschikbaar is in [this.$nuxt.context];
De regels 112 en 172 worden daarom als volgt herschreven:
this.$session().save(this.$nuxt.context)
Merk op dat deze code niet geoptimaliseerd is. In plaats van de functie [this.$session()] meerdere keren te gebruiken, zou het beter zijn om het volgende te schrijven:
const session=this.$session()
en vervolgens de variabele [session] te gebruiken. Dezelfde redenering geldt voor de functie [this.$dao()].
Nu deze correcties zijn aangebracht, kunnen we de URL [http://localhost:81/nuxt-20/] opnieuw laden in een browser. We krijgen nog steeds dezelfde pagina te zien als eerder:

Laten we de logbestanden van de browser bekijken:

Het logboek [1] is het laatste logboek dat is aangemaakt door de client [nuxt]. In [2] zien we dat de eigenschap [started] op [vrai] staat, wat betekent dat de functie [mounted] erin geslaagd is een sessie jSON met de belastingberekeningsserver te starten. We zien ook dat de store eigenschappen heeft die moeten worden verwijderd of hernoemd. Ter herinnering: we gebruiken de store uit het voorbeeld [nuxt-12].
Laten we nu de URL [http://localhost:81/nuxt-20/] opnieuw opvragen terwijl de belastingberekeningsserver niet is gestart. We zorgen er eerst voor dat we de cookie van de sessie [nuxt] verwijderen:

De bovenstaande schermafbeelding is een schermafbeelding van Chrome. Zodra dit is gebeurd, levert de URL [http://localhost:81/nuxt-20/] het volgende resultaat op:

De fout werd correct afgehandeld door het project [vuejs-22]. Deze wordt nog steeds correct afgehandeld door het project [nuxt-20].
17.4. stap 3
Nu we de authenticatiepagina hebben, moeten we kijken naar de code die wordt uitgevoerd wanneer de gebruiker op de knop [Valider] klikt:
// gebeurtenisverwerkers
methods: {
// ----------- authenticatie
async login() {
try {
// begin wachtrij
this.$emit('loading', true)
// nog niet geauthenticeerd
this.$store.commit('replace', { authenticated: false })
// blokkerende authenticatie bij de server
const response = await this.$dao().authentifierUtilisateur(this.user, this.password)
// einde van het laden
this.$emit('loading', false)
// analyse van het antwoord van de server
if (response.état !== 200) {
// de fout wordt weergegeven
this.message = response.réponse
this.showError = true
// terug naar de gebeurtenislus
return
}
// geen fout
this.showError = false
// authenticatie geslaagd
this.$store.commit('replace', { authenticated: true })
// --------- nu worden de gegevens van de belastingdienst opgevraagd
// in eerste instantie geen gegevens
this.$métier.setTaxAdminData(null)
// wachten begint
this.$emit('loading', true)
// verzoek bij de server is geblokkeerd
const response2 = await this.$dao().getAdminData()
// einde van het laden
this.$emit('loading', false)
// analyse van het antwoord
if (response2.état !== 1000) {
// er wordt een foutmelding weergegeven
this.message = response2.réponse
this.showError = true
// terug naar de gebeurtenislus
return
}
// geen fout
this.showError = false
// de ontvangen gegevens worden opgeslagen in de laag [métier]
this.$métier.setTaxAdminData(response2.réponse)
// we kunnen doorgaan met de belastingberekening
this.$router.push({ name: 'calculImpot' })
} catch (error) {
// de fout wordt doorgegeven aan de hoofdcomponent
this.$emit('error', error)
} finally {
// update opslag
this.$store.commit('replace', { métier: this.$métier })
// de sessie wordt opgeslagen
this.$session().save(this.$nuxt.context)
}
}
}
Het belangrijkste probleem lijkt hier het ontbreken van de gegevens [this.$métier] te zijn. Om dit op te lossen gaan we:
- de klasse [Métier] uit het voorbeeld [vuejs-22] opnemen. We plaatsen deze in de map [api];
- een functie [$métier] invoegen in de context van de client [nuxt], die toegang tot deze klasse zal verlenen;
Allereerst kopiëren we de klasse [Métier] naar de map [api]:

Zodra de klasse [Métier] in het project aanwezig is, maken we een nieuwe plug-in aan voor de client [nuxt]. Deze plug-in, genaamd [pluginMétier], voegt een functie [$métier] toe die toegang geeft tot de klasse [Métier]:
/* eslint-disable no-console */
// er wordt een toegangspunt tot de laag [métier] aangemaakt
import Métier from '@/api/client/Métier'
export default (context, inject) => {
// instantie van de laag [métier]
const métier = new Métier()
// injectie van een functie [$métier] in de context
inject('métier', () => métier)
// log
console.log('[fonction client $métier créée]')
}
Nu dit is gebeurd, kunnen we de pagina [index] aanpassen:
// levenscyclus: de component is zojuist aangemaakt
mounted() {
// eslint-disable-next-line
console.log("Authentification mounted");
// kan de gebruiker simulaties uitvoeren?
if (this.$store.state.started && this.$store.state.authenticated && this.$métier().taxAdminData) {
// dan kan de gebruiker simulaties uitvoeren
this.$router.push({ name: 'calcul-impot' })
// terug naar de gebeurtenislus
return
}
// als de sessie jSON al is gestart, wordt deze niet opnieuw gestart
...
},
// gebeurtenishandlers
methods: {
// ----------- authenticatie
async login() {
try {
// begin wachttijd
this.$emit('loading', true)
// er is nog geen authenticatie uitgevoerd
this.$store.commit('replace', { authenticated: false })
// blokkerende authenticatie bij de server
const response = await this.$dao().authentifierUtilisateur(this.user, this.password)
// einde van het laden
this.$emit('loading', false)
// analyse van het antwoord van de server
if (response.état !== 200) {
// de fout wordt weergegeven
this.message = response.réponse
this.showError = true
// terug naar de gebeurtenislus
return
}
// geen fout
this.showError = false
// authenticatie geslaagd
this.$store.commit('replace', { authenticated: true })
// --------- nu worden de gegevens van de belastingdienst opgevraagd
// in eerste instantie geen gegevens
this.$métier().setTaxAdminData(null)
// wachten begint
this.$emit('loading', true)
// verzoek bij de server is geblokkeerd
const response2 = await this.$dao().getAdminData()
// einde van het laden
this.$emit('loading', false)
// analyse van het antwoord
if (response2.état !== 1000) {
// de fout wordt weergegeven
this.message = response2.réponse
this.showError = true
// terug naar de gebeurtenislus
return
}
// geen fout
this.showError = false
// de ontvangen gegevens worden opgeslagen in de laag [métier]
this.$métier().setTaxAdminData(response2.réponse)
// we kunnen doorgaan met de belastingberekening
this.$router.push({ name: 'calcul-impot' })
} catch (error) {
// de fout wordt doorgegeven aan de hoofdcomponent
this.$emit('error', error)
} finally {
// update opslag
this.$store.commit('replace', { métier: this.$métier() })
// de sessie wordt opgeslagen
this.$session().save(this.$nuxt.context)
}
}
}
- regels 43, 61, 69: [this.$métier] is vervangen door [this.$métier()];
- regels 8, 63: de naam van de pagina [CalculImpot] van het project [vuejs-22] is gewijzigd in de pagina [calcul-impot] in het project [nuxt-20];
Nu deze correcties zijn aangebracht, kunnen we proberen de authenticatiepagina te valideren:

De verkregen pagina ziet er als volgt uit:

We hebben inderdaad de pagina voor de belastingberekening gekregen. Laten we nu eens naar de logbestanden kijken:

In [2] zien we dat de authenticatie correct is opgeslagen. In [3-4] zien we dat de gegevens [taxAdminData] zijn opgehaald, waarmee de belasting kan worden berekend door de klasse [Métier].
17.5. stap 4
Laten we de pagina [calcul-impot] bekijken die we hebben verkregen:
<!-- definitie HTML van de weergave -->
<template>
<div>
<Layout :left="true" :right="true">
<!-- formulier voor belastingberekening aan de rechterkant -->
<FormCalculImpot slot="right" @resultatObtenu="handleResultatObtenu" />
<!-- navigatiemenu aan de linkerkant -->
<Menu slot="left" :options="options" />
</Layout>
<!-- weergavegebied voor de resultaten van de belastingberekening onder het formulier -->
<b-row v-if="résultatObtenu" class="mt-3">
<!-- leeg gebied met drie kolommen -->
<b-col sm="3" />
<!-- gebied met negen kolommen -->
<b-col sm="9">
<b-alert show variant="success">
<span v-html="résultat"></span>
</b-alert>
</b-col>
</b-row>
</div>
</template>
<script>
// importen
import FormCalculImpot from '@/components/form-calcul-impot'
import Menu from '@/components/menu'
import Layout from '@/components/layout'
export default {
// gebruikte componenten
components: {
Layout,
FormCalculImpot,
Menu
},
// interne status
data() {
return {
// menuopties
options: [
{
text: 'Liste des simulations',
path: '/liste-des-simulations'
},
{
text: 'Fin de session',
path: '/fin-session'
}
],
// resultaat van de belastingberekening
résultat: '',
résultatObtenu: false
}
},
// levenscyclus
created() {
// eslint-disable-next-line
console.log("CalculImpot created");
},
// methoden voor gebeurtenisbeheer
methods: {
// resultaat van de belastingberekening
handleResultatObtenu(résultat) {
// het resultaat wordt als string opgebouwd HTML
const impôt = "Montant de l'impôt : " + résultat.impôt + ' euro(s)'
const décôte = 'Décôte : ' + résultat.décôte + ' euro(s)'
const réduction = 'Réduction : ' + résultat.réduction + ' euro(s)'
const surcôte = 'Surcôte : ' + résultat.surcôte + ' euro(s)'
const taux = "Taux d'imposition : " + résultat.taux
this.résultat = impôt + '<br/>' + décôte + '<br/>' + réduction + '<br/>' + surcôte + '<br/>' + taux
// weergave van het resultaat
this.résultatObtenu = true
// ---- update van de store [Vuex]
// een simulatie van +
this.$store.commit('addSimulation', résultat)
// de sessie wordt opgeslagen
this.$session.save()
}
}
}
</script>
- regels 44 en 48: de links in het navigatiemenu zijn correct. De pagina [/fin-session] bestaat niet. Het project [vuejs-22] loste dit probleem op met routing. We zullen hetzelfde doen met het project [nuxt-20];
- regel 76: er wordt verwezen naar een mutatie [addSimulation] die op dit moment nog niet bestaat. We gaan deze aanmaken;
- regel 78: net als op de pagina [index] moet hier [this.$session().save(this.$nuxt.context)] worden geschreven;
Laten we de store [store/index] aanpassen. Deze is overgenomen van het project [nuxt-12] en ziet er momenteel als volgt uit:
/* eslint-disable no-console */
// status van de store
export const state = () => ({
// sessie jSON gestart
jsonSessionStarted: false,
// gebruiker geauthenticeerd
userAuthenticated: false,
// sessiecookie PHP
phpSessionCookie: '',
// adminData
adminData: ''
})
// wijzigingen in de store
export const mutations = {
// vervanging van de status
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
},
// reset van de store
reset() {
this.commit('replace', { jsonSessionStarted: false, userAuthenticated: false, phpSessionCookie: '', adminData: '' })
}
}
// acties van de store
export const actions = {
nuxtServerInit(store, context) {
// wie voert deze code uit?
console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
// sessie initialiseren
initStore(store, context)
}
}
function initStore(store, context) {
// de store is de store die moet worden geïnitialiseerd
// we halen de sessie op
const session = context.app.$session()
// is de sessie al geïnitialiseerd?
if (!session.value.initStoreDone) {
// we starten een nieuwe store
console.log("nuxtServerInit, initialisation d'un nouveau store")
// de store wordt aan de sessie toegevoegd
session.value.store = store.state
// de store is nu geïnitialiseerd
session.value.initStoreDone = true
} else {
console.log("nuxtServerInit, reprise d'un store existant")
// de store wordt bijgewerkt met de store van de sessie
store.commit('replace', session.value.store)
}
// de sessie wordt opgeslagen
session.save(context)
// logboek
console.log('initStore terminé, store=', store.state)
}
- regels 3-27: we nemen de status en de wijzigingen over van de applicatie [vuejs-22] (zie document [3]):
// status van de zonwering
export const state = () => ({
// sessie jSON gestart
started: false,
// gebruiker geauthenticeerd
authenticated: false,
// sessiecookie PHP
phpSessionCookie: '',
// lijst met simulaties
simulations: [],
// het nummer van de laatste simulatie
idSimulation: 0,
// laag [métier]
métier: null
})
// wijzigingen in de jaloezie
export const mutations = {
// vervanging van de status
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
},
// reset van de opslag
reset() {
this.commit('replace', { started: false, authenticated: false, phpSessionCookie: '', idSimulation: 0, simulations: [], métier: null })
},
// verwijdering van regel nr. index
deleteSimulation(state, index) {
// eslint-disable-next-line no-console
console.log('mutation deleteSimulation')
// regel nr. [index] verwijderen
state.simulations.splice(index, 1)
console.log('store simulations', state.simulations)
},
// een simulatie toevoegen
addSimulation(state, simulation) {
// eslint-disable-next-line no-console
console.log('mutation addSimulation')
// nummer van de simulatie
state.idSimulation++
simulation.id = state.idSimulation
// de simulatie wordt toegevoegd aan de tabel met simulaties
state.simulations.push(simulation)
}
}
- regels 4 en 6: we voeren de reeds gebruikte eigenschappen in;
- regel 8: we behouden de sessiecookie PHP. Dit is essentieel om ervoor te zorgen dat de client en de server [nuxt] dezelfde sessie PHP hebben met de server voor de belastingberekening;
- regel 10: de lijst met simulaties die de gebruiker heeft uitgevoerd;
- regel 12: het nummer van de laatste simulatie die door de gebruiker is uitgevoerd;
- regel 14: de laag [métier];
- regels 30-47: de mutaties die aanwezig zijn in de store van het project [vuejs-22] en waarnaar door de pagina's van de applicatie wordt verwezen. Het project [vuejs-22] had een mutatie met de naam [clear] die de lijst met simulaties leegmaakte. Deze wordt niet opgenomen omdat de reeds aanwezige mutatie [reset] voldoende zou moeten zijn;
- regels 26-28: de mutatie [reset] wordt aangepast om rekening te houden met de nieuwe inhoud van de state;
De pagina [calcul-impot] maakt gebruik van de volgende component [form-calcul-impot]:
<!-- definitie HTML van de weergave -->
<template>
<!-- formulier HTML -->
<b-form @submit.prevent="calculerImpot" class="mb-3">
<!-- bericht met 12 kolommen op een blauwe achtergrond -->
<b-row>
<b-col sm="12">
<b-alert show variant="primary">
<h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
</b-alert>
</b-col>
</b-row>
<!-- formulierelementen -->
<!-- eerste regel -->
<b-form-group label="Etes-vous marié(e) ou pacsé(e) ?">
<!-- keuzerondjes in 5 kolommen-->
<b-col sm="5">
<b-form-radio v-model="marié" value="oui">Oui</b-form-radio>
<b-form-radio v-model="marié" value="non">Non</b-form-radio>
</b-col>
</b-form-group>
<!-- tweede regel -->
<b-form-group label="Nombre d'enfants à charge" label-for="enfants">
<b-form-input id="enfants" v-model="enfants" :state="enfantsValide" type="text" placeholder="Indiquez votre nombre d'enfants"></b-form-input>
<!-- eventuele foutmelding -->
<b-form-invalid-feedback :state="enfantsValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
</b-form-group>
<!-- derde regel -->
<b-form-group label="Salaire annuel net imposable" label-for="salaire" description="Arrondissez à l'euro inférieur">
<b-form-input id="salaire" v-model="salaire" :state="salaireValide" type="text" placeholder="Salaire annuel"></b-form-input>
<!-- eventuele foutmelding -->
<b-form-invalid-feedback :state="salaireValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
</b-form-group>
<!-- vierde regel, knop [submit] -->
<b-col sm="3">
<b-button :disabled="formInvalide" type="submit" variant="primary">Valider</b-button>
</b-col>
</b-form>
</template>
<!-- script -->
<script>
export default {
// interne status
data() {
return {
// getrouwd of niet
marié: 'non',
// aantal kinderen
enfants: '',
// jaarsalaris
salaire: ''
}
},
// berekende interne status
computed: {
// validatie van het formulier
formInvalide() {
return (
// salaris ongeldig
!this.salaire.match(/^\s*\d+\s*$/) ||
// of ongeldige kinderen
!this.enfants.match(/^\s*\d+\s*$/) ||
// of fiscale gegevens niet verkregen
!this.$métier.taxAdminData
)
},
// validatie van het salaris
salaireValide() {
// moet een getal zijn >=0
return Boolean(this.salaire.match(/^\s*\d+\s*$/) || this.salaire.match(/^\s*$/))
},
// validatie van kinderen
enfantsValide() {
// moet een getal zijn >=0
return Boolean(this.enfants.match(/^\s*\d+\s*$/) || this.enfants.match(/^\s*$/))
}
},
// levenscyclus
created() {
// log
// eslint-disable-next-line
console.log("FormCalculImpot created");
},
// gebeurtenisbeheerder
methods: {
calculerImpot() {
// de belasting wordt berekend met behulp van de laag [métier]
const résultat = this.$métier.calculerImpot(this.marié, Number(this.enfants), Number(this.salaire))
// eslint-disable-next-line
console.log("résultat=", résultat);
// het resultaat wordt aangevuld
résultat.marié = this.marié
résultat.enfants = this.enfants
résultat.salaire = this.salaire
// het [resultatObtenu]-gebeurtenis wordt gegenereerd
this.$emit('resultatObtenu', résultat)
}
}
}
</script>
- regels 65, 89: de referentie [this.$métier] moet worden gewijzigd in [this.$métier()];
Nadat deze correcties zijn aangebracht, kan een simulatie worden uitgevoerd:

We krijgen het volgende antwoord:

Als we de logbestanden bekijken:

- in [9-10], zien we dat de eerste simulatie inderdaad in [store] staat;
- in [5] is het nummer van de laatste simulatie inderdaad verhoogd;
17.6. stap 5
Nu we een simulatie hebben uitgevoerd, klikken we op de link [Liste des simulations]. We krijgen de volgende pagina te zien:

De routering van de client [nuxt] is correct verlopen. Laten we de code van de pagina [liste-des-simulations] bekijken:
<!-- definitie HTML van de weergave -->
<template>
<div>
<!-- pagina-indeling -->
<Layout :left="true" :right="true">
<!-- simulaties in de rechterkolom -->
<template slot="right">
<template v-if="simulations.length == 0">
<!-- geen simulaties -->
<b-alert show variant="primary">
<h4>Votre liste de simulations est vide</h4>
</b-alert>
</template>
<template v-if="simulations.length != 0">
<!-- er zijn simulaties -->
<b-alert show variant="primary">
<h4>Liste de vos simulations</h4>
</b-alert>
<!-- tabel met simulaties -->
<b-table :items="simulations" :fields="fields" striped hover responsive>
<template v-slot:cell(action)="data">
<b-button @click="supprimerSimulation(data.index)" variant="link">Supprimer</b-button>
</template>
</b-table>
</template>
</template>
<!-- navigatiemenu in de linkerkolom -->
<Menu slot="left" :options="options" />
</Layout>
</div>
</template>
<script>
// importen
import Layout from '@/components/layout'
import Menu from '@/components/menu'
export default {
// componenten
components: {
Layout,
Menu
},
// interne status
data() {
return {
// opties van het navigatiemenu
options: [
{
text: "Calcul de l'impôt",
path: '/calcul-impot'
},
{
text: 'Fin de session',
path: '/fin-session'
}
],
// tabelparameters HTML
fields: [
{ label: '#', key: 'id' },
{ label: 'Marié', key: 'marié' },
{ label: "Nombre d'enfants", key: 'enfants' },
{ label: 'Salaire', key: 'salaire' },
{ label: 'Impôt', key: 'impôt' },
{ label: 'Décôte', key: 'décôte' },
{ label: 'Réduction', key: 'réduction' },
{ label: 'Surcôte', key: 'surcôte' },
{ label: '', key: 'action' }
]
}
},
// berekende interne status
computed: {
// lijst met simulaties uit de Vuex-store
simulations() {
return this.$store.state.simulations
}
},
// levenscyclus
created() {
// eslint-disable-next-line
console.log("ListeSimulations created");
},
// methoden
methods: {
supprimerSimulation(index) {
// eslint-disable-next-line
console.log("supprimerSimulation", index);
// verwijdering van simulatie nr. [index]
this.$store.commit('deleteSimulation', index)
// de sessie wordt opgeslagen
this.$session.save()
}
}
}
</script>
- regels 47-56: de bestemmingen in het navigatiemenu zijn correct;
- regel 75: de store wordt correct aangeduid;
- regel 89: er wordt gebruikgemaakt van een mutatie [deleteSimulation] die we in de vorige stap hebben geïntegreerd;
- regel 91: deze regel moet worden herschreven als [this.$session().save(this.$nuxt.context)];
We brengen de nodige wijzigingen aan en proberen vervolgens de weergegeven simulatie te verwijderen:

We krijgen dan de volgende pagina te zien:

Laten we de logbestanden bekijken:

- in [6] zien we dat de simulatietabel leeg is;
Laten we nu teruggaan naar het formulier voor de belastingberekening:

We krijgen de volgende pagina te zien:

De doorsturing is dus gelukt.
17.7. stap 6
Nu moeten we nog de navigatieoptie [Fin de session] in het navigatiemenu instellen:
// menuopties
options: [
{
text: 'Liste des simulations',
path: '/liste-des-simulations'
},
{
text: 'Fin de session',
path: '/fin-session'
}
]
- regel 9: de pagina [/fin-session] bestaat niet. Het project [vuejs-22] behandelde dit geval met routeringsregels in een bestand [router.js]:
// imports
import Vue from 'vue'
import VueRouter from 'vue-router'
// de weergaven
import Authentification from './views/Authentification'
import CalculImpot from './views/CalculImpot'
import ListeSimulations from './views/ListeSimulations'
import NotFound from './views/NotFound'
// de sessie
import session from './session'
// routeringsplugin
Vue.use(VueRouter)
// de routes van de applicatie
const routes = [
// authenticatie
{ path: '/', name: 'authentification', component: Authentification },
{ path: '/authentification', name: 'authentification2', component: Authentification },
// belastingberekening
{
path: '/calcul-impot', name: 'calculImpot', component: CalculImpot,
meta: { authenticated: true }
},
// lijst met simulaties
{
path: '/liste-des-simulations', name: 'listeSimulations', component: ListeSimulations,
meta: { authenticated: true }
},
// sessie beëindigen
{
path: '/fin-session', name: 'finSession'
},
// onbekende pagina
{
path: '*', name: 'notFound', component: NotFound,
},
]
// de router
const router = new VueRouter({
// de routes
routes,
// de weergavemodus van URL
mode: 'history',
// de basis-URL van de applicatie
base: '/client-vuejs-impot/'
})
// controle van routes
router.beforeEach((to, from, next) => {
// eslint-disable-next-line no-console
console.log("router to=", to, "from=", from);
// route gereserveerd voor geauthentificeerde gebruikers?
if (to.meta.authenticated && !session.authenticated) {
next({
// we gaan over naar authenticatie
name: 'authentification',
})
// terug naar de gebeurtenislus
return;
}
// speciaal geval: einde van de sessie
if (to.name === "finSession") {
// de sessie wordt opgeschoond
session.clear();
// we gaan naar de weergave [authentification]
next({
name: 'authentification',
})
// terug naar de gebeurtenislus
return;
}
// andere gevallen – volgende normale weergave van de routering
next();
})
// export van de router
export default router
- de regels 64-76 behandelden het specifieke geval van de route naar het pad [/fin-session];
- regel 66: de huidige sessie wordt geleegd;
- regels 68-70: de weergave [authentification] wordt weergegeven;
We gaan proberen iets soortgelijks te doen in het routeringsbestand van de client [nuxt]:

Het script [client/routing.js] ziet er nu als volgt uit:
/* eslint-disable no-console */
export default function(context) {
// wie voert deze code uit?
console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
// beheer van de sessiecookie PHP in de browser
// de sessiecookie PHP van de browser moet identiek zijn aan die in de Nuxt-sessie
// de actie [fin-session] ontvangt een nieuwe cookie PHP (zowel de server als de Nuxt-client)
// als de server het ontvangt, moet de client het doorgeven aan de browser
// voor zijn eigen communicatie met de server PHP
// hier gaat het om client-routing
// we halen de sessiecookie op PHP
const phpSessionCookie = context.store.state.phpSessionCookie
if (phpSessionCookie) {
// indien aanwezig, wordt de sessiecookie PHP aan de browser toegewezen
document.cookie = phpSessionCookie
}
// waar gaan we heen?
const to = context.route.path
if (to === '/fin-session') {
// de sessie wordt gewist
const session = context.app.$session()
session.reset(context)
// we sturen door naar de startpagina
context.redirect({ name: 'index' })
}
}
- we hebben de regels [19-27] aan de bestaande code toegevoegd;
- regel 20: we halen de [path] op uit het doel van de huidige route;
- regel 21: er wordt gecontroleerd of dit [/fin-session] is. Zo ja:
- regels 23-24: de sessie wordt gereset;
- regel 26: de client [nuxt] wordt doorgestuurd naar de startpagina;
De methode [session.reset(context)] (regel 24) van de sessie is als volgt:
// de sessie wordt gereset
reset(context) {
console.log('nuxt-session reset')
// de store resetten
context.store.commit('reset')
// de nieuwe store in de sessie opslaan en de sessie opslaan
this.save(context)
}
De methode [context.store.commit('reset')] (regel 5) is als volgt:
// de store resetten
reset() {
this.commit('replace', { started: false, authenticated: false, phpSessionCookie: '', idSimulation: 0, simulations: [], métier: null })
}
Wanneer we nu de link [Fin de session] gebruiken, wordt de startpagina weergegeven met de volgende logbestanden:

- in [3] zien we dat we niet meer geauthenticeerd zijn;
- in [4] is te zien dat de sessie jSON is gestart;
- in [6] is de laag [métier] niet meer aanwezig in de store (deze is nog steeds aanwezig op de pagina's met [this.$métier()]);
- in [5, 7] zijn er geen simulaties meer;
Het is belangrijk om goed te begrijpen wat er gebeurt aan het einde van een sessie:
- de sessie [nuxt] wordt gereset: de eigenschap [started] van de store verandert in [false];
- er vindt een omleiding plaats naar de pagina [index];
- de methode [mounted] van de pagina [index] wordt uitgevoerd. Deze start een nieuwe sessie jSON met de server voor de belastingberekening. Als de bewerking slaagt, verandert de eigenschap [started] van de store in [true];
17.8. stap 7
In dit stadium beschikt de applicatie [nuxt-20] over alle functionaliteiten van de applicatie [vuejs-22]. De porting lijkt voltooid.
We gaan nog een stapje verder in de geest van [nuxt]. De methode [mounted] van de pagina [index] levert een probleem op. Deze start een asynchrone bewerking waarvan een zoekmachine niet zal wachten tot deze is voltooid. We weten dat in dit geval de asynchrone bewerking in een functie [asyncData] moet worden geplaatst, omdat de server [nuxt] die deze uitvoert dan wacht tot deze is voltooid voordat de pagina aan de zoekmachine wordt geleverd.
We maken hier gebruik van de functie [asyncData], die in de applicatie [nuxt-12] is geschreven voor de pagina [index]:
export default {
name: 'InitSession',
// gebruikte componenten
components: {
Layout,
Navigation
},
// asynchrone gegevens
async asyncData(context) {
// logboek
console.log('[index asyncData started]')
// we doen dingen niet twee keer als de pagina al is opgevraagd
if (process.server && context.store.state.jsonSessionStarted) {
console.log('[index asyncData canceled]')
return { result: '[succès]' }
}
try {
// er wordt een sessie gestart jSON
const dao = context.app.$dao()
const response = await dao.initSession()
// log
console.log('[index asyncData response=]', response)
// we halen de sessiecookie PHP op voor de volgende verzoeken
const phpSessionCookie = dao.getPhpSessionCookie()
// de sessiecookie PHP wordt opgeslagen in de sessie [nuxt]
context.store.commit('replace', { phpSessionCookie })
// is er een fout opgetreden?
if (response.état !== 700) {
// de fout bevindt zich in response.réponse
throw new Error(response.réponse)
}
// er wordt opgemerkt dat de sessie jSON is gestart
context.store.commit('replace', { jsonSessionStarted: true })
// het resultaat wordt weergegeven
return { result: '[succès]' }
} catch (e) {
// log
console.log('[index asyncData error=]', e)
// er wordt genoteerd dat de sessie jSON niet is gestart
context.store.commit('replace', { jsonSessionStarted: false })
// de fout wordt gemeld
return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
} finally {
// de store wordt opgeslagen
const session = context.app.$session()
session.save(context)
// log
console.log('[index asyncData finished]')
}
},
// levenscyclus
beforeCreate() {
console.log('[index beforeCreate]')
},
created() {
console.log('[index created]')
},
beforeMount() {
console.log('[index beforeMount]')
},
mounted() {
console.log('[index mounted]')
// alleen client
if (this.showErrorLoading) {
console.log('[index mounted, showErrorLoading=true]')
this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
}
}
- regels 13, 33, 40: de eigenschap [jsonSessionStarted] moet worden gewijzigd in [started];
- regel 13: in de applicatie [nuxt-12] voerde alleen de server [nuxt] de pagina [index] en de bijbehorende functie [asyncData] uit. De client [nuxt] voerde de pagina [index] pas uit nadat hij deze van de server [nuxt] had ontvangen, en voerde de functie [asyncData] toen niet uit. In [nuxt-20] is dit anders: de link [Fin de session] zal de pagina [index] weergeven in de omgeving van de client [nuxt]. De functie [asyncData] wordt dan uitgevoerd. Wanneer men echter op deze manier op de pagina [index] terechtkomt, is de sessie [nuxt] inmiddels gereset en is de eigenschap [started] van de store gelijk aan [false], waardoor de voorwaarde op regel 13 noodzakelijkerwijs onwaar zal zijn. We kunnen dus [process.server] laten staan, waardoor de client [nuxt] deze test niet zal uitvoeren;
- regels 15, 35, 42: een eigenschap [result] wordt toegevoegd aan de eigenschappen [data] van de pagina [index]. In [nuxt-20] wordt deze eigenschap niet gebruikt en zullen we deze uit het door de functie gerenderde resultaat verwijderen;
- regels 61-67: deze methode [mounted] moet behouden blijven, omdat deze ervoor zorgt dat de client [nuxt] de foutmelding kan weergeven. De manier waarop de fout wordt afgehandeld, wordt echter aangepast;
Op de huidige pagina [index] integreren we de bovenstaande functie [asyncData] in plaats van de oude functie [mounted] en voegen we een nieuwe functie [mounted] toe. De code van de pagina [index] uit het voorbeeld [nuxt-20] ziet er dan als volgt uit:
...
<!-- weergavedynamiek -->
<script>
/* eslint-disable no-console */
import Layout from '@/components/layout'
export default {
// gebruikte componenten
components: {
Layout
},
// status van de component
data() {
return {
// gebruiker
user: '',
// zijn wachtwoord
password: '',
// foutmelding
showError: false
}
},
// berekende eigenschappen
computed: {
// geldige invoer
valid() {
return this.user && this.password && this.$store.state.started
}
},
// asynchrone gegevens
async asyncData(context) {
// logboek
console.log('[index asyncData started]')
// we doen dingen niet twee keer als de pagina al is opgevraagd
if (process.server && context.store.state.started) {
console.log('[index asyncData canceled]')
return
}
try {
// er wordt een sessie gestart jSON
const dao = context.app.$dao()
const response = await dao.initSession()
// log
console.log('[index asyncData response=]', response)
// we halen de sessiecookie PHP op voor de volgende verzoeken
const phpSessionCookie = dao.getPhpSessionCookie()
// de sessiecookie PHP wordt opgeslagen in de sessie [nuxt]
context.store.commit('replace', { phpSessionCookie })
// is er een fout opgetreden?
if (response.état !== 700) {
// de fout bevindt zich in response.réponse
throw new Error(response.réponse)
}
// we merken op dat de sessie jSON is gestart
context.store.commit('replace', { started: true })
// geen resultaat
return
} catch (e) {
// logboek
console.log('[index asyncData error=]', e.message)
// er wordt gemeld dat de sessie jSON niet is gestart
context.store.commit('replace', { started: false })
// de fout wordt gemeld
return { showErrorLoading: true, errorLoadingMessage: e.message }
} finally {
// de store wordt opgeslagen
const session = context.app.$session()
session.save(context)
// log
console.log('[index asyncData finished]')
}
},
// levenscyclus
beforeCreate() {
console.log('[index beforeCreate]')
},
created() {
console.log('[index created]')
},
beforeMount() {
// alleen voor de klant
console.log('[index beforeMount]')
// afhandeling van eventuele fouten
if (this.showErrorLoading) {
// log
console.log('[index beforeMount, showErrorLoading=true]')
// de fout wordt doorgegeven aan de hoofdcomponent [default]
this.$emit('error', new Error(this.errorLoadingMessage))
}
},
mounted() {
console.log('[index mounted]')
},
// gebeurtenisverwerkers
methods: {
// ----------- authenticatie
async login() {
...
}
</script>
- regels 58, 65: de functie [asyncData] maakt de eigenschap [result] hier niet langer ongebruikt;
- regel 81: de methode [beforeMount] van de client [nuxt]. Deze methode kreeg de voorkeur boven de methode [mounted] om een eventuele fout van [asyncData] af te handelen;
- regel 85: er wordt gecontroleerd of de eigenschap [errorLoading] is ingesteld. Deze kan alleen worden ingesteld door de functie [asyncData];
- regels 85-90: als de functie [asyncData] een fout heeft gemeld, wordt deze via de gebeurtenis [error] doorgegeven aan de pagina [default]. Zo werd de eventuele fout afgehandeld door de oude functie [created], die we zojuist hebben vervangen;
Laten we een paar tests uitvoeren.
We verwijderen eerst zowel de sessiecookie [nuxt] als de sessiecookie PHP, indien deze bestaan. Vervolgens vragen we de pagina [http://localhost:81/nuxt-20/] op terwijl de server voor de belastingberekening niet is gestart. We krijgen de volgende pagina te zien:

We laden dezelfde pagina opnieuw nadat we de server voor de belastingberekening hebben gestart:

Laten we de logbestanden bekijken:

- in [2-3] zien we dat de sessie jSON is gestart;
- in [4] zien we de sessiecookie PHP die de server [nuxt] heeft opgehaald tijdens de communicatie met de belastingberekeningsserver. De client [nuxt] gaat deze nu gebruiken;
Laten we ons nu identificeren:

We krijgen de volgende pagina te zien:

Bij [1] kregen we een foutmelding. Dit betekent dat de browser niet de juiste sessiecookie PHP heeft verzonden, die in de vorige stap door de server [nuxt] was gestart. In [nuxt-12] vond de overdracht van de sessiecookie PHP van de server [nuxt] naar de client [nuxt] plaats in de routing van de client [nuxt] van het script [middleware/client/routing] :
/* eslint-disable no-console */
export default function(context) { // wie voert deze code uit?
console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
// beheer van de sessiecookie PHP in de browser
// de sessiecookie PHP van de browser moet identiek zijn aan die in de Nuxt-sessie
// de actie [fin-session] ontvangt een nieuwe sessiecookie PHP (zowel de server als de Nuxt-client)
// als de server het ontvangt, moet de client het doorgeven aan de browser
// voor zijn eigen communicatie met de server PHP
// hier gaat het om client-routing
// we halen de sessiecookie op PHP
const phpSessionCookie = context.store.state.phpSessionCookie
if (phpSessionCookie) {
// indien aanwezig, wordt de sessiecookie PHP aan de browser toegewezen
document.cookie = phpSessionCookie
}
// waar gaan we heen?
const to = context.route.path
if (to === '/fin-session') {
// de sessie wordt gewist
const session = context.app.$session()
session.reset(context)
// we sturen door naar de indexpagina
context.redirect({ name: 'index' })
}
}
Dit zijn de regels 13-17 waarmee de client [nuxt] de sessiecookie PHP van de server [nuxt] kan ophalen.
Het probleem hier is dat wanneer er op de knop [Valider] wordt geklikt, er geen routing plaatsvindt vanuit de client [nuxt]. De routingfunctie ervan wordt dan niet aangeroepen. We lossen het probleem op door de regels 12-17 te dupliceren aan het begin van de authenticatiemethode van de pagina [index]:
// gebeurtenisverwerkers
methods: {
// ----------- authenticatie
async login() {
// de sessiecookie PHP wordt uit de opslag opgehaald
const phpSessionCookie = this.$store.state.phpSessionCookie
if (phpSessionCookie) {
// indien aanwezig, wordt de sessiecookie PHP aan de browser toegewezen
document.cookie = phpSessionCookie
}
try {
// het wachten begint
this.$emit('loading', true)
// we zijn nog niet geauthenticeerd
In de regels 5-10 wordt uit de store de sessiecookie PHP opgehaald, die is geïnitieerd door de server [nuxt]. Na deze wijziging wordt de pagina voor de belastingberekening correct opgehaald, wat betekent dat de authenticatie is gelukt.
17.9. stap 8
We hebben nu een werkende applicatie die functioneert volgens de [nuxt]-filosofie. Net zoals we bij de applicatie [nuxt-13] hebben gedaan, gaan we nu kijken naar de navigatie van de server [nuxt]. Zoals eerder vermeld, hoef de gebruiker de URL van de applicatie niet handmatig in te voeren. Hij hoort de aangeboden links te gebruiken, die worden uitgevoerd door de client [nuxt], die dan in de modus SPA werkt. Niettemin gaan we ervoor zorgen dat de navigatie op de server [nuxt] de applicatie altijd in een stabiele toestand laat.
Uit het onderzoek dat is uitgevoerd voor [nuxt-13] (zie paragraaf ‘link’) weten we dat het volgende nodig is:
- het script [midleware/routing] aan te passen;
- een script [middleware/server/routing] toevoegen;

Het script [middleware/routing] wordt als volgt aangepast:
/* eslint-disable no-console */
// we importeren de middleware van de server en de client
import serverRouting from './server/routing'
import clientRouting from './client/routing'
export default function(context) {
// wie voert deze code uit?
console.log('[middleware], process.server', process.server, ', process.client=', process.client)
if (process.server) {
// serverroutering
serverRouting(context)
} else {
// client-routing
clientRouting(context)
}
}
- regel 4: het routeringsscript van de server [nuxt] wordt geïmporteerd;
- regels 10-12: als de code wordt uitgevoerd door de server [nuxt], wordt de routeringsfunctie van deze server gebruikt;
Het script [middleware/server/routing] is als volgt:
/* eslint-disable no-console */
export default function(context) {
// wie voert deze code uit?
console.log('[middleware server], process.server', process.server, ', process.client=', process.client)
// we verzamelen hier en daar wat informatie
const store = context.store
// waar komen we vandaan?
const from = store.state.from || 'nowhere'
// waar gaan we heen?
let to = context.route.name
// speciaal geval van /fin-session zonder het attribuut [name]
if (context.route.path === '/fin-session') {
to = 'fin-session'
}
// eventuele omleiding
let redirection = ''
// routering voltooid
let done = false
// bevinden we ons al in een omleiding van de server [nuxt]?
if (store.state.serverRedirection) {
// niets te doen
done = true
}
// gaat het om het herladen van de pagina?
if (!done && from === to) {
// niets te doen
done = true
}
// controle van de navigatie van de server [nuxt]
// we volgen het navigatiemenu van de klant
// we behandelen eerst het geval van het einde van de sessie
if (!done && store.state.started && store.state.authenticated && to === 'fin-session') {
// de sessie wordt opgeschoond
const session = context.app.$session()
session.reset(context)
// we sturen door naar de indexpagina
redirection = 'index'
// taak voltooid
done = true
}
// geval waarin de sessie PHP niet is gestart
if (!done && !store.state.started && to !== 'index') {
// omleiding naar [index]
redirection = 'index'
// taak voltooid
done = true
}
// in het geval dat de gebruiker niet is geauthenticeerd
if (!done && store.state.started && !store.state.authenticated && to !== 'index') {
redirection = 'index'
// taak voltooid
done = true
}
// geval waarin [adminData] niet is verkregen
if (!done && store.state.started && store.state.authenticated && !store.state.métier.taxAdminData && to !== 'index') {
// omleiding naar [index]
redirection = 'index'
// taak voltooid
done = true
}
// in het geval dat [adminData] is verkregen
if (
!done &&
store.state.started &&
store.state.authenticated &&
store.state.métier.taxAdminData &&
to !== 'calcul-impot' &&
to !== 'liste-des-simulations'
) {
// we blijven op dezelfde pagina
redirection = from
// taak voltooid
done = true
}
// normaal gesproken zijn alle controles uitgevoerd ---------------------
// omleiding?
if (redirection) {
// de omleiding wordt genoteerd in de store
store.commit('replace', { serverRedirection: true })
} else {
// geen omleiding
store.commit('replace', { serverRedirection: false, from: to })
}
// de store wordt opgeslagen in de sessie [nuxt]
const session = context.app.$session()
session.value.store = store.state
session.save(context)
// de eventuele omleiding vanaf de server wordt uitgevoerd [nuxt]
if (redirection) {
context.redirect({ name: redirection })
}
}
- in dit script nemen we de ideeën over die al zijn uitgewerkt en gebruikt in de routing van de server [nuxt] van de applicatie [nuxt-13];
- we voegen twee eigenschappen toe aan de store van de applicatie:
- [from]: de naam van de laatst weergegeven pagina. We weten dat de client [nuxt] over deze informatie beschikt, maar de server [nuxt] niet. We gaan deze informatie toevoegen door bij elke routering van de server [nuxt] de naam van de pagina die zal worden weergegeven in de store op te slaan. Hetzelfde doen we bij elke routering van de client [nuxt]. Zo zal de server [nuxt] bij de volgende routering in de store de naam vinden van de laatst door de applicatie weergegeven pagina;
- [serverRedirection]: wanneer een routeringsbestemming wordt geweigerd door de server [nuxt], voert deze een omleiding uit. Hij geeft dan in de store aan dat de volgende bestemming van de server [nuxt] een omleidingspagina is. Deze omleiding zorgt ervoor dat de router van de server [nuxt] opnieuw wordt uitgevoerd. Als deze ontdekt dat de huidige bestemming het gevolg is van een omleiding, laat hij dit toe;
- regels 6-11: de informatie die nodig is voor de routering wordt opgehaald;
- regels 13-16: het doel [/fin-session] is niet gekoppeld aan een pagina met de naam [fin-session]. Het heeft dus geen naam. We geven het er een;
- regel 19: het doel van een eventuele omleiding;
- regel 21: [done=true] wanneer de routeringstests zijn voltooid;
- regels 23-27: zoals gezegd, als de huidige routering het gevolg is van een omleiding, hoeft er niets te gebeuren. Tijdens de vorige routering heeft de router namelijk besloten dat de clientbrowser moest worden omgeleid. Er is geen reden om deze beslissing te herzien;
- regels 29-33: als het om het herladen van een pagina gaat, laten we dit gewoon gebeuren. Dit is geen algemeen geldend principe voor elke toepassing [nuxt]: voor elke pagina moet worden bekeken wat de gevolgen van het herladen zijn. In dit geval blijkt dat het herladen van de [index, calcul-impot, liste-des-simulations]-pagina’s geen ongewenste effecten veroorzaakt;
- regels 35-85: de routing van de server [nuxt] neemt de routing van de client [nuxt] over. Wanneer men zich op een pagina bevindt, moet de routing van de server [nuxt] het navigatiemenu weerspiegelen dat door de client [nuxt] wordt aangeboden wanneer men zich op die pagina bevindt;
- regels 38-47: eerst wordt het geval behandeld waarbij het doel [fin-session] niet overeenkomt met een bestaande pagina. Als aan de voorwaarden is voldaan (sessie gestart, gebruiker geauthenticeerd), wordt de sessie gewist en wordt de gebruiker doorgestuurd naar de pagina [index];
- regels 49-55: als de sessie jSON met de belastingberekeningsserver nog niet is gestart, is de enige mogelijke bestemming de pagina [index];
- regels 57-62: als de sessie jSON is gestart en de gebruiker niet is geauthenticeerd en de authenticatiepagina niet heeft opgevraagd, dan wordt hij doorgestuurd naar de authenticatiepagina, namelijk de pagina [index];
- regels 64-70: als de gebruiker is geauthenticeerd maar de gegevens [adminData] niet zijn verkregen, wordt er doorgestuurd naar de authenticatiepagina. De authenticatie doet twee dingen: ze verifieert de gebruiker en, als de authenticatie is geslaagd, vraagt ze bovendien om de gegevens [adminData]. Als deze gegevens niet zijn verkregen, moet de authenticatie opnieuw worden uitgevoerd;
- regels 72-85: als de gegevens [adminData] zijn verkregen, zijn de enige mogelijke doelen [calcul-impot] en [liste-des-simulations]. Als dat niet het geval is, wordt de routering geweigerd;
- regels 88-95: de store wordt bijgewerkt, afhankelijk van of er al dan niet een omleiding plaatsvindt;
- regel 94: er vindt geen omleiding plaats. Daarom wordt de huidige [to] de [from] voor de volgende routering;
- regels 96-99: de informatie uit de store wordt opgeslagen in de sessiecookie [nuxt];
- regels 100-103: als er een omleiding moet plaatsvinden, voeren we die uit;
Om de tests uit te voeren, moet je ervoor zorgen dat je vanuit een lege situatie begint door de sessiecookie [nuxt] en de sessiecookie PHP op de belastingberekeningsserver te verwijderen:

Om de routering van de server [nuxt] te testen, probeert u op elke pagina alle mogelijke URL- en [/, /calcul-impot, /liste-des-simulations]-waarden uit. Telkens moet de applicatie in een consistente toestand blijven.
17.10. stap 9
Stap 9 betreft de implementatie van de applicatie [nuxt-20]. Hiervoor is een hostingomgeving nodig die een [node.js]-omgeving biedt om de server [nuxt] uit te voeren. Ik beschik daar niet over. De lezer kan de procedures volgen die worden beschreven in de paragraaf ‘link’, om de applicatie [nuxt-20] op zijn ontwikkelingsmachine te implementeren en deze te beveiligen met een protocol HTTPS.
17.11. Conclusion
De migratie van de applicatie [vuejs-22] naar de applicatie [nuxt-20] is nu voltooid. Laten we enkele punten van deze migratie onthouden:
- de pagina’s van [vuejs-22] zijn behouden;
- de asynchrone bewerkingen die in de pagina’s van [vuejs-22] voorkwamen, zijn gemigreerd naar een functie [asyncData];
- in [nuxt-20] moesten twee entiteiten worden beheerd: de client [nuxt] en de server [nuxt]. Deze laatste entiteit bestond niet in [vuejs-22]. Om de consistentie tussen de twee entiteiten te behouden, hadden we een sessie [nuxt] nodig;
- moesten we de routering van de server [nuxt] beheren;
In de praktijk is het ongetwijfeld beter om direct te beginnen met een architectuur [nuxt] dan een architectuur [vue.js] te maken die vervolgens wordt overgezet naar een omgeving [nuxt].