diff --git a/R4.01_R4.A.10/README.md b/R4.01_R4.A.10/README.md index 6e2786d..c3c6f44 100644 --- a/R4.01_R4.A.10/README.md +++ b/R4.01_R4.A.10/README.md @@ -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) + diff --git a/R4.01_R4.A.10/cours/dom.pdf b/R4.01_R4.A.10/cours/dom.pdf new file mode 100644 index 0000000..f5834c3 Binary files /dev/null and b/R4.01_R4.A.10/cours/dom.pdf differ diff --git a/R4.01_R4.A.10/td_tp/tp2/README.md b/R4.01_R4.A.10/td_tp/tp2/README.md new file mode 100644 index 0000000..1ae0688 --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2/README.md @@ -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. diff --git a/R4.01_R4.A.10/td_tp/tp2/aide.md b/R4.01_R4.A.10/td_tp/tp2/aide.md new file mode 100644 index 0000000..f2ce6f8 --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2/aide.md @@ -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") + +``` + diff --git a/R4.01_R4.A.10/td_tp/tp2/img/lights.png b/R4.01_R4.A.10/td_tp/tp2/img/lights.png new file mode 100644 index 0000000..917547e Binary files /dev/null and b/R4.01_R4.A.10/td_tp/tp2/img/lights.png differ diff --git a/R4.01_R4.A.10/td_tp/tp2/img/ligue1.png b/R4.01_R4.A.10/td_tp/tp2/img/ligue1.png new file mode 100644 index 0000000..a73f1c4 Binary files /dev/null and b/R4.01_R4.A.10/td_tp/tp2/img/ligue1.png differ diff --git a/R4.01_R4.A.10/td_tp/tp2/img/todo.png b/R4.01_R4.A.10/td_tp/tp2/img/todo.png new file mode 100644 index 0000000..7478a61 Binary files /dev/null and b/R4.01_R4.A.10/td_tp/tp2/img/todo.png differ diff --git a/R4.01_R4.A.10/td_tp/tp2/src/ex1/app.js b/R4.01_R4.A.10/td_tp/tp2/src/ex1/app.js new file mode 100644 index 0000000..6aa4937 --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2/src/ex1/app.js @@ -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() + diff --git a/R4.01_R4.A.10/td_tp/tp2/src/ex1/index.html b/R4.01_R4.A.10/td_tp/tp2/src/ex1/index.html new file mode 100644 index 0000000..e724aaf --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2/src/ex1/index.html @@ -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> diff --git a/R4.01_R4.A.10/td_tp/tp2/src/ex1/style.css b/R4.01_R4.A.10/td_tp/tp2/src/ex1/style.css new file mode 100644 index 0000000..2d2be44 --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2/src/ex1/style.css @@ -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; +} +*/ diff --git a/R4.01_R4.A.10/td_tp/tp2/src/ex2/data.js b/R4.01_R4.A.10/td_tp/tp2/src/ex2/data.js new file mode 100644 index 0000000..a8ecae3 --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2/src/ex2/data.js @@ -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"} +] + diff --git a/R4.01_R4.A.10/td_tp/tp2/src/ex2/index.html b/R4.01_R4.A.10/td_tp/tp2/src/ex2/index.html new file mode 100644 index 0000000..c40ed47 --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2/src/ex2/index.html @@ -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> diff --git a/R4.01_R4.A.10/td_tp/tp2/src/ex2/script.js b/R4.01_R4.A.10/td_tp/tp2/src/ex2/script.js new file mode 100644 index 0000000..259af41 --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2/src/ex2/script.js @@ -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)) + +}) diff --git a/R4.01_R4.A.10/td_tp/tp2/src/ex3/css/style.css b/R4.01_R4.A.10/td_tp/tp2/src/ex3/css/style.css new file mode 100644 index 0000000..465a430 --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2/src/ex3/css/style.css @@ -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; +} diff --git a/R4.01_R4.A.10/td_tp/tp2/src/ex3/index.html b/R4.01_R4.A.10/td_tp/tp2/src/ex3/index.html new file mode 100644 index 0000000..1a0f2e5 --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2/src/ex3/index.html @@ -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> diff --git a/R4.01_R4.A.10/td_tp/tp2/src/ex3/js/app.js b/R4.01_R4.A.10/td_tp/tp2/src/ex3/js/app.js new file mode 100644 index 0000000..b97d13a --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2/src/ex3/js/app.js @@ -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()) diff --git a/R4.01_R4.A.10/td_tp/tp2/src/ex3/js/controller.js b/R4.01_R4.A.10/td_tp/tp2/src/ex3/js/controller.js new file mode 100644 index 0000000..bb027a8 --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2/src/ex3/js/controller.js @@ -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() + } +} + diff --git a/R4.01_R4.A.10/td_tp/tp2/src/ex3/js/model.js b/R4.01_R4.A.10/td_tp/tp2/src/ex3/js/model.js new file mode 100644 index 0000000..7e4c7f8 --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2/src/ex3/js/model.js @@ -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) + } +} + diff --git a/R4.01_R4.A.10/td_tp/tp2/src/ex3/js/view.js b/R4.01_R4.A.10/td_tp/tp2/src/ex3/js/view.js new file mode 100644 index 0000000..038b9eb --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2/src/ex3/js/view.js @@ -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 + } +}