├── 14-image-loader ├── styles.css ├── large-image.jpg ├── index.html └── script.js ├── .vscode └── settings.json ├── 02-modal ├── background.jpg ├── script.js ├── index.html └── styles.css ├── 13-car-race ├── car-1.png ├── car-2.png ├── styles.css ├── index.html └── script.js ├── 08-testimonials ├── author-01.jpg ├── author-02.jpg ├── author-03.jpg ├── author-04.jpg ├── index.html ├── styles.css └── script.js ├── 09-countdown-timer ├── timer-bg.jpg ├── styles.css ├── index.html └── script.js ├── README.md ├── 11-sortable-table ├── styles.css ├── index.html └── script.js ├── 01-welcome ├── script.js ├── index.html └── style.css ├── 10-star-rating ├── index.html ├── styles.css └── script.js ├── 07-faqs ├── index.html ├── styles.css └── script.js ├── 04-counter ├── styles.css ├── index.html └── script.js ├── 05-sticky-navbar ├── styles.css ├── script.js └── index.html ├── 17-user-directory ├── styles.css ├── index.html └── script.js ├── 03-colour-flipper ├── index.html ├── styles.css └── script.js ├── 15-lorem-ipsum ├── index.html ├── styles.css └── script.js ├── 12-shopping-list ├── index.html ├── styles.css └── script.js ├── 06-tabs ├── styles.css ├── script.js └── index.html └── 16-autocomplete ├── index.html ├── style.css └── script.js /14-image-loader/styles.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5505 3 | } -------------------------------------------------------------------------------- /02-modal/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebubb/17-javascript-projects/HEAD/02-modal/background.jpg -------------------------------------------------------------------------------- /13-car-race/car-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebubb/17-javascript-projects/HEAD/13-car-race/car-1.png -------------------------------------------------------------------------------- /13-car-race/car-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebubb/17-javascript-projects/HEAD/13-car-race/car-2.png -------------------------------------------------------------------------------- /08-testimonials/author-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebubb/17-javascript-projects/HEAD/08-testimonials/author-01.jpg -------------------------------------------------------------------------------- /08-testimonials/author-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebubb/17-javascript-projects/HEAD/08-testimonials/author-02.jpg -------------------------------------------------------------------------------- /08-testimonials/author-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebubb/17-javascript-projects/HEAD/08-testimonials/author-03.jpg -------------------------------------------------------------------------------- /08-testimonials/author-04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebubb/17-javascript-projects/HEAD/08-testimonials/author-04.jpg -------------------------------------------------------------------------------- /09-countdown-timer/timer-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebubb/17-javascript-projects/HEAD/09-countdown-timer/timer-bg.jpg -------------------------------------------------------------------------------- /14-image-loader/large-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebubb/17-javascript-projects/HEAD/14-image-loader/large-image.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 17 JavaScript Projects 2 | 3 | Source code for projects built on the [@codewithbubb](https://youtube.com/@codewithbubb) YouTube Channel found here -> https://youtu.be/AGeRXBW9vsg 4 | -------------------------------------------------------------------------------- /11-sortable-table/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | } 4 | 5 | table, th { 6 | text-align: left; 7 | border: 1px solid black; 8 | } 9 | 10 | th > span { 11 | margin: .25rem; 12 | cursor: pointer; 13 | } -------------------------------------------------------------------------------- /13-car-race/styles.css: -------------------------------------------------------------------------------- 1 | #race { 2 | display: flex; 3 | align-items: center; 4 | font-family: sans-serif; 5 | } 6 | 7 | #race div { 8 | text-align: center; 9 | } 10 | #race img { 11 | width: 200px; 12 | display: block; 13 | } -------------------------------------------------------------------------------- /01-welcome/script.js: -------------------------------------------------------------------------------- 1 | const btnElement = document.querySelector('button'); 2 | const spanElement = document.querySelector('h1 > span'); 3 | 4 | btnElement.onclick = function() { 5 | const yourName = prompt('Please enter your name: '); 6 | spanElement.textContent = yourName; 7 | } 8 | -------------------------------------------------------------------------------- /13-car-race/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 - Car Race 7 | 8 | 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /10-star-rating/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 - Star Rating 7 | 8 | 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /14-image-loader/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 - Image Loader 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | -------------------------------------------------------------------------------- /11-sortable-table/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 - Sortable Table 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /07-faqs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 07 - FAQs 8 | 9 | 10 | 11 | 12 | 13 |

FAQs

14 |
15 | 16 | -------------------------------------------------------------------------------- /01-welcome/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 01 - Welcome 7 | 8 | 9 | 10 | 11 |

Hello World!

12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /10-star-rating/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | } 4 | 5 | #ratings { 6 | display: flex; 7 | flex-direction: column; 8 | gap: 1rem; 9 | border: 2px solid black; 10 | padding: 1rem; 11 | width: 100px; 12 | } 13 | 14 | .empty::before { 15 | content: '☆'; 16 | cursor: pointer; 17 | } 18 | 19 | .filled::before { 20 | content: '★'; 21 | cursor: pointer; 22 | } 23 | 24 | /* ☆★ */ 25 | -------------------------------------------------------------------------------- /02-modal/script.js: -------------------------------------------------------------------------------- 1 | const openModalBtnElement = document.querySelector('#openModal'); 2 | const modalElement = document.querySelector('.modal'); 3 | const modalContentElement = modalElement.querySelector('.modal__content') 4 | 5 | openModalBtnElement.addEventListener('click', () => { 6 | modalElement.classList.add('open'); 7 | }); 8 | 9 | modalContentElement.addEventListener('click', () => { 10 | modalElement.classList.remove('open'); 11 | }); -------------------------------------------------------------------------------- /08-testimonials/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 08 - Testimonials 8 | 9 | 10 | 11 | 12 | 13 |

Testimonials

