1
0
forked from monnerat/web_2024
This commit is contained in:
Denis Monnerat 2025-01-30 11:39:17 +01:00
parent d209ab2e82
commit 9f0c67b9f0
19 changed files with 993 additions and 0 deletions

@ -3,4 +3,7 @@
#### Semaine 1
[Compléments de javascript](cours/jscomp.pdf) javascript, [tp1](./td_tp/tp1)
#### Semaine 2
[DOM](cours/dom.pdf), [tp2](./td_tp/tp2)

BIN
R4.01_R4.A.10/cours/dom.pdf Normal file

Binary file not shown.

@ -0,0 +1,211 @@
# TP javascript : DOM
Un peu [d'aide](./aide.md).
#### Ex1 : manipulation du dom
Vous devez compléter les [sources](./src/ex1) d'un jeu qui consiste à essayer
d'éteindre toutes les lumières d'une grille en utilisant la règle suivante.
Quand on allume/éteint une lumière, on allume/éteint aussi ses voisines.
<div align="center">
<img src="./img/lights.png">
</div>
Chaque lumière, via l'interface dataset, possède un numéro (de 0 à size^2-1).
#### Ex2 : une vue html à partir de données js
Le fichier [data.js](./src/ex2/data.js) déclare un tableau contenant le classement de la ligue de football, récupéré depuis l'api [the sport db](https://www.thesportsdb.com/).
```js
let data = [
{
"idStanding":"2282066",
"intRank":"1",
"idTeam":"133714",
"strTeam":"Paris SG",
"strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/rwqrrq1473504808.png/tiny",
"idLeague":"4334",
"strLeague":"French Ligue 1",
"strSeason":"2022-2023",
"strForm":"WLWWW",
"strDescription":"Promotion - Champions League (Group Stage)",
"intPlayed":"18",
"intWin":"15",
"intLoss":"1",
"intDraw":"2",
"intGoalsFor":"48",
"intGoalsAgainst":"13",
"intGoalDifference":"35",
"intPoints":"47",
"dateUpdated":"2023-01-12 23:01:12"
},
...
]
```
Chaque entrée est une objet json contenant différents champs.
Le but de l'exercice est de construire une vue de ces données sous forme d'une table html.
<div align="center">
<img src="./img/ligue1.png">
</div>
Un [squelette](./src/ex2/index.html)
- la colonne du rang sera triable.
- un champ de recherche permettra de filtrer les lignes du classement selon le nom d'équipe.
> - Il faut itérer le tableau data, construire et ajouter au `tbody` de la table les lignes nécessaires.
> - Ajouter ensuite le traitement évenementielle pour le tri de la première colonne. On pourra effacer et recréer la vue.
> - Ajouter la possibilité de recherche à partir de champ de recherche.
#### Ex3 : modele MVC
Le but est d'écrire une todolist en javascript, en respectant le pattern MVC. Il est important de mener à bout cet exercice, car il nous servira
de fil rouge notamment en ajoutant une api rest et ajax pour la communication avec le service.
<div align="center">
<img src="./img/todo.png">
</div>
Le contrôleur a accès à la vue et au modèle.
##### Le modèle
Cette "classe" (rien à compléter) utilise l'objet [localStorage](https://developer.mozilla.org/fr/docs/Web/API/Window/localStorage) pour sauvegarder la todolist.
Chaque todo est un objet json de la forme
```js
{ id : 1 , text : "apprendre le js", done : false }
```
La liste des méthodes publiques de cette classe
```js
/** @brief return the todolist
* @param filter "all" or "active" or "done"
* @return array of todos
*/
getTodos(filter)
/** @brief add (and save) a new todo with todoText
* @param todoText : text of the new todo
* @return the new todo
*/
add(todoText)
/** @brief delete a todo
* @param id id of the todo
*/
delete(id)
/** @brief update a todo
* @param id id of the todo
*/
edit(id,updatedText)
/** @brief toggle a todo (done<->active)
* @param id id of the todo
* @param updatedText text of the todo
*/
toggle(id)
```
##### La vue
Cette "classe" permet au contrôleur de gérer la vue.
Liste des méthodes publiques
```js
/** @brief change the active tab (all,active or done)
* @param filter the active tab (all, active or done)
*/
setFilterTabs(filter)
/** @brief update the todo list with todos
* @param todos array of todo
*/
renderTodoList(todos)
```
Le contrôleur peut s'abonner (notification de la vue au acontrôleur) aux événements suivant :
add/delete/edit/toggle todo : avec `bind{Add|Delete|Edit|Toggle}Todo`
Ces méthodes d'abonnement prennent en paramètre une fonction du contrôleur qui est appelé par la vue pour lui notifier
une requête.
```js
/** @brief subscribe to add event
* @param handler function(text)
*/
bindAddTodo(handler)
/** @brief suscribe to delete event
* @param handler function(id)
*/
bindDeleteTodo(handler)
/** @brief suscribe to edit event
* @param handler function(id,text)
*/
bindEditTodo(handler)
/** @brief suscribe to toggle event
* @param handler function(id)
*/
bindToggleTodo(handler)
```
##### Le contrôleur
C'est lui qui gére le routage. Il y a trois urls possibles `index.html/#/{all|active|done}` (listener sur l'évenement
dom `hashchange`.
Liste des méthodes
```js
/** @brief get the todolist from the model
* and use the view to render
*/
filterTodoList()
/** @brief binding function called by the view
* to add a new todo
* @param text text of the new todo
*/
addTodo(text)
/** @brief binding function called by the view
* to delete a todo
* @param id id of the todo
*/
deleteTodo(id)
/** @brief binding function to toggle the state
* of a todo
* @param id id of the todo
*/
toggleTodo(id)
/** @brief binding function to change the text
* of a todo
* @param id id of the todo
* @param changedText new text for the todo
*/
editTodo(id,changedText)
```
Par exemple, lorsque l'utilisateur ajoute une todo :
1. la vue est sensible à l'événement de soumission du formulaire,
2. la fonction reflexe récupére le texte saisie,
3. elle appelle la fonction avec laquelle le contrôleur s'est abonné, en lui passant le texte,
4. la fonction du contrôleur `addTodo` récupére le texte,
5. le contrôleur demande au modèle la création d'une nouvelle todo,
6. le contrôleur demande un rafraichissement de la vue.
#### Travail à faire
- Compléter la méthode `bindDeleteTodo` de la vue.
- Compléter la méthode `bindToggleTodo` de la vue.
- Compléter la méthode `bindEditTodo` de la vue.

@ -0,0 +1,132 @@
#### Selection d'éléments
```js
// le premier
document.querySelector(".box")
// ou tous
document.querySelectorAll(".box")
// avec l'id
document.getElementById("toto")
// selection d'un élément dans un autre
let container = document.querySeletor('.container')
container.querySelector('.box')
```
#### Traverser le dom
```js
var box = document.querySelector(".box")
box.nextElementSibling
box.previousElementSibling
box.parentElement
box.childNodes
```
#### Création/insertion d'éléments
```js
let p = document.createElement('p')
p.textContent="blabla"
document
.getElementById("myDiv")
.appendChild(p)
div.replaceChildren(p)
```
#### Gestionnaire évènementiels
```js
document.querySelector(".button").addEventListener("click", (e) => { /* ... */ })
document.querySelector(".button").addEventListener("mouseenter", (e) => { /* ... */ })
document.addEventListener("keyup", (e) => { /* ... */ })
```
#### window/document prêt
```js
document.addEventListener("DOMContentLoaded",() = > { .....})
// le dom a été construit (on n'attend pas le chargement du css, images, etc.
windon.addEventListener('load',()=>{...})
// le dom est prêt et toutes les ressources ont été chargées.
```
#### dataset
```html
<div id="user" data-id="1234567890" data-user="carinaanand" data-date-of-birth>
Carina Anand
</div>
```
```javascript
const el = document.querySelector("#user");
// el.id === 'user'
// el.dataset.id === '1234567890'
// el.dataset.user === 'carinaanand'
// el.dataset.dateOfBirth === ''
```
#### Local storage
```js
localStorage.setItem('monChat', 'Tom')
let cat = localStorage.getItem('myCat')
localStorage.clear()
```
#### Limiter les appels successifs à une fonction
```js
function debounce(f,wait)
{
let timeout
return function(...args){
clearTimeout(timeout)
timeout=setTimeout(()=>f(...args),wait)
}
}
```
#### Gestion du css depuis le DOM
```js
// Select the first .box and change its text color to #000
document.querySelector(".box").style.color = "#000";
// Set color to #000 and background to red
var box = document.querySelector(".box")
box.style.color = "#000"
box.style.backgroundColor = "red"
// Set all styles at once (and override any existing styles)
box.style.cssText = "color: #000; background-color: red"
// Hide and show an element by changing "display" to block and none
document.querySelector(".box").style.display = "none"
document.querySelector(".box").style.display = "block"
// Add, remove, and the toggle the "focus" class
var box = document.querySelector(".box")
box.classList.add("focus")
box.classList.remove("focus")
box.classList.toggle("focus")
box.classList.add("focus", "highlighted")
box.classList.remove("focus", "highlighted")
box.classList.add("focus", "highlighted")
box.classList.remove("focus", "highlighted")
```

Binary file not shown.

After

(image error) Size: 48 KiB

Binary file not shown.

After

(image error) Size: 97 KiB

Binary file not shown.

After

(image error) Size: 25 KiB

@ -0,0 +1,72 @@
// variables de vue
const button = document.querySelector("input[type=submit]")
const select = document.querySelector("select")
const board = document.querySelector(".board")
const message = document.querySelector("#message")
let lights = null
// state <-> tableau de booléen pour
// stocker l'état des lumières
// neighbors <-> tableau des voisins de chaque lumière
let state = []
let neighbors = []
newBoard = ( (size ) => {
let frag = document.createDocumentFragment()
// TODO
// la fonction crée dans le dom la grille des lumière
board.replaceChildren(frag)
})
calcNeighbours = ( (size) =>{
let neighbors = [];
for(let i = 0; i < size; i ++){
for(let j = 0; j < size; j++){
let v = [];
v.push(i*size + j);
if ( ( j - 1) >= 0) v.push(i*size + j - 1);
if ( ( j + 1) < size) v.push(i*size + j + 1);
if ( (i - 1 ) >= 0) v.push(size*(i-1) + j );
if ( (i + 1 ) < size) v.push(size*(i+1) + j);
neighbors.push(v)
}
}
return neighbors
})
function play(){
let size = select.value
newBoard(size)
neighbors = calcNeighbours(size)
lights = document.querySelectorAll(".board span")
state = Array.from({length: size*size}, () => Math.random() < 0.5 ? true:false);
state.forEach((el,i)=>{
if (el) lights[i].classList.toggle("off")
})
message.textContent = "";
}
// abonnements
document
.querySelector(".board")
.addEventListener("click", ev => {
// TODO
// permet de choisir d'éteindre/allumer une lumière et
// ses voisines
// Il faut en outre detecter la fin de partie
});
button.addEventListener("click",play)
play()

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<!-- Centered viewport -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"
>
<link rel="stylesheet" href="style.css" type="text/css" media="screen" title="no title" charset="utf-8">
</head>
<body>
<main>
<h4>Lights</h4>
<p>Le but est d'éteindre l'ensemble des lumières.</p>
<p id="message"></p>
<article>
<div class="board">
<!--div class="row">
<span data-num="0"></span>
<span data-num="1"></span>
<span data-num="2"></span>
</div>
<div class="row">
<span data-num="3"></span>
<span data-num="4"></span>
<span data-num="5"></span>
</div>
<div class="row">
<span data-num="6"></span>
<span data-num="7"></span>
<span data-num="8"></span-->
</div>
</div>
</article>
<div role="group">
<select aria-label="Select size" required>
<option selected value="3">3x3</option>
<option value="5">5x5</option>
<option value="7">7x7</option>
</select>
<input type="submit" value="New Game">
</div>
</main>
<script src="app.js"></script>
</body>
</html>

@ -0,0 +1,50 @@
/*.grid {
display:grid;
grid-template-columns : 100px 100px 100px;
grid-template-rows: auto;
grid-gap: 10px;
}*/
.board > div {
display : flex;
}
.board span {
display: inline-block;
vertical-align:baseline;
width: 100px;
height: 100px;
/*padding : 1em;*/
margin : 0.1em;
cursor: pointer;
border: 1px solid #444;
border-radius: 50px;
background: radial-gradient(rgb(150,250,150), #00ee00);
}
.board span.off {
background: #009900;
}
.board span.off:hover {
background: #3AB903;
}
.board span:hover {
background: #3AB903;
background: radial-gradient(rgb(66, 255, 66), #065006);
}
/*
.wrapper {
display : flex;
justify-content: space-around;
}
.wrapper .grid {
flex-basis : auto;
}
*/

@ -0,0 +1,23 @@
let data = [
{"idStanding":"2282066","intRank":"1","idTeam":"133714","strTeam":"Paris SG","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/rwqrrq1473504808.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"WLWWW","strDescription":"Promotion - Champions League (Group Stage)","intPlayed":"18","intWin":"15","intLoss":"1","intDraw":"2","intGoalsFor":"48","intGoalsAgainst":"13","intGoalDifference":"35","intPoints":"47","dateUpdated":"2023-01-12 23:01:12"},
{"idStanding":"2282067","intRank":"2","idTeam":"133822","strTeam":"Lens","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/3pxoum1598797195.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"DWDWW","strDescription":"Promotion - Champions League (Group Stage)","intPlayed":"18","intWin":"12","intLoss":"1","intDraw":"5","intGoalsFor":"31","intGoalsAgainst":"13","intGoalDifference":"18","intPoints":"41","dateUpdated":"2023-01-12 23:01:12"},
{"idStanding":"2282068","intRank":"3","idTeam":"133707","strTeam":"Marseille","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/uutsyt1473504764.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"WWWWW","strDescription":"Promotion - Champions League (Qualification)","intPlayed":"18","intWin":"12","intLoss":"3","intDraw":"3","intGoalsFor":"36","intGoalsAgainst":"15","intGoalDifference":"21","intPoints":"39","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282069","intRank":"4","idTeam":"133719","strTeam":"Rennes","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/ypturx1473504818.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"LWLWD","strDescription":"Promotion - Europa League (Group Stage)","intPlayed":"18","intWin":"10","intLoss":"4","intDraw":"4","intGoalsFor":"35","intGoalsAgainst":"20","intGoalDifference":"15","intPoints":"34","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282070","intRank":"5","idTeam":"133823","strTeam":"Monaco","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/819x3z1655593495.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"DWWLW","strDescription":"Promotion - Europa Conference League (Qualification)","intPlayed":"18","intWin":"10","intLoss":"4","intDraw":"4","intGoalsFor":"35","intGoalsAgainst":"25","intGoalDifference":"10","intPoints":"34","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282071","intRank":"6","idTeam":"133715","strTeam":"Lorient","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/sxsttw1473504748.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"DWLDL","strDescription":"","intPlayed":"18","intWin":"9","intLoss":"4","intDraw":"5","intGoalsFor":"30","intGoalsAgainst":"26","intGoalDifference":"4","intPoints":"32","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282072","intRank":"7","idTeam":"133711","strTeam":"Lille","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/2giize1534005340.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"DDWWD","strDescription":"","intPlayed":"18","intWin":"9","intLoss":"5","intDraw":"4","intGoalsFor":"30","intGoalsAgainst":"24","intGoalDifference":"6","intPoints":"31","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282073","intRank":"8","idTeam":"133713","strTeam":"Lyon","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/blk9771656932845.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"DLWDL","strDescription":"","intPlayed":"18","intWin":"7","intLoss":"7","intDraw":"4","intGoalsFor":"27","intGoalsAgainst":"21","intGoalDifference":"6","intPoints":"25","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282074","intRank":"9","idTeam":"134713","strTeam":"Clermont Foot","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/wrytst1426871249.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"WWLLD","strDescription":"","intPlayed":"18","intWin":"7","intLoss":"7","intDraw":"4","intGoalsFor":"22","intGoalsAgainst":"26","intGoalDifference":"-4","intPoints":"25","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282075","intRank":"10","idTeam":"133712","strTeam":"Nice","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/msy7ly1621593859.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"WLDDW","strDescription":"","intPlayed":"18","intWin":"6","intLoss":"6","intDraw":"6","intGoalsFor":"22","intGoalsAgainst":"20","intGoalDifference":"2","intPoints":"24","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282076","intRank":"11","idTeam":"133934","strTeam":"Stade de Reims","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/xcrw1b1592925946.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"WDWDW","strDescription":"","intPlayed":"18","intWin":"5","intLoss":"4","intDraw":"9","intGoalsFor":"21","intGoalsAgainst":"23","intGoalDifference":"-2","intPoints":"24","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282077","intRank":"12","idTeam":"133703","strTeam":"Toulouse","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/3kqxs61547893229.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"WWLLL","strDescription":"","intPlayed":"18","intWin":"6","intLoss":"8","intDraw":"4","intGoalsFor":"28","intGoalsAgainst":"33","intGoalDifference":"-5","intPoints":"22","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282078","intRank":"13","idTeam":"134789","strTeam":"Troyes","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/swuwpq1426544753.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"LWDLD","strDescription":"","intPlayed":"18","intWin":"4","intLoss":"8","intDraw":"6","intGoalsFor":"29","intGoalsAgainst":"35","intGoalDifference":"-6","intPoints":"18","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282079","intRank":"14","idTeam":"133861","strTeam":"Nantes","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/8r0dab1598797469.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"DWDDL","strDescription":"","intPlayed":"18","intWin":"3","intLoss":"6","intDraw":"9","intGoalsFor":"18","intGoalsAgainst":"24","intGoalDifference":"-6","intPoints":"18","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282080","intRank":"15","idTeam":"133709","strTeam":"Montpellier","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/spxtvu1473504783.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"LLWDD","strDescription":"","intPlayed":"18","intWin":"5","intLoss":"11","intDraw":"2","intGoalsFor":"28","intGoalsAgainst":"37","intGoalDifference":"-9","intPoints":"17","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282081","intRank":"16","idTeam":"133702","strTeam":"Ajaccio","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/qpxvwy1473505505.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"LLWDW","strDescription":"","intPlayed":"18","intWin":"4","intLoss":"11","intDraw":"3","intGoalsFor":"15","intGoalsAgainst":"27","intGoalDifference":"-12","intPoints":"15","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282082","intRank":"17","idTeam":"133704","strTeam":"Brest","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/z69be41598797026.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"DLLWL","strDescription":"Relegation - Ligue 2","intPlayed":"18","intWin":"3","intLoss":"10","intDraw":"5","intGoalsFor":"18","intGoalsAgainst":"33","intGoalDifference":"-15","intPoints":"14","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282083","intRank":"18","idTeam":"134788","strTeam":"Auxerre","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/lzdtbf1658753355.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"LLLLD","strDescription":"Relegation - Ligue 2","intPlayed":"18","intWin":"3","intLoss":"11","intDraw":"4","intGoalsFor":"16","intGoalsAgainst":"40","intGoalDifference":"-24","intPoints":"13","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282084","intRank":"19","idTeam":"133882","strTeam":"Strasbourg","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/yuxtyy1464540071.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"DLLDL","strDescription":"Relegation - Ligue 2","intPlayed":"18","intWin":"1","intLoss":"8","intDraw":"9","intGoalsFor":"22","intGoalsAgainst":"33","intGoalDifference":"-11","intPoints":"12","dateUpdated":"2023-01-12 23:01:13"},
{"idStanding":"2282085","intRank":"20","idTeam":"134709","strTeam":"Angers","strTeamBadge":"https://www.thesportsdb.com/images/media/team/badge/445gc21622560255.png/tiny","idLeague":"4334","strLeague":"French Ligue 1","strSeason":"2022-2023","strForm":"LLLLL","strDescription":"Relegation - Ligue 2","intPlayed":"18","intWin":"2","intLoss":"14","intDraw":"2","intGoalsFor":"16","intGoalsAgainst":"39","intGoalDifference":"-23","intPoints":"8","dateUpdated":"2023-01-12 23:01:13"}
]

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1,witdh=device-width">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css" integrity="sha512-q3eWabyZPc1XTCmF+8/LuE1ozpg5xxn7iO89yfSOd5/oKvyqLngoNGsx8jq92Y8eXJ/IRxQbEC+FGSYxtk2oiw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<!--link rel="stylesheet" href="./style.css"-->
<title>Classement Ligue 1</title>
</head>
<body class="m-6">
<main class="container">
<h3 class="title is-3 has-text-primary-dark">
Classement Ligue 1 de football<span class="tag" id="date"></span>
<div class="block control has-icons-left is-inline-block is-pulled-right">
<input class="input" type="search" placeholder="Equipe" id="myInput">
<span class="icon is-small is-left">
<i class="fas fa-search"></i>
</span>
</div>
</h3>
<table class="table is-fullwidth is-hoverable">
<thead>
<tr>
<th><abbr title="Position">Pos</abbr><a id="sort" href="#"><span class="icon"><i class="fas fa-sort"></i></span></a></th>
<th></th>
<th>Team</th>
<th><abbr title="Played">Pld</abbr></th>
<th><abbr title="Won">W</abbr></th>
<th><abbr title="Drawn">D</abbr></th>
<th><abbr title="Lost">L</abbr></th>
<th><abbr title="Goals for">GF</abbr></th>
<th><abbr title="Goals against">GA</abbr></th>
<th><abbr title="Goal difference">GD</abbr></th>
<th><abbr title="Points">Pts</abbr></th>
</tr>
</thead>
<tfoot>
<tr>
<th><abbr title="Position">Pos</abbr></th>
<th></th>
<th>Team</th>
<th><abbr title="Played">Pld</abbr></th>
<th><abbr title="Won">W</abbr></th>
<th><abbr title="Drawn">D</abbr></th>
<th><abbr title="Lost">L</abbr></th>
<th><abbr title="Goals for">GF</abbr></th>
<th><abbr title="Goals against">GA</abbr></th>
<th><abbr title="Goal difference">GD</abbr></th>
<th><abbr title="Points">Pts</abbr></th>
</tr>
</tfoot>
<tbody></tbody>
</table>
</main>
<script src="data.js"></script>
<script src="script.js"></script>
</body>
</html>

@ -0,0 +1,74 @@
// TODO
renderTable=((mountPoint,standings,keys)=>{
let frag = document.createDocumentFragment();
for (team of standings){
let tr = document.createElement("tr");
for (key of keys){
let td = document.createElement("td");
let elt;
if (key === "strTeamBadge"){
elt = document.createElement("img");
elt.src= team[key];
}else{
elt = team[key];
}
td.append(elt)
tr.append(td);
}
frag.append(tr);
}
mountPoint.replaceChildren();
mountPoint.append(frag);
})
sortAndFilter=((standings,sort,filter)=>{
return standings
.filter((team)=>team.strTeam.toLowerCase().includes(filter.toLowerCase()))
.sort((teamA,teamB)=>(teamA.intRank - teamB.intRank)*sort)
})
debounce=((f,time)=>{
let timer
return function(...args){
clearTimeout(timer)
timer = setTimeout(()=>f(...args),time)
}
})
window.onload = (()=>{
let sort = 1;
let text = "";
let tbody = document.querySelector("tbody");
const keys = ["intRank","strTeam","strTeamBadge","intPlayed","intWin","intLoss","intDraw","intGoalsFor"
,"intGoalsAgainst","intGoalDifference","intPoints"];
renderTable(tbody,data,keys);
document
.querySelector("#sort")
.addEventListener("click",(e)=>{
sort = - sort;
renderTable(tbody,sortAndFilter(data,sort,text),keys);
})
document
.querySelector("input")
.addEventListener("input",debounce((e)=>{
text = e.target.value
console.log("MAJ")
renderTable(tbody,sortAndFilter(data,sort,text),keys);
},1000))
})

@ -0,0 +1,32 @@
:focus-visible {outline:none;}
.strike {
text-decoration-line : line-through;
}
#loader.is-loading {
position: fixed;
z-index: 999;
overflow: show;
margin: auto;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
#loader.is-loading:after {
animation: spinAround 500ms infinite linear;
border: 2px solid hsl(0deg, 0%, 86%);
border-radius: 9999px;
border-right-color: transparent;
border-top-color: transparent;
content: "";
display: block;
position: relative;
top: calc(50% - 5em);
left: calc(50% - 5em);
width: 10em;
height: 10em;
border-width: 0.25em;
}

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Todo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css" integrity="sha512-q3eWabyZPc1XTCmF+8/LuE1ozpg5xxn7iO89yfSOd5/oKvyqLngoNGsx8jq92Y8eXJ/IRxQbEC+FGSYxtk2oiw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="./css/style.css">
</head>
<body>
<div class="container content is-max-desktop p-4">
<div id="loader"></div>
<div class="has-text-centered">
</div>
<!-- formulaire pour la saisie -->
<form id="add_form" class="block">
<p class="control has-icons-left is-expanded">
<input autocomplete="off" id="input_todo" class="input is-large is-expanded" type="text" placeholder="Enter Todo">
<span class="icon is-left">
<i class="fas fa-plus" aria-hidden="true"></i>
</span>
</p>
</form>
<!-- filtrage de l'affichage -->
<div id="todos_filter" class="tabs is-small is-centered">
<ul>
<li class="is-active"><a href="#/" id="all">All</a></li>
<li><a href="#/active" id="active">Active</a></li>
<li><a href="#/done" id="done">Done</a></li>
</ul>
</div>
<!-- Affichage des todos -->
<section id="todos_list">
</section>
</body>
<script type="module" src="./js/app.js"></script>
</html>

@ -0,0 +1,6 @@
import Controller from './controller.js'
import Model from './model.js'
import View from './view.js'
const app = new Controller(new Model(),new View())

@ -0,0 +1,54 @@
export default class Controller {
constructor(model, view) {
this.model = model
this.view = view
this.filter = "all"
this.view.bindAddTodo(this.addTodo.bind(this))
this.view.bindDeleteTodo(this.deleteTodo.bind(this))
this.view.bindToggleTodo(this.toggleTodo.bind(this))
this.view.bindEditTodo(this.editTodo.bind(this))
/** Routage **/
this.routes = ['all','active','done'];
/** Routage **/
window.addEventListener("load",this.routeChanged.bind(this));
window.addEventListener("hashchange",this.routeChanged.bind(this));
}
routeChanged(){
let route = window.location.hash.replace(/^#\//,'');
this.filter = this.routes.find( r => r === route) || 'all';
this.filterTodoList();
}
filterTodoList () {
let todos = this.model.getTodos(this.filter)
this.view.renderTodoList(todos)
this.view.setFilterTabs(this.filter)
}
addTodo(text) {
let todo = this.model.add(text)
this.filterTodoList()
}
deleteTodo(id) {
this.model.delete(parseInt(id))
this.filterTodoList()
}
toggleTodo(id) {
this.model.toggle(parseInt(id))
this.filterTodoList()
}
editTodo(id, text) {
this.model.edit(parseInt(id),text)
this.filterTodoList()
}
}

@ -0,0 +1,77 @@
const todos = [
{
id : 1,
text : "apprendre le javascript",
done : false
},
{
id : 2,
text : "Faire des maths",
done : false
}]
export default class Model {
constructor() {
//this.todos = JSON.parse(localStorage.getItem('todos')) || []
this.todos = JSON.parse(localStorage.getItem('todos')) || todos
}
_commit(todos) {
localStorage.setItem('todos', JSON.stringify(todos))
}
getTodos(filter){
if (filter == "active")
return this.todos.filter(todo => !todo.done)
if (filter == "done")
return this.todos.filter(todo => todo.done)
return this.todos
}
add(todoText) {
const todo = {
id: this.todos.length > 0 ? this.todos[this.todos.length - 1].id + 1 : 1,
text: todoText,
done : false,
}
this.todos.push(todo)
this._commit(this.todos)
return todo
}
edit(id, updatedText) {
let todo = this.todos.find(t => t.id === id)
todo.text = updatedText
// this.todos = this.todos.map(todo =>
// todo.id === id ? { id: todo.id, text: updatedText, done: todo.done} : todo
// )
this._commit(this.todos)
}
:q!
delete(id) {
this.todos = this.todos.filter(todo => todo.id !== id)
this._commit(this.todos)
}
toggle(id) {
let todo = this.todos.find(t => t.id === id)
todo.done = !todo.done
//this.todos = this.todos.map(todo =>
// todo.id === id ? { id: todo.id, text: todo.text, done: !todo.done } : todo
//)
this._commit(this.todos)
}
}

@ -0,0 +1,95 @@
export default class View {
constructor() {
this.form = document.querySelector("#add_form")
this.input = document.querySelector("#input_todo")
this.list = document.querySelector("#todos_list")
this.tabs = document.querySelector("#todos_filter")
this.loader = document.querySelector("#loader")
}
_getTodo() {
return this.input.value
}
_resetInput() {
this.input.value = ''
}
_getNewTodoElement(todo){
let div = document.createElement("div")
div.classList.add("box","is-flex","is-align-items-center","m-2")
div.dataset.id = todo.id
let input = document.createElement("input")
input.type = "checkbox"
input.classList.add("mr-3")
let span = document.createElement("p")
span.classList.add("mb-0","is-size-3","mr-auto")
span.textContent = todo.text
span.setAttribute("contenteditable",true)
let button = document.createElement("button")
button.classList.add("delete","is-large")
if (todo.done){
span.classList.add("strike")
input.checked = true
}
div.append(input,span,button)
return div
}
setLoader(){
this.loader.classList.add("is-loading")
}
unsetLoader(){
this.loader.classList.remove("is-loading")
}
setFilterTabs(filter){
let li = this.tabs.querySelectorAll("li")
li.forEach( tab => {
tab.classList.remove("is-active")
})
let active = this.tabs.querySelector(`#${filter}`)
active.parentNode.classList.add("is-active")
}
renderTodoList(todos) {
let list = new DocumentFragment()
for (let todo of todos){
list.appendChild(this._getNewTodoElement(todo))
}
this.list.replaceChildren(list)
}
/** Abonnements événements **/
bindAddTodo(handler) {
this.form.addEventListener("submit", (e=>{
e.preventDefault()
let text = this._getTodo()
handler(text)
this._resetInput()
}))
}
bindDeleteTodo(handler) {
// TODO
}
bindEditTodo(handler) {
// TODO
}
bindToggleTodo(handler) {
// TODO
}
}