Skip to content

19. Verbeteringen aan de client Vue.js

19.1. Introduction

We gaan het project [vuejs-21] testen met de ontwikkelingsserver. Daarom moet de server opnieuw de headers CORS verzenden. Het bestand [config.json] van versie 14 van de belastingberekeningsserver moet deze headers dus toestaan:

Image

Het project [vuejs-21] wordt in eerste instantie aangemaakt door het project [vuejs-20] te dupliceren. Vervolgens wordt het gewijzigd in [3].

Er verschijnen nieuwe bestanden:

  • [session.js]: exporteert een object [session] dat informatie over de huidige sessie zal bevatten;
  • [pluginSession]: maakt het voorgaande object [session] beschikbaar in de eigenschap [$session] van de weergaven;
  • [NotFound.vue]: een nieuwe weergave die wordt getoond wanneer de gebruiker handmatig een URL opvraagt die niet bestaat;

Er worden bestanden gewijzigd:

  • [main.js]: initialiseert de huidige sessie en herstelt deze vervolgens wanneer de gebruiker handmatig URL invoert;
  • [router.js]: er worden controles toegevoegd om het geval te verwerken waarin de gebruiker URL invoert;
  • [store.js]: er wordt een nieuwe mutatie toegevoegd;
  • [config.js]: er wordt een nieuwe configuratie toegevoegd;
  • verschillende weergaven, voornamelijk om de huidige sessie op belangrijke momenten in de levenscyclus van de applicatie op te slaan. Deze wordt vervolgens telkens hersteld wanneer de gebruiker handmatig URL invoert;

19.2. De store [Vuex]

Het script [./store] evolueert als volgt:


// Vuex-plugin
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);

// Vuex-store
const store = new Vuex.Store({
  state: {
    // de simulatietabel
    simulations: [],
    // het nummer van de laatste simulatie
    idSimulation: 0
  },
  mutations: {
    // verwijdering van rij nr. index
    deleteSimulation(state, index) {
      ...
    },
    // een simulatie toevoegen
    addSimulation(state, simulation) {
      ...
    },
    // status opschonen
    clear(state) {
      // geen simulaties meer
      state.simulations = [];
      // de nummering van de simulaties begint opnieuw bij 0
      state.idSimulation = 0;
    }
  }
});
// object exporteren [store]
export default store;
  • regels 24-29: de wijziging [clear] verwijdert de lijst met opgeslagen simulaties en zet het nummer van de laatste simulatie terug op 0.

19.3. De sessie

De noodzaak van een sessie komt voort uit het feit dat wanneer de gebruiker een URL in het adresveld van de browser invoert, het script [main.js] opnieuw wordt uitgevoerd. Dit script bevat echter de instructie:


// Vuex-store
import store from './store'

Deze instructie importeert het volgende bestand [./store]:


// Vuex-plugin
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);

// Vuex-store
const store = new Vuex.Store({
  state: {
    // de simulatietabel
    simulations: [],
    // het nummer van de laatste simulatie
    idSimulation: 0
  },
  mutations: {
    ...
  }
});
// export van het object [store]
export default store;

We zien in de regels 7-13 dat er een lege simulatietabel wordt geïmporteerd. Als er dus simulaties waren voordat de gebruiker een URL in het adresveld van de browser invoerde, zijn die daarna verdwenen. Het idee is:

  • een sessie te gebruiken waarin de informatie wordt opgeslagen die we willen behouden als de gebruiker handmatig URL invoert;
  • deze op te slaan op belangrijke momenten in de applicatie;
  • deze te herstellen in [main.js], dat altijd wordt uitgevoerd wanneer een URL handmatig wordt ingevoerd;

Het script [./session] is als volgt:


// de Vuex-store wordt geïmporteerd
import store from './store'
// de configuratie wordt geïmporteerd
import config from './config';