14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /01-welcome/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 4rem; 3 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; 4 | } 5 | 6 | h1>span { 7 | color: red; 8 | } 9 | 10 | button { 11 | background-color: teal; 12 | color: whitesmoke; 13 | padding: .5rem 1rem; 14 | border: 0; 15 | box-shadow: 0 0 12px 8px rgba(0, 0, 0, 0.3); 16 | cursor: pointer; 17 | } 18 | 19 | button:hover { 20 | background-color:darkslategrey; 21 | } -------------------------------------------------------------------------------- /07-faqs/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family:'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; 3 | } 4 | 5 | details { 6 | cursor: pointer; 7 | padding: 1rem; 8 | margin: .5rem 0; 9 | background-color: rgba(119, 25, 25, 0.2);; 10 | font-weight: 400; 11 | border: 2px solid red; 12 | box-shadow: 0 0 16px 8px rgba(0, 5, 5, 0.2); 13 | } 14 | 15 | details p { 16 | font-size: .75rem; 17 | } 18 | 19 | summary { 20 | font-weight: 700; 21 | } -------------------------------------------------------------------------------- /04-counter/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | } 4 | 5 | #counter { 6 | display: flex; 7 | width: 100%; 8 | justify-content: center; 9 | } 10 | 11 | #counterDisplay { 12 | font-size: 4rem; 13 | text-align: center; 14 | color: white; 15 | } 16 | 17 | #counter button { 18 | border: 0; 19 | padding: 1rem; 20 | font-size: 2rem; 21 | cursor: pointer; 22 | } 23 | 24 | #counterAdd { 25 | background-color: chartreuse; 26 | } 27 | 28 | #counterSub { 29 | background-color:brown; 30 | } -------------------------------------------------------------------------------- /05-sticky-navbar/styles.css: -------------------------------------------------------------------------------- 1 | main { 2 | display: flex; 3 | width: 400px; 4 | margin: 0 auto; 5 | } 6 | 7 | nav { 8 | position: relative; 9 | } 10 | 11 | nav ul { 12 | width: 6rem; 13 | list-style-type: none; 14 | } 15 | 16 | nav ul li { 17 | padding: .5rem; 18 | } 19 | 20 | nav ul a { 21 | padding: .5rem; 22 | text-decoration: none; 23 | } 24 | 25 | /* nav.sticky { 26 | position: sticky; 27 | top: 0; 28 | } */ 29 | 30 | nav a.active { 31 | background-color: lightseagreen; 32 | color: white; 33 | } -------------------------------------------------------------------------------- /17-user-directory/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | } 4 | 5 | #userDirectory { 6 | width: 800px; 7 | margin: 1rem .5rem; 8 | padding: 1rem; 9 | background-color: rgb(60, 60, 200); 10 | border-radius: 1rem; 11 | color: white; 12 | box-shadow: 0 0 24px 12px rgba(0, 0, 0, 0.3); 13 | } 14 | 15 | #userTable { 16 | margin: 1rem 0; 17 | } 18 | 19 | #userTable img { 20 | width: 40px; 21 | } 22 | 23 | #userTable th { 24 | text-align: left; 25 | } 26 | 27 | #filterUsers { 28 | padding: .5rem 1rem; 29 | } -------------------------------------------------------------------------------- /03-colour-flipper/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 03 - Colour Flipper 7 | 8 | 9 | 10 | 11 |
12 |

13 | Background: #fffff 14 |

15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /17-user-directory/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 - User Directory 7 | 8 | 9 | 10 | 11 |
12 |

User Directory

13 | 14 | 15 | 16 |
17 |
18 | 19 | -------------------------------------------------------------------------------- /15-lorem-ipsum/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 - Lorem Ipsum 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 | -------------------------------------------------------------------------------- /12-shopping-list/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 - Shopping List 7 | 8 | 9 | 10 | 11 |
12 |

Shopping List

13 | 14 |
15 | 16 | 17 |
18 |
19 | 20 | -------------------------------------------------------------------------------- /02-modal/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 02 - Modal 8 | 9 | 10 | 11 | 12 | 13 | 14 | 22 | 23 | -------------------------------------------------------------------------------- /04-counter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 04 - Counter 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |

0

