with task
+ const divElementId = buttonId.join("-");
+ document.querySelector(`#${divElementId}`).remove();
+
+ // remove task from list
+ const taskText = buttonId.join(" ");
+ const elementIndex = listOfTasks.findIndex((task) => {
+ return task === taskText;
+ });
+ listOfTasks.splice(elementIndex, 1);
+
+ //show weolcome if list is empty
+ if (listOfTasks.length === 0) {
+ showWelcomePart();
+ }
+}
+
+function clearAll() {
+ listOfTasks.forEach((task) => {
+ const taskId = task.split(" ").join("-");
+ document.querySelector(`#${taskId}`).remove();
+ });
+ listOfTasks.splice(0);
+
+ showWelcomePart();
+}
+
+function sortByName() {
+ listOfTasks.forEach((task) => {
+ const taskId = task.split(" ").join("-");
+ document.querySelector(`#${taskId}`).remove();
+ });
+
+ listOfTasks.sort();
+ console.log(listOfTasks);
+
+ listOfTasks.forEach((task) => {
+ createTask(task);
+ });
+}
+
+function capitalize(string) {
+ const updatedString = string.charAt(0).toUpperCase() + string.slice(1);
+ return updatedString;
+}
diff --git a/src/ex1/style.css b/src/ex1/style.css
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/ex1/style_ex1.css b/src/ex1/style_ex1.css
new file mode 100644
index 000000000..ad6a50fc5
--- /dev/null
+++ b/src/ex1/style_ex1.css
@@ -0,0 +1,173 @@
+body {
+ font-family: "Dosis", sans-serif;
+ background-color: rgb(153, 203, 205);
+}
+
+.white-box {
+ position: relative;
+ margin: 0 auto;
+ width: 400px;
+ height: 600px;
+ background-color: white;
+ border-radius: 1%;
+}
+
+.main-header {
+ margin-left: 25px;
+ padding-top: 15px;
+}
+
+.task-input-wrapper {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.task-input {
+ margin-left: 25px;
+ padding: 10px;
+ width: 60%;
+ border: 1px solid rgb(197, 196, 196);
+ border-radius: 3px;
+ outline: none;
+}
+
+.task-button {
+ margin-right: 25px;
+ font-size: medium;
+ padding: 9px 13px;
+ width: 20%;
+ background-color: rgb(153, 90, 212);
+ color: white;
+ border: none;
+ border-radius: 3px;
+}
+
+.task-button:hover {
+ cursor: pointer;
+ background-color: cadetblue;
+}
+
+.task-list {
+ display: flex;
+ flex-direction: column;
+ list-style: none;
+ padding: 0;
+}
+
+.task {
+ display: flex;
+}
+
+.task-item {
+ margin: 5px;
+ margin-left: 25px;
+ margin-right: 0;
+ padding: 10px;
+ width: 85%;
+ border-radius: 3px;
+ background-color: rgb(240, 239, 239);
+}
+
+.task-item:hover {
+ cursor: pointer;
+ background-color: cadetblue;
+}
+
+.delete-button {
+ margin: 5px;
+ margin-right: 25px;
+ margin-left: 0;
+ font-size: medium;
+ padding: 5px 13px;
+ background-color: rgb(216, 87, 113);
+ color: white;
+ border: none;
+ border-radius: 3px;
+}
+
+.delete-button:hover {
+ cursor: pointer;
+ background-color: rgb(175, 34, 62);
+}
+
+.triangle {
+ margin: 30px auto 0 auto;
+ width: 0;
+ height: 0;
+ border-left: 100px solid transparent;
+ border-right: 100px solid transparent;
+ border-bottom: 100px solid rgb(205, 223, 223);
+ border-radius: 5px;
+}
+
+.box {
+ margin: 0 auto;
+ width: 100px;
+ height: 200px;
+ background-color: rgb(205, 223, 223);
+ border-radius: 0 0 5px 5px;
+}
+
+.welcome {
+ margin: 40px auto;
+ color: rgb(153, 90, 212);
+}
+
+.hithere {
+ animation: hithere 2s ease infinite;
+ animation-delay: 3s;
+}
+@keyframes hithere {
+ 30% {
+ transform: scale(1.2);
+ }
+ 40%,
+ 60% {
+ transform: rotate(-20deg) scale(1.2);
+ }
+ 50% {
+ transform: rotate(20deg) scale(1.2);
+ }
+ 70% {
+ transform: rotate(0deg) scale(1.2);
+ }
+ 100% {
+ transform: scale(1);
+ }
+}
+
+.grow {
+ animation: grow 2s ease;
+}
+@keyframes grow {
+ from {
+ transform: scale(0);
+ }
+ to {
+ transform: scale(1);
+ }
+}
+
+.hidden {
+ display: none;
+}
+
+#clear-all-button {
+ position: absolute;
+ width: 25%;
+ left: 275px;
+ top: 540px;
+}
+
+#sort-by-name-button {
+ position: absolute;
+ width: 35%;
+ left: 25px;
+ top: 540px;
+ background-color: cadetblue;
+}
+
+#sort-by-name-button:hover {
+ background-color: rgb(153, 90, 212);
+}
diff --git a/src/ex2/ItemManager.js b/src/ex2/ItemManager.js
new file mode 100644
index 000000000..2f4673b2e
--- /dev/null
+++ b/src/ex2/ItemManager.js
@@ -0,0 +1,179 @@
+import { pokemonClient } from "./PokemonClient.js";
+
+class ItemManager {
+ constructor() {
+ this.itemList = [];
+ this.pokemons = new Set();
+
+ this.addItem = this.addItem.bind(this);
+ this.sortByName = this.sortByName.bind(this);
+ this.removeAll = this.removeAll.bind(this);
+ }
+
+ async addItem(input) {
+ const item = input.value;
+ const inputArray = item.split(",");
+
+ if (!isNaN(+inputArray[0])) {
+ // if first item input is a number - add pokemon
+ await this.addPokemon(inputArray);
+ } else {
+ // if input is a string - add todo
+ this.addToDo(item);
+ }
+
+ //clear input
+ this.clearInputField();
+
+ // render the list
+ this.renderItems(this.itemList.at(-1));
+ }
+
+ addToDo(item) {
+ this.itemList.push(this.capitalize(item));
+ }
+
+ async addPokemon(inputArray) {
+ // add promises to allPromises array
+ const allPromises = this.createPromises(inputArray);
+
+ // fetch all pokemons simulteniously
+ const pokemonData = await Promise.all(allPromises);
+
+ //filter out new pokemons
+ const newPokemons = pokemonData.filter((pokemon) => {
+ if (this.pokemons.has(`${pokemon.name}`))
+ alert(`Pokemon with ID ${pokemon.id} was alrady added!`); //alert existing pokemons
+ return !this.pokemons.has(`${pokemon.name}`);
+ });
+
+ // add new pokemons to list of pokemons
+ const pokemonNames = newPokemons.map((pokemon) => {
+ if (pokemon.name) {
+ this.pokemons.add(`${pokemon.name}`);
+ return pokemon.name;
+ } else {
+ return pokemon;
+ }
+ });
+
+ // push pokemons to todo list
+ pokemonNames.forEach((name) => {
+ const id = name.split(" ")[0];
+
+ if (!isNaN(+id)) {
+ //if first element is a number - it's id of a not found pokemon
+ this.itemList.push(`Pokemon with ID ${id} was not found`);
+ } else {
+ //if first element is not a number - it's name of a found pokemon
+ this.itemList.push(`Catch ${name}`);
+ }
+ });
+ }
+
+ createPromises(inputArray) {
+ let allPromises = [];
+
+ inputArray.forEach((elm) => {
+ const id = elm.trim();
+
+ allPromises.push(pokemonClient.fetchPokemon(id));
+ });
+
+ return allPromises;
+ }
+
+ removeItem(e, liElm) {
+ e.stopPropagation();
+
+ const itemId = liElm.id.split("-");
+ const pokemonName = itemId[1];
+ const item = itemId.join(" ");
+
+ const itemIndex = this.itemList.findIndex((listItem) => {
+ return listItem === item;
+ });
+
+ this.itemList.splice(itemIndex, 1);
+ this.pokemons.delete(pokemonName);
+
+ this.renderItems();
+ }
+
+ removeAll() {
+ this.itemList.length = 0;
+ this.pokemons.clear();
+
+ this.renderItems();
+ }
+
+ renderItems(current) {
+ this.toggleFooter();
+
+ // clear list innerHTML
+ const list = document.querySelector("#list");
+ list.innerHTML = "";
+
+ // create elements for exisitng items
+ this.itemList.forEach((item) => {
+ const itemNode = this.createItemElement(item, current);
+
+ list.appendChild(itemNode);
+ });
+ }
+
+ toggleFooter() {
+ const addItemButton = document.querySelector("#list-item-submit");
+ const clearAllButton = document.querySelector("#clear-all-button");
+ const sortByNameButton = document.querySelector("#sort-by-name-button");
+
+ if (this.itemList.length === 0) {
+ addItemButton.classList.add("hithere"); //add AddButton animation
+ clearAllButton.classList.add("hidden"); //hide ClearAll button
+ sortByNameButton.classList.add("hidden"); //hide Sort button
+ } else {
+ addItemButton.classList.remove("hithere"); //remove AddButton animation
+ clearAllButton.classList.remove("hidden"); //show ClearAll button
+ sortByNameButton.classList.remove("hidden"); //show Sort button
+ }
+ }
+
+ createItemElement(input, current) {
+ const itemId = input.split(" ").join("-");
+
+ //create list element, add class, innerHTML, eventListener
+ const liElm = document.createElement("li");
+ liElm.setAttribute("id", `${itemId}`);
+ liElm.classList.add("list-item");
+ if (input === current) liElm.classList.add("grow");
+ liElm.innerHTML = input;
+ liElm.addEventListener("click", () => alert(`Task: ${input}`));
+
+ //create delete button, add id, class, src and clickListener
+ const deleteButton = document.createElement("img");
+ deleteButton.setAttribute("id", `${itemId}-delete`);
+ deleteButton.classList.add("list-item-delete-button");
+ deleteButton.src = "../images/delete_icon.svg";
+ deleteButton.addEventListener("click", (e) => this.removeItem(e, liElm));
+ liElm.appendChild(deleteButton);
+
+ return liElm;
+ }
+
+ clearInputField() {
+ document.querySelector("#list-item-input").value = "";
+ }
+
+ sortByName() {
+ this.itemList.sort();
+
+ this.renderItems();
+ }
+
+ capitalize(string) {
+ const updatedString = string.charAt(0).toUpperCase() + string.slice(1);
+ return updatedString;
+ }
+}
+
+export const itemManager = new ItemManager();
diff --git a/src/ex2/PokemonClient.js b/src/ex2/PokemonClient.js
new file mode 100644
index 000000000..22a1ab27a
--- /dev/null
+++ b/src/ex2/PokemonClient.js
@@ -0,0 +1,20 @@
+class PokemonClient {
+ constructor() {}
+
+ async fetchPokemon(pokemonId) {
+ try {
+ const response = await fetch(
+ `https://pokeapi.co/api/v2/pokemon/${pokemonId}`
+ );
+
+ const data = await response.json();
+
+ return data;
+ } catch (error) {
+ console.log(error);
+ return `${pokemonId} Not found`;
+ }
+ }
+}
+
+export const pokemonClient = new PokemonClient();
diff --git a/src/ex2/README.md b/src/ex2/README.md
new file mode 100644
index 000000000..53e556b86
--- /dev/null
+++ b/src/ex2/README.md
@@ -0,0 +1,46 @@
+# Exercise 2 - In depth JS, Async, & MVC
+
+Time for task #2!
+Here we'll get your code to look a little more professional + modern, _and_ you'll get to access data from an external API which opens up a lot of doors for you.
+
+## In this section you will practice
+
+**JS** - More in depth, using classes, methods, iterators
+**Async JS** - Working with async code to retrieve data from a public API
+**MVC** - Basic design pattern for separating concerns
+
+## What you are going to build
+
+We already have a todo app where you can add your own tasks but... what about adding _pokemon_ related tasks?
+Yes, you'll be reaching out to the pokemon API (https://pokeapi.co/) in order to retrieve pokemon information to populate your todos.
+
+But this kind of work requires a bit more code organization, so...
+
+### The requirements:
+
+- [ ] Refactor your current code to use classes with methods (you can copy+paste the code to a new file and refactor there - just make sure to update the `scripts` tag in your `index.html`)
+- [ ] Create an ItemManager class (in a new file) to manage the item adding/removing + pokemon fetching - this class does _not_ deal with the DOM
+- [ ] Store todos in an array (class attribute) - this should be in the ItemManager class
+- [ ] Render todos from the array using a separate render method
+- [ ] Remove todos by updating the list and re-rendering
+- [ ] Create a PokemonClient class (in a new file) to get data from the Pokemon API - remember the HTML has to be aware of this file...
+- [ ] If the user only inputs a number, add a `Catch ${pokemon}` todo to your array of todos (and render it, of course)
+- [ ] If the user inputs a comma separated list of IDs, retrieve multiple pokemon in parallel using `Promise.all` and render them all
+- [ ] Handle any errors in retrieving the pokemon (i.e. when a user inputs an invalid ID like 44124. See below gif for an example)
+- [ ] Add a normal todo item if the input is not a pokemon
+
+When you finish it should look like this:
+
+
+
+### Bonus
+
+- [ ] Add a delete all option - make sure you're actually deleting the data, not just removing from the DOM
+- [ ] Validate that the user isn't adding the same pokemon todo more than once
+- [ ] Get more nested data from the pokemon API and display it as part of the todo item (e.g. “catch bulbasaur the grass/leaf type pokemon”)
+ - you'll have to explore the API to understand where to extract that data from =]
+- [ ] Modify the API request to use a pokemon’s name instead of its ID if you find a pokemon name (from a closed list of values) in the user’s input. For example, if the user inputs "charmender", you should get the data about charmender from the API by this pokemon's name - you'll have to read the docs for this too to see how that works ;)
+- [ ] In the solution file you'll see this piece of code: `pokemons.forEach(this.addPokemonItem);`
+ - The `addPokemonItem` method adds the pokemon to the array of todos and renders the todos list again
+ - Can you figure out why this line of code is inefficient? Can you improve it?
+- [ ] Have another cool idea? Go wild!
diff --git a/src/ex2/index.html b/src/ex2/index.html
new file mode 100644
index 000000000..2d2950cc9
--- /dev/null
+++ b/src/ex2/index.html
@@ -0,0 +1,38 @@
+
+
+
+
Listy
+
+
+
+
+
+
+
+
+
Task Manager
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ex2/main.js b/src/ex2/main.js
new file mode 100644
index 000000000..ac0ca957f
--- /dev/null
+++ b/src/ex2/main.js
@@ -0,0 +1,34 @@
+import { itemManager } from "./ItemManager.js";
+
+// Implement the `Main` class here
+class Main {
+ constructor() {}
+
+ addButton = document.querySelector("#list-item-submit");
+ addItemInput = document.querySelector("#list-item-input");
+ sortByNameButton = document.querySelector("#sort-by-name-button");
+ clearAllButton = document.querySelector("#clear-all-button");
+
+ init() {
+ this.addButton.addEventListener("click", () => {
+ itemManager.addItem(this.addItemInput);
+ });
+
+ this.sortByNameButton.addEventListener("click", itemManager.sortByName);
+ this.clearAllButton.addEventListener("click", itemManager.removeAll);
+
+ this.addItemInput.addEventListener("keypress", (event) => {
+ if (event.key === "Enter") {
+ itemManager.addItem(this.addItemInput);
+ }
+ });
+ }
+}
+
+const main = new Main();
+
+document.addEventListener("DOMContentLoaded", function () {
+ // you should create an `init` method in your class
+ // the method should add the event listener to your "add" button
+ main.init();
+});
diff --git a/src/ex2/style.css b/src/ex2/style.css
new file mode 100644
index 000000000..81af2a4e7
--- /dev/null
+++ b/src/ex2/style.css
@@ -0,0 +1,201 @@
+html {
+ height: 100%;
+}
+body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+}
+
+.app-container {
+ width: 100%;
+ height: 100%;
+ font-family: "Dosis", sans-serif;
+ font-weight: lighter;
+ background-image: linear-gradient(
+ to top right,
+ white,
+ rgba(213, 245, 246, 0.76),
+ rgba(138, 210, 212, 0.76),
+ rgba(48, 155, 159, 0.76),
+ rgba(17, 150, 154, 0.76)
+ );
+
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.list-container {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+ width: 30%;
+ height: 50%;
+ background-color: white;
+ border: 1px silver solid;
+ border-radius: 8px;
+ -webkit-box-shadow: 12px 12px 38px -13px #000000;
+ box-shadow: 12px 12px 38px -13px #000000;
+}
+
+.app-name {
+ padding-left: 32px;
+ font-weight: 500;
+}
+
+.list-controls {
+ padding: 0px 32px;
+ background-color: white;
+ display: flex;
+ align-items: center;
+ border-top-right-radius: 8px;
+ border-top-left-radius: 8px;
+}
+
+.list-controls #list-item-input {
+ width: 85%;
+ height: 30px;
+ font-size: 16px;
+ padding-left: 8px;
+ border: 1px solid rgb(197, 196, 196);
+ border-radius: 3px;
+ outline: none;
+}
+
+.list-controls #list-item-submit {
+ color: white;
+ background-color: #8181d6;
+ border-style: solid;
+ border-color: transparent;
+ border-radius: 4px;
+ height: 35px;
+ width: 35px;
+ font-size: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ margin-left: auto;
+}
+
+.list-controls #list-item-submit:hover {
+ background-color: rgba(17, 150, 154, 0.76);
+ transition: background-color 0.2s ease-in-out;
+}
+
+#list {
+ list-style-type: none;
+ margin: 0;
+ padding: 0px 30px;
+ height: 55%;
+ overflow-y: scroll;
+}
+
+.list-item {
+ padding: 8px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ border-radius: 3px;
+ font-weight: 400;
+}
+
+.list-item:not(:last-of-type) {
+ border-bottom: 1px #bbb5b5 solid;
+}
+
+.list-item:hover {
+ background-color: #a7a7d9;
+ color: white;
+ transition: color 0.2s ease-in-out;
+ transition: background-color 0.2s ease-in-out;
+}
+
+.list-item-delete-button {
+ margin-left: auto;
+ padding: 8px;
+ cursor: pointer;
+ border-radius: 8px;
+}
+
+.list-item-delete-button:hover {
+ background-color: #f0f0f5;
+ transition: background-color 0.2s ease-in-out;
+}
+
+.hidden {
+ display: none;
+}
+
+.list-footer {
+ display: flex;
+ justify-content: space-evenly;
+ margin: 10px 0;
+}
+
+.footer-button {
+ font-size: medium;
+ padding: 9px 13px;
+ border: none;
+ border-radius: 3px;
+ width: 25%;
+ color: rgb(57, 53, 53);
+ cursor: pointer;
+}
+
+#sort-by-name-button {
+ background-color: #a7a7d9;
+}
+
+#sort-by-name-button:hover {
+ background-color: #8181d6;
+ color: white;
+ transition: background-color 0.2s ease-in-out;
+}
+
+#clear-all-button {
+ background-color: rgba(77, 170, 173, 0.76);
+}
+
+#clear-all-button:hover {
+ background-color: rgba(17, 150, 154, 0.76);
+ color: white;
+ transition: background-color 0.2s ease-in-out;
+}
+
+.hithere {
+ animation: hithere 2s ease infinite;
+ animation-delay: 3s;
+}
+@keyframes hithere {
+ 30% {
+ transform: scale(1.2);
+ }
+ 40%,
+ 60% {
+ transform: rotate(-20deg) scale(1.2);
+ }
+ 50% {
+ transform: rotate(20deg) scale(1.2);
+ }
+ 70% {
+ transform: rotate(0deg) scale(1.2);
+ }
+ 100% {
+ transform: scale(1);
+ }
+}
+
+.grow {
+ animation: grow 2s ease;
+}
+@keyframes grow {
+ from {
+ transform: scale(0);
+ }
+ to {
+ transform: scale(1);
+ }
+}
diff --git a/src/images/delete_icon.svg b/src/images/delete_icon.svg
new file mode 100644
index 000000000..5122cab73
--- /dev/null
+++ b/src/images/delete_icon.svg
@@ -0,0 +1,3 @@
+