// het object [session]
const session = {
  // sessie gestart
  started: false,
  // authenticatie
  authenticated: false,
  // opslagtijdstip
  saveTime: "",
  // laag [métier]
  métier: null,
  // status Vuex
  state: null,

  // sessie opslaan in een string jSON
  save() {
    // er worden enkele eigenschappen aan de sessie toegevoegd
    this.saveTime = Date.now();
    this.state = store.state;
    // de sessie wordt omgezet in jSON
    const json = JSON.stringify(this);
    // de sessie wordt opgeslagen in de browser
    localStorage.setItem("session", json);
    // eslint-disable-next-line no-console
    console.log("session save", json);
  },

  // de sessie wordt hersteld
  restore() {
    // de sessie jSON wordt opgehaald uit de browser
    const json = localStorage.getItem("session")
    // als er iets is opgehaald
    if (json) {
      // worden alle sleutels van de sessie hersteld
      const restore = JSON.parse(json);
      for (var key in restore) {
        if (restore.hasOwnProperty(key)) {
          this[key] = restore[key];
        }
      }
      // als er sinds het begin van de sessie een bepaalde tijd van inactiviteit is verstreken, beginnen we opnieuw vanaf nul
      let durée = Date.now() - this.saveTime;
      if (durée > config.duréeSession) {
        // de sessie wordt leeggemaakt – deze wordt ook opgeslagen
        session.clear();
      } else {
        // de Vuex-store wordt opnieuw gegenereerd
        store.replaceState(JSON.parse(JSON.stringify(this.state)));
      }
    }
    // eslint-disable-next-line no-console
    console.log("session restore", this);
  },

    // de sessie wordt opgeschoond
  clear() {
    // eslint-disable-next-line no-console
    console.log("session clear");
    // bepaalde velden van de sessie worden gewist
    this.authenticated = false;
    this.saveTime = "";
    this.started = false;
    if (this.métier) {
      // het veld wordt gereset [taxAdminData]
      this.métier.taxAdminData = null;
    }
    // de Vuex-store wordt ook gewist
    store.commit("clear");
    // de nieuwe sessie wordt opgeslagen
    this.save();
  },
}

// het object [session] wordt geëxporteerd
export default session;

Opmerkingen

  • regel 2: de sessie zal ook de store [Vuex] (lijst met simulaties, nummer van de laatst uitgevoerde simulatie) omvatten;
  • regels 7-17: de informatie die door de sessie wordt bewaard:
    • [started]: of de sessie jSON met de server is gestart of niet;
    • [authenticated]: of de gebruiker zich al dan niet heeft geauthenticeerd;
    • [saveTime]: de datum in milliseconden van de laatste opslag;
    • [métier]: een verwijzing naar de laag [métier]. Deze bevat de gegevens [taxAdminData] waarmee de belasting kan worden berekend;
    • [state]: de status van de store [Vuex] (lijst met simulaties, nummer van de laatst uitgevoerde simulatie);
  • regels 20-30: de methode [save] slaat de sessie lokaal op in de browser waarin de applicatie wordt uitgevoerd;
    • regel 22: de tijdstip van opslag wordt genoteerd;
    • regel 23: de [state] wordt opgehaald uit de opslag [Vuex];
    • regel 25: de sessie-string jSON wordt aangemaakt;
    • regel 27: deze wordt lokaal opgeslagen in de browser die gekoppeld is aan de sleutel [session];
  • regels 33-57: met de methode [restore] kan een sessie worden hersteld vanuit de lokale back-up in de browser;
    • regel 35: de lokale back-up jSON wordt opgehaald;
    • regel 37: als er iets is opgehaald;
    • regels 39-44: het object [session] wordt gereconstrueerd;
    • regel 46: de tijd die is verstreken sinds de laatste back-up wordt berekend;
    • regels 47-50: als deze tijd langer is dan een via de configuratie vastgestelde waarde [config.duréeSession], wordt de sessie gereset (regel 49) en daarbij opgeslagen;
    • regel 52: anders wordt het attribuut [state] van de store [Vuex] opnieuw gegenereerd;
  • regels 60-75: de methode [clear] reset de sessie;
    • regels 64-70: de eigenschappen van de sessie worden teruggezet naar hun oorspronkelijke waarden;
    • regel 72: evenals de store [Vuex];
    • regel 74: de nieuwe sessie wordt opgeslagen;

19.4. Het configuratiebestand [config]

Het bestand [./config] verandert als volgt:


// gebruik van de bibliotheek [axios]
const axios = require('axios');
// time-out van verzoeken HTTP
axios.defaults.timeout = 2000;
...

// configuratie exporteren
export default {
  // object [axios]
  axios: axios,
  // maximale inactiviteitstijd van de sessie: 5 min = 300 s = 300000 ms
  duréeSession: 300000
}
  • regel 12: we gaan de sessie van de applicatie op ongeveer dezelfde manier beheren als een websessie. We stellen hier een maximale inactiviteitsduur van 5 minuten in;