16 |
17 | 18 | 19 |
20 |
21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /15-lorem-ipsum/styles.css: -------------------------------------------------------------------------------- 1 | #loremIpsumGenerator { 2 | font-family: sans-serif; 3 | margin: 1rem; 4 | } 5 | 6 | #paragraphsCount { 7 | width: 4rem; 8 | } 9 | 10 | #result { 11 | margin-top: 1rem; 12 | padding: 1rem; 13 | border: 1px dashed gray; 14 | font-size: .75rem; 15 | position: relative; 16 | display: none;; 17 | } 18 | 19 | #result.show { 20 | display: block; 21 | } 22 | 23 | .copy { 24 | position: absolute; 25 | right: .25rem; 26 | top: .25rem; 27 | border: 0; 28 | text-transform: uppercase; 29 | font-size: .5rem; 30 | padding: .25rem .5rem; 31 | cursor: pointer; 32 | } 33 | 34 | .copy:hover { 35 | background-color: blanchedalmond; 36 | } 37 | -------------------------------------------------------------------------------- /06-tabs/styles.css: -------------------------------------------------------------------------------- 1 | main { 2 | width: 400px; 3 | margin: 0 auto; 4 | } 5 | 6 | nav { 7 | width: 100%; 8 | } 9 | 10 | nav ul { 11 | list-style-type: none; 12 | display: flex; 13 | padding: 0; 14 | margin: 0; 15 | justify-content: flex-start; 16 | border-bottom: 1px solid gray; 17 | } 18 | 19 | nav ul li { 20 | padding: .5rem; 21 | border-top: 1px solid gray; 22 | border-left: 1px solid gray; 23 | border-right: 1px solid gray; 24 | border-top-left-radius: .5rem; 25 | border-top-right-radius: .5rem; 26 | } 27 | 28 | nav ul a { 29 | text-decoration: none; 30 | } 31 | 32 | nav li.active { 33 | background-color: lightseagreen; 34 | color: white; 35 | } 36 | 37 | .hidden { 38 | display: none; 39 | } -------------------------------------------------------------------------------- /05-sticky-navbar/script.js: -------------------------------------------------------------------------------- 1 | const navElement = document.querySelector('nav'); 2 | const navPosition = navElement.getBoundingClientRect().top; 3 | const navbarLinks = navElement.querySelectorAll('a'); 4 | 5 | window.addEventListener('scroll', () => { 6 | const scrollPos = window.scrollY; 7 | navElement.style.top = scrollPos + 'px'; 8 | 9 | navbarLinks.forEach(link => { 10 | const offset = 10; 11 | const sectionElement = document.querySelector(link.hash); 12 | if (scrollPos + offset > sectionElement.offsetTop && scrollPos + offset < sectionElement.offsetTop + sectionElement.offsetHeight) { 13 | link.classList.add("active"); 14 | } else { 15 | link.classList.remove("active"); 16 | } 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /06-tabs/script.js: -------------------------------------------------------------------------------- 1 | const navElement = document.querySelector('nav'); 2 | const navbarLinks = navElement.querySelectorAll('a'); 3 | const sectionElements = document.querySelectorAll('section'); 4 | 5 | navbarLinks.forEach(link => { 6 | link.addEventListener('click', () => { 7 | removeActiveLinks(); 8 | hideSections(); 9 | link.parentElement.classList.add('active'); 10 | const sectionElement = document.querySelector(link.hash); 11 | sectionElement.classList.remove('hidden'); 12 | }); 13 | }); 14 | 15 | const removeActiveLinks = () => { 16 | navbarLinks.forEach(link => link.parentElement.classList.remove('active')); 17 | }; 18 | const hideSections = () => { 19 | sectionElements.forEach(section => section.classList.add('hidden')); 20 | }; -------------------------------------------------------------------------------- /16-autocomplete/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 - Autocomplete 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
ProductPriceCategory
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /03-colour-flipper/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | transition: background-color 0.5s; 3 | } 4 | 5 | section#colour-display { 6 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; 7 | margin: 100px auto; 8 | width: 50%; 9 | background-color: white; 10 | box-shadow: 0 0 30px rgba(0,0,0, 0.5); 11 | padding: 4rem; 12 | border-radius: .5rem; 13 | } 14 | 15 | section#colour-display h1 { 16 | font-weight: 900; 17 | } 18 | 19 | section#colour-display #new-colour-button { 20 | width: 100%; 21 | background-color: black; 22 | color: white; 23 | font-weight: 900; 24 | padding: 1rem; 25 | border: none; 26 | font-size: 1rem; 27 | cursor: pointer; 28 | } 29 | 30 | section#colour-display #new-colour-button:hover { 31 | background-color: darkgray; 32 | } 33 | -------------------------------------------------------------------------------- /13-car-race/script.js: -------------------------------------------------------------------------------- 1 | const carStart = (carNumber, minTime = 3, maxTime = 5) => new Promise((resolve, reject) => { 2 | const randomTime = Math.floor(Math.random() * maxTime) * 1000; 3 | console.log(randomTime); 4 | setTimeout(() => resolve(carNumber), randomTime); 5 | }); 6 | 7 | const carsOrder = []; 8 | 9 | const updateDisplay = () => { 10 | const raceElement = document.getElementById('race'); 11 | raceElement.innerHTML = ''; 12 | carsOrder.forEach((id, position) => { 13 | raceElement.innerHTML += `
#${position + 1} Place
` 14 | }) 15 | } 16 | 17 | carStart(1) 18 | .then(result => { 19 | carsOrder.push(result) 20 | }) 21 | .then(updateDisplay) 22 | 23 | carStart(2) 24 | .then(result => { 25 | carsOrder.push(result) 26 | }) 27 | .then(updateDisplay) 28 | 29 | -------------------------------------------------------------------------------- /04-counter/script.js: -------------------------------------------------------------------------------- 1 | const addButtonElement = document.getElementById('counterAdd') 2 | const subButtonElement = document.getElementById('counterSub'); 3 | const counterDisplayElement = document.getElementById('counterDisplay'); 4 | 5 | let total = 0; 6 | const limit = 20; 7 | 8 | const updateCounterDisplay = function() { 9 | if (total > limit) { 10 | total = limit; 11 | } 12 | 13 | if (total < 0) { 14 | total = 0; 15 | } 16 | 17 | counterDisplayElement.textContent = total; 18 | document.body.style.setProperty('background-color', `rgb(${(total / limit) * 255}, 64, 0)`) 19 | }; 20 | 21 | addButtonElement.addEventListener('click', () => { 22 | total += 1; 23 | updateCounterDisplay(); 24 | }); 25 | 26 | subButtonElement.addEventListener('click', () => { 27 | total -=1; 28 | updateCounterDisplay(); 29 | }); 30 | 31 | updateCounterDisplay(); 32 | -------------------------------------------------------------------------------- /08-testimonials/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | } 4 | #testimonials-container { 5 | width: 400px; 6 | margin: 0 auto; 7 | } 8 | 9 | .testimonial-card { 10 | padding: 1rem; 11 | box-shadow: 0 0 16px 8px rgba(0,0,0, 0.1); 12 | width: 100%; 13 | border-radius: .5rem; 14 | line-height: 1.4; 15 | } 16 | 17 | .testimonial-card img { 18 | border-radius: 50%; 19 | margin: 0 auto; 20 | display: block; 21 | width: 8rem; 22 | } 23 | 24 | .testimonial-card h2 { 25 | text-align: center; 26 | text-transform: uppercase; 27 | } 28 | 29 | .testimonial-card date { 30 | text-align: right; 31 | display: block; 32 | font-style: italic; 33 | } 34 | 35 | nav { 36 | display: flex; 37 | justify-content: center; 38 | margin: 1rem 0; 39 | gap: 1rem; 40 | } 41 | 42 | nav button { 43 | cursor: pointer; 44 | width: 6rem; 45 | background-color:steelblue; 46 | color: white; 47 | border: 0; 48 | padding: .5rem 1rem; 49 | } 50 | 51 | nav button:hover { 52 | background-color:royalblue; 53 | } -------------------------------------------------------------------------------- /14-image-loader/script.js: -------------------------------------------------------------------------------- 1 | const imgLoad = url => { 2 | return new Promise((resolve, reject) => { 3 | const request = new XMLHttpRequest(); 4 | request.open('GET', url); 5 | request.responseType = 'blob'; 6 | request.onload = () => { 7 | if (request.status === 200) { 8 | resolve(request.response); 9 | } else { 10 | reject(Error(`Image didn't load successfully; error code: ${request.statusText}`)); 11 | } 12 | }; 13 | request.onerror = function () { 14 | reject(Error('There was a network error.')); 15 | }; 16 | request.send(); 17 | }); 18 | } 19 | const imageGoesHere = document.getElementById('imageGoesHere'); 20 | const myImage = new Image(); 21 | 22 | imgLoad('large-image.jp') 23 | .then(response => { 24 | const imageURL = window.URL.createObjectURL(response); 25 | myImage.src = imageURL; 26 | imageGoesHere.appendChild(myImage); 27 | alert('Image loaded!'); 28 | }) 29 | .catch(error => alert(error)); 30 | -------------------------------------------------------------------------------- /16-autocomplete/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; 3 | } 4 | 5 | #productSearch { 6 | margin: 2rem; 7 | background-color:silver; 8 | padding: 2rem; 9 | box-shadow: 0 0 16px 8px rgba(0, 0, 0, 0.1); 10 | } 11 | 12 | #searchText { 13 | padding: .5rem; 14 | } 15 | 16 | #autoCompleteResults { 17 | margin: 1rem 0; 18 | width: 100%; 19 | border-collapse: collapse; 20 | display: none; 21 | } 22 | 23 | #autoCompleteResults.show { 24 | display: block; 25 | } 26 | 27 | #autoCompleteResults img { 28 | width: 50px; 29 | height: 100%; 30 | } 31 | 32 | #autoCompleteResults th { 33 | text-align: left; 34 | } 35 | 36 | #autoCompleteResults td { 37 | font-size: .75rem; 38 | } 39 | #autoCompleteResults tbody tr { 40 | cursor: pointer; 41 | width: 100%; 42 | } 43 | 44 | #autoCompleteResults tbody tr:nth-child(even) { 45 | background-color: lightgrey; 46 | } 47 | 48 | #autoCompleteResults tbody tr:hover { 49 | background-color: lightblue; 50 | } 51 | -------------------------------------------------------------------------------- /09-countdown-timer/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif; 7 | margin: 2rem; 8 | background-image: url(timer-bg.jpg); 9 | background-size: cover; 10 | } 11 | 12 | h1 { 13 | color: whitesmoke; 14 | } 15 | 16 | .container { 17 | display: flex; 18 | gap: 2rem; 19 | box-shadow: 0 0 24px 8px rgba(0, 0, 0, 0.2); 20 | padding: 1rem; 21 | border-radius: .5rem; 22 | background-color: whitesmoke; 23 | } 24 | 25 | .inputs { 26 | display: flex; 27 | flex-direction: column; 28 | gap: .5rem; 29 | width: 100px; 30 | } 31 | 32 | .inputs input { 33 | padding: .5rem; 34 | } 35 | 36 | .inputs button { 37 | border: 0; 38 | padding: .5rem 1rem; 39 | background-color: lightgrey; 40 | cursor: pointer; 41 | } 42 | 43 | .inputs button:hover { 44 | background-color: white; 45 | 46 | } 47 | 48 | .timer { 49 | width: 100%; 50 | display: flex; 51 | align-items: center; 52 | justify-content: center; 53 | font-size: 2rem; 54 | } -------------------------------------------------------------------------------- /03-colour-flipper/script.js: -------------------------------------------------------------------------------- 1 | const newColourBtnElement = document.getElementById( 2 | 'new-colour-button' 3 | ); 4 | 5 | const currentColourElement = document.getElementById( 6 | 'current-colour' 7 | ); 8 | 9 | // 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F 10 | 11 | const hexValues = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 12 | ]; 13 | 14 | function getRandomHexValue() { 15 | const randomIndexPosition = Math.floor( 16 | Math.random() * hexValues.length 17 | ); 18 | 19 | const randomHexValue = hexValues[randomIndexPosition]; 20 | 21 | return randomHexValue; 22 | } 23 | 24 | function getRandomHexString(stringLength) { 25 | let hexString = ''; 26 | for (let i = 0; i < stringLength; i++) { 27 | hexString += getRandomHexValue(); 28 | } 29 | 30 | return hexString; 31 | } 32 | 33 | newColourBtnElement.addEventListener('click', function () { 34 | const randomHexString = '#' + getRandomHexString(6); 35 | 36 | document.body.style.setProperty( 37 | 'background-color', 38 | randomHexString 39 | ); 40 | 41 | currentColourElement.textContent = randomHexString; 42 | }); 43 | -------------------------------------------------------------------------------- /12-shopping-list/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; 7 | } 8 | 9 | #shoppingList { 10 | width: 300px; 11 | margin: 0 auto; 12 | border-radius: .5rem; 13 | padding: 1rem; 14 | box-shadow: 0 0 24px 8px rgba(0, 0, 0, 0.1); 15 | } 16 | 17 | #shoppingListItems { 18 | margin: 0; 19 | padding: 0; 20 | list-style-type: none; 21 | } 22 | 23 | #shoppingListItems li { 24 | padding: .5rem; 25 | display: flex; 26 | justify-content: space-between; 27 | align-items: center; 28 | } 29 | 30 | #shoppingListItems li:nth-child(even) { 31 | background-color: aliceblue; 32 | } 33 | 34 | .addItemSection { 35 | width: 100%; 36 | margin-top: 1rem; 37 | } 38 | 39 | .addItemSection input, .addItemSection button { 40 | width: 100%; 41 | padding: .5rem; 42 | margin: .5rem 0; 43 | } 44 | 45 | .addItemSection button { 46 | background-color: orangered; 47 | color: white; 48 | border: 0; 49 | } 50 | 51 | .remove-item { 52 | font-size: .5rem; 53 | cursor: pointer; 54 | color: blue; 55 | } -------------------------------------------------------------------------------- /09-countdown-timer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 09 - Countdown Timer 8 | 9 | 10 | 11 | 12 | 13 |

