├── 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 |
15 |
16 |
17 | Hours:
18 |
19 |
20 | Minutes:
21 |
22 |
23 | Seconds:
24 |
25 |
26 | Start
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 += `
65 | Previous
66 | Next
67 | `
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 |
15 |
20 |
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 |
15 |
20 |
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 | Section 2
35 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Adipisci hic dolores placeat doloribus
36 | libero
37 | natus aperiam magni architecto, cupiditate eius! Facere cupiditate praesentium eveniet laudantium ex
38 | vero suscipit officiis quisquam.
39 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Adipisci hic dolores placeat doloribus
40 | libero
41 | natus aperiam magni architecto, cupiditate eius! Facere cupiditate praesentium eveniet laudantium ex
42 | vero suscipit officiis quisquam.
43 |
44 |
45 | Section 3
46 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Adipisci hic dolores placeat doloribus
47 | libero
48 | natus aperiam magni architecto, cupiditate eius! Facere cupiditate praesentium eveniet laudantium ex
49 | vero suscipit officiis quisquam.
50 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Adipisci hic dolores placeat doloribus
51 | libero
52 | natus aperiam magni architecto, cupiditate eius! Facere cupiditate praesentium eveniet laudantium ex
53 | vero suscipit officiis quisquam.
54 |
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 |
--------------------------------------------------------------------------------