From 186b3839f03efb2ec72cd2b09693e7fac8b0187a Mon Sep 17 00:00:00 2001 From: Denis Monnerat Date: Mon, 2 Feb 2026 13:56:42 +0100 Subject: [PATCH] tp2mvc --- R4.01_R4.A.10/README.md | 4 + R4.01_R4.A.10/td_tp/tp2mvc/README.md | 163 ++++++++++++++++++ R4.01_R4.A.10/td_tp/tp2mvc/img/todo.png | Bin 0 -> 26516 bytes R4.01_R4.A.10/td_tp/tp2mvc/src/css/style.css | 63 +++++++ R4.01_R4.A.10/td_tp/tp2mvc/src/index.html | 53 ++++++ .../td_tp/tp2mvc/src/js/controller.js | 64 +++++++ R4.01_R4.A.10/td_tp/tp2mvc/src/js/model.js | 48 ++++++ R4.01_R4.A.10/td_tp/tp2mvc/src/js/view.js | 83 +++++++++ 8 files changed, 478 insertions(+) create mode 100644 R4.01_R4.A.10/td_tp/tp2mvc/README.md create mode 100644 R4.01_R4.A.10/td_tp/tp2mvc/img/todo.png create mode 100644 R4.01_R4.A.10/td_tp/tp2mvc/src/css/style.css create mode 100644 R4.01_R4.A.10/td_tp/tp2mvc/src/index.html create mode 100644 R4.01_R4.A.10/td_tp/tp2mvc/src/js/controller.js create mode 100644 R4.01_R4.A.10/td_tp/tp2mvc/src/js/model.js create mode 100644 R4.01_R4.A.10/td_tp/tp2mvc/src/js/view.js diff --git a/R4.01_R4.A.10/README.md b/R4.01_R4.A.10/README.md index 088660f..a0312ba 100644 --- a/R4.01_R4.A.10/README.md +++ b/R4.01_R4.A.10/README.md @@ -3,3 +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), [tpmvc](td_tp/tp2mvc) + + diff --git a/R4.01_R4.A.10/td_tp/tp2mvc/README.md b/R4.01_R4.A.10/td_tp/tp2mvc/README.md new file mode 100644 index 0000000..152456f --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2mvc/README.md @@ -0,0 +1,163 @@ +#### 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. + +
+ +
+ +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 + */ +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 suivants : + +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 +- Commpléter la méthode privée dans la vue `#createNewTodoElement` qui permet de créer un + nouvel élément dom représentant une todo. Ce qui est attendu est de la forme + ```html +
  • + + +
  • + ``` +- 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/tp2mvc/img/todo.png b/R4.01_R4.A.10/td_tp/tp2mvc/img/todo.png new file mode 100644 index 0000000000000000000000000000000000000000..26a6585c121ad1e4c4421df20cd2742e42e5f2e4 GIT binary patch literal 26516 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 I&MBb@0OsgD0RR91 literal 0 HcmV?d00001 diff --git a/R4.01_R4.A.10/td_tp/tp2mvc/src/css/style.css b/R4.01_R4.A.10/td_tp/tp2mvc/src/css/style.css new file mode 100644 index 0000000..a2185a4 --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2mvc/src/css/style.css @@ -0,0 +1,63 @@ +:focus-visible {outline:none;} + +.is-active { + color : #8EB901; +} + +#add_form > div { + display : flex; + justify-content:space-between; +} + +.todo { + display : flex; + align-items: center; +} +.todo checkbox,.todo i { + flex-grow : 0; + flex-shrink : 0; + flex-basis : auto; +} +.todo span { + flex-grow : 1; + flex-shrink : 1; + flex-basis : auto; +} + + +.todolist { + padding-left: 0; +} + +.todolist li { + list-style: none; + padding: var(--pico-form-element-spacing-vertical) 0; + display: flex; + justify-content: space-between; + align-items: center; +} +#todos_filter { + display:flex; + justify-content : center; +} + +#todos_filter a { + margin:1em; +} + +.todolist i { + cursor: pointer; +} + +.todolist li:not(:last-child) { + border-bottom: 1.5px solid var(--pico-form-element-border-color); +} + +.todolist > li > label:has(input:checked) { + text-decoration: line-through; +} + +footer { + text-align: center; +} + diff --git a/R4.01_R4.A.10/td_tp/tp2mvc/src/index.html b/R4.01_R4.A.10/td_tp/tp2mvc/src/index.html new file mode 100644 index 0000000..a652640 --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2mvc/src/index.html @@ -0,0 +1,53 @@ + + + + + + + + Todo + + + + + + + +
    +

    To Do List

    +
    + +
    +
    +
    +
    +
    + + +
    +
    + + + + + + + 0/50 characters +
    +
    +
    + +
      +
      +
      + + + + + + + + diff --git a/R4.01_R4.A.10/td_tp/tp2mvc/src/js/controller.js b/R4.01_R4.A.10/td_tp/tp2mvc/src/js/controller.js new file mode 100644 index 0000000..c837fa1 --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2mvc/src/js/controller.js @@ -0,0 +1,64 @@ +class Controller { + constructor(model, view) { + + this.model = model; + this.view = view; + this.filter = "all"; + + /** Abonnements à la vue **/ + + 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)); + + /** filtrages par url **/ + + 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) { + 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() + } +} + +const model = new Model() +const view = new View() +const app = new Controller(model, view) + diff --git a/R4.01_R4.A.10/td_tp/tp2mvc/src/js/model.js b/R4.01_R4.A.10/td_tp/tp2mvc/src/js/model.js new file mode 100644 index 0000000..cde92cc --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2mvc/src/js/model.js @@ -0,0 +1,48 @@ +class Model { + constructor() { + this.todos = JSON.parse(localStorage.getItem('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) + } + + edit(id, updatedText) { + this.todos = this.todos.map(todo => + todo.id === id ? { id: todo.id, text: updatedText, done: todo.done} : todo + ) + this.#commit(this.todos) + } + + delete(id) { + this.todos = this.todos.filter(todo => todo.id !== id) + this.#commit(this.todos) + } + + toggle(id) { + 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/tp2mvc/src/js/view.js b/R4.01_R4.A.10/td_tp/tp2mvc/src/js/view.js new file mode 100644 index 0000000..fe85933 --- /dev/null +++ b/R4.01_R4.A.10/td_tp/tp2mvc/src/js/view.js @@ -0,0 +1,83 @@ +class View { + charLimit = 70; + + constructor() { + this.form = document.querySelector("#add_form") + this.input = document.querySelector("#input_todo") + this.list = document.querySelector("#todos_list") + this.tabs = document.querySelectorAll("#add_form span a") + this.loader = document.querySelector("#loader") + this.count = document.querySelector("#count") + + + this.input.addEventListener("input", (e) => { + if (e.target.value.length >= this.charLimit) { + e.target.value = e.target.value.substring(0,this.charLimit); + } + count.textContent = e.target.value.length; + }); + count.nextSibling.textContent = `/${this.charLimit} characters` + } + + #getTodo() { + return this.input.value + } + + #resetInput() { + this.input.value = '' + count.textContent = 0 + } + + #createNewTodoElement(todo){ + let li = document.createElement("li") + + // TODO + + return li + } + + + setFilterTabs(filter){ + this.tabs.forEach( tab => { + if (filter === tab.id) + tab.classList.add("is-active") + else + tab.classList.remove("is-active") + }) + } + + renderTodoList(todos) { + let list = new DocumentFragment() + for (let todo of todos){ + list.appendChild(this.#createNewTodoElement(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 + } +}