CountDown Timer

14 |
15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 |
30 |

31 |
32 |
33 |
34 |

35 |
36 |
37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /02-modal/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | height: 100%; 7 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; 8 | padding: 0; 9 | margin: 0; 10 | background-image: url(background.jpg); 11 | background-size: cover; 12 | background-repeat: no-repeat 13 | } 14 | 15 | button { 16 | margin: 2rem; 17 | display: inline; 18 | background-color: teal; 19 | color: whitesmoke; 20 | padding: .5rem 1rem; 21 | border: 0; 22 | box-shadow: 0 0 12px 8px rgba(0, 0, 0, 0.3); 23 | cursor: pointer; 24 | } 25 | 26 | button:hover { 27 | background-color:darkslategrey; 28 | } 29 | 30 | .modal { 31 | position: absolute; 32 | left: 0; 33 | top: 0; 34 | right: 0; 35 | bottom: 0; 36 | display: none; 37 | height: 100vh; 38 | justify-content: center; 39 | align-items: center; 40 | } 41 | 42 | .modal.open { 43 | display: flex; 44 | } 45 | 46 | .modal__overlay { 47 | position: absolute; 48 | top: 0; 49 | left: 0; 50 | right: 0; 51 | bottom: 0; 52 | background-color: rgba(0, 0, 0, 0.2); 53 | } 54 | 55 | .modal__content { 56 | background-color: white; 57 | padding: 4rem; 58 | position: relative; 59 | cursor: pointer; 60 | } 61 | 62 | -------------------------------------------------------------------------------- /07-faqs/script.js: -------------------------------------------------------------------------------- 1 | const dataArray = [ 2 | { 3 | title: 'Why is JavaScript cool?', 4 | detail: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veritatis doloremque debitis aspernatur iusto molestias facilis itaque, perspiciatis tempore unde commodi eius. Distinctio ipsa numquam magni dolorum pariatur vel, explicabo accusantium?', 5 | }, 6 | { 7 | title: 'What is JavaScript?', 8 | detail: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veritatis doloremque debitis aspernatur iusto molestias facilis itaque, perspiciatis tempore unde commodi eius. Distinctio ipsa numquam magni dolorum pariatur vel, explicabo accusantium?', 9 | }, 10 | { 11 | title: 'How do I learn JavaScript?', 12 | detail: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veritatis doloremque debitis aspernatur iusto molestias facilis itaque, perspiciatis tempore unde commodi eius. Distinctio ipsa numquam magni dolorum pariatur vel, explicabo accusantium?', 13 | }, 14 | { 15 | title: 'What are the best things about JavaScript?', 16 | detail: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veritatis doloremque debitis aspernatur iusto molestias facilis itaque, perspiciatis tempore unde commodi eius. Distinctio ipsa numquam magni dolorum pariatur vel, explicabo accusantium?', 17 | } 18 | ]; 19 | 20 | const makeHTML = data => { 21 | return `
22 | ${data.title} 23 |

${data.detail}

24 |
` 25 | }; 26 | 27 | const container = document.getElementById('faq-container'); 28 | 29 | container.innerHTML = dataArray.map(data => makeHTML(data)).join(''); -------------------------------------------------------------------------------- /15-lorem-ipsum/script.js: -------------------------------------------------------------------------------- 1 | 2 | const getLoremIpsum = numberOfParagraphs => { 3 | fetch(`https://baconipsum.com/api/?type=meat-and-filler¶s=${numberOfParagraphs}`) 4 | .then(response => response.json()) 5 | .then(loremIpsumTextArray => { 6 | updateResult(loremIpsumTextArray); 7 | }) 8 | .catch(error => { 9 | showError(error); 10 | }); 11 | }; 12 | 13 | const updateResult = textArray => { 14 | const resultElement = document.getElementById('result'); 15 | resultElement.classList.add('show'); 16 | resultElement.innerHTML = ''; 17 | resultElement.innerHTML = textArray 18 | .map(paragraph => `

${paragraph}

`) 19 | .join(''); 20 | addCopyButton(textArray.join('')); 21 | }; 22 | 23 | const showError = error => { 24 | resultElement.classList.add('show'); 25 | const resultElement = document.getElementById('result'); 26 | resultElement.innerHTML = ''; 27 | resultElement.innerHTML = `

${error.message}

` 28 | } 29 | 30 | const addCopyButton = text => { 31 | const resultElement = document.getElementById('result'); 32 | const copyBtn = document.createElement('button'); 33 | 34 | copyBtn.textContent = 'Copy'; 35 | copyBtn.classList.add('copy'); 36 | copyBtn.onclick = () => { 37 | navigator.clipboard.writeText(text); 38 | copyBtn.textContent = 'Copied!'; 39 | setTimeout(() => { 40 | copyBtn.textContent = 'Copy'; 41 | }, 2000); 42 | } 43 | 44 | resultElement.appendChild(copyBtn); 45 | } 46 | 47 | const getLoremIpsumElement = document.getElementById('getLoremIpsum'); 48 | const paragraphsCountElement = document.getElementById('paragraphsCount'); 49 | 50 | getLoremIpsumElement.addEventListener('click', () => { 51 | getLoremIpsum(parseInt(paragraphsCountElement.value)); 52 | paragraphsCountElement.value = ''; 53 | 54 | }) 55 | -------------------------------------------------------------------------------- /16-autocomplete/script.js: -------------------------------------------------------------------------------- 1 | const searchTextElement = document.getElementById('searchText'); 2 | const autoCompleteResultsElement = document.querySelector('#autoCompleteResults'); 3 | const autoCompleteBodyElement = document.querySelector('#autoCompleteResults tbody'); 4 | 5 | const debounce = (callback, delay) => { 6 | let timeout; 7 | return () => { 8 | clearTimeout(timeout); 9 | timeout = setTimeout(callback, delay); 10 | } 11 | } 12 | 13 | const getData = query => { 14 | const searchURL = new URL('https://dummyjson.com/products/search'); 15 | searchURL.searchParams.append('q', query); 16 | 17 | return fetch(searchURL) 18 | .then(response => response.json()) 19 | .then(result => { 20 | const products = result.products; 21 | const mappedProducts = products.map(product => ({ 22 | title: product.title, 23 | price: product.price, 24 | category: product.category, 25 | image: product.images[0], 26 | })); 27 | 28 | return mappedProducts; 29 | }); 30 | } 31 | 32 | const autoComplete = () => { 33 | const query = searchTextElement.value.trim(); 34 | if (!query) { 35 | return; 36 | 37 | } 38 | 39 | getData(query) 40 | .then(products => { 41 | products.forEach(product => { 42 | autoCompleteResultsElement.classList.add('show'); 43 | autoCompleteBodyElement.innerHTML += ` 44 | ${product.title} 45 | $${product.price} 46 | ${product.category} 47 | 48 | ` 49 | }); 50 | }); 51 | } 52 | 53 | searchTextElement.addEventListener('keyup', debounce(autoComplete, 1000)); 54 | autoCompleteResultsElement.addEventListener('click', () => { 55 | autoCompleteResultsElement.classList.remove('show') 56 | }); -------------------------------------------------------------------------------- /10-star-rating/script.js: -------------------------------------------------------------------------------- 1 | const initialQuestions = [ 2 | { 3 | label: 'Friendliness', 4 | rating: 0, 5 | }, 6 | { 7 | label: 'Cleanliness', 8 | rating: 0, 9 | }, 10 | { 11 | label: 'Food', 12 | rating: 0, 13 | }, 14 | { 15 | label: 'Service', 16 | rating: 0, 17 | }, 18 | ]; 19 | 20 | const storedQuestions = JSON.parse(localStorage.getItem('storedQuestions')); 21 | 22 | const questions = storedQuestions || initialQuestions; 23 | 24 | const makeStarRating = question => { 25 | const container = document.createElement('div'); 26 | const label = document.createElement('label'); 27 | label.textContent = question.label; 28 | container.appendChild(label); 29 | container.appendChild(makeStars(5, question)); 30 | 31 | return container; 32 | }; 33 | 34 | const makeStars = (maxValue, question) => { 35 | const starContainer = document.createElement('div'); 36 | 37 | for (let starPosition = 1; starPosition <= maxValue; starPosition++) { 38 | const starElement = document.createElement('span'); 39 | starContainer.appendChild(starElement); 40 | if (starPosition <= question.rating) { 41 | starElement.classList.add('filled'); 42 | } else { 43 | starElement.classList.add('empty'); 44 | } 45 | 46 | starElement.onclick = () => { 47 | for (let i = 0; i < maxValue; i++) { 48 | if (i < starPosition) { 49 | starContainer.children[i].classList.add('filled'); 50 | } else { 51 | starContainer.children[i].classList.remove('filled'); 52 | starContainer.children[i].classList.add('empty'); 53 | } 54 | } 55 | question.rating = starPosition; 56 | localStorage.setItem('storedQuestions', JSON.stringify(questions)); 57 | } 58 | } 59 | 60 | return starContainer; 61 | } 62 | 63 | const ratingsElement = document.getElementById('ratings'); 64 | 65 | questions.forEach(question => { 66 | ratingsElement.appendChild(makeStarRating(question)) 67 | }); -------------------------------------------------------------------------------- /09-countdown-timer/script.js: -------------------------------------------------------------------------------- 1 | const startTimerButtonElement = document.getElementById('startTimer'); 2 | const hoursInputElement = document.getElementById('hoursInput'); 3 | const minutesInputElement = document.getElementById('minutesInput'); 4 | const secondsInputElement = document.getElementById('secondsInput'); 5 | 6 | const hoursOutputElement = document.getElementById('hoursOutput'); 7 | const minutesOutputElement = document.getElementById('minutesOutput'); 8 | const secondsOutputElement = document.getElementById('secondsOutput'); 9 | 10 | let targetTime; 11 | let timerInterval; 12 | 13 | 14 | const updateTimer = () => { 15 | if (targetTime) { 16 | const differenceInSeconds = Math.floor(targetTime - Date.now()) / 1000; 17 | if (differenceInSeconds < 1) { 18 | clearInterval(timerInterval); 19 | } 20 | 21 | const hours = Math.floor(differenceInSeconds / 3600); 22 | const minutes = Math.floor(differenceInSeconds / 60) % 60; 23 | const seconds = Math.floor(differenceInSeconds % 60); 24 | 25 | hoursOutputElement.textContent = `${hours} hours`; 26 | minutesOutputElement.textContent = `${minutes} minutes`; 27 | secondsOutputElement.textContent = `${seconds} seconds`; 28 | } 29 | } 30 | 31 | startTimerButtonElement.addEventListener('click', () => { 32 | clearInterval(timerInterval); 33 | const futureHours = parseInt(hoursInputElement.value); 34 | const futureMinutes = parseInt(minutesInputElement.value); 35 | const futureSeconds = parseInt(secondsInputElement.value); 36 | 37 | const date = new Date(); 38 | const currentHours = date.getHours(); 39 | const currentMinutes = date.getMinutes(); 40 | const currentSeconds = date.getSeconds(); 41 | 42 | date.setHours(currentHours + futureHours); 43 | date.setMinutes(futureMinutes + currentMinutes); 44 | date.setSeconds(currentSeconds + futureSeconds); 45 | 46 | targetTime = date.getTime(); 47 | localStorage.setItem('targetTime', targetTime); 48 | updateTimer(); 49 | timerInterval = setInterval(updateTimer, 500); 50 | }); 51 | 52 | 53 | const storedTargetTime = localStorage.getItem('targetTime'); 54 | 55 | if (storedTargetTime) { 56 | targetTime = storedTargetTime; 57 | updateTimer(); 58 | timerInterval = setInterval(updateTimer, 500); 59 | } 60 | -------------------------------------------------------------------------------- /12-shopping-list/script.js: -------------------------------------------------------------------------------- 1 | class ShoppingList { 2 | constructor(itemsSelector, addItemButtonSelector, newItemTextSelector, storageKey = 'shoppingList') { 3 | this.itemsElement = document.querySelector(itemsSelector); 4 | this.addItemButtonElement = document.querySelector(addItemButtonSelector); 5 | this.newItemTextElement = document.querySelector(newItemTextSelector); 6 | this.storageKey = storageKey; 7 | this.items = JSON.parse(localStorage.getItem(storageKey)) || ['apples', 'oranges']; 8 | 9 | this.initialise(); 10 | } 11 | 12 | initialise() { 13 | this.addItemButtonElement.addEventListener('click', () => { 14 | this.addItem(this.newItemTextElement.value); 15 | this.newItemTextElement.value = ''; 16 | this.renderItems(); 17 | this.storeItems(); 18 | }); 19 | 20 | this.renderItems(); 21 | } 22 | 23 | renderItems() { 24 | this.itemsElement.innerHTML = ''; 25 | if (this.items.length === 0) { 26 | const itemElement = document.createElement('li'); 27 | itemElement.textContent = 'No items'; 28 | this.itemsElement.appendChild(itemElement); 29 | } 30 | 31 | this.items.forEach((item, index) => { 32 | const itemElement = document.createElement('li'); 33 | itemElement.textContent = item; 34 | const removeElement = document.createElement('span'); 35 | removeElement.textContent = 'Remove'; 36 | removeElement.classList.add('remove-item'); 37 | removeElement.onclick = () => { 38 | this.removeItemAt(index); 39 | this.renderItems(); 40 | this.storeItems(); 41 | } 42 | itemElement.appendChild(removeElement); 43 | this.itemsElement.appendChild(itemElement); 44 | }) 45 | } 46 | 47 | addItem(newItem) { 48 | this.items.push(newItem); 49 | } 50 | 51 | removeItemAt(indexToRemove) { 52 | this.items = this.items.filter((item, itemIndex) => itemIndex != indexToRemove); 53 | } 54 | 55 | storeItems() { 56 | localStorage.setItem(this.storageKey, JSON.stringify(this.items)); 57 | } 58 | } 59 | 60 | const myShoppingList = new ShoppingList('#shoppingListItems', '#addItem', '#newItemText') 61 | -------------------------------------------------------------------------------- /08-testimonials/script.js: -------------------------------------------------------------------------------- 1 | const testimonials = [ 2 | { 3 | author: { 4 | name: 'Gabriel Moore', 5 | image: 'author-01.jpg', 6 | }, 7 | text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Maiores, repellendus? Impedit mollitia nesciunt itaque optio incidunt enim quae, molestiae, accusamus ratione illum eum sapiente tempore fugiat quam, expedita vel perferendis!', 8 | date: '23rd May', 9 | }, 10 | { 11 | author: { 12 | name: 'Billy Bailey', 13 | image: 'author-02.jpg', 14 | }, 15 | text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Maiores, repellendus? Impedit mollitia nesciunt itaque optio incidunt enim quae, molestiae, accusamus ratione illum eum sapiente tempore fugiat quam, expedita vel perferendis!', 16 | date: '25th May', 17 | }, 18 | { 19 | author: { 20 | name: 'Jackie Oliver', 21 | image: 'author-03.jpg', 22 | }, 23 | text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Maiores, repellendus? Impedit mollitia nesciunt itaque optio incidunt enim quae, molestiae, accusamus ratione illum eum sapiente tempore fugiat quam, expedita vel perferendis!', 24 | date: '02nd June', 25 | }, 26 | { 27 | author: { 28 | name: 'Pauline Carter', 29 | image: 'author-04.jpg', 30 | }, 31 | text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Maiores, repellendus? Impedit mollitia nesciunt itaque optio incidunt enim quae, molestiae, accusamus ratione illum eum sapiente tempore fugiat quam, expedita vel perferendis!', 32 | date: '09th June', 33 | }, 34 | ]; 35 | 36 | const makeTestimonialCard = testimonial => { 37 | return `
38 | 39 |

${testimonial.author.name}

40 |

${testimonial.text}

41 | Written on ${testimonial.date} 42 |
` 43 | }; 44 | 45 | let currentTestimonial = 0; 46 | 47 | const nextTestimonial = () => { 48 | if (currentTestimonial < testimonials.length - 1) { 49 | currentTestimonial += 1; 50 | updatePage(); 51 | } 52 | } 53 | const prevTestimonial = () => { 54 | if (currentTestimonial > 0) { 55 | currentTestimonial -= 1; 56 | updatePage(); 57 | } 58 | } 59 | 60 | const updatePage = () => { 61 | let markup = makeTestimonialCard(testimonials[currentTestimonial]); 62 | 63 | if (testimonials.length > 1) { 64 | markup += `` 68 | } 69 | 70 | const container = document.getElementById('testimonials-container'); 71 | 72 | container.innerHTML = markup; 73 | } 74 | 75 | updatePage(); 76 | 77 | 78 | -------------------------------------------------------------------------------- /05-sticky-navbar/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 05 - Sticky Nav 8 | 9 | 10 | 11 | 12 | 13 |
14 | 21 |
22 |
23 |

