07 Projet
Faire un projet avec Vue.js.
Rendu
- GitHub Classroom : https://classroom.github.com/a/0bPUTptp
./report.md
: Rapport individuel en Markdown.
- Rendu le 26 décembre 2024 à 23h59.
Journal de bord
- Écrire un journal de bord dans le fichier
report.md
.- La mise en forme est libre, structurez-le afin de s'y retrouver facilement chaque semaine.
- Éléments obligatoires qui doit figurer pour chaque semaine :
- Temps estimé et temps passé sur le projet.
- Tâches réalisées.
- Difficultés rencontrées et solutions trouvées.
- Explications et réflexions sur le code (Il y aura quelques questions pour vous guider chaque semaine).
- Suite du projet (Que pourrait-on faire pour la suite du projet ?).
Semaine 1
Vue.js
- Cloner le nouveau dépôt Git dans le répertoire du cours.
- Ouvrir le répertoire du dépôt Git dans Visual Studio Code.
- Installer l'extension Vue - Official.
- Créer le fichier
report.md
. - Créer un projet Vue.js depuis le répertoire du dépôt :
npm create vue@latest
- Project name:
quiz
- Add TypeScript?
Yes
- Add JSX Support?
No
- Add Vue Router for Single Page Application development?
Yes
- Add Pinia for state management?
No
- Add Vitest for Unit Testing?
No
- Add an End-to-End Testing Solution?
No
- Add ESLint for code quality?
Yes
- Add Prettier for code formatting?
Yes
- Add Vue DevTools 7 extension for debugging? (experimental)
Yes
- Project name:
- Vérifier où se trouve le projet :
sem07-project-{pseudo}
├── .git
├── .github
├── quiz
└── ...
├── README.md
└── report.md - Copier tous les fichiers du projet Vue.js (dossier
quiz
) dans le répertoire du dépôt Git (dossiersem07-project-{pseudo}
) en écrasant les fichiers existants et supprimer le dossierquiz
:sem07-project-{pseudo}
├── .git
├── .github
├── .vscode
├── public
├── src
├── .editorconfig
├── .gitignore
├── .prettierrc.json
├── env.d.ts
├── eslint.config.js
├── index.html
├── package.json
├── README.md
├── report.md
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts - Installer les dépendances et formater le code :
npm install
npm run format - Pour lancer le projet en mode développement :
npm run dev
- Ouvrir le navigateur à l'adresse indiquée pour voir le projet.
Bootstrap
- Installer Bootstrap et Bootstrap Icons :
npm install bootstrap @popperjs/core bootstrap-icons
- Changer la langue et le titre de l'application en modifiant
index.html
:<!doctype html>
<html lang="fr">
<head>
...
<title>Quiz</title>
</head>
...
</html> - Dans
eslint.config.js
, remplacerpluginVue.configs['flat/essential']
parpluginVue.configs['flat/recommended']
. - Supprimer tous les fichiers dans
src/components
et danssrc/assets
. - Créer ou modifier les fichiers suivants :
- main.ts
- App.vue
- main.css
- AboutView.vue
- HomeView.vue
- QuizForm.vue
import "bootstrap-icons/font/bootstrap-icons.min.css";
import "bootstrap/dist/css/bootstrap.min.css";
import "./assets/main.css";
import "bootstrap";
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
const app = createApp(App);
app.use(router);
app.mount("#app");
<script setup lang="ts">
import { RouterLink, RouterView } from "vue-router";
</script>
<template>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<RouterLink class="navbar-brand" to="/">
<i class="bi bi-question-square"></i>
Quiz
</RouterLink>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbar"
aria-controls="navbar"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div id="navbar" class="collapse navbar-collapse">
<ul class="navbar-nav">
<li class="nav-item">
<RouterLink class="nav-link" to="/about">
<i class="bi bi-info-square"></i>
À propos
</RouterLink>
</li>
</ul>
</div>
</div>
</nav>
<RouterView />
</template>
/* https://getbootstrap.com/docs/5.3/components/buttons/#variables */
.btn-primary {
--bs-btn-color: #fff;
--bs-btn-bg: #0d6efd;
--bs-btn-border-color: #0d6efd;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #0b5ed7;
--bs-btn-hover-border-color: #0a58ca;
--bs-btn-focus-shadow-rgb: 49, 132, 253;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #0a58ca;
--bs-btn-active-border-color: #0a53be;
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #0d6efd;
--bs-btn-disabled-border-color: #0d6efd;
}
<template>
<div class="container mt-3">
<h1>À propos</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
</div>
</template>
<script setup lang="ts">
import QuizForm from "@/components/QuizForm.vue";
</script>
<template>
<div class="container mt-3">
<h1>Quiz</h1>
<QuizForm />
</div>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
const cheval = ref<string | null>(null);
const filled = computed<boolean>(() => cheval.value !== null);
function submit(event: Event): void {
event.preventDefault();
if (filled.value) {
alert(`Vous avez choisi la couleur ${cheval.value} !`);
}
}
</script>
<template>
<form>
De quelle couleur est le cheval blanc de Napoléon ?
<div class="form-check">
<input
id="chevalBlanc"
v-model="cheval"
class="form-check-input"
type="radio"
name="cheval"
value="blanc"
/>
<label class="form-check-label" for="chevalBlanc">Blanc</label>
</div>
<div class="form-check">
<input
id="chevalBrun"
v-model="cheval"
class="form-check-input"
type="radio"
name="cheval"
value="brun"
/>
<label class="form-check-label" for="chevalBrun">Brun</label>
</div>
<div class="form-check">
<input
id="chevalNoir"
v-model="cheval"
class="form-check-input"
type="radio"
name="cheval"
value="noir"
/>
<label class="form-check-label" for="chevalNoir">Noir</label>
</div>
<button
class="btn btn-primary"
:class="{ disabled: !filled }"
@click="submit"
>
Terminer
</button>
</form>
</template>
- Vérifier que l'application fonctionne correctement.
- Voici le code source et le site web final.
- Ne pas oublier de faire régulièrement des commits à chaque fois qu'on a un état stable du projet (code fonctionnel, comme ici).
Quiz
- Modifier le quiz pour qu'il y ait deux-trois questions à choix multiples. Voici quelques idées :
- De quelle couleur est le cheval blanc de Napoléon ?
- Combien de pattes a un chat ?
- Quelle est la capitale de la Suisse ?
- Proposer quatre réponses possibles pour chaque question.
- Afficher le score à la fin du quiz (mettre la logique du calcul dans la fonction
submit
, juste après leevent.preventDefault();
). - Afficher un message de félicitations si le score est parfait.
- Ajouter un bouton pour réinitialiser le quiz.
- Ajouter un bouton dans
QuizForm.vue
:<button class="btn btn-secondary" @click="reset">Réinitialiser</button>
- Votre bouton va appeler une fonction
reset
qu'il faudra créer.
- Ajouter un bouton dans
- Modifier la couleur des
.btn-primary
dansmain.css
. - Changer les icônes dans la bar de navigation (en haut) en utilisant Bootstrap Icons.
- Répondre aux questions suivantes dans le rapport :
- Expliquer le rôle des fichiers suivants :
main.ts
main.css
App.vue
router/index.ts
AboutView.vue
HomeView.vue
QuizForm.vue
- Dans le fichier
QuizForm.vue
:- Quelles sont les similarités et les différences entre
ref
etcomputed
? - Que se passe-t-il lorsqu'on clique sur le bouton "Terminer" ?
- Qu'est-ce qu'un
v-model
? - À quoi sert le
:class="{ disabled: !filled }"
?
- Quelles sont les similarités et les différences entre
- Expliquer le rôle des fichiers suivants :
Semaine 2
QuestionRadio
C'est fastidieux de devoir répéter les mêmes étapes pour chaque question. On va donc créer un composant pour les questions : QuestionRadio.vue
.
Commencer par définir comment on voudrait que le composant fonctionne. On pourrait vouloir remplacer chaque question par un composant QuestionRadio
:
<template>
<form>
<QuestionRadio
id="cheval"
v-model="cheval"
text="De quelle couleur est le cheval blanc de Napoléon ?"
:options="[
{ value: 'blanc', text: 'Blanc' },
{ value: 'brun', text: 'Brun' },
{ value: 'noir', text: 'Noir' },
]"
/>
...
</form>
</template>
- Le composant
QuestionRadio
doit recevoir les propriétés suivantes :v-model
: la valeur de la réponse (bi-directionnel, car on veut pouvoir modifier la réponse depuis le composant parent lorsqu'on clique sur le bouton "Réinitialiser" et récupérer la réponse depuis le composant parent pour calculer le score).id
: un identifiant unique pour le groupe de boutons radio.text
: le texte de la question.options
: un tableau d'objets pour les options de réponse. Chaque objet doit avoir une propriétévalue
pour la valeur de la réponse et une propriététext
pour le texte affiché de l'option.
- Ne pas oublier d'importer le nouveau composant dans
QuizForm.vue
:<script setup lang="ts">
import QuestionRadio from "@/components/QuestionRadio.vue";
...
</script>
Créer le fichier QuestionRadio.vue
dans le dossier src/components
:
<script setup lang="ts">
import { defineModel, defineProps, type PropType } from "vue";
const model = defineModel<string | null>();
const props = defineProps({
id: { type: String, required: true },
text: { type: String, required: true },
options: {
type: Array as PropType<Array<{ value: string; text: string }>>,
required: true,
},
});
</script>
<template>
{{ props.text }}
<div v-for="option in props.options" :key="option.value" class="form-check">
<input
:id="`${props.id}-${option.value}`"
v-model="model"
class="form-check-input"
type="radio"
:name="props.id"
:value="option.value"
/>
<label class="form-check-label" :for="`${props.id}-${option.value}`">
{{ option.text }}
</label>
</div>
</template>
- Dans la partie
<script>
, on utilise les fonctionsdefineModel
etdefineProps
pour définir le modèle (v-model
) et les propriétés (text
,name
,options
) du composant. - Dans la partie
<template>
:- On affiche le texte de la question :
{{ props.text }}
. - On affiche les options de réponse en utilisant une boucle
v-for
surprops.options
: le<div>
sera répété pour chaque option. - La différence entre les attributs qui commencent par
:
et ceux qui ne commencent pas par:
est que les premiers sont des expressions JavaScript (interprétées) et les seconds sont des chaînes de caractères (non interprétées).
- On affiche le texte de la question :
Quelle est la différence entre un prop et un modèle (v-model
) ?
QuestionText
De manière similaire, créer un composant QuestionText.vue
pour les questions à réponse textuelle libre. Voici un code qu'on voudrait extraire dans le composant QuestionText.vue
:
<label for="exampleFormControlInput" class="form-label">
Combien de pattes a un chat ?
</label>
<input
id="exampleFormControlInput"
v-model="reponse"
class="form-control"
placeholder="Veuillez saisir un nombre"
/>
Et on souhaiterait l'utiliser comme ceci dans QuizForm.vue
:
<QuestionText
id="chat"
v-model="reponse"
text="Combien de pattes a un chat ?"
placeholder="Veuillez saisir un nombre"
/>
Comment rendre la propriété placeholder
optionnelle ?
Documentation : Vue.js + Bootstrap.
API
Open Trivia Database est une API qui fournit des questions de quiz. On va utiliser cette API pour obtenir des questions aléatoires pour notre quiz :
En naviguant sur le site, on peut voir qu'on peut obtenir des questions en faisant une requête HTTP GET à l'URL suivante : https://opentdb.com/api.php?amount=10&type=multiple
amount
: le nombre de questions à obtenir.type
: le type de questions (multiple ou boolean).
Ajouter une nouvelle tab Trivia
dans App.vue
:
...
<ul class="navbar-nav">
<li class="nav-item">
<RouterLink class="nav-link" to="/trivia">
<i class="bi bi-question"></i>
Trivia
</RouterLink>
</li>
...
</ul>
...
Créer une nouvelle vue TriviaView.vue
dans le dossier src/views
:
<script setup lang="ts">
import QuizTrivia from "@/components/QuizTrivia.vue";
</script>
<template>
<div class="container mt-3">
<h1>Trivia</h1>
<QuizTrivia />
Source :
<a href="https://opentdb.com/" target="_blank">Open Trivia Database</a>
</div>
</template>
Mettre à jour le fichier router/index.ts
en ajoutant une nouvelle route et en remplaçant createWebHistory
par createWebHashHistory
:
...
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
...
{
path: '/trivia',
name: 'trivia',
component: () => import('../views/TriviaView.vue'),
},
]
...
Finalement ajouter le composant QuizTrivia.vue
dans le dossier src/components
:
<script setup lang="ts">
import QuestionRadio from "@/components/QuestionRadio.vue";
import { reactive, ref } from "vue";
const questions = ref<
{
question: string;
correct_answer: string;
incorrect_answers: string[];
}[]
>([]);
const answers = reactive<{ [key: number]: string | null }>({});
fetch("https://opentdb.com/api.php?amount=10&type=multiple")
.then((response) => response.json())
.then((data) => (questions.value = data.results));
</script>
<template>
<form>
<QuestionRadio
v-for="(question, index) in questions"
:id="index.toString()"
:key="index"
v-model="answers[index]"
:text="question.question"
:options="[
{ value: question.correct_answer, text: question.correct_answer },
...question.incorrect_answers.map(answer => ({
value: answer,
text: answer,
})),
]"
/>
</form>
</template>
À sa création, ce composant va récupérer 10 questions avec l'API (https://opentdb.com/api.php?amount=10&type=multiple) et stocker les questions dans la ref
questions
. Ensuite, on affiche chaque question avec le composant QuestionRadio
(avec une boucle v-for
) en passant les propriétés nécessaires.
QuestionCheckbox (bonus)
Les checkboxes sont comme les radios, mais on peut en sélectionner plusieurs. Créer un composant QuestionCheckbox.vue
pour les questions à choix multiples. Voici un exemple d'utilisation :
<div class="form-check">
<input
id="checkboxJane"
v-model="checkedNames"
class="form-check-input"
type="checkbox"
value="Jane"
/>
<label class="form-check-label" for="checkboxJane">Jane</label>
</div>
<div class="form-check">
<input
id="checkboxJohn"
v-model="checkedNames"
class="form-check-input"
type="checkbox"
value="John"
/>
<label class="form-check-label" for="checkboxJohn">John</label>
</div>
Noter que comme la réponse est une liste, il faut initialiser la ref
avec une liste vide :
const checkedNames = ref<string[]>([]);
Documentation : Vue.js + Bootstrap.
Semaine 3
Réponse
On voudrait incorporer la vérification de la réponse dans chaque composant de question car elle est spécifique à chaque type de question.
Modifier la partie script
de QuestionRadio.vue
:
- Modifier la propriété
v-model
pour qu'elle soit de typeboolean
.- Chaque question va juste indiquer si la réponse de l'utilisateur est correcte ou non.
- Ajouter une nouvelle propriété
answer
qui contient la réponse correcte. - Ajouter une nouvelle
ref
value
pour stocker la réponse de l'utilisateur. - Ajouter un
watch
survalue
qui permet d'exécuter une fonction à chaque fois quevalue
change (Documentation).- Cette fonction prend en paramètre la nouvelle valeur de
value
. - Elle va comparer la réponse de l'utilisateur avec la réponse correcte et mettre à jour le modèle
model
en conséquence.
- Cette fonction prend en paramètre la nouvelle valeur de
- Dans le template, modifier le
v-model
des inputs pour qu'il soit lié àvalue
.
<script setup lang="ts">
import { ref, watch, type PropType } from "vue";
const model = defineModel<boolean>();
const props = defineProps({
id: { type: String, required: true },
text: { type: String, required: true },
answer: { type: String, required: true },
options: {
type: Array as PropType<Array<{ value: string; text: string }>>,
required: true,
},
});
const value = ref<string | null>(null);
watch(
value,
(newValue) => {
model.value = newValue === props.answer;
},
{ immediate: true },
);
</script>
<template>
{{ props.text }}
<div v-for="option in props.options" :key="option.value" class="form-check">
<input
:id="`${props.id}-${option.value}`"
v-model="value"
...
Dans QuizForm.vue
, ajouter une nouvelle ref
correctAnswers
pour stocker l'exactitude de chaque réponse :
- Modifier la propriété
v-model
de chaqueQuestionRadio
pour qu'elle soit un élément de la listecorrectAnswers
. - Ajouter l'attribut
answer
à chaqueQuestionRadio
pour indiquer la réponse correcte. - Ajouter une nouvelle
div
pour afficher la valeur decorrectAnswers
.
<script setup lang="ts">
...
const correctAnswers = ref<boolean[]>([])
...
</script>
<template>
<form>
<QuestionRadio v-model="correctAnswers[0]" answer="blanc" ... />
...
<div>Réponses correctes : {{ correctAnswers }}</div>
</form>
</template>
Tester votre application :
- Les valeurs de
correctAnswers
doivent êtretrue
si la réponse de l'utilisateur est correcte etfalse
sinon. - Les valeurs de
correctAnswers
doivent être mises à jour à chaque fois que l'utilisateur change sa réponse.
À quoi sert l'option immediate: true
dans le watch
? Que se passe-t-il si on l'enlève ou si on met immediate: false
?
Mettre à jour le composant QuestionText.vue
de manière similaire à QuestionRadio.vue
.
Score
Dans QuizForm.vue
, le calcul du score se fera désormais selon les valeurs de correctAnswers
:
- Ajouter une nouvelle
computed
score
qui calcule le score en fonction des valeurs decorrectAnswers
(Documentation).correctAnswers.value
est un tableau de booléens.- La méthode
filter
retourne un nouveau tableau avec les éléments qui satisfassent la condition (Exemple). - Ici, on ne va garder que les éléments qui sont
true
(réponses correctes). - La propriété
length
retourne la taille du nouveau tableau qui ne contient que destrue
. - On a donc compté le nombre de
true
danscorrectAnswers.value
.
- Ajouter une nouvelle
computed
totalScore
qui calcule le score maximal possible. - Ajouter une nouvelle
div
pour afficher le score actuel et le score maximal possible.
<script setup lang="ts">
...
const score = computed<number>(() => correctAnswers.value.filter((value) => value).length);
const totalScore = ... // à compléter
...
</script>
<template>
<form>
...
<div>Score : {{ score }} / {{ totalScore }}</div>
</form>
</template>
Nettoyer le code en enlevant les parties qui ne sont plus nécessaires (l'ancienne logique de calcul du score).
Proposer une autre manière de calculer le score (réécrire la fonction du computed) et comparer les deux méthodes.
Le calcul du score se fait maintenant en temps réel : à chaque fois que l'utilisateur change sa réponse, le score est mis à jour automatiquement.
La logique des boutons "Terminer" et "Réinitialiser" sera revue la prochaine semaine.
Semaine 4
États
Il n'est pas idéal que les questions soient corrigées en temps réel. Chaque question peut donc avoir plusieurs états :
Empty
: la question n'a pas été répondue.Fill
: la question a été répondue.Submit
: la question a été soumise pour correction.Correct
: la réponse est juste.Wrong
: la réponse est fausse.
Créer un nouveau fichier src/utils/models.ts
:
export enum QuestionState {
Empty = "Empty",
Fill = "Fill",
Submit = "Submit",
Correct = "Correct",
Wrong = "Wrong",
}
- Un enum (type énuméré) est un type qui peut prendre une valeur parmi un ensemble de valeurs pré-définies.
- On crée un enum
QuestionState
qui contient les états possibles pour une question. export
permet d'importer cet enum dans d'autres fichiers.
Le modèle de chaque question va donc contenir un état au lieu d'un booléen :
...
import { QuestionState } from '@/utils/models'
...
const model = defineModel<QuestionState>()
...
Ajouter un nouveau watch
sur model
pour corriger la question si l'état est Submit
:
- Si l'état est
Submit
, il deviendraCorrect
si la réponse est juste etWrong
sinon.
...
watch(
model,
(newModel) => {
if (newModel === QuestionState.Submit) {
model.value = value.value === props.answer ? QuestionState.Correct : QuestionState.Wrong
}
},
);
...
Comment pourrait-on réécrire la ligne suivante sans l'opérateur ternaire (avec des if
et else
) ?
model.value =
value.value === props.answer ? QuestionState.Correct : QuestionState.Wrong;
Adapter le watch
sur value
pour qu'il mette à jour le modèle (Empty
ou Fill
) :
- Si la valeur devient
null
, l'état devientEmpty
. - Sinon, l'état devient
Fill
.
...
watch(
value,
(newValue) => {
if (newValue === null) {
model.value = QuestionState.Empty
} else {
model.value = QuestionState.Fill
}
},
{ immediate: true },
);
...
Comment pourrait-on réécrire autrement la logique du watch
sur value
?
Adapter QuestionText.vue
de manière similaire à QuestionRadio.vue
.
Dans QuizForm.vue
, on va stocker l'état de chaque question :
- Renommer
correctAnswers
enquestionStates
. - Changer le type de
questionStates
pour qu'il soit un tableau deQuestionState
. - Adapter le calcul du score (compter le nombre de
Correct
dansquestionStates
).
...
const questionStates = ref<QuestionState[]>([])
const score = computed<number>(
() =>
questionStates.value
.filter(state => state === QuestionState.Correct)
.length,
)
const totalScore = computed<number>(() => questionStates.value.length)
...
Afin de plus facilement debug l'application, ajouter une nouvelle div
pour afficher les états de chaque question :
<template>
<form>
...
<div>Debug états : {{ questionStates }}</div>
</form>
</template>
L'application devrait être fonctionnelle et les états des questions devraient être correctement mis à jour.
Boutons
Le bouton "Terminer" nous permet de soumettre toutes les réponses pour correction. Adapter la fonction submit
dans QuizForm.vue
:
questionStates
va devenir une liste avec que desSubmit
.map
permet d'appliquer une fonction à chaque élément d'un tableau (Documentation). Ici, la fonction retourneQuestionState.Submit
pour chaque élément.
...
function submit(event: Event): void {
event.preventDefault()
questionStates.value = questionStates.value.map(() => QuestionState.Submit)
}
...
De manière similaire, adapter la fonction reset
pour mettre tous les états à Empty
:
Solution
...
function reset(event: Event): void {
event.preventDefault()
questionStates.value = questionStates.value.map(() => QuestionState.Empty)
}
...
Pour que le reset
fonctionne, il faut aussi adapter le watch
sur model
dans QuestionRadio.vue
et QuestionText.vue
:
- Si l'état est
Empty
,value
doit redevenirnull
.
Solution
...
watch(
model,
(newModel) => {
if (newModel === QuestionState.Submit) {
model.value = value.value === props.answer ? QuestionState.Correct : QuestionState.Wrong
} else if (newModel === QuestionState.Empty) {
value.value = null
}
},
);
...
De retour sur QuizForm
, adapter filled
pour qu'il vérifie si toutes les questions sont dans l'état Fill
:
every
retournetrue
si toutes les valeurs du tableau satisfont la condition (Documentation).
...
const filled = computed<boolean>(() => questionStates.value.every(state => state === QuestionState.Fill))
...
Afficher le score uniquement si toutes les questions ont été soumises et corrigées :
<template>
<form>
...
<div v-if="submitted">Score : {{ score }} / {{ totalScore }}</div>
</form>
</template>
Définir submitted
(de manière similaire à filled
) : vérifier si tous les états sont Correct
ou Wrong
.
Solution
...
const submitted = computed<boolean>(() => questionStates.value.every(state => state === QuestionState.Correct || state === QuestionState.Wrong))
...
Réponses immuables
Rendre les réponses immuables (non modifiables) après avoir soumis le quiz :
- Dans
QuestionRadio.vue
etQuestionText.vue
, lesinput
doivent êtredisabled
si l'état estSubmit
,Correct
ouWrong
.
<template>
...
<input
...
:disabled="
model === QuestionState.Submit ||
model === QuestionState.Correct ||
model === QuestionState.Wrong
"
/>
...
</template>
Semaine 5
Réponse détaillée
Lors de la correction, on voudrait afficher un texte expliquant la réponse correcte.
- Ajouter une nouvelle propriété
answerDetail
auxQuestionText
:src/components/QuestionText.vueconst props = defineProps({
id: { type: String, required: true },
text: { type: String, required: true },
answer: { type: String, required: true },
answerDetail: { type: String, default: "" },
placeholder: { type: String, default: "Veuillez saisir une réponse" },
}); - Ajouter ceci à la fin du template :
src/components/QuestionText.vue
<template>
...
<div
v-if="model === QuestionState.Correct || model === QuestionState.Wrong"
>
<p v-if="model === QuestionState.Correct" class="text-success">Juste !</p>
<p v-else class="text-danger">
Faux ! La réponse était : {{ props.answer }}
</p>
<p class="blockquote-footer">{{ props.answerDetail }}</p>
</div>
</template>v-if
permet d'afficher une balise HTML si la condition est vraie (Documentation).v-else
est lié au dernierv-if
et affiche une balise HTML si la condition duv-if
est fausse.class="text-success"
etclass="text-danger"
permettent de changer la couleur du texte avec Bootstrap (Documentation).class="blockquote-footer"
a été utilisé pour afficher les détails (Documentation).
- Dans
QuizForm.vue
, ajouter la nouvelle propriétéanswer-detail
dans lesQuestionText
:src/components/QuizForm.vue<QuestionText answer-detail="Le chat est un mammifère quadrupède." />
- Adapter
QuestionRadio.vue
de manière similaire àQuestionText.vue
.
Ajouter ce computed
dans QuestionRadio.vue
:
const answerText = computed<string>(
() =>
props.options.find((option) => option.value === props.answer)?.text ??
props.answer,
);
Remplacer {{ props.answer }}
par {{ answerText }}
dans le template.
Expliquer pourquoi on a fait ce changement ainsi que le code du computed
.
Que se passe-t-il lorsqu'on ne met pas de valeur à answer-detail
? Est-ce satisfaisant ? Si ce n'est pas le cas, proposer une amélioration.
Style
Pour changer les couleurs dans un composant, ajouter un <style scoped>
à la fin du fichier :
...
<style scoped>
.text-danger {
color: purple !important;
}
</style>
scoped
permet d'appliquer les styles uniquement au composant actuel (Documentation).!important
permet de forcer l'application du style (Documentation).- Aussi possible de créer les noms de classes personnalisés (par exemple,
.answer-correct
et.answer-wrong
).
Semaine 6
Déploiement
Publier le projet sur GitHub Pages :
- Configurer GitHub Pages dans les paramètres du dépôt :
- Changer la visibilité du dépôt en public (Documentation) :
Settings
>General
>Danger Zone
(tout en bas) >Change visibility
. - Sur GitHub, dans le dépôt, aller dans
Settings
>Pages
> SousBuild and deployment
puisSource
, sélectionnerGitHub Actions
.
- Changer la visibilité du dépôt en public (Documentation) :
- Créer un fichier
.github/workflows/deploy.yml
avec le même contenu que ce fichierdeploy.yml
. - GitHub Actions va automatiquement construire le site et le déployer sur GitHub Pages à chaque push sur la branche
main
. - Ajouter le lien du site dans le rapport :
https://hepl-bs21inf5.github.io/sem07-project-{pseudo]/
. - Le lien devrait vous renvoyer vers une page blanche, pour corriger cela, modifier le fichier
vite.config.ts
pour ajouter le chemin de base :
import vue from "@vitejs/plugin-vue";
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vueDevTools from "vite-plugin-vue-devtools";
// https://vite.dev/config/
export default defineConfig({
base: "/sem07-project-{pseudo}/",
plugins: [vue(), vueDevTools()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});
Améliorations
Voici quelques idées pour améliorer le projet :
QuestionCheckbox.vue
: Sélectionner plusieurs réponses.QuestionSelect.vue
: Sélectionner une réponse dans une liste déroulante.- Accepter plusieurs réponses possibles pour
QuestionText.vue
(par exemple, "2" ou "deux"). - Adapter le Trivia pour pouvoir y jouer.
- Ordre aléatoire des choix dans
QuestionRadio.vue
. - Ordre aléatoire des questions.
Comme les améliorations ne demandent pas toutes le même travail, elles seront pondérées différemment selon leur complexité (par exemple, l'ordre aléatoire des choix est simple donc il ne comptera que pour 0.5 pour le critère).
Expliquer votre démarche pour les améliorations que vous avez choisies :
- Pourquoi avez-vous choisi ces améliorations ?
- Comment les avez-vous implémentées ?
- Quels problèmes avez-vous rencontrés ?
- Quelles améliorations pourriez-vous encore apporter ?
Vous devoir pouvoir expliquer votre code afin de valider une amélioration.
Nettoyage et vérification
- Vérifier tout le projet et nettoyer les codes qui ne sont plus utilisés.
- Vérifier que le code est correct localement, on peut construire le projet :
npm run build
- Vérifier que le site est correct en ligne et fonctionne sur plusieurs appareils (ordinateur, téléphone, …).
- Vérifier que le rendu du rapport (Cmd/Ctrl + Shift + V) est correct.
Aides
Documentations
Aidez-vous des documentations officielles pour réaliser le projet :
Vérification
Pour vérifier que le code est correct localement, on peut construire le projet :
npm run build
Exemple
Évaluation
L'évaluation du projet se portera sur les critères suivants :
- Rapport
- Le journal de bord est à jour et complet.
- Le journal de bord est bien structuré et synthétique.
- Fonctionnalités
- L'application a les mêmes fonctionnalités que l'exemple.
- L'application est personnalisée.
- L'application a plus de fonctionnalités que l'exemple.
- L'application a encore plus de fonctionnalités que l'exemple.
- Bootstrap est correctement utilisé pour rendre l'application responsive.
- Code
- Le code suit les conventions de codage (formatage, nommage, organisation, …).
- Le code est lisible et maintenable (nommage, commentaires, …).
Note | 1 | 2 | 2.5 | 3 | 3.5 | 4 | 4.5 | 5 | 5.5 | 6 |
---|---|---|---|---|---|---|---|---|---|---|
Nombre de critères validés | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
- En gras : critères principaux.
- En italique : critères secondaires.