thomas
This commit is contained in:
58
TP1/EX1/Ant.js
Normal file
58
TP1/EX1/Ant.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
class Ant {
|
||||||
|
constructor(grid, startX, startY) {
|
||||||
|
this.grid = grid;
|
||||||
|
this.x = startX;
|
||||||
|
this.y = startY;
|
||||||
|
this.direction = 0; // 0: N, 90: E, 180: S, 270: W
|
||||||
|
this.colors = { white: 0, black: 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Effectue un pas de la fourmi
|
||||||
|
step() {
|
||||||
|
const currentColor = this.grid[this.y][this.x];
|
||||||
|
|
||||||
|
if (currentColor === this.colors.white) {
|
||||||
|
// Case blanche : la repeindre en noir et tourner à droite
|
||||||
|
this.grid[this.y][this.x] = this.colors.black;
|
||||||
|
this.direction = (this.direction + 90) % 360;
|
||||||
|
} else {
|
||||||
|
// Case noire : la repeindre en blanc et tourner à gauche
|
||||||
|
this.grid[this.y][this.x] = this.colors.white;
|
||||||
|
this.direction = (this.direction - 90 + 360) % 360;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Déplacer la fourmi d'une case dans sa nouvelle direction
|
||||||
|
switch (this.direction) {
|
||||||
|
case 0:
|
||||||
|
this.y--;
|
||||||
|
break; // Nord
|
||||||
|
case 90:
|
||||||
|
this.x++;
|
||||||
|
break; // Est
|
||||||
|
case 180:
|
||||||
|
this.y++;
|
||||||
|
break; // Sud
|
||||||
|
case 270:
|
||||||
|
this.x--;
|
||||||
|
break; // Ouest
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gérer les bords de la grille (déplacement cyclique)
|
||||||
|
const gridSize = this.grid.length;
|
||||||
|
this.x = (this.x + gridSize) % gridSize;
|
||||||
|
this.y = (this.y + gridSize) % gridSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Réinitialise la fourmi et la grille
|
||||||
|
reset() {
|
||||||
|
const gridSize = this.grid.length;
|
||||||
|
for (let y = 0; y < gridSize; y++) {
|
||||||
|
for (let x = 0; x < gridSize; x++) {
|
||||||
|
this.grid[y][x] = this.colors.white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.x = Math.floor(gridSize / 2);
|
||||||
|
this.y = Math.floor(gridSize / 2);
|
||||||
|
this.direction = 90; // Réinitialise la direction vers l'est
|
||||||
|
}
|
||||||
|
}
|
40
TP1/EX1/app.js
Normal file
40
TP1/EX1/app.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import Ant from './Ant.js';
|
||||||
|
// Supposons que vous avez une fonction render pour l'affichage
|
||||||
|
// Par exemple : function render(grid) { ... }
|
||||||
|
|
||||||
|
let ant;
|
||||||
|
let grid;
|
||||||
|
const gridSize = 50;
|
||||||
|
const intervalId;
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
grid = createGrid(gridSize);
|
||||||
|
ant = new Ant(grid, Math.floor(gridSize / 2), Math.floor(gridSize / 2));
|
||||||
|
document.getElementById('resetBtn').addEventListener('click', resetSimulation);
|
||||||
|
startSimulation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createGrid(size) {
|
||||||
|
const newGrid = [];
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
newGrid[i] = new Array(size).fill(0);
|
||||||
|
}
|
||||||
|
return newGrid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startSimulation() {
|
||||||
|
intervalId = setInterval(() => {
|
||||||
|
ant.step();
|
||||||
|
render(grid); // Appelez votre fonction de rendu ici
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetSimulation() {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
ant.reset();
|
||||||
|
render(grid);
|
||||||
|
startSimulation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assurez-vous d'appeler setup() au chargement de la page
|
||||||
|
window.onload = setup;
|
63
TP1/EX2/Ant.js
Normal file
63
TP1/EX2/Ant.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
class Ant {
|
||||||
|
constructor(grid, startX, startY, transitionTable) {
|
||||||
|
this.grid = grid;
|
||||||
|
this.x = startX;
|
||||||
|
this.y = startY;
|
||||||
|
this.direction = 90; // 0: N, 90: E, 180: S, 270: W
|
||||||
|
this.antState = 0;
|
||||||
|
this.transitionTable = transitionTable;
|
||||||
|
this.numAntStates = transitionTable.length;
|
||||||
|
this.numTileStates = transitionTable[0].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
step() {
|
||||||
|
const tileState = this.grid[this.y][this.x];
|
||||||
|
const transition = this.transitionTable[this.antState][tileState];
|
||||||
|
|
||||||
|
const [antStateChange, directionChange, tileStateChange] = transition;
|
||||||
|
|
||||||
|
// Appliquer les changements
|
||||||
|
this.antState = (this.antState + antStateChange) % this.numAntStates;
|
||||||
|
this.direction = (this.direction + directionChange + 360) % 360;
|
||||||
|
this.grid[this.y][this.x] = (tileState + tileStateChange) % this.numTileStates;
|
||||||
|
|
||||||
|
// Déplacer la fourmi
|
||||||
|
switch (this.direction) {
|
||||||
|
case 0: this.y--; break;
|
||||||
|
case 90: this.x++; break;
|
||||||
|
case 180: this.y++; break;
|
||||||
|
case 270: this.x--; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gérer les bords de la grille
|
||||||
|
const gridSize = this.grid.length;
|
||||||
|
this.x = (this.x + gridSize) % gridSize;
|
||||||
|
this.y = (this.y + gridSize) % gridSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
const gridSize = this.grid.length;
|
||||||
|
for (let y = 0; y < gridSize; y++) {
|
||||||
|
for (let x = 0; x < gridSize; x++) {
|
||||||
|
this.grid[y][x] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.x = Math.floor(gridSize / 2);
|
||||||
|
this.y = Math.floor(gridSize / 2);
|
||||||
|
this.direction = 90;
|
||||||
|
this.antState = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exemple d'utilisation dans app.js
|
||||||
|
// const langtonTable = [
|
||||||
|
// [[0, 90, 1], [0, -90, 0]]
|
||||||
|
// ];
|
||||||
|
// ant = new Ant(grid, Math.floor(gridSize / 2), Math.floor(gridSize / 2), langtonTable);
|
||||||
|
|
||||||
|
// Exemple de la table du TP
|
||||||
|
const tpTable = [
|
||||||
|
// Tuile 0 Tuile 1
|
||||||
|
[[1, 0, 1], [0, 90, 1]], // Fourmi 0
|
||||||
|
[[0, -90, 1], [1, 0, 1]] // Fourmi 1
|
||||||
|
];
|
25
TP1/EX3/array.html
Normal file
25
TP1/EX3/array.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Array Operations</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const numbers = [1, 5, 2, 8, 3, 7, 4, 6];
|
||||||
|
|
||||||
|
// Utilisation de map pour créer un nouveau tableau avec chaque nombre doublé
|
||||||
|
const doubledNumbers = numbers.map(num => num * 2);
|
||||||
|
console.log("Doubled numbers:", doubledNumbers); // [2, 10, 4, 16, 6, 14, 8, 12]
|
||||||
|
|
||||||
|
// Utilisation de filter pour garder seulement les nombres pairs
|
||||||
|
const evenNumbers = numbers.filter(num => num % 2 === 0);
|
||||||
|
console.log("Even numbers:", evenNumbers); // [2, 8, 4, 6]
|
||||||
|
|
||||||
|
// Utilisation de reduce pour calculer la somme de tous les nombres
|
||||||
|
const sum = numbers.reduce((total, num) => total + num, 0);
|
||||||
|
console.log("Sum:", sum); // 36
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
31
TP1/EX3/minmax.html
Normal file
31
TP1/EX3/minmax.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Min/Max with Reduce</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function minmax(array) {
|
||||||
|
if (array.length === 0) {
|
||||||
|
return { min: undefined, max: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
return array.reduce(
|
||||||
|
(acc, val) => {
|
||||||
|
return {
|
||||||
|
min: Math.min(acc.min, val),
|
||||||
|
max: Math.max(acc.max, val)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{ min: array[0], max: array[0] }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = [10, 5, 20, 3, 15];
|
||||||
|
const result = minmax(data);
|
||||||
|
console.log("Min/Max:", result); // { min: 3, max: 20 }
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
63
TP1/EX4/gameoflife.js
Normal file
63
TP1/EX4/gameoflife.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
class GameOfLife {
|
||||||
|
constructor(width, height) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.grid = new Array(height).fill(null).map(() => new Array(width).fill(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialise la grille avec l'état de départ
|
||||||
|
setInitialState(initialStateString) {
|
||||||
|
this.grid = new Array(this.height).fill(null).map(() => new Array(this.width).fill(0));
|
||||||
|
const liveCells = initialStateString.split(';').map(cell => cell.split(','));
|
||||||
|
liveCells.forEach(([x, y]) => {
|
||||||
|
const cellX = parseInt(x);
|
||||||
|
const cellY = parseInt(y);
|
||||||
|
if (cellX >= 0 && cellX < this.width && cellY >= 0 && cellY < this.height) {
|
||||||
|
this.grid[cellY][cellX] = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calcule le nombre de voisins vivants d'une cellule
|
||||||
|
getNumberActiveNeighbourCells(x, y) {
|
||||||
|
let count = 0;
|
||||||
|
for (let i = -1; i <= 1; i++) {
|
||||||
|
for (let j = -1; j <= 1; j++) {
|
||||||
|
if (i === 0 && j === 0) continue;
|
||||||
|
|
||||||
|
const neighborX = x + j;
|
||||||
|
const neighborY = y + i;
|
||||||
|
|
||||||
|
if (neighborX >= 0 && neighborX < this.width && neighborY >= 0 && neighborY < this.height) {
|
||||||
|
count += this.grid[neighborY][neighborX];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calcule la génération suivante
|
||||||
|
computeNextGeneration() {
|
||||||
|
const nextGrid = new Array(this.height).fill(null).map(() => new Array(this.width).fill(0));
|
||||||
|
|
||||||
|
for (let y = 0; y < this.height; y++) {
|
||||||
|
for (let x = 0; x < this.width; x++) {
|
||||||
|
const liveNeighbors = this.getNumberActiveNeighbourCells(x, y);
|
||||||
|
const cellState = this.grid[y][x];
|
||||||
|
|
||||||
|
if (cellState === 1) {
|
||||||
|
// Règle 2 : Cellule vivante
|
||||||
|
if (liveNeighbors === 2 || liveNeighbors === 3) {
|
||||||
|
nextGrid[y][x] = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Règle 1 : Cellule morte
|
||||||
|
if (liveNeighbors === 3) {
|
||||||
|
nextGrid[y][x] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.grid = nextGrid;
|
||||||
|
}
|
||||||
|
}
|
62
TP2/EX0/simpsons.html
Normal file
62
TP2/EX0/simpsons.html
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Les Simpson</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>Membres des familles Simpson</h1>
|
||||||
|
<div id="characters-container"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let characters = [
|
||||||
|
{"id":"1","nom":"Simpson","prenom":"Homer","age":"38"},
|
||||||
|
{"id":"2","nom":"Simpson","prenom":"Marge","age":"34"},
|
||||||
|
{"id":"3","nom":"Simpson","prenom":"Bart","age":"10"},
|
||||||
|
{"id":"4","nom":"Simpson","prenom":"Lisa","age":"8"},
|
||||||
|
{"id":"5","nom":"Simpson","prenom":"Maggie","age":"1"},
|
||||||
|
{"id":"6","nom":"Flanders","prenom":"Ned","age":"60"},
|
||||||
|
{"id":"7","nom":"Flanders","prenom":"Todd","age":"8"},
|
||||||
|
{"id":"8","nom":"Flanders","prenom":"Rod","age":"10"},
|
||||||
|
{"id":"9","nom":"Van Houten","prenom":"Kirk","age":null},
|
||||||
|
{"id":"10","nom":"Van Houten","prenom":"Milhouse","age":null},
|
||||||
|
{"id":"11","nom":"Van Houten","prenom":"Luann","age":null},
|
||||||
|
{"id":"12","nom":"Muntz","prenom":"Nelson","age":null},
|
||||||
|
{"id":"13","nom":"Muntz","prenom":"Véronica","age":null},
|
||||||
|
{"id":"14","nom":"Muntz","prenom":"Thomas Jr","age":null},
|
||||||
|
{"id":"15","nom":"Lovejoy","prenom":"Timothy","age":null},
|
||||||
|
{"id":"16","nom":"Burns","prenom":"Charles","age":null}
|
||||||
|
];
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const container = document.getElementById('characters-container');
|
||||||
|
const families = {};
|
||||||
|
|
||||||
|
// Regrouper les personnages par nom de famille
|
||||||
|
characters.forEach(char => {
|
||||||
|
if (!families[char.nom]) {
|
||||||
|
families[char.nom] = [];
|
||||||
|
}
|
||||||
|
families[char.nom].push(char);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Créer et ajouter les éléments HTML
|
||||||
|
for (const family in families) {
|
||||||
|
const familyTitle = document.createElement('h2');
|
||||||
|
familyTitle.textContent = family;
|
||||||
|
container.appendChild(familyTitle);
|
||||||
|
|
||||||
|
const familyList = document.createElement('ul');
|
||||||
|
families[family].forEach(char => {
|
||||||
|
const memberItem = document.createElement('li');
|
||||||
|
const age = char.age !== null ? `(${char.age} ans)` : '';
|
||||||
|
memberItem.textContent = `${char.prenom} ${age}`;
|
||||||
|
familyList.appendChild(memberItem);
|
||||||
|
});
|
||||||
|
container.appendChild(familyList);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
73
TP2/EX1/lights.html
Normal file
73
TP2/EX1/lights.html
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Lights Out</title>
|
||||||
|
<style>
|
||||||
|
.grid-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 50px);
|
||||||
|
gap: 5px;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
.light {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
background-color: #ddd;
|
||||||
|
border: 1px solid #999;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.light.on {
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>Lights Out</h1>
|
||||||
|
<div class="grid-container" id="game-grid"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const gridSize = 5;
|
||||||
|
const gameGrid = document.getElementById('game-grid');
|
||||||
|
|
||||||
|
// Créer la grille de lumières
|
||||||
|
for (let i = 0; i < gridSize * gridSize; i++) {
|
||||||
|
const light = document.createElement('div');
|
||||||
|
light.classList.add('light');
|
||||||
|
light.dataset.id = i;
|
||||||
|
gameGrid.appendChild(light);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lights = document.querySelectorAll('.light');
|
||||||
|
|
||||||
|
gameGrid.addEventListener('click', event => {
|
||||||
|
const target = event.target;
|
||||||
|
if (!target.classList.contains('light')) return;
|
||||||
|
|
||||||
|
const id = parseInt(target.dataset.id);
|
||||||
|
const row = Math.floor(id / gridSize);
|
||||||
|
const col = id % gridSize;
|
||||||
|
|
||||||
|
// Fonction pour basculer une lumière
|
||||||
|
const toggleLight = (index) => {
|
||||||
|
if (index >= 0 && index < lights.length) {
|
||||||
|
lights[index].classList.toggle('on');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Basculer la lumière cliquée
|
||||||
|
toggleLight(id);
|
||||||
|
|
||||||
|
// Basculer les voisins
|
||||||
|
if (col > 0) toggleLight(id - 1); // Gauche
|
||||||
|
if (col < gridSize - 1) toggleLight(id + 1); // Droite
|
||||||
|
if (row > 0) toggleLight(id - gridSize); // Haut
|
||||||
|
if (row < gridSize - 1) toggleLight(id + gridSize); // Bas
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
89
TP2/EX2/classement_ligue1.html
Normal file
89
TP2/EX2/classement_ligue1.html
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Classement Ligue 1</title>
|
||||||
|
<style>
|
||||||
|
table {
|
||||||
|
width: 80%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Classement Ligue 1 2022-2023</h1>
|
||||||
|
<input type="text" id="searchInput" placeholder="Rechercher une équipe...">
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th id="rankHeader">Rang</th>
|
||||||
|
<th></th>
|
||||||
|
<th>Équipe</th>
|
||||||
|
<th>Buts Pour</th>
|
||||||
|
<th>Buts Contre</th>
|
||||||
|
<th>Points</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="table-body">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script src="data.js"></script>
|
||||||
|
<script>
|
||||||
|
const tbody = document.getElementById('table-body');
|
||||||
|
const searchInput = document.getElementById('searchInput');
|
||||||
|
const rankHeader = document.getElementById('rankHeader');
|
||||||
|
|
||||||
|
function renderTable(dataToRender) {
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
dataToRender.forEach(team => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.innerHTML = `
|
||||||
|
<td>${team.intRank}</td>
|
||||||
|
<td><img src="${team.strTeamBadge}" alt="${team.strTeam}" width="30"></td>
|
||||||
|
<td>${team.strTeam}</td>
|
||||||
|
<td>${team.intGoalsFor}</td>
|
||||||
|
<td>${team.intGoalsAgainst}</td>
|
||||||
|
<td>${team.intPoints}</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tri par rang
|
||||||
|
rankHeader.addEventListener('click', () => {
|
||||||
|
data.sort((a, b) => parseInt(a.intRank) - parseInt(b.intRank));
|
||||||
|
renderTable(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Recherche par nom d'équipe
|
||||||
|
searchInput.addEventListener('input', (event) => {
|
||||||
|
const searchTerm = event.target.value.toLowerCase();
|
||||||
|
const filteredData = data.filter(team => team.strTeam.toLowerCase().includes(searchTerm));
|
||||||
|
renderTable(filteredData);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Affichage initial
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
renderTable(data);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
65
TP2/EX3/View.js
Normal file
65
TP2/EX3/View.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
class View {
|
||||||
|
constructor() {
|
||||||
|
this.app = document.getElementById('root');
|
||||||
|
this.form = this.app.querySelector('.todo-form');
|
||||||
|
this.input = this.form.querySelector('.todo-input');
|
||||||
|
this.todoList = this.app.querySelector('.todo-list');
|
||||||
|
this.filterTabs = this.app.querySelector('.tabs');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... autres méthodes de la classe View
|
||||||
|
|
||||||
|
bindAddTodo(handler) {
|
||||||
|
this.form.addEventListener('submit', event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const text = this.input.value.trim();
|
||||||
|
if (text !== '') {
|
||||||
|
handler(text);
|
||||||
|
this.input.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bindDeleteTodo(handler) {
|
||||||
|
this.todoList.addEventListener('click', event => {
|
||||||
|
if (event.target.classList.contains('delete-button')) {
|
||||||
|
const id = parseInt(event.target.closest('li').dataset.id);
|
||||||
|
handler(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bindToggleTodo(handler) {
|
||||||
|
this.todoList.addEventListener('click', event => {
|
||||||
|
if (event.target.type === 'checkbox') {
|
||||||
|
const id = parseInt(event.target.closest('li').dataset.id);
|
||||||
|
handler(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEditTodo(handler) {
|
||||||
|
// Événement 'blur' pour détecter la fin de l'édition d'une tâche
|
||||||
|
this.todoList.addEventListener('blur', event => {
|
||||||
|
if (event.target.classList.contains('editable-text')) {
|
||||||
|
const id = parseInt(event.target.closest('li').dataset.id);
|
||||||
|
const newText = event.target.textContent.trim();
|
||||||
|
handler(id, newText);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthode pour le double-clic
|
||||||
|
bindDoubleClickTodo(handler) {
|
||||||
|
this.todoList.addEventListener('dblclick', event => {
|
||||||
|
const li = event.target.closest('li');
|
||||||
|
if (li) {
|
||||||
|
const textElement = li.querySelector('.editable-text');
|
||||||
|
if (textElement) {
|
||||||
|
textElement.contentEditable = true;
|
||||||
|
textElement.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
42
TP3/EX1/app.js
Normal file
42
TP3/EX1/app.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { getMovies } from './model.js';
|
||||||
|
|
||||||
|
const searchInput = document.getElementById('searchInput');
|
||||||
|
const searchBtn = document.getElementById('searchBtn');
|
||||||
|
const movieList = document.getElementById('movie-list');
|
||||||
|
|
||||||
|
async function search(searchMovie) {
|
||||||
|
if (searchMovie.trim() === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const movies = await getMovies(searchMovie);
|
||||||
|
renderMovies(movies);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMovies(movies) {
|
||||||
|
movieList.innerHTML = '';
|
||||||
|
if (movies.length === 0) {
|
||||||
|
movieList.innerHTML = '<p>Aucun film trouvé.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
movies.forEach(movie => {
|
||||||
|
const movieCard = document.createElement('div');
|
||||||
|
movieCard.classList.add('movie-card');
|
||||||
|
movieCard.innerHTML = `
|
||||||
|
<h3>${movie.Title} (${movie.Year})</h3>
|
||||||
|
<img src="${movie.Poster !== 'N/A' ? movie.Poster : 'https://via.placeholder.com/200x300.png?text=Pas+d%27image'}" alt="${movie.Title}">
|
||||||
|
`;
|
||||||
|
movieList.appendChild(movieCard);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
searchBtn.addEventListener('click', () => {
|
||||||
|
search(searchInput.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
searchInput.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
search(searchInput.value);
|
||||||
|
}
|
||||||
|
});
|
24
TP3/EX1/index.html
Normal file
24
TP3/EX1/index.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Recherche de films</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; padding: 20px; }
|
||||||
|
#search-container { margin-bottom: 20px; }
|
||||||
|
#movie-list { display: flex; flex-wrap: wrap; gap: 20px; }
|
||||||
|
.movie-card { width: 200px; border: 1px solid #ccc; padding: 10px; text-align: center; }
|
||||||
|
.movie-card img { max-width: 100%; height: auto; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Recherche de films</h1>
|
||||||
|
<div id="search-container">
|
||||||
|
<input type="text" id="searchInput" placeholder="Titre du film...">
|
||||||
|
<button id="searchBtn">Rechercher</button>
|
||||||
|
</div>
|
||||||
|
<div id="movie-list"></div>
|
||||||
|
|
||||||
|
<script type="module" src="app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
16
TP3/EX1/model.js
Normal file
16
TP3/EX1/model.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export async function getMovies(search) {
|
||||||
|
const apiKey = '2fcb2848';
|
||||||
|
const url = `https://www.omdbapi.com/?apikey=${apiKey}&s=${encodeURIComponent(search)}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
return data.Search || []; // Renvoie un tableau vide si aucun résultat
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la recherche de films:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
114
TP3/EX2/thrones.html
Normal file
114
TP3/EX2/thrones.html
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Personnages Game of Thrones</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
|
||||||
|
<style>
|
||||||
|
.character-card { text-align: center; }
|
||||||
|
.character-card img { width: 128px; height: 128px; object-fit: cover; border-radius: 50%; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h1 class="title has-text-centered">Personnages Game of Thrones</h1>
|
||||||
|
<div class="field is-grouped is-grouped-centered">
|
||||||
|
<p>Personnages par page:</p>
|
||||||
|
<div class="control">
|
||||||
|
<div class="select">
|
||||||
|
<select id="pageSizeSelect">
|
||||||
|
<option value="6">6</option>
|
||||||
|
<option value="12">12</option>
|
||||||
|
<option value="24">24</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="characters-container" class="columns is-multiline"></div>
|
||||||
|
|
||||||
|
<div class="buttons is-centered">
|
||||||
|
<button id="prevBtn" class="button is-info">Précédent</button>
|
||||||
|
<button id="nextBtn" class="button is-info">Suivant</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const charactersContainer = document.getElementById('characters-container');
|
||||||
|
const pageSizeSelect = document.getElementById('pageSizeSelect');
|
||||||
|
const prevBtn = document.getElementById('prevBtn');
|
||||||
|
const nextBtn = document.getElementById('nextBtn');
|
||||||
|
let allCharacters = [];
|
||||||
|
let currentPage = 1;
|
||||||
|
let pageSize = parseInt(pageSizeSelect.value);
|
||||||
|
|
||||||
|
const fetchCharacters = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://thronesapi.com/api/v2/Characters');
|
||||||
|
allCharacters = await response.json();
|
||||||
|
renderCharacters();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la récupération des personnages:', error);
|
||||||
|
charactersContainer.innerHTML = '<p class="has-text-danger has-text-centered">Erreur de chargement des données.</p>';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCharacters = () => {
|
||||||
|
charactersContainer.innerHTML = '';
|
||||||
|
const startIndex = (currentPage - 1) * pageSize;
|
||||||
|
const endIndex = startIndex + pageSize;
|
||||||
|
const charactersToDisplay = allCharacters.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
charactersToDisplay.forEach(char => {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.classList.add('column', 'is-one-quarter-desktop', 'is-one-third-tablet', 'is-half-mobile');
|
||||||
|
card.innerHTML = `
|
||||||
|
<div class="card character-card">
|
||||||
|
<div class="card-image">
|
||||||
|
<figure class="image is-128x128 is-inline-block mt-4">
|
||||||
|
<img src="${char.imageUrl}" alt="${char.fullName}">
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<p class="title is-4">${char.fullName}</p>
|
||||||
|
<p class="subtitle is-6">${char.title}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
charactersContainer.appendChild(card);
|
||||||
|
});
|
||||||
|
|
||||||
|
updatePaginationButtons();
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePaginationButtons = () => {
|
||||||
|
prevBtn.disabled = currentPage === 1;
|
||||||
|
nextBtn.disabled = currentPage * pageSize >= allCharacters.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
prevBtn.addEventListener('click', () => {
|
||||||
|
if (currentPage > 1) {
|
||||||
|
currentPage--;
|
||||||
|
renderCharacters();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
nextBtn.addEventListener('click', () => {
|
||||||
|
if (currentPage * pageSize < allCharacters.length) {
|
||||||
|
currentPage++;
|
||||||
|
renderCharacters();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pageSizeSelect.addEventListener('change', () => {
|
||||||
|
pageSize = parseInt(pageSizeSelect.value);
|
||||||
|
currentPage = 1;
|
||||||
|
renderCharacters();
|
||||||
|
});
|
||||||
|
|
||||||
|
fetchCharacters();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
177
TP3/EX3/communes.html
Normal file
177
TP3/EX3/communes.html
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Communes de France</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
|
||||||
|
<style>
|
||||||
|
.container { max-width: 800px; }
|
||||||
|
.is-loading::after {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
border-top-color: #333;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s infinite linear;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h1 class="title has-text-centered">Rechercher des communes</h1>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Région</label>
|
||||||
|
<div class="control">
|
||||||
|
<div class="select is-fullwidth">
|
||||||
|
<select id="regionSelect"></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Département</label>
|
||||||
|
<div class="control">
|
||||||
|
<div class="select is-fullwidth">
|
||||||
|
<select id="departementSelect" disabled></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Rechercher une commune</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" type="text" id="searchInput" placeholder="Nom de la commune..." disabled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<div class="control">
|
||||||
|
<button id="sortBtn" class="button is-info" disabled>Trier</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="communesList" class="box mt-4"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const regionSelect = document.getElementById('regionSelect');
|
||||||
|
const departementSelect = document.getElementById('departementSelect');
|
||||||
|
const searchInput = document.getElementById('searchInput');
|
||||||
|
const sortBtn = document.getElementById('sortBtn');
|
||||||
|
const communesList = document.getElementById('communesList');
|
||||||
|
let allCommunes = [];
|
||||||
|
let sortOrder = 'asc';
|
||||||
|
|
||||||
|
const fetchRegions = async () => {
|
||||||
|
const url = 'https://geo.api.gouv.fr/regions';
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const regions = await response.json();
|
||||||
|
regionSelect.innerHTML = '<option value="">-- Sélectionnez une région --</option>';
|
||||||
|
regions.forEach(region => {
|
||||||
|
regionSelect.innerHTML += `<option value="${region.code}">${region.nom}</option>`;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur de chargement des régions:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchDepartements = async (regionCode) => {
|
||||||
|
departementSelect.disabled = true;
|
||||||
|
departementSelect.classList.add('is-loading');
|
||||||
|
const url = `https://geo.api.gouv.fr/regions/${regionCode}/departements`;
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const departements = await response.json();
|
||||||
|
departementSelect.innerHTML = '<option value="">-- Sélectionnez un département --</option>';
|
||||||
|
departements.forEach(dep => {
|
||||||
|
departementSelect.innerHTML += `<option value="${dep.code}">${dep.nom} (${dep.code})</option>`;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur de chargement des départements:', error);
|
||||||
|
} finally {
|
||||||
|
departementSelect.disabled = false;
|
||||||
|
departementSelect.classList.remove('is-loading');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchCommunes = async (departementCode) => {
|
||||||
|
searchInput.disabled = true;
|
||||||
|
sortBtn.disabled = true;
|
||||||
|
communesList.innerHTML = '<p class="has-text-centered is-loading">Chargement des communes...</p>';
|
||||||
|
const url = `https://geo.api.gouv.fr/departements/${departementCode}/communes`;
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
allCommunes = await response.json();
|
||||||
|
renderCommunes(allCommunes);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur de chargement des communes:', error);
|
||||||
|
communesList.innerHTML = '<p class="has-text-danger has-text-centered">Erreur de chargement des communes.</p>';
|
||||||
|
} finally {
|
||||||
|
searchInput.disabled = false;
|
||||||
|
sortBtn.disabled = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCommunes = (communes) => {
|
||||||
|
communesList.innerHTML = '';
|
||||||
|
if (communes.length === 0) {
|
||||||
|
communesList.innerHTML = '<p class="has-text-centered">Aucune commune trouvée.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ul = document.createElement('ul');
|
||||||
|
communes.forEach(commune => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.textContent = `${commune.nom} (Code postal: ${commune.codesPostaux[0]})`;
|
||||||
|
ul.appendChild(li);
|
||||||
|
});
|
||||||
|
communesList.appendChild(ul);
|
||||||
|
};
|
||||||
|
|
||||||
|
regionSelect.addEventListener('change', (e) => {
|
||||||
|
if (e.target.value) {
|
||||||
|
fetchDepartements(e.target.value);
|
||||||
|
} else {
|
||||||
|
departementSelect.innerHTML = '<option value="">-- Sélectionnez un département --</option>';
|
||||||
|
departementSelect.disabled = true;
|
||||||
|
searchInput.disabled = true;
|
||||||
|
sortBtn.disabled = true;
|
||||||
|
communesList.innerHTML = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
departementSelect.addEventListener('change', (e) => {
|
||||||
|
if (e.target.value) {
|
||||||
|
fetchCommunes(e.target.value);
|
||||||
|
} else {
|
||||||
|
searchInput.disabled = true;
|
||||||
|
sortBtn.disabled = true;
|
||||||
|
communesList.innerHTML = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
searchInput.addEventListener('input', (e) => {
|
||||||
|
const searchTerm = e.target.value.toLowerCase();
|
||||||
|
const filteredCommunes = allCommunes.filter(c => c.nom.toLowerCase().includes(searchTerm));
|
||||||
|
renderCommunes(filteredCommunes);
|
||||||
|
});
|
||||||
|
|
||||||
|
sortBtn.addEventListener('click', () => {
|
||||||
|
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
|
||||||
|
allCommunes.sort((a, b) => {
|
||||||
|
const nameA = a.nom.toLowerCase();
|
||||||
|
const nameB = b.nom.toLowerCase();
|
||||||
|
if (nameA < nameB) return sortOrder === 'asc' ? -1 : 1;
|
||||||
|
if (nameA > nameB) return sortOrder === 'asc' ? 1 : -1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
renderCommunes(allCommunes);
|
||||||
|
});
|
||||||
|
|
||||||
|
fetchRegions();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
137
TP3/EX4/foxes.html
Normal file
137
TP3/EX4/foxes.html
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Jeu des renards</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background-color: #f0f0f0; }
|
||||||
|
.grid-container {
|
||||||
|
display: grid;
|
||||||
|
border: 2px solid #333;
|
||||||
|
background-color: #555;
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: #777;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.cell:hover { background-color: #999; }
|
||||||
|
.cell.shot { background-color: #333; cursor: not-allowed; }
|
||||||
|
.cell.won { background-color: green; }
|
||||||
|
#info-panel { margin-top: 20px; text-align: center; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Jeu des renards</h1>
|
||||||
|
<div id="grid-container" class="grid-container"></div>
|
||||||
|
<div id="info-panel">
|
||||||
|
<p id="foxes-info">Renards restants : ?</p>
|
||||||
|
<p id="tries-info">Coups joués : 0</p>
|
||||||
|
<button id="reset-btn">Nouvelle partie</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const serverUrl = 'https://dwarves.iut-fbleau.fr/foxes/foxes.php';
|
||||||
|
const gridContainer = document.getElementById('grid-container');
|
||||||
|
const foxesInfo = document.getElementById('foxes-info');
|
||||||
|
const triesInfo = document.getElementById('tries-info');
|
||||||
|
const resetBtn = document.getElementById('reset-btn');
|
||||||
|
|
||||||
|
let gridSize = 10;
|
||||||
|
let initialFoxes = 10;
|
||||||
|
let currentFoxes = initialFoxes;
|
||||||
|
let tries = 0;
|
||||||
|
let gameOver = false;
|
||||||
|
|
||||||
|
const updateInfo = (foxes, newTries) => {
|
||||||
|
currentFoxes = foxes;
|
||||||
|
tries = newTries;
|
||||||
|
foxesInfo.textContent = `Renards restants : ${currentFoxes}`;
|
||||||
|
triesInfo.textContent = `Coups joués : ${tries}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetGame = async () => {
|
||||||
|
gameOver = false;
|
||||||
|
gridContainer.innerHTML = '';
|
||||||
|
const url = `${serverUrl}?new&size=${gridSize}&foxes=${initialFoxes}`;
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, { credentials: 'omit' }); // 'omit' pour ignorer les cookies
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.status === 'ok') {
|
||||||
|
gridContainer.style.gridTemplateColumns = `repeat(${gridSize}, 1fr)`;
|
||||||
|
for (let i = 0; i < gridSize * gridSize; i++) {
|
||||||
|
const cell = document.createElement('div');
|
||||||
|
cell.classList.add('cell');
|
||||||
|
cell.dataset.x = i % gridSize;
|
||||||
|
cell.dataset.y = Math.floor(i / gridSize);
|
||||||
|
cell.addEventListener('click', handleShot);
|
||||||
|
gridContainer.appendChild(cell);
|
||||||
|
}
|
||||||
|
updateInfo(data.foxes, data.tries);
|
||||||
|
} else {
|
||||||
|
alert('Erreur lors de la réinitialisation du jeu.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur:', error);
|
||||||
|
alert('Erreur de connexion au serveur.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShot = async (event) => {
|
||||||
|
if (gameOver || event.target.classList.contains('shot')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = event.target.dataset.x;
|
||||||
|
const y = event.target.dataset.y;
|
||||||
|
const url = `${serverUrl}?X=${x}&Y=${y}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, { credentials: 'omit' });
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.status === 'ok') {
|
||||||
|
event.target.classList.add('shot');
|
||||||
|
if (data.foxes === -1) {
|
||||||
|
event.target.textContent = 'X';
|
||||||
|
updateInfo(currentFoxes - 1, data.tries);
|
||||||
|
} else {
|
||||||
|
event.target.textContent = data.foxes;
|
||||||
|
updateInfo(currentFoxes, data.tries);
|
||||||
|
}
|
||||||
|
} else if (data.status === 'win') {
|
||||||
|
gameOver = true;
|
||||||
|
updateInfo(0, data.tries);
|
||||||
|
event.target.classList.add('won');
|
||||||
|
event.target.textContent = 'X';
|
||||||
|
setTimeout(() => alert(`Félicitations ! Vous avez gagné en ${data.tries} coups.`), 100);
|
||||||
|
} else if (data.status === 'nok') {
|
||||||
|
console.error('Erreur du serveur:', data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for win condition client-side (if currentFoxes reaches 0)
|
||||||
|
if (currentFoxes === 0 && !gameOver) {
|
||||||
|
gameOver = true;
|
||||||
|
setTimeout(() => alert(`Félicitations ! Vous avez gagné !`), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
resetBtn.addEventListener('click', resetGame);
|
||||||
|
resetGame();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
39
TP4/EX0/app.riot
Normal file
39
TP4/EX0/app.riot
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<app>
|
||||||
|
<h1>Personnages des Simpsons</h1>
|
||||||
|
<div class="controls">
|
||||||
|
<button onclick="{ () => sort('prenom') }">Trier par Prénom</button>
|
||||||
|
<button onclick="{ () => sort('age') }">Trier par Âge</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<my-list personnages="{ state.personnages }" onselect="{ selectCharacter }"></my-list>
|
||||||
|
|
||||||
|
<div if="{ state.selectedCharacter }" class="selected-char">
|
||||||
|
<h3>Personnage sélectionné :</h3>
|
||||||
|
<p><strong>Nom :</strong> { state.selectedCharacter.prenom } { state.selectedCharacter.nom }</p>
|
||||||
|
<p><strong>Âge :</strong> { state.selectedCharacter.age !== null ? state.selectedCharacter.age : 'inconnu' }</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
onMounted(props) {
|
||||||
|
this.update({
|
||||||
|
personnages: [...props.personnages],
|
||||||
|
selectedCharacter: null
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
sort(key) {
|
||||||
|
const sorted = [...this.state.personnages].sort((a, b) => {
|
||||||
|
if (a[key] === null) return 1;
|
||||||
|
if (b[key] === null) return -1;
|
||||||
|
return a[key] > b[key] ? 1 : -1;
|
||||||
|
});
|
||||||
|
this.update({ personnages: sorted });
|
||||||
|
},
|
||||||
|
|
||||||
|
selectCharacter(character) {
|
||||||
|
this.update({ selectedCharacter: character });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</app>
|
43
TP4/EX0/index.html
Normal file
43
TP4/EX0/index.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Les Simpson avec Riot.js</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/riot@6.0.1/riot.min.js"></script>
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; padding: 20px; }
|
||||||
|
.controls { margin-bottom: 20px; }
|
||||||
|
button { margin: 0 5px; padding: 8px 12px; cursor: pointer; }
|
||||||
|
.selected-char { margin-top: 20px; padding: 10px; border: 1px solid #ccc; border-radius: 5px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<app></app>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import './my-list.riot';
|
||||||
|
import './app.riot';
|
||||||
|
|
||||||
|
const personnages = [
|
||||||
|
{"id":"1","nom":"Simpson","prenom":"Homer","age":"38"},
|
||||||
|
{"id":"2","nom":"Simpson","prenom":"Marge","age":"34"},
|
||||||
|
{"id":"3","nom":"Simpson","prenom":"Bart","age":"10"},
|
||||||
|
{"id":"4","nom":"Simpson","prenom":"Lisa","age":"8"},
|
||||||
|
{"id":"5","nom":"Simpson","prenom":"Maggie","age":"1"},
|
||||||
|
{"id":"6","nom":"Flanders","prenom":"Ned","age":"60"},
|
||||||
|
{"id":"7","nom":"Flanders","prenom":"Todd","age":"8"},
|
||||||
|
{"id":"8","nom":"Flanders","prenom":"Rod","age":"10"},
|
||||||
|
{"id":"9","nom":"Van Houten","prenom":"Kirk","age":null},
|
||||||
|
{"id":"10","nom":"Van Houten","prenom":"Milhouse","age":null},
|
||||||
|
{"id":"11","nom":"Van Houten","prenom":"Luann","age":null},
|
||||||
|
{"id":"12","nom":"Muntz","prenom":"Nelson","age":null},
|
||||||
|
{"id":"13","nom":"Muntz","prenom":"Véronica","age":null},
|
||||||
|
{"id":"14","nom":"Muntz","prenom":"Thomas Jr","age":null},
|
||||||
|
{"id":"15","nom":"Lovejoy","prenom":"Timothy","age":null},
|
||||||
|
{"id":"16","nom":"Burns","prenom":"Charles","age":null}
|
||||||
|
];
|
||||||
|
|
||||||
|
riot.mount('app', { personnages });
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
22
TP4/EX0/my-list.riot
Normal file
22
TP4/EX0/my-list.riot
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<my-list>
|
||||||
|
<ul>
|
||||||
|
<li each="{ char in props.personnages }" onclick="{ () => onSelect(char) }">
|
||||||
|
{ char.prenom } { char.nom } ({ char.age !== null ? char.age : 'âge inconnu' })
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
ul { list-style: none; padding: 0; }
|
||||||
|
li { padding: 10px; border-bottom: 1px solid #eee; cursor: pointer; }
|
||||||
|
li:hover { background-color: #f0f0f0; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
onSelect(character) {
|
||||||
|
// Émet un événement 'select' vers le parent avec le personnage sélectionné
|
||||||
|
this.parent.selectCharacter(character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</my-list>
|
21
TP4/EX1/index.html
Normal file
21
TP4/EX1/index.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Todo List avec Riot.js</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/riot@6.0.1/riot.min.js"></script>
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; padding: 20px; }
|
||||||
|
.filters button { margin: 0 5px; }
|
||||||
|
ul { list-style: none; padding: 0; }
|
||||||
|
li { margin: 5px 0; display: flex; align-items: center; gap: 10px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<todo-app></todo-app>
|
||||||
|
<script type="module">
|
||||||
|
import './todo-app.riot';
|
||||||
|
riot.mount('todo-app');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
96
TP4/EX1/todo-app.riot
Normal file
96
TP4/EX1/todo-app.riot
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<todo-app>
|
||||||
|
<h1>Ma Todo List</h1>
|
||||||
|
<form onsubmit="{ addTodo }">
|
||||||
|
<input type="text" placeholder="Ajouter une tâche..." value="{ state.newTodoText }">
|
||||||
|
<button type="submit">Ajouter</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="filters">
|
||||||
|
<button onclick="{ () => filter('all') }">Toutes ({ state.todos.length })</button>
|
||||||
|
<button onclick="{ () => filter('active') }">Actives ({ state.activeTodosCount })</button>
|
||||||
|
<button onclick="{ () => filter('done') }">Terminées ({ state.doneTodosCount })</button>
|
||||||
|
<button onclick="{ clearDone }" disabled="{ state.doneTodosCount === 0 }">
|
||||||
|
Effacer les terminées
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li each="{ todo in state.filteredTodos }" class="{ done: todo.done }">
|
||||||
|
<input type="checkbox" checked="{ todo.done }" onchange="{ () => toggleTodo(todo.id) }">
|
||||||
|
<span ondblclick="{ () => editTodo(todo.id) }">{ todo.text }</span>
|
||||||
|
<button onclick="{ () => deleteTodo(todo.id) }">X</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.done { text-decoration: line-through; color: #999; }
|
||||||
|
button[disabled] { cursor: not-allowed; opacity: 0.5; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
onMounted() {
|
||||||
|
this.loadTodos();
|
||||||
|
},
|
||||||
|
|
||||||
|
loadTodos() {
|
||||||
|
const savedTodos = localStorage.getItem('todos');
|
||||||
|
this.update({ todos: savedTodos ? JSON.parse(savedTodos) : [], filter: 'all' });
|
||||||
|
this.updateCounts();
|
||||||
|
},
|
||||||
|
|
||||||
|
saveTodos() {
|
||||||
|
localStorage.setItem('todos', JSON.stringify(this.state.todos));
|
||||||
|
this.updateCounts();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCounts() {
|
||||||
|
const activeTodosCount = this.state.todos.filter(t => !t.done).length;
|
||||||
|
const doneTodosCount = this.state.todos.filter(t => t.done).length;
|
||||||
|
this.update({ activeTodosCount, doneTodosCount, filteredTodos: this.getFilteredTodos() });
|
||||||
|
},
|
||||||
|
|
||||||
|
getFilteredTodos() {
|
||||||
|
if (this.state.filter === 'active') {
|
||||||
|
return this.state.todos.filter(todo => !todo.done);
|
||||||
|
} else if (this.state.filter === 'done') {
|
||||||
|
return this.state.todos.filter(todo => todo.done);
|
||||||
|
}
|
||||||
|
return this.state.todos;
|
||||||
|
},
|
||||||
|
|
||||||
|
addTodo(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const text = this.state.newTodoText.trim();
|
||||||
|
if (text) {
|
||||||
|
const newTodo = { id: Date.now(), text, done: false };
|
||||||
|
this.update({ todos: [...this.state.todos, newTodo], newTodoText: '' });
|
||||||
|
this.saveTodos();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteTodo(id) {
|
||||||
|
this.update({ todos: this.state.todos.filter(t => t.id !== id) });
|
||||||
|
this.saveTodos();
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleTodo(id) {
|
||||||
|
const updatedTodos = this.state.todos.map(t =>
|
||||||
|
t.id === id ? { ...t, done: !t.done } : t
|
||||||
|
);
|
||||||
|
this.update({ todos: updatedTodos });
|
||||||
|
this.saveTodos();
|
||||||
|
},
|
||||||
|
|
||||||
|
clearDone() {
|
||||||
|
this.update({ todos: this.state.todos.filter(t => !t.done) });
|
||||||
|
this.saveTodos();
|
||||||
|
},
|
||||||
|
|
||||||
|
filter(type) {
|
||||||
|
this.update({ filter: type });
|
||||||
|
this.updateCounts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</todo-app>
|
21
TP4/EX2/index.html
Normal file
21
TP4/EX2/index.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Recherche de films Riot.js</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/riot@6.0.1/riot.min.js"></script>
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; padding: 20px; }
|
||||||
|
#movie-list { display: flex; flex-wrap: wrap; gap: 20px; justify-content: center; }
|
||||||
|
.movie-card { width: 200px; border: 1px solid #ccc; padding: 10px; text-align: center; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
||||||
|
.movie-card img { max-width: 100%; height: auto; border-radius: 5px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<search-app></search-app>
|
||||||
|
<script type="module">
|
||||||
|
import './search-app.riot';
|
||||||
|
riot.mount('search-app');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
34
TP4/EX2/search-app.riot
Normal file
34
TP4/EX2/search-app.riot
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<search-app>
|
||||||
|
<h1>Recherche de films</h1>
|
||||||
|
<div class="search-container">
|
||||||
|
<input type="text" placeholder="Titre du film..." oninput="{ handleInput }">
|
||||||
|
<button onclick="{ searchMovies }">Rechercher</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="movie-list">
|
||||||
|
<div each="{ movie in state.movies }" class="movie-card">
|
||||||
|
<h3>{ movie.Title } ({ movie.Year })</h3>
|
||||||
|
<img src="{ movie.Poster !== 'N/A' ? movie.Poster : 'https://via.placeholder.com/200x300.png?text=Pas+d%27image' }" alt="{ movie.Title }">
|
||||||
|
</div>
|
||||||
|
<p if="{ !state.movies.length && state.searched }">Aucun film trouvé.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getMovies } from './service.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
onMounted() {
|
||||||
|
this.update({ movies: [], searched: false, searchTerm: '' });
|
||||||
|
},
|
||||||
|
|
||||||
|
handleInput(e) {
|
||||||
|
this.update({ searchTerm: e.target.value });
|
||||||
|
},
|
||||||
|
|
||||||
|
async searchMovies() {
|
||||||
|
const movies = await getMovies(this.state.searchTerm);
|
||||||
|
this.update({ movies, searched: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</search-app>
|
13
TP4/EX2/service.js
Normal file
13
TP4/EX2/service.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export const getMovies = async (search) => {
|
||||||
|
const apiKey = '2fcb2848';
|
||||||
|
const url = `https://www.omdbapi.com/?apikey=${apiKey}&s=${encodeURIComponent(search)}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const data = await response.json();
|
||||||
|
return data.Search || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la recherche de films:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
24
TP4/EX3/index.html
Normal file
24
TP4/EX3/index.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Todo List avec Router</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/riot@6.0.1/riot.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/riot-route@4.0.0/dist/route.min.js"></script>
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; padding: 20px; }
|
||||||
|
.filters a { margin: 0 5px; padding: 5px 10px; border: 1px solid #ccc; text-decoration: none; color: #333; }
|
||||||
|
.filters a.active { background-color: #eee; }
|
||||||
|
.done { text-decoration: line-through; color: #999; }
|
||||||
|
ul { list-style: none; padding: 0; }
|
||||||
|
li { margin: 5px 0; display: flex; align-items: center; gap: 10px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<todo-router></todo-router>
|
||||||
|
<script type="module">
|
||||||
|
import './todo-router.riot';
|
||||||
|
riot.mount('todo-router');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
98
TP4/EX3/todo-router.riot
Normal file
98
TP4/EX3/todo-router.riot
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<todo-router>
|
||||||
|
<h1>Ma Todo List</h1>
|
||||||
|
<form onsubmit="{ addTodo }">
|
||||||
|
<input type="text" placeholder="Ajouter une tâche..." value="{ state.newTodoText }">
|
||||||
|
<button type="submit">Ajouter</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="filters">
|
||||||
|
<a href="#/all" class="{ active: state.filter === 'all' }">Toutes ({ state.todos.length })</a>
|
||||||
|
<a href="#/active" class="{ active: state.filter === 'active' }">Actives ({ state.activeTodosCount })</a>
|
||||||
|
<a href="#/done" class="{ active: state.filter === 'done' }">Terminées ({ state.doneTodosCount })</a>
|
||||||
|
<button onclick="{ clearDone }" disabled="{ state.doneTodosCount === 0 }">
|
||||||
|
Effacer les terminées
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li each="{ todo in state.filteredTodos }" class="{ done: todo.done }">
|
||||||
|
<input type="checkbox" checked="{ todo.done }" onchange="{ () => toggleTodo(todo.id) }">
|
||||||
|
<span ondblclick="{ () => editTodo(todo.id) }">{ todo.text }</span>
|
||||||
|
<button onclick="{ () => deleteTodo(todo.id) }">X</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.done { text-decoration: line-through; color: #999; }
|
||||||
|
button[disabled] { cursor: not-allowed; opacity: 0.5; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as route from 'riot-route';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
onMounted() {
|
||||||
|
this.loadTodos();
|
||||||
|
route.start(true);
|
||||||
|
route.on('route', (collection, id, action) => {
|
||||||
|
this.update({ filter: collection || 'all' });
|
||||||
|
this.updateCounts();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
loadTodos() {
|
||||||
|
const savedTodos = localStorage.getItem('todos');
|
||||||
|
this.update({ todos: savedTodos ? JSON.parse(savedTodos) : [], filter: route.current[0] || 'all' });
|
||||||
|
this.updateCounts();
|
||||||
|
},
|
||||||
|
|
||||||
|
saveTodos() {
|
||||||
|
localStorage.setItem('todos', JSON.stringify(this.state.todos));
|
||||||
|
this.updateCounts();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCounts() {
|
||||||
|
const activeTodosCount = this.state.todos.filter(t => !t.done).length;
|
||||||
|
const doneTodosCount = this.state.todos.filter(t => t.done).length;
|
||||||
|
this.update({ activeTodosCount, doneTodosCount, filteredTodos: this.getFilteredTodos() });
|
||||||
|
},
|
||||||
|
|
||||||
|
getFilteredTodos() {
|
||||||
|
if (this.state.filter === 'active') {
|
||||||
|
return this.state.todos.filter(todo => !todo.done);
|
||||||
|
} else if (this.state.filter === 'done') {
|
||||||
|
return this.state.todos.filter(todo => todo.done);
|
||||||
|
}
|
||||||
|
return this.state.todos;
|
||||||
|
},
|
||||||
|
|
||||||
|
addTodo(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const text = this.state.newTodoText.trim();
|
||||||
|
if (text) {
|
||||||
|
const newTodo = { id: Date.now(), text, done: false };
|
||||||
|
this.update({ todos: [...this.state.todos, newTodo], newTodoText: '' });
|
||||||
|
this.saveTodos();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteTodo(id) {
|
||||||
|
this.update({ todos: this.state.todos.filter(t => t.id !== id) });
|
||||||
|
this.saveTodos();
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleTodo(id) {
|
||||||
|
const updatedTodos = this.state.todos.map(t =>
|
||||||
|
t.id === id ? { ...t, done: !t.done } : t
|
||||||
|
);
|
||||||
|
this.update({ todos: updatedTodos });
|
||||||
|
this.saveTodos();
|
||||||
|
},
|
||||||
|
|
||||||
|
clearDone() {
|
||||||
|
this.update({ todos: this.state.todos.filter(t => !t.done) });
|
||||||
|
this.saveTodos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</todo-router>
|
54
TP5/EX1/app.js
Normal file
54
TP5/EX1/app.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// app.js
|
||||||
|
|
||||||
|
import { TodoAPI } from './api.js';
|
||||||
|
|
||||||
|
const todoList = document.getElementById('todo-list');
|
||||||
|
const todoForm = document.getElementById('todo-form');
|
||||||
|
const todoInput = document.getElementById('todo-input');
|
||||||
|
|
||||||
|
async function renderTodos() {
|
||||||
|
todoList.innerHTML = '';
|
||||||
|
const todos = await TodoAPI.getTodos();
|
||||||
|
todos.forEach(todo => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.classList.add('todo-item');
|
||||||
|
li.dataset.id = todo.id;
|
||||||
|
li.innerHTML = `
|
||||||
|
<input type="checkbox" ${todo.done ? 'checked' : ''}>
|
||||||
|
<span>${todo.title}</span>
|
||||||
|
<button class="delete-btn">X</button>
|
||||||
|
`;
|
||||||
|
todoList.appendChild(li);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
todoForm.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const title = todoInput.value.trim();
|
||||||
|
if (title) {
|
||||||
|
await TodoAPI.addTodo(title);
|
||||||
|
todoInput.value = '';
|
||||||
|
renderTodos();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
todoList.addEventListener('click', async (e) => {
|
||||||
|
const li = e.target.closest('.todo-item');
|
||||||
|
if (!li) return;
|
||||||
|
|
||||||
|
const id = li.dataset.id;
|
||||||
|
|
||||||
|
if (e.target.type === 'checkbox') {
|
||||||
|
const isDone = e.target.checked;
|
||||||
|
await TodoAPI.toggleTodo(id, isDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.target.classList.contains('delete-btn')) {
|
||||||
|
await TodoAPI.deleteTodo(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTodos();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial rendering
|
||||||
|
document.addEventListener('DOMContentLoaded', renderTodos);
|
110
TP5/EX1/index.php
Normal file
110
TP5/EX1/index.php
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
require 'flight/Flight.php';
|
||||||
|
require 'model/model.php';
|
||||||
|
|
||||||
|
// Connexion à la base de données
|
||||||
|
R::setup('mysql:host=localhost;dbname=votre_base_de_donnees','votre_nom_utilisateur', 'votre_mot_de_passe');
|
||||||
|
R::freeze(true);
|
||||||
|
|
||||||
|
// CORS Headers
|
||||||
|
Flight::map('cors', function() {
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||||
|
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||||
|
});
|
||||||
|
|
||||||
|
Flight::route('OPTIONS /todo(/@id)', function(){
|
||||||
|
Flight::cors();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET all todos or a single todo
|
||||||
|
Flight::route('GET /todo(/@id)', function($id = null) {
|
||||||
|
Flight::cors();
|
||||||
|
$filter = Flight::request()->query->filter ?? "all";
|
||||||
|
|
||||||
|
if ($id === null) {
|
||||||
|
switch($filter) {
|
||||||
|
case "done":
|
||||||
|
$todos = Todo::findCompleted();
|
||||||
|
break;
|
||||||
|
case "active":
|
||||||
|
$todos = Todo::findUnCompleted();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$todos = Todo::findAll();
|
||||||
|
}
|
||||||
|
Flight::json(["results" => $todos]);
|
||||||
|
} else {
|
||||||
|
$todo = Todo::find($id);
|
||||||
|
if ($todo) {
|
||||||
|
Flight::json($todo);
|
||||||
|
} else {
|
||||||
|
Flight::halt(404, 'Todo not found.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST a new todo
|
||||||
|
Flight::route('POST /todo', function() {
|
||||||
|
Flight::cors();
|
||||||
|
$requestData = Flight::request()->data;
|
||||||
|
if (!isset($requestData->title)) {
|
||||||
|
Flight::halt(400, 'Title is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$todoData = [
|
||||||
|
"title" => $requestData->title,
|
||||||
|
"done" => $requestData->done ?? false
|
||||||
|
];
|
||||||
|
|
||||||
|
$id = Todo::create($todoData);
|
||||||
|
$todoData['id'] = $id;
|
||||||
|
|
||||||
|
Flight::response()->header("Location", Flight::request()->url . $id);
|
||||||
|
Flight::json($todoData, 201);
|
||||||
|
});
|
||||||
|
|
||||||
|
// DELETE a todo
|
||||||
|
Flight::route('DELETE /todo/@id', function($id) {
|
||||||
|
Flight::cors();
|
||||||
|
$success = Todo::delete($id);
|
||||||
|
if ($success) {
|
||||||
|
Flight::halt(204);
|
||||||
|
} else {
|
||||||
|
Flight::halt(404, 'Todo not found.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// PUT (update) a todo
|
||||||
|
Flight::route('PUT /todo/@id', function($id) {
|
||||||
|
Flight::cors();
|
||||||
|
$requestData = Flight::request()->data;
|
||||||
|
|
||||||
|
$todo = Todo::find($id);
|
||||||
|
if (!$todo) {
|
||||||
|
Flight::halt(404, 'Todo not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$updateData = [];
|
||||||
|
if (isset($requestData->title)) {
|
||||||
|
$updateData['title'] = $requestData->title;
|
||||||
|
}
|
||||||
|
if (isset($requestData->done)) {
|
||||||
|
$updateData['done'] = $requestData->done;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($updateData)) {
|
||||||
|
Flight::halt(400, 'No data to update.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$updatedTodo = Todo::update($id, $updateData);
|
||||||
|
if ($updatedTodo) {
|
||||||
|
Flight::json($updatedTodo);
|
||||||
|
} else {
|
||||||
|
Flight::halt(500, 'Update failed.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Flight::start();
|
||||||
|
?>
|
51
TP5/EX1/model.php
Normal file
51
TP5/EX1/model.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
require 'rb-mysql.php';
|
||||||
|
|
||||||
|
class Todo {
|
||||||
|
public static function findAll() {
|
||||||
|
return R::findAll('todo');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function find($id) {
|
||||||
|
return R::load('todo', $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function findCompleted() {
|
||||||
|
return R::find('todo', 'done = ?', [true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function findUnCompleted() {
|
||||||
|
return R::find('todo', 'done = ?', [false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function create($data) {
|
||||||
|
$todo = R::dispense('todo');
|
||||||
|
$todo->title = $data['title'];
|
||||||
|
$todo->done = $data['done'];
|
||||||
|
$id = R::store($todo);
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function update($id, $data) {
|
||||||
|
$todo = R::load('todo', $id);
|
||||||
|
if ($todo->id === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($data as $key => $value) {
|
||||||
|
$todo->$key = $value;
|
||||||
|
}
|
||||||
|
R::store($todo);
|
||||||
|
return $todo->export();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function delete($id) {
|
||||||
|
$todo = R::load('todo', $id);
|
||||||
|
if ($todo->id !== 0) {
|
||||||
|
R::trash($todo);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
Reference in New Issue
Block a user