19.5. De plug-in [pluginSession]

Zoals al vele malen eerder is gedaan, zorgt de plug-in [pluginSession] ervoor dat de weergaven toegang krijgen tot de sessie via de eigenschap [this.$session]:


export default {
  install(Vue, session) {
    // voegt een eigenschap [$session] toe aan de weergaveklasse
    Object.defineProperty(Vue.prototype, '$session', {
      // wanneer er naar Vue.$session wordt verwezen, wordt de tweede parameter [session] gerenderd
      get: () => session,
    })
  }
}

19.6. Het hoofdscript [main]

Het hoofdscript [./main.js] wordt als volgt aangepast:


// opstartlogboek
// eslint-disable-next-line no-console
console.log("main started");

// imports
import Vue from 'vue'

...

// instantiatie van de laag [métier]
import Métier from './couches/Métier';
const métier = new Métier();

// plugin [métier]
import pluginMétier from './plugins/pluginMétier'
Vue.use(pluginMétier, métier)

// Vuex-store
import store from './store'

// sessie
import session from './session';
import pluginSession from './plugins/pluginSession'
Vue.use(pluginSession, session)

// de sessie wordt hersteld voordat er opnieuw wordt opgestart
session.restore();

// de laag wordt hersteld [métier]
if (session.métier && session.métier.taxAdminData) {
  métier.setTaxAdminData(session.métier.taxAdminData);
}

// opstarten van de UI
new Vue({
  el: '#app',
  // de router
  router: router,
  // de Vuex-app
  store: store,
  // het hoofdscherm
  render: h => h(Main),
})

// eindlog
// eslint-disable-next-line no-console
console.log("main terminated, session=", session);
  • regel 19: de sessie wordt geïmporteerd;
  • regel 20: de bijbehorende plug-in wordt geïmporteerd;
  • regel 21: de plug-in [pluginSession] wordt geïntegreerd in [Vue]. Na deze instructie beschikken alle weergaven over de sessie in hun attribuut [$session];
  • regel 27: de sessie wordt hersteld. De in regel 11 geïmporteerde sessie wordt vervolgens geïnitialiseerd met de inhoud van de laatste opslag;
  • na regel 16 beschikken de weergaven over een eigenschap [$métier] die in regel 12 is geïnitialiseerd. Deze eigenschap bevat niet de informatie [taxAdminData] waarmee de belasting kan worden berekend;
  • regels 30-32: als bij het zojuist uitgevoerde herstel de eigenschap [session.métier.taxAdminData] is hersteld, dan wordt de eigenschap [$métier] van de weergaven geïnitialiseerd met deze waarde;

19.7. Het routeringsbestand [router]

Het routeringsbestand [./router] verandert als volgt:


// 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: 'authentification', 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 de URL
  mode: 'history',
  // de basis-URL van de applicatie
  base: '/client-vuejs-impot/'
})