Section 1

24 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Adipisci hic dolores placeat doloribus 25 | libero 26 | natus aperiam magni architecto, cupiditate eius! Facere cupiditate praesentium eveniet laudantium ex 27 | vero suscipit officiis quisquam.

28 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Adipisci hic dolores placeat doloribus 29 | libero 30 | natus aperiam magni architecto, cupiditate eius! Facere cupiditate praesentium eveniet laudantium ex 31 | vero suscipit officiis quisquam.

32 |
33 |
34 | 35 |

Section 2

36 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Adipisci hic dolores placeat doloribus 37 | libero 38 | natus aperiam magni architecto, cupiditate eius! Facere cupiditate praesentium eveniet laudantium ex 39 | vero suscipit officiis quisquam.

40 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Adipisci hic dolores placeat doloribus 41 | libero 42 | natus aperiam magni architecto, cupiditate eius! Facere cupiditate praesentium eveniet laudantium ex 43 | vero suscipit officiis quisquam.

44 |
45 |
46 |

Section 3

47 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Adipisci hic dolores placeat doloribus 48 | libero 49 | natus aperiam magni architecto, cupiditate eius! Facere cupiditate praesentium eveniet laudantium ex 50 | vero suscipit officiis quisquam.

51 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Adipisci hic dolores placeat doloribus 52 | libero 53 | natus aperiam magni architecto, cupiditate eius! Facere cupiditate praesentium eveniet laudantium ex 54 | vero suscipit officiis quisquam.