// controle van de routes
router.beforeEach((to, from, next) => {
  // eslint-disable-next-line no-console
  console.log("router to=", to, "from=", from);
  // route voorbehouden aan 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

Opmerkingen

  • regels 16-38: sommige routes zijn aangevuld met extra informatie;
  • regel 19: er is een nieuwe route aangemaakt naar het uitzicht [Authentification];
  • regels 21-24: de route die naar de weergave [CalculImpot] leidt, heeft nu een eigenschap [meta] (deze naam is verplicht). De inhoud van dit object kan willekeurig zijn en wordt bepaald door de ontwikkelaar;
  • regel 23: we voegen in [meta] de eigenschap [authenticated] toe (deze naam kan willekeurig zijn). Dit betekent voor ons dat de gebruiker geauthenticeerd moet zijn om naar de weergave [CalculImpot] te gaan;
  • regels 26-29: we doen hetzelfde voor de route die naar de weergave [ListeSimulations] leidt. Ook hier moet de gebruiker geauthenticeerd zijn;
  • met de eigenschap [meta.authenticated] kunnen we controleren of een gebruiker die de URL van de weergaven [CalculImpot, ListeSimulations] handmatig invoert, deze niet kan verkrijgen als hij niet is geauthenticeerd;
  • regels 51-76: de methode [beforeEach] wordt uitgevoerd voordat een weergave wordt doorgestuurd. Dit is het juiste moment om controles uit te voeren;
    • [to]: de volgende route als er niets wordt gedaan;
    • [from]: de laatst weergegeven route;
    • [next]: functie waarmee de volgende weergegeven route kan worden gewijzigd;
  • regel 55: we controleren of de volgende route vereist dat de gebruiker is geauthenticeerd;
  • regels 56-59: zo ja, en als de gebruiker niet is geauthenticeerd, wordt de volgende route gewijzigd naar de weergave [Authentification];
  • regels 64-73: we behandelen het speciale geval van de route [finSession] uit de regels 30-32. Deze heeft geen bijbehorende weergave;
    • regel 66: de sessie wordt teruggezet naar de beginwaarde;
    • regels 68-70: we stellen de weergave [Authentification] in als volgende weergave;
  • regel 75: als geen van beide voorgaande gevallen van toepassing is, gaan we gewoon door naar de route die in het routeringsbestand is vastgelegd;
  • regels 35-37: er wordt een weergave [NotFound] voorzien als de door de gebruiker ingevoerde route niet overeenkomt met een bekende route. Deze weergave wordt geïmporteerd in regel 8. De routes worden gecontroleerd in de volgorde van het routeringsbestand. Als we dus bij regel 36 aankomen, betekent dit dat de gevraagde route geen van de routes uit de regels 18-33 is;

19.8. De weergave [NotFound]

De weergave [NotFound] wordt weergegeven als de door de gebruiker ingevoerde route niet overeenkomt met een bekende route:

Image

De code van de weergave is als volgt:


<!-- definitie HTML van de weergave -->
<template>
  <!-- pagina-indeling -->
  <Layout :left="true" :right="true">
    <!-- waarschuwing in de rechterkolom -->
    <template slot="right">
      <!-- bericht op gele achtergrond -->
      <b-alert show variant="danger" align="center">
        <h4>Cette page n'existe pas</h4>
      </b-alert>
    </template>
    <!-- navigatiemenu in de linkerkolom -->
    <Menu slot="left" :options="options" />
  </Layout>
</template>

<script>
// imports
import Layout from "./Layout";
import Menu from "./Menu";
export default {
  // componenten
  components: {
    Layout,
    Menu
  },
  // interne status van de component
  data() {
    return {
      // opties van het navigatiemenu
      options: [
        {
          text: "Authentification",
          path: "/"
        }
      ]
    };
  },
  // levenscyclus
  created() {
    // eslint-disable-next-line
    console.log("NotFound created");
    // we bekijken welke menuopties we aanbieden
    if (this.$session.authenticated && this.$métier.taxAdminData) {
      // de gebruiker kan simulaties uitvoeren
      Array.prototype.push.apply(this.options, [
        {
          text: "Calcul de l'impôt",
          path: "/calcul-impot"
        },
        {
          text: "Liste des simulations",
          path: "/liste-des-simulations"
        }
      ]);
    }
  }
};
</script>

Opmerkingen

  • regel 4: hier worden de twee kolommen van de gerouteerde weergaven gebruikt;
  • regels 6-11: een foutmelding;
  • regel 13: het navigatiemenu beslaat de linkerkolom;
  • regels 31-36: de standaardopties van het menu;
  • regels 40-57: code die wordt uitgevoerd wanneer de weergave wordt aangemaakt;
  • regel 44: er wordt gecontroleerd of de gebruiker simulaties kan uitvoeren;
  • regels 45-55: zo ja, dan worden twee opties aan het navigatiemenu toegevoegd, namelijk die waarvoor men moet zijn ingelogd en waarvoor een operationele laag [métier] vereist is (regels 46-55);

19.9. De weergave [Authentification]

De weergave [Authentification] verandert als volgt:


<!-- definitie HTML van de weergave -->
<template>
  <Layout :left="false" :right="true">
    ...
  </Layout>
</template>

<!-- dynamiek van de weergave -->
<script>
import Layout from "./Layout";
export default {
  // status van de component
  data() {
    return {
      // gebruiker
      user: "",
      // zijn wachtwoord
      password: "",
      // controleert de weergave van een foutmelding
      showError: false,
      // de foutmelding
      message: ""
    };
  },

  // gebruikte componenten
  components: {
    Layout
  },

  // berekende eigenschappen
  computed: {
    // geldige invoer
    valid() {
      return this.user && this.password && this.$session.started;
    }
  },

  // gebeurtenishandlers
  methods: {
    // ----------- authenticatie
    async login() {
      try {
        // begin wachtrij
        this.$emit("loading", true);
        // we zijn nog niet geauthenticeerd
        this.$session.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.$session.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 {
        // sessie bijwerken
        this.$session.métier = this.$métier;
        // de sessie wordt opgeslagen
        this.$session.save();
      }
    }
  },
  // levenscyclus: de component is zojuist aangemaakt
  created() {
    // eslint-disable-next-line
    console.log("Authentification created");
    // kan de gebruiker simulaties uitvoeren?
    if (
      this.$session.started &&
      this.$session.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.$session.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 [dao]-laag 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.$session.started = true;
        })
        // 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();
        });
    }
  }
};
</script>

Opmerkingen

  • de instructies die gebruikmaken van de sessie die in deze versie van de client [Vue.js] is ingevoerd, zijn geel gemarkeerd;
  • regels 97, 148: aan het einde van de methoden [login, created] wordt de sessie opgeslagen, ongeacht het resultaat van de HTTP-verzoeken die in deze methoden plaatsvinden (clausule [finally] in beide gevallen);
  • de methode [created] in de regels 102-150 wordt telkens uitgevoerd wanneer de weergave [Authentification] wordt aangemaakt. Als de gebruiker de URL van de weergave heeft ingevoerd, zal de sessie ons laten weten wat we moeten doen;
  • regels 106-115: als de sessie jSON is gestart, de gebruiker is geauthenticeerd en de gegevens van [this.$métier.taxAdminData] zijn geïnitialiseerd, dan kan de gebruiker direct naar het formulier voor de belastingberekening gaan (regel 112);
  • regel 117: de methode [created] werd in de vorige versie gebruikt om een sessie jSON met de server te initialiseren. Deze fase is overbodig als deze al heeft plaatsgevonden;
  • regels 42-66: de authenticatiemethode;
  • regel 66: als de authenticatie slaagt, wordt dit in de sessie genoteerd;
  • regels 67-92: het verzoek aan de server om de gegevens van de belastingdienst [taxAdminData];
  • regel 95: aan het einde van deze fase wordt de eigenschap [métier] van de sessie bijgewerkt, ongeacht of de bewerking is geslaagd of niet;

19.10. De weergave [CalculImpot]

De code van de weergave [CalculImpot] verandert als volgt:


<!-- definitie HTML van de weergave -->
<template>
  ...
</template>

<script>
// imports
import FormCalculImpot from "./FormCalculImpot";
import Menu from "./Menu";
import Layout from "./Layout";

export default {
  // 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
    };
  },
  // gebruikte componenten
  components: {
    Layout,
    FormCalculImpot,
    Menu
  },
  // methoden voor gebeurtenisbeheer
  methods: {
    // resultaat van de belastingberekening
    handleResultatObtenu(résultat) {
      // het resultaat wordt in een reeks samengesteld HTML
      ...
      // een simulatie van +
      this.$store.commit("addSimulation", résultat);
      // de sessie wordt opgeslagen
      this.$session.save();
    }
  },
  // levenscyclus
  created() {
    // eslint-disable-next-line
    console.log("CalculImpot created");
  }
};
</script>

Opmerkingen

  • regel 45: de berekende simulatie wordt toegevoegd aan de store [Vuex]. Dit heeft gevolgen voor de sessie die de eigenschap [state] van de store omvat. Daarom wordt de sessie opgeslagen (regel 47);
  • regel 51: er wordt een methode [created] aangemaakt om het aanmaken van weergaven in de logbestanden bij te houden;

19.11. De weergave [ListeSimulations]

De weergave [ListeSimulations] verandert als volgt:


<!-- definitie HTML van de weergave -->
<template>
  ...
  </div>
</template>

<script>
// imports
import Layout from "./Layout";
import Menu from "./Menu";
export default {
  // componenten
  components: {
    Layout,
    Menu
  },
  // interne status
  data() {
    ...
  },
  // berekende interne status
  computed: {
    // lijst met simulaties uit de Vuex-store
    simulations() {
      return this.$store.state.simulations;
    }
  },
  // 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();
    }
  },
  // levenscyclus
  created() {
    // eslint-disable-next-line
    console.log("ListeSimulations created");
  }
};
</script>