55 |
56 |
57 |
58 | 59 | 60 | -------------------------------------------------------------------------------- /06-tabs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 06 - Tabs 8 | 9 | 10 | 11 | 12 | 13 |
14 | 21 |
22 |
23 |

Section 1

24 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Adipisci hic dolores placeat doloribus 25 | libero 26 | natus aperiam magni architecto, cupiditate eius! Facere cupiditate praesentium eveniet laudantium ex 27 | vero suscipit officiis quisquam.

28 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Adipisci hic dolores placeat doloribus 29 | libero 30 | natus aperiam magni architecto, cupiditate eius! Facere cupiditate praesentium eveniet laudantium ex 31 | vero suscipit officiis quisquam.

32 |
33 | 44 | 55 |
56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /17-user-directory/script.js: -------------------------------------------------------------------------------- 1 | class UserDirectory { 2 | constructor(options) { 3 | const { apiUrl, userMapperFn, displaySelector, filterSelector, storageKey = 'usersData' } = options; 4 | this.apiUrl = apiUrl; 5 | this.userMapperFn = userMapperFn; 6 | this.displayElement = document.querySelector(displaySelector); 7 | this.filterElement = document.querySelector(filterSelector); 8 | this.storageKey = storageKey; 9 | 10 | this.initialse(); 11 | } 12 | 13 | initialse() { 14 | this.loadData() 15 | .then(users => { 16 | console.log(users); 17 | this.populateDisplay(users); 18 | this.createHeader(); 19 | }); 20 | 21 | this.filterElement.addEventListener('keyup', () => { 22 | this.filterUsers(this.filterElement.value) 23 | }); 24 | } 25 | 26 | loadData() { 27 | const storedUserData = JSON.parse(localStorage.getItem(this.storageKey)); 28 | if (storedUserData) { 29 | return new Promise((resolve, reject) => resolve(storedUserData)) 30 | .then(users => { 31 | this.users = users; 32 | 33 | return users; 34 | }) 35 | } 36 | 37 | return fetch(this.apiUrl) 38 | .then(response => response.json()) 39 | .then(results => this.userMapperFn(results)) 40 | .then(users => { 41 | this.users = users; 42 | localStorage.setItem(this.storageKey, JSON.stringify(users)) 43 | return users; 44 | }); 45 | } 46 | 47 | createHeader() { 48 | const thead = document.createElement('thead'); 49 | const tr = document.createElement('tr'); 50 | Object.keys(this.users[0]) 51 | .forEach(columnName => { 52 | const th = document.createElement('th'); 53 | th.textContent = columnName[0].toUpperCase() + columnName.slice(1); 54 | tr.appendChild(th); 55 | }); 56 | 57 | thead.appendChild(tr); 58 | this.displayElement.insertAdjacentElement('beforebegin', thead); 59 | } 60 | 61 | populateDisplay(users) { 62 | Array.from(this.displayElement.children).forEach(row => row.remove()); 63 | users.forEach(user => { 64 | const tr = document.createElement('tr'); 65 | Object.entries(user) 66 | .forEach(([key, value]) => { 67 | const td = document.createElement('td'); 68 | if (key === 'photo') { 69 | const img = document.createElement('img'); 70 | img.src = value; 71 | td.appendChild(img) 72 | } else { 73 | td.textContent = value; 74 | } 75 | 76 | tr.appendChild(td); 77 | }); 78 | this.displayElement.appendChild(tr); 79 | }); 80 | } 81 | 82 | filterUsers(searchTerm) { 83 | const filteredUsers = this.users.filter(user => user.name.toLowerCase().includes(searchTerm.toLowerCase())); 84 | this.populateDisplay(filteredUsers); 85 | } 86 | } 87 | 88 | const userDirectory = new UserDirectory({ 89 | apiUrl: 'https://dummyjson.com/users', 90 | userMapperFn: (userData) => { 91 | return userData.users.map(({ firstName, lastName, birthDate, image, phone, email }) => ({ 92 | name: `${firstName} ${lastName}`, 93 | birthDate, 94 | phone, 95 | email, 96 | photo: image, 97 | })) 98 | }, 99 | displaySelector: '#userTable tbody', 100 | filterSelector: '#filterUsers', 101 | }); 102 | 103 | -------------------------------------------------------------------------------- /11-sortable-table/script.js: -------------------------------------------------------------------------------- 1 | const data = [ 2 | { 3 | id: 1, 4 | title: 'iPhone 9', 5 | price: 549, 6 | inStock: true, 7 | category: 'smartphones', 8 | }, 9 | { 10 | id: 2, 11 | title: 'iPhone X', 12 | price: 899, 13 | inStock: true, 14 | category: 'smartphones', 15 | }, 16 | { 17 | 18 | id: 3, 19 | title: 'Samsung Universe 9', 20 | price: 1249, 21 | inStock: true, 22 | category: 'smartphones', 23 | }, 24 | { 25 | id: 4, 26 | title: 'Huawei P30', 27 | price: 499, 28 | inStock: false, 29 | category: 'smartphones', 30 | }, 31 | { 32 | id: 5, 33 | title: 'OPPOF19', 34 | price: 280, 35 | inStock: false, 36 | category: 'smartphones', 37 | }, 38 | { 39 | id: 6, 40 | title: 'MacBook Pro', 41 | price: 1749, 42 | inStock: true, 43 | category: 'laptops', 44 | }, 45 | { 46 | id: 7, 47 | title: 'Samsung Galaxy Book', 48 | price: 1499, 49 | inStock: false, 50 | category: 'laptops' 51 | }, 52 | { 53 | id: 8, 54 | title: 'Microsoft Surface Laptop 4', 55 | price: 1499, 56 | inStock: false, 57 | category: 'laptops', 58 | }, 59 | { 60 | id: 9, 61 | title: 'HP Pavilion 15-DK1056WM', 62 | price: 1099, 63 | inStock: true, 64 | category: 'laptops', 65 | }, 66 | { 67 | 68 | id: 10, 69 | title: 'Infinix INBOOK', 70 | price: 1099, 71 | inStock: true, 72 | category: 'laptops', 73 | } 74 | ]; 75 | 76 | 77 | const createTable = productData => { 78 | const tableElem = document.createElement('table'); 79 | const tableHead = document.createElement('thead'); 80 | const tableBody = document.createElement('tbody'); 81 | 82 | const headers = Object.keys(productData[0]); 83 | tableHead.appendChild(createHeaderRow(headers)); 84 | 85 | productData.forEach(rowData => { 86 | tableBody.appendChild(createProductRow(rowData)); 87 | }) 88 | 89 | tableElem.appendChild(tableHead); 90 | tableElem.appendChild(tableBody); 91 | 92 | return tableElem; 93 | } 94 | 95 | const createHeaderRow = columnNames => { 96 | const tr = document.createElement('tr'); 97 | columnNames.forEach(columnName => { 98 | const th = document.createElement('th'); 99 | th.textContent = columnName[0].toUpperCase() + columnName.slice(1); 100 | const searchUp = document.createElement('span'); 101 | searchUp.textContent = '🔼'; 102 | const searchDown = document.createElement('span'); 103 | searchDown.textContent = '🔽'; 104 | 105 | searchUp.onclick = () => sortDataBy(columnName, 'ASC'); 106 | searchDown.onclick = () => sortDataBy(columnName, 'DESC'); 107 | 108 | th.appendChild(searchDown); 109 | th.appendChild(searchUp); 110 | tr.appendChild(th); 111 | }) 112 | 113 | return tr; 114 | } 115 | 116 | const createProductRow = product => { 117 | const tr = document.createElement('tr'); 118 | Object.values(product).forEach(value => { 119 | const td = document.createElement('td'); 120 | td.textContent = value; 121 | tr.appendChild(td); 122 | }); 123 | 124 | return tr; 125 | } 126 | 127 | const sortDataBy = (columnName, direction) => { 128 | const sortedASCData = [...data.sort((a, b) => a[columnName] > b[columnName] ? 1 : -1)]; 129 | const sortedDESCData = [...data.sort((a, b) => a[columnName] < b[columnName] ? 1 : -1)]; 130 | renderTable(direction === 'ASC' ? sortedASCData : sortedDESCData); 131 | }; 132 | 133 | const renderTable = productData => { 134 | const sortableTableElement = document.getElementById('sortableTable'); 135 | sortableTableElement.innerHTML = ''; 136 | sortableTableElement.appendChild(createTable(productData)); 137 | }; 138 | 139 | renderTable(data); 140 | 141 | --------------------------------------------------------------------------------