Opmerkingen

  • regel 36: na het verwijderen van een simulatie op regel 34 wordt de sessie opgeslagen om rekening te houden met deze statuswijziging;
  • regels 40-43: we volgen verder het aanmaken van de weergaven;

19.12. Uitvoering van het project

Image

Controleer tijdens het testen de volgende punten:

  • of de applicatie werkt wanneer de gebruiker deze ‘gebruikt’ via de links in het navigatiemenu en de actieknoppen/-links;
  • als de gebruiker handmatig URL invoert, blijft de applicatie functioneren. Voer met name de volgende test uit:
    • voer simulaties uit;
    • zodra u op de weergave [ListeSimulations] bent, laadt u de weergave opnieuw (F5). In de vorige applicatie [vuejs-20] gingen de simulaties dan verloren. Hier is dat niet het geval: de reeds uitgevoerde simulaties zijn nog steeds aanwezig;
  • bekijk de logbestanden om te begrijpen:
    • op welk moment het script [main] wordt uitgevoerd. Je moet zien dat dit gebeurt telkens wanneer de gebruiker handmatig een URL invoert;
    • op welke momenten de weergaven worden aangemaakt. U zult zien dat dit gebeurt telkens wanneer ze worden weergegeven;
    • hoe de routing werkt. Vóór elke routing wordt er een logboek gemaakt dat u het volgende aangeeft:
      • de route waar u vandaan komt;
      • de route waarnaar u gaat;

19.13. Implementatie van de applicatie op een lokale server

Volg als oefening de paragraaf |Implementatie op een lokale server| om het project [vuejs-21] op de lokale Laragon-server te implementeren. Test het vervolgens.

19.14. Afstemming van de mobiele versie

In theorie zou het gebruik van Bootstrap ons in staat moeten stellen om een applicatie te hebben die bruikbaar is op verschillende apparaten: smartphones, tablets, laptops en desktopcomputers. Wat deze apparaten van elkaar onderscheidt, is de grootte van hun scherm.

Als we de versie [vuejs-21] op een mobiel apparaat testen, zien we dat de weergave van de views een chaos is. De versie [vuejs-22] lost dit probleem op. De wijzigingen zijn allemaal aangebracht in de sjablonen van de weergaven. Ze bestonden voornamelijk uit het afstemmen van de weergave op een smartphonescherm. Zodra dit is afgestemd, verloopt de weergave op grotere schermen soepel dankzij Bootstrap.

Image

19.14.1. De weergave [Main]

De weergave [Main] verandert als volgt:


<!-- definitie HTML van de weergave -->
<template>
  <div class="container">
    <b-card>
      <!-- jumbotron -->
      <b-jumbotron>
        <b-row>
          <b-col sm="4">
            <img src="../assets/logo.jpg" alt="Cerisier en fleurs" />
          </b-col>
          <b-col sm="8">
            <h1>Calculez votre impôt</h1>
          </b-col>
        </b-row>
      </b-jumbotron>
      ....
    </b-card>
  </div>
</template>

Opmerkingen

  • regel 8: waar voorheen [cols=’4’] stond, wordt nu [sm=’4’] geschreven. [sm] betekent [small]. Smartphoneschermen vallen in deze categorie. De andere categorieën zijn [xs=extra small, md=medium, lg=large, xl=extra large];
  • regel 11: idem;

19.14.2. De weergave [Layout]

De weergave [Layout] verandert als volgt:


<!-- definitie HTML van de lay-out van de gerouteerde weergave -->
<template>
  <!-- regel -->
  <div>
    <b-row>
      <!-- gebied met drie kolommen links -->
      <b-col sm="3" v-if="left">
        <slot name="left" />
      </b-col>
      <!-- gebied met negen kolommen rechts -->
      <b-col sm="9" v-if="right">
        <slot name="right" />
      </b-col>
    </b-row>
  </div>
</template>

19.14.3. De weergave [Authentification]

De weergave [Authentification] verandert als volgt:


<!-- 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" description="Tapez admin">
          <!-- invoerveld gebruiker -->
          <b-col sm="6">
            <b-form-input type="text" id="user" placeholder="Nom d'utilisateur" v-model="user" />
          </b-col>
        </b-form-group>
        <!-- 2e regel -->
        <b-form-group label="Mot de passe" label-for="password" description="Tapez admin">
          <!-- wachtwoordveld -->
          <b-col sm="6">
            <b-input type="password" id="password" placeholder="Mot de passe" v-model="password" />
          </b-col>
        </b-form-group>
        <!-- derde regel -->
        <b-alert
          show
          variant="danger"
          v-if="showError"
          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 sm="2">
            <b-button variant="primary" type="submit" :disabled="!valid">Valider</b-button>
          </b-col>
        </b-row>
      </b-form>
    </template>
  </Layout>
</template>

Opmerkingen

  • regels 11 en 19: het attribuut [label-cols], dat het aantal kolommen van het invoerlabel vastlegde, is verwijderd. Zonder dit attribuut staat het label boven het invoerveld. Dit is beter geschikt voor smartphoneschermen;

19.14.4. De weergave [CalculImpot]

De weergave [CalculImpot] is als volgt gewijzigd:


<!-- 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>

19.14.5. De weergave [FormCalculImpot]

De weergave [FormCalculImpot] verandert als volgt:


<!-- 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
        type="text"
        id="enfants"
        placeholder="Indiquez votre nombre d'enfants"
        v-model="enfants"
        :state="enfantsValide"
      ></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
        type="text"
        id="salaire"
        placeholder="Salaire annuel"
        v-model="salaire"
        :state="salaireValide"
      ></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 type="submit" variant="primary" :disabled="formInvalide">Valider</b-button>
    </b-col>
  </b-form>
</template>

Opmerkingen

  • regels 15, 23, 35: het attribuut [label-cols] is verwijderd;

Daarnaast worden de validiteitstests aangepast:


...
// berekende interne status
  computed: {
    // formulier valideren
    formInvalide() {
      return (
        // ongeldig salaris
        !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*$/)
      );
    }
},
...

Opmerkingen

  • regel 19: wanneer er niets is ingevoerd, wordt de invoer als geldig beschouwd. Hierdoor is de invoer geldig wanneer de weergave voor het eerst wordt getoond. In de vorige versie werd de invoer aanvankelijk als onjuist weergegeven;
  • regel 26: idem;
  • regels 5-14: de validatieknop is alleen actief als beide velden iets bevatten en geldig zijn;

19.14.6. Het scherm [Menu]

Het scherm [Menu] wordt als volgt aangepast:


<!-- definitie HTML van de weergave -->
<template>
  <b-card class="mb-3">
    <!-- verticaal Bootstrap-menu -->
    <b-nav vertical>
      <!-- menuopties -->
      <b-nav-item
        v-for="(option,index) of options"
        :key="index"
        :to="option.path"
        exact
        exact-active-class="active"
      >{{option.text}}</b-nav-item>
    </b-nav>
  </b-card>
</template>

Opmerkingen

  • regel 3: de tag <b-card> wordt toegevoegd om het menu met een dunne rand te omringen. Hierdoor is het menu beter te vinden op de smartphone;

19.14.7. De weergave [ListeSimulations]

De weergave [ListeSimulations] blijft ongewijzigd:


<!-- 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 striped hover responsive :items="simulations" :fields="fields">
            <template v-slot:cell(action)="data">
              <b-button variant="link" @click="supprimerSimulation(data.index)">Supprimer</b-button>
            </template>
          </b-table>
        </template>
      </template>
      <!-- navigatiemenu in de linkerkolom -->
      <Menu slot="left" :options="options" />
    </Layout>
  </div>
</template>

Opmerkingen

  • regel 20: let op het attribuut [responsive], waardoor de weergave van de tabel zich aanpast aan de schermgrootte:

Image

  • in [2] zorgt een horizontale schuifbalk ervoor dat de tabel op kleine schermen kan worden weergegeven;

19.14.8. De weergave [NotFound]

Blijft ongewijzigd.

19.14.9. De weergaven op mobiele apparaten

Image

Image

Opmerking: er zijn ongetwijfeld mogelijkheden om weergaven te realiseren die nog beter zijn afgestemd op mobiele apparaten. Ik denk met name aan het navigatiemenu, dat verbeterd zou kunnen worden, maar er zijn ook andere punten. Het primaire doel van dit document was niet het ontwikkelen van een mobiele app. In dat geval zouden we wellicht hebben gekozen voor een framework zoals Ionic |https://ionicframework.com/|.