├── google84deea3d7c3bd30c.html
├── images
├── preview1.png
├── fork-and-knife.ico
└── css
│ └── responsive.css
├── sitemap.xml
├── favourite.html
├── country.html
├── search.html
├── index.html
├── styles.css
├── favourite.js
├── search.js
├── country.js
└── index.js
/google84deea3d7c3bd30c.html:
--------------------------------------------------------------------------------
1 | google-site-verification: google84deea3d7c3bd30c.html
--------------------------------------------------------------------------------
/images/preview1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateoCollective/chiefela/HEAD/images/preview1.png
--------------------------------------------------------------------------------
/images/fork-and-knife.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateoCollective/chiefela/HEAD/images/fork-and-knife.ico
--------------------------------------------------------------------------------
/images/css/responsive.css:
--------------------------------------------------------------------------------
1 | @media (max-width: 768px) {
2 | .meal-info {
3 | width: 100%;
4 | }
5 |
6 | .meal-photo {
7 | text-align: center;
8 | }
9 |
10 | .meal-photo img {
11 | width: 100%;
12 | height: auto;
13 | }
14 |
15 | .meal-title {
16 | margin: 10px;
17 | font-size: 1rem;
18 | text-align: center;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | https://chiefela.netlify.app/
4 | 2023-05-30
5 | weekly
6 | 0.8
7 |
8 |
9 | https://chiefela.netlify.app/search
10 | 2023-05-31
11 | monthly
12 | 0.6
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/favourite.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
20 |
21 |
24 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
My Foods | Home
24 |
25 |
26 |
27 |
28 |
29 |
Pembaruan
30 |
penyesuain ui dan lain sebagainya.
31 |
X
32 |
33 |
34 |
35 |
39 |
40 |
46 |
47 |
50 |
51 |
55 |
56 |
57 |
61 |
62 |
63 |
64 |
67 |
68 |
72 |
73 |
76 |
79 |
82 |
85 |
88 |
91 |
94 |
97 |
100 |
103 |
106 |
109 |
112 |
115 |
116 |
117 |
Rekomendasi
118 |
See All
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
143 |
144 |
145 |
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;700&display=swap");
2 |
3 | @import url("https://fonts.googleapis.com/css2?family=WindSong&display=swap");
4 |
5 | /* variables */
6 |
7 | :root {
8 | --dark-grey: #fafafa;
9 | --medium-grey: #FFFFFF;
10 | --light-grey: #666666;
11 | --very-light-grey: #e9e9e9;
12 | --active: #05994a;
13 | --white: #202020;
14 | }
15 |
16 |
17 |
18 | * {
19 | margin: 0;
20 | padding: 0;
21 | box-sizing: border-box;
22 |
23 | }
24 |
25 | body {
26 | font-family: "Open Sans", sans-serif;
27 | background-color: var(--dark-grey);
28 | }
29 |
30 | .container {
31 | margin: 0 auto;
32 | background-color: var(--dark-grey);
33 | width: auto;
34 | }
35 |
36 | .container::-webkit-scrollbar {
37 | display: none;
38 | }
39 |
40 | /* utility classes */
41 |
42 | a {
43 | text-decoration: none;
44 | }
45 |
46 | .pink {
47 | color: tomato !important;
48 | }
49 |
50 | .active {
51 | color: var(--active) !important;
52 | }
53 |
54 | .active-background {
55 | background-color: var(--active) !important;
56 | color: var(--dark-grey) !important;
57 | }
58 |
59 | .not-active {
60 | color: var(--light-grey);
61 | }
62 |
63 | .none {
64 | display: none !important;
65 | }
66 |
67 | .extra-bottom-space {
68 | height: 50px;
69 | width: 100%;
70 | }
71 |
72 | .disable-select {
73 | user-select: none;
74 | /* supported by Chrome and Opera */
75 | -webkit-user-select: none;
76 | /* Safari */
77 | -khtml-user-select: none;
78 | /* Konqueror HTML */
79 | -moz-user-select: none;
80 | /* Firefox */
81 | -ms-user-select: none;
82 | /* Internet Explorer/Edge */
83 | }
84 |
85 | /* main title */
86 |
87 | .main-title {
88 | color: white;
89 | display: flex;
90 | justify-content: center;
91 | align-items: center;
92 | }
93 |
94 | .title-container {
95 | display: flex;
96 | align-items: center;
97 | margin: 15px 20px;
98 | font-size: 10px;
99 | }
100 |
101 | .title {
102 | margin-right: auto;
103 | color: var(--white);
104 | }
105 |
106 | .see-all {
107 | margin-left: 10px;
108 | text-decoration: none;
109 | color: var(--active);
110 | font-size: 12px;
111 |
112 | }
113 |
114 |
115 | /* navbar */
116 |
117 | .navbar {
118 | position: fixed;
119 | display: flex;
120 | justify-content: space-evenly;
121 | align-items: center;
122 | z-index: 100;
123 | background-color: var(--medium-grey);
124 | left: 0;
125 | right: 0;
126 | bottom: 0;
127 | height: 50px;
128 | border-top: 1px solid var(--very-light-grey);
129 | }
130 |
131 | .navbar a {
132 | font-size: 1.5rem;
133 | color: var(--very-light-grey);
134 | font-weight: 100;
135 | }
136 |
137 | .header {
138 | display: flex;
139 | justify-content: space-between;
140 | align-items: center;
141 | padding: 10px 20px;
142 | background-color: var(--medium-grey);
143 | border-bottom: 1px solid var(--very-light-grey);
144 | color: var(--very-light-grey);
145 | position: fixed;
146 | left: 0;
147 | right: 0;
148 | top: 0;
149 | z-index: 999;
150 | text-decoration: none;
151 | }
152 |
153 | .header a {
154 | color: var(--very-light-grey);
155 | font-size: 20px;
156 | }
157 |
158 | /* Style untuk logo */
159 | .logo {
160 | width: 35px;
161 | }
162 |
163 |
164 |
165 | /* Style untuk ikon layanan */
166 | .service-icon {
167 | width: 30px;
168 | height: 30px;
169 | margin-left: 10px;
170 | }
171 |
172 | /* random meal */
173 |
174 | .random-meal-section {
175 | width: 100%;
176 | height: 177px;
177 | transition: all 0.2s ease-in-out;
178 | }
179 |
180 | .random-meal-container {
181 | display: flex;
182 | margin: 0 auto;
183 | width: 85%;
184 | height: 150px;
185 | overflow: hidden;
186 | border-radius: 20px;
187 | overflow: hidden;
188 | cursor: pointer;
189 | transition: border 0.2s;
190 | transition: box-shadow 0.2s;
191 | animation: opacityGrow 0.5s linear;
192 | }
193 |
194 | .random-meal-label {
195 | display: flex;
196 | justify-content: center;
197 | margin-bottom: 5px;
198 | }
199 |
200 | .random-meal-info {
201 | width: 100%;
202 | padding: 10px;
203 | display: flex;
204 | flex-direction: column;
205 | justify-content: center;
206 | align-items: center;
207 | font-size: 0.9rem;
208 | background-color: var(--medium-grey);
209 | }
210 |
211 | .random-meal-title {
212 | font-weight: 600;
213 | font-size: 0.9rem;
214 | margin-bottom: 10px;
215 | }
216 |
217 | .random-meal-photo {
218 | width: 100%;
219 | }
220 |
221 | .random-meal-photo img {
222 | width: 100%;
223 | }
224 |
225 | .random-meal-category {
226 | color: var(--white);
227 | margin-bottom: 4px;
228 | }
229 |
230 | .random-meal-category i {
231 | margin-right: 8px;
232 | }
233 |
234 | .random-meal-area {
235 | color: var(--white);
236 | margin-bottom: 10px;
237 | }
238 |
239 | .random-meal-area i {
240 | margin-right: 8px;
241 | }
242 |
243 | /* meal categories */
244 |
245 | .meal-categories-section {
246 | margin-top: 10px;
247 | }
248 |
249 | .meal-categories-title {
250 | color: var(--white);
251 | display: flex;
252 | justify-content: center;
253 | }
254 |
255 | .categories-container {
256 | display: flex;
257 | padding: 8px;
258 | overflow-x: scroll;
259 | }
260 |
261 | .categories-container::-webkit-scrollbar {
262 | display: none;
263 | }
264 |
265 | .category {
266 | background-color: var(--very-light-grey);
267 | display: flex;
268 | justify-content: center;
269 | align-items: center;
270 | padding: 10px;
271 | border-radius: 10px;
272 | width: 100px;
273 | cursor: pointer;
274 | color: #202020;
275 | font-size: 12px;
276 | font-weight: 500;
277 | margin: 0px 5px;
278 | }
279 |
280 | .category i {
281 | margin-right: 5px;
282 | }
283 |
284 | /* grid */
285 |
286 | .grid {
287 | display: grid;
288 | grid-template-columns: repeat(2, 1fr);
289 | margin: 0 auto;
290 | flex-wrap: nowrap;
291 | }
292 |
293 | .grid-item {
294 | display: flex;
295 | flex-direction: column;
296 | justify-content: space-evenly;
297 | align-items: center;
298 | margin: 6px auto;
299 | width: 180px;
300 | height: 250px;
301 | background-color: var(--medium-grey);
302 | border-radius: 15px;
303 | overflow: hidden;
304 | cursor: pointer;
305 | border: 2px solid var(--dark-grey);
306 | transition: border 0.2s;
307 | transition: box-shadow 0.2s;
308 | }
309 |
310 | .grid-item:hover {
311 | border: 2px solid var(--active);
312 | }
313 |
314 | .meal-info {
315 | text-align: left;
316 | justify-content: left;
317 | align-items: left;
318 | width: 200px;
319 | }
320 |
321 | .meal-photo {}
322 |
323 | .meal-photo img {
324 | object-fit: cover;
325 | width: 200px;
326 | height: 150px;
327 | }
328 |
329 | .meal-title {
330 | font-size: 0.9rem;
331 | margin-bottom: 10px;
332 | margin: 10px 20px;
333 | overflow: hidden;
334 | display: -webkit-box;
335 | -webkit-box-orient: vertical;
336 | -webkit-line-clamp: 1;
337 | /* Jumlah baris maksimum */
338 | text-align: left;
339 | color: var(--white);
340 | }
341 |
342 |
343 |
344 |
345 | .meal-category {
346 | color: var(--white);
347 |
348 | margin-left: 20px;
349 | font-size: 0.9rem;
350 | margin-bottom: 4px;
351 | text-align: left;
352 | }
353 |
354 | .meal-category i {
355 | margin-right: 8px;
356 | }
357 |
358 | .meal-area {
359 | color: var(--white);
360 | margin-left: 20px;
361 | font-size: 0.9rem;
362 | text-align: left;
363 | }
364 |
365 | .meal-area i {
366 | margin-right: 8px;
367 | }
368 |
369 | /* heart */
370 |
371 | .heart-container {
372 | display: flex;
373 | justify-content: center;
374 | align-items: center;
375 | width: 30px;
376 | height: 30px;
377 | cursor: pointer;
378 | margin: 10px;
379 | position: relative;
380 | top: -95%;
381 | right: -65px;
382 | }
383 |
384 | .heart-container i {
385 | font-size: 1.5rem;
386 | color: var(--medium-grey);
387 | }
388 |
389 | .heart-full {
390 | transition: color 0.2s;
391 | z-index: 1;
392 | }
393 |
394 | .animate-heart {
395 | animation: heartAni 0.5s forwards;
396 | }
397 |
398 | /* FAVOURITE PAGE */
399 |
400 | .favourite-title {
401 | color: var(--white);
402 | display: flex;
403 | justify-content: center;
404 | margin-bottom: 10px;
405 | }
406 |
407 | .grid-favourite {
408 | display: grid;
409 | grid-template-columns: repeat(2, 1fr);
410 | }
411 |
412 | /* SEARCH PAGE */
413 |
414 | .search-input-text {
415 | color: var(--white);
416 | text-align: center;
417 |
418 | }
419 |
420 | .input-container {
421 | display: flex;
422 | justify-content: center;
423 | margin-top: 10px;
424 | margin-bottom: 10px;
425 | }
426 |
427 | #search-input {
428 | text-align: left;
429 | font-size: 1rem;
430 | font-family: inherit;
431 | padding: 5px 30px;
432 | border-radius: 15px;
433 | border: 2px solid var(--dark-grey);
434 | background-color: var(--very-light-grey);
435 | outline: none;
436 | }
437 |
438 |
439 |
440 | /* COUNTRY PAGE */
441 |
442 | .country-section {
443 | margin-top: 10px;
444 | }
445 |
446 | .country-title {
447 | color: var(--white);
448 | display: flex;
449 | justify-content: center;
450 | }
451 |
452 | .country-container {
453 | display: flex;
454 | padding: 8px;
455 | overflow-x: scroll;
456 | }
457 |
458 | .grid-country {
459 | display: grid;
460 | grid-template-columns: repeat(2, 1fr);
461 | }
462 |
463 | .country-container::-webkit-scrollbar {
464 | display: none;
465 | }
466 |
467 | .country {
468 | background-color: var(--light-grey);
469 | display: flex;
470 | justify-content: center;
471 | align-items: center;
472 | margin-right: 10px;
473 | padding: 10px;
474 | border-radius: 10px;
475 | width: 100px;
476 | cursor: pointer;
477 | }
478 |
479 | .country i {
480 | margin-right: 5px;
481 | }
482 |
483 | /* ---------- RECIPE --------- */
484 |
485 | .recipe-section {
486 | position: fixed;
487 | top: 0;
488 | left: 0;
489 | right: 0;
490 | bottom: 0;
491 | z-index: 5000;
492 | display: flex;
493 | flex-direction: column;
494 | align-items: center;
495 | overflow-y: scroll;
496 | background-color: var(--dark-grey);
497 | }
498 |
499 | .retop {
500 | z-index: 5000;
501 | border-top-left-radius: 20px;
502 | border-top-right-radius: 20px;
503 | background-color: var(--dark-grey);
504 | position: relative;
505 | top: -20px;
506 |
507 | }
508 |
509 | /* recipe-button */
510 |
511 |
512 | i.fa-times {
513 | font-size: 2.2rem;
514 | }
515 |
516 |
517 | /* recipe-image-container */
518 |
519 | .recipe-image-container {
520 | width: 100%;
521 | overflow: hidden;
522 | }
523 |
524 | .recipe-image-container img {
525 | width: 100%;
526 | }
527 |
528 | /* recipe info */
529 |
530 | .recipe-info-container {
531 | display: flex;
532 | width: 100%;
533 | flex-direction: column;
534 | justify-content: center;
535 | }
536 |
537 | /* title */
538 |
539 | .recipe-title {
540 | font-size: 1rem;
541 | margin-top: 10px;
542 | color: var(--white);
543 | text-align: left;
544 | margin-left: 20px;
545 | }
546 |
547 |
548 | /* category */
549 |
550 | .recipe-category-container {
551 | display: flex;
552 | margin-top: 20px;
553 | margin-left: 20px;
554 | }
555 |
556 | .recipe-category-container p {
557 | color: var(--light-grey);
558 | font-weight: 500;
559 | letter-spacing: 1.5px;
560 | font-size: 0.9rem;
561 |
562 | }
563 |
564 | .recipe-category-result {
565 | margin-left: 5px;
566 | }
567 |
568 | /* area */
569 |
570 | .recipe-area-container {
571 | display: flex;
572 | margin-top: 2px;
573 | }
574 |
575 | .recipe-area-container {
576 | color: var(--light-grey);
577 | font-weight: 500;
578 | letter-spacing: 1.5px;
579 | font-size: 0.9rem;
580 | margin-left: 20px;
581 | }
582 |
583 | .recipe-area-result {
584 | margin-left: 5px;
585 | }
586 |
587 | /* ingredients */
588 |
589 | .ingredients-container {
590 | border-top: 1px solid var(--very-light-grey);
591 | padding-left: 20px;
592 | width: 100%;
593 | display: flex;
594 | flex-direction: column;
595 | justify-content: space-between;
596 | }
597 |
598 | .ingredients-title {
599 | margin-top: 40px;
600 | margin-bottom: 40px;
601 | text-align: left;
602 | }
603 |
604 | .ingredients-list {
605 | margin-top: 10px;
606 | font-size: 0.8rem;
607 | list-style: none;
608 | width: 100%;
609 | height: 100%;
610 | display: flex;
611 | flex-direction: column;
612 | align-items: center;
613 | margin-bottom: 40px;
614 | }
615 |
616 | .ingredient {
617 | width: 100%;
618 | display: flex;
619 | flex-wrap: wrap;
620 | justify-content: center;
621 | align-items: center;
622 | }
623 |
624 | .ingr-title {
625 | color: var(--active);
626 | flex: 1;
627 | margin-left: 10px;
628 | }
629 |
630 | .ingr-measure {
631 | color: var(--white);
632 | margin-left: 2px;
633 | flex: 1;
634 | }
635 |
636 | /* instructions */
637 |
638 | .instructions-container {
639 | border-top: 1px solid var(--very-light-grey);
640 | }
641 |
642 | .instructions-title {
643 | margin-top: 40px;
644 | margin-bottom: 40px;
645 | text-align: left;
646 | margin-left: 20px;
647 | font-weight: 600;
648 | }
649 |
650 |
651 |
652 | .instructions {
653 | color: var(--white);
654 | margin-bottom: 20px;
655 | width: 100%;
656 | padding: 20px;
657 | display: flex;
658 | flex-direction: column;
659 | justify-content: center;
660 | align-items: center;
661 | line-height: 25px;
662 | }
663 |
664 | .note {
665 | font-size: 2rem;
666 | font-weight: 600;
667 | text-align: center;
668 | margin-bottom: 40px;
669 | font-family: "WindSong", cursive;
670 | }
671 |
672 | .video {
673 | width: 90%;
674 | height: 200px;
675 | margin: 0 auto;
676 | margin-bottom: 40px;
677 | }
678 |
679 | .video iframe {
680 | width: 100%;
681 | }
682 |
683 | .container-play {
684 | position: fixed;
685 | bottom: 0%;
686 | left: 50%;
687 | transform: translate(-50%, -50%);
688 | text-align: center;
689 | z-index: 5555555;
690 | }
691 |
692 | .play-button {
693 | width: 130px;
694 | height: 40px;
695 | font-size: 16px;
696 | background-color: var(--active);
697 | color: #fff;
698 | border: none;
699 | border-radius: 10px;
700 | }
701 |
702 | .header-details {
703 | display: flex;
704 | justify-content: space-between;
705 | align-items: center;
706 | padding: 10px;
707 | color: var(--white);
708 | position: absolute;
709 | top: 0;
710 | left: 0;
711 | right: 0;
712 | z-index: 5555555;
713 | }
714 |
715 | .icon {
716 | background-color: #fff;
717 | border-radius: 50%;
718 | padding: 10px;
719 | margin: 0 5px;
720 | }
721 |
722 | .notification {
723 | display: none;
724 | position: fixed;
725 | top: 8%;
726 | left: 0;
727 | width: 100%;
728 | padding: 20px;
729 | background-color: #f8f8f8;
730 | border-bottom: 1px solid #ccc;
731 | text-align: center;
732 | font-size: 16px;
733 | z-index: 999;
734 | }
735 | .notification p {
736 | margin: 0;
737 | }
738 | .notification h1 {
739 | margin: 0;
740 | font-size: 15px;
741 | }
742 | .notification .close {
743 | position: absolute;
744 | top: 10px;
745 | right: 10px;
746 | cursor: pointer;
747 | }
748 |
749 | #greeting {
750 | font-weight: bold;
751 | font-size: 20px;
752 | margin-top: 60px;
753 | margin-left: 20px;
754 | color: #333;
755 | }
756 |
757 | .section-ser {
758 | width: 100%;
759 | align-items: center;
760 | justify-content: center;
761 | }
762 |
763 | .search-button {
764 | padding: 10px 15px;
765 | border: none;
766 | background-color: var(--medium-grey);
767 | color: var(--light-grey);
768 | font-size: 16px;
769 | border-radius: 50px;
770 | cursor: pointer;
771 | margin: 20px;
772 | text-align: left;
773 | opacity: 60%;
774 | width: 85%;
775 | }
776 |
777 | .ukr {
778 | }
779 |
780 | .search-button i {
781 | margin-right: 5px;
782 | }
783 |
784 | /* Jika Anda menggunakan Font Awesome, pastikan Anda telah menghubungkan file CSS Font Awesome */
785 |
786 |
787 |
788 | @keyframes heartAni {
789 | 0% {
790 | transform: scale(0.8);
791 | }
792 |
793 | 50% {
794 | transform: scale(1.3);
795 | }
796 |
797 | 100% {
798 | transform: scale(1);
799 | }
800 | }
801 |
802 | @keyframes opacityGrow {
803 | from {
804 | opacity: 0;
805 | }
806 |
807 | to {
808 | opacity: 1;
809 | }
810 | }
811 |
812 |
813 | @media (max-width: 768px) {
814 | .meal-info {
815 | width: 100%;
816 | }
817 |
818 | .meal-photo {
819 | text-align: center;
820 | }
821 |
822 | .meal-photo img {
823 | width: 100%;
824 | height: auto;
825 | }
826 |
827 | .meal-title {
828 | margin: 10px;
829 | font-size: 1rem;
830 | text-align: center;
831 | }
832 | }
833 |
834 |
--------------------------------------------------------------------------------
/favourite.js:
--------------------------------------------------------------------------------
1 | //API DATA FUNCTIONS
2 |
3 | const randomMealURL = 'https://www.themealdb.com/api/json/v1/1/random.php';
4 | const categoryBaseURL = 'https://www.themealdb.com/api/json/v1/1/filter.php?c=';
5 | const idBaseURL = 'https://www.themealdb.com/api/json/v1/1/lookup.php?i=';
6 |
7 |
8 | //fetch random meal
9 | async function fetchRandomMeal() {
10 | try {
11 | const res = await fetch(randomMealURL)
12 | const data = await res.json()
13 | UI.createRandomMealCard(data)
14 | } catch (err) {
15 | console.log(err);
16 | }
17 |
18 | }
19 |
20 | //fetch all meals within a category
21 | async function fetchCategoryMeals(category) {
22 | try {
23 | const res = await fetch(categoryBaseURL + category)
24 | const data = await res.json()
25 | return data
26 | //UI.getMealIds(data)
27 | } catch (err) {
28 | console.log(err);
29 | }
30 |
31 | }
32 |
33 | //fetch a meal by its id
34 | async function fetchMealById(id) {
35 | try {
36 | const res = await fetch(idBaseURL + id)
37 | const data = await res.json()
38 | UI.createMeals(data)
39 | return data
40 | } catch (err) {
41 | console.log(err);
42 | }
43 | }
44 |
45 | //get recipe with id
46 | async function fetchRecipe(id) {
47 | try {
48 | const res = await fetch(idBaseURL + id)
49 | const data = await res.json()
50 | //find a way to return an array of objects
51 | //console.log(data);
52 | UI.createRecipe(data)
53 | return data
54 | } catch (err) {
55 | console.log(err);
56 | }
57 | }
58 |
59 | //LOAD FUNCTIONS ON WINDOW LOAD
60 | document.addEventListener('DOMContentLoaded', () => {
61 | //filter favourite meals by category
62 | UI.filterFavouriteCategories()
63 | //show favourite meals
64 | UI.showFavouriteMeals()
65 | //make the heart function work
66 | })
67 |
68 | ///////////////////////////////////////////////////////////////////
69 |
70 | let categories = document.querySelectorAll('.category');
71 | let grid = document.querySelector('.grid-favourite')
72 | let body = document.querySelector('body')
73 | let mealCategoriesSection = document.querySelector('.meal-categories-section')
74 | let recipeSection = document.querySelector('.recipe-section')
75 | let closeBtn = document.querySelector('.close-btn')
76 | let favouriteTitle = document.querySelector('.favourite-title')
77 | let container = document.querySelector('.container')
78 |
79 |
80 | //REPRESENTS A MEAL
81 | class MealCard {
82 | constructor(data) {
83 | this.name = data.meals[0].strMeal
84 | this.type = data.meals[0].strCategory
85 | this.area = data.meals[0].strArea
86 | this.thumb = data.meals[0].strMealThumb
87 | this.id = data.meals[0].idMeal
88 | }
89 | }
90 |
91 | //HANDLES UI TASKS
92 | class UI {
93 |
94 | //create recipe
95 | static createRecipe(data) {
96 | recipeSection.innerHTML = `
97 |
103 |
104 |
![${ data.meals[0].strMeal }](${ data.meals[0].strMealThumb })
105 |
106 |
107 |
108 | ${ data.meals[0].strMeal }
109 |
110 |
111 |
Category:
112 |
${ data.meals[0].strCategory }
113 |
114 |
115 |
Area:
116 |
${ data.meals[0].strArea }
117 |
118 |
119 |
120 |
121 |
122 |
Ingredients
123 |
124 | - ${ data.meals[0].strIngredient1 }${ data.meals[0].strMeasure1 }
125 | - ${ data.meals[0].strIngredient2 }${ data.meals[0].strMeasure2 }
126 | - ${ data.meals[0].strIngredient3 }${ data.meals[0].strMeasure3 }
127 | - ${ data.meals[0].strIngredient4 }${ data.meals[0].strMeasure4 }
128 | - ${ data.meals[0].strIngredient5 }${ data.meals[0].strMeasure5 }
129 | - ${ data.meals[0].strIngredient6 }${ data.meals[0].strMeasure6 }
130 | - ${ data.meals[0].strIngredient7 }${ data.meals[0].strMeasure7 }
131 | - ${ data.meals[0].strIngredient8 }${ data.meals[0].strMeasure8 }
132 | - ${ data.meals[0].strIngredient9 }${ data.meals[0].strMeasure9 }
133 | - ${ data.meals[0].strIngredient10 }${ data.meals[0].strMeasure10 }
134 | - ${ data.meals[0].strIngredient11 }${ data.meals[0].strMeasure11 }
135 | - ${ data.meals[0].strIngredient12 }${ data.meals[0].strMeasure12 }
136 | - ${ data.meals[0].strIngredient13 }${ data.meals[0].strMeasure13 }
137 | - ${ data.meals[0].strIngredient14 }${ data.meals[0].strMeasure14 }
- ${ data.meals[0].strIngredient15 }${ data.meals[0].strMeasure15 }
- ${ data.meals[0].strIngredient16 }${ data.meals[0].strMeasure16 }
138 | - ${ data.meals[0].strIngredient17 }${ data.meals[0].strMeasure17 }
139 | - ${ data.meals[0].strIngredient18 }${ data.meals[0].strMeasure18 }
140 | - ${ data.meals[0].strIngredient19 }${ data.meals[0].strMeasure19 }
141 | - ${ data.meals[0].strIngredient20 }${ data.meals[0].strMeasure20 }
142 |
143 |
144 |
145 |
Instructions
146 |
147 | "${ data.meals[0].strInstructions }"
148 |
149 |
Buon Appetito!
150 |
151 |
152 |
153 |
154 |
155 | `
156 | }
157 |
158 | //changes category background color on click
159 | static changeCategoryColor() {
160 | categories.forEach((category) => {
161 | category.addEventListener('click', () => {
162 | categories.forEach((category) => {
163 | category.classList.remove('active-background')
164 | })
165 | category.classList.add('active-background')
166 | UI.getCategoryMeals(category.id)
167 | //UI.showCategoryMeals(category)
168 | })
169 | })
170 | }
171 |
172 | //fetch all category ids and all meal by those ids
173 | static getCategoryMeals(cat) {
174 | fetchCategoryMeals(cat).then((res) => {
175 | for (let i = 0; i < res.meals.length; i++) {
176 | fetchMealById(res.meals[i].idMeal)
177 | }
178 | if (grid === null) {
179 | return
180 | } else {
181 | grid.innerHTML = ''
182 | }
183 | })
184 | }
185 |
186 | //create meal card
187 | static createMeals(data) {
188 | //create a new meal obj
189 | const meal = new MealCard(data)
190 | let gridItem = document.createElement('div')
191 | gridItem.classList.add('grid-item')
192 | gridItem.classList.add('meal')
193 | gridItem.id = `${ meal.id }`
194 | gridItem.innerHTML = `
195 |
196 |

197 |
198 |
199 |
${ meal.name }
200 |
${ meal.type }
201 |
${ meal.area }
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 | `
210 | grid.appendChild(gridItem)
211 | //if id is in LS, add pink to heart
212 | const meals = Storage.getMealFromLS();
213 | if (meals.includes(gridItem.id)) {
214 | gridItem.innerHTML = `
215 |
216 |

217 |
218 |
219 |
${ meal.name }
220 |
${ meal.type }
221 |
${ meal.area }
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 | `
230 | }
231 | }
232 |
233 | //remove filtered meals from ui
234 | static removeMealFromUI(id) {
235 | let gridItems = document.querySelectorAll('.grid-item')
236 | gridItems.forEach((item) => {
237 | if (item.id === id) {
238 | item.remove()
239 | }
240 | })
241 | }
242 |
243 | static showFavouriteMeals() {
244 | const meals = Storage.getMealFromLS();
245 | meals.forEach((meal) => {
246 | fetchMealById(meal)
247 | })
248 | }
249 |
250 | static filterFavouriteCategories() {
251 | //show favourite meals
252 | // let meals = Storage.getMealFromLS();
253 | //change category background color
254 | categories.forEach((category) => {
255 | category.addEventListener('click', () => {
256 | //
257 | //
258 | categories.forEach((category) => {
259 | category.classList.remove('active-background')
260 | })
261 | category.classList.add('active-background')
262 | })
263 | })
264 | }
265 |
266 | //NOT WORKING PROPERLY///////----/////////-----///////
267 | static filterFunction(category) {
268 | let gridItems = document.querySelectorAll('.grid-item')
269 | let newArray = Array.from(gridItems).filter(function (el) {
270 | return el.children[1].children[1].textContent !== category
271 | });
272 | newArray.forEach((arr) => {
273 | let id = arr.id
274 | UI.removeMealFromUI(id)
275 | })
276 | }
277 | }
278 |
279 | //HANDLES STORAGE
280 |
281 | class Storage {
282 |
283 | static getMealFromLS() {
284 | let meals;
285 | if (localStorage.getItem('meals') === null) {
286 | meals = [];
287 | } else {
288 | meals = JSON.parse(localStorage.getItem('meals'));
289 | }
290 |
291 | return meals;
292 | }
293 |
294 | static addMealToLS(meal) {
295 | const meals = Storage.getMealFromLS();
296 | //check if id is already in LS
297 | if (meals.includes(meal)) {
298 | return
299 | } else {
300 | meals.push(meal);
301 | localStorage.setItem('meals', JSON.stringify(meals));
302 | }
303 | }
304 |
305 | static removeMealFromLS(id) {
306 | const meals = Storage.getMealFromLS();
307 | meals.forEach((meal, index) => {
308 | if (meal === id) {
309 | meals.splice(index, 1);
310 | }
311 | });
312 | localStorage.setItem('meals', JSON.stringify(meals));
313 | }
314 | }
315 |
316 | // EVENT LISTENERS
317 |
318 | //remove meal on heart click
319 | body.addEventListener('click', (e) => {
320 | if (e.target.classList.contains('fa-heart') && e.target.classList.contains('fas') && e.target.parentElement.parentElement.parentElement.classList.contains('grid-item') === true) {
321 | e.target.parentElement.parentElement.parentElement.remove()
322 | //remove favourite meal to/from storage
323 | let id = e.target.parentElement.parentElement.parentElement.id
324 | Storage.removeMealFromLS(id)
325 |
326 | }
327 | })
328 |
329 | //remove meal on heart click on RECIPE page
330 | body.addEventListener('click', (e) => {
331 | if (e.target.classList.contains('fa-heart') && e.target.classList.contains('fas') && e.target.parentElement.parentElement.parentElement.classList.contains('grid-item') === false) {
332 | //remove pink color
333 | e.target.classList.remove('pink')
334 | //remove favourite meal to/from storage
335 | let id = e.target.parentElement.id
336 | Storage.removeMealFromLS(id)
337 | location.reload()
338 | }
339 | })
340 |
341 | //on category click, show filtered meals
342 | //NOT WORKING PROPERLY /////-----/////////------//////
343 | categories.forEach((category) => {
344 | category.addEventListener('click', () => {
345 | UI.filterFunction(category.id)
346 | })
347 | })
348 |
349 | //get meal id on grid card click
350 | grid.addEventListener('click', (e) => {
351 | if (e.target.classList.contains('grid-item')) {
352 | fetchRecipe(e.target.id)
353 | console.log(e.target.id);
354 | //remove none class from recipe section
355 | recipeSection.classList.remove('none')
356 | //add none class to favourite title
357 | favouriteTitle.classList.add('none')
358 | //add none class to favourite grid
359 | grid.classList.add('none')
360 | //scroll to top of recipe
361 | container.scrollTo(0, 0)
362 |
363 | } else if (e.target.parentElement.parentElement.classList.contains('grid-item')) {
364 | fetchRecipe(e.target.parentElement.parentElement.id)
365 | console.log(e.target.parentElement.parentElement.id);
366 | //remove none class from recipe section
367 | recipeSection.classList.remove('none')
368 | //add none class to favourite title
369 | favouriteTitle.classList.add('none')
370 | //add none class to favourite grid
371 | grid.classList.add('none')
372 | //scroll to top of recipe
373 | container.scrollTo(0, 0)
374 | }
375 | })
376 |
377 | //close button listener
378 | recipeSection.addEventListener('click', (e) => {
379 | if (e.target.classList.contains('fa-times')) {
380 | recipeSection.classList.add('none')
381 | //remove none class to favourite title
382 | favouriteTitle.classList.remove('none')
383 | //remove none class to favourite grid
384 | grid.classList.remove('none')
385 | }
386 | })
387 |
388 |
389 | //TO DO:
390 |
391 | //1. swap \r\ in recipe for
using regex?
392 |
--------------------------------------------------------------------------------
/search.js:
--------------------------------------------------------------------------------
1 | // API DATA FUNCTIONS
2 |
3 | const nameBaseURL = 'https://www.themealdb.com/api/json/v1/1/search.php?s=';
4 |
5 | const idBaseURL = 'https://www.themealdb.com/api/json/v1/1/lookup.php?i=';
6 |
7 | //fetch a meal by name
8 | async function fetchMealByName(letters) {
9 | try {
10 | const res = await fetch(nameBaseURL + letters)
11 | const data = await res.json()
12 | UI.createMeals(data)
13 | return data
14 | } catch (err) {
15 | console.log(err);
16 | }
17 | }
18 |
19 | //get recipe with id
20 | async function fetchRecipe(id) {
21 | try {
22 | const res = await fetch(idBaseURL + id)
23 | const data = await res.json()
24 | //find a way to return an array of objects
25 | //console.log(data);
26 | UI.createRecipe(data)
27 | return data
28 | } catch (err) {
29 | console.log(err);
30 | }
31 | }
32 |
33 | /////////////////////////////////////////////////////
34 |
35 | let grid = document.querySelector('.grid-favourite')
36 | let body = document.querySelector('body')
37 | let container = document.querySelector('.container')
38 | let gridItems = document.querySelectorAll('.grid-item')
39 | let searchInput = document.getElementById('search-input')
40 | let mealCategoriesSection = document.querySelector('.meal-categories-section')
41 | let recipeSection = document.querySelector('.recipe-section')
42 | let searchSection = document.querySelector('.search-section')
43 | let closeBtn = document.querySelector('.close-btn')
44 |
45 | //REPRESENTS A MEAL
46 | class MealCard {
47 | constructor(data) {
48 | this.name = data.strMeal
49 | this.type = data.strCategory
50 | this.area = data.strArea
51 | this.thumb = data.strMealThumb
52 | this.id = data.idMeal
53 | }
54 | }
55 |
56 | //HANDLES UI TASKS
57 | class UI {
58 |
59 | //create recipe
60 | static createRecipe(data) {
61 | const meals = Storage.getMealFromLS();
62 | if (meals.includes(data.meals[0].idMeal)) {
63 | recipeSection.innerHTML = `
64 |
65 |
66 |
![${ data.meals[0].strMeal }](${ data.meals[0].strMealThumb })
67 |
68 |
69 |
70 | ${ data.meals[0].strMeal }
71 |
72 |
73 |
Category:
74 |
${ data.meals[0].strCategory }
75 |
76 |
77 |
Area:
78 |
${ data.meals[0].strArea }
79 |
80 |
81 |
82 |
83 |
84 |
Ingredients
85 |
86 | - ${ data.meals[0].strIngredient1 }${ data.meals[0].strMeasure1 }
87 | - ${ data.meals[0].strIngredient2 }${ data.meals[0].strMeasure2 }
88 | - ${ data.meals[0].strIngredient3 }${ data.meals[0].strMeasure3 }
89 | - ${ data.meals[0].strIngredient4 }${ data.meals[0].strMeasure4 }
90 | - ${ data.meals[0].strIngredient5 }${ data.meals[0].strMeasure5 }
91 | - ${ data.meals[0].strIngredient6 }${ data.meals[0].strMeasure6 }
92 | - ${ data.meals[0].strIngredient7 }${ data.meals[0].strMeasure7 }
93 | - ${ data.meals[0].strIngredient8 }${ data.meals[0].strMeasure8 }
94 | - ${ data.meals[0].strIngredient9 }${ data.meals[0].strMeasure9 }
95 | - ${ data.meals[0].strIngredient10 }${ data.meals[0].strMeasure10 }
96 | - ${ data.meals[0].strIngredient11 }${ data.meals[0].strMeasure11 }
97 | - ${ data.meals[0].strIngredient12 }${ data.meals[0].strMeasure12 }
98 | - ${ data.meals[0].strIngredient13 }${ data.meals[0].strMeasure13 }
99 | - ${ data.meals[0].strIngredient14 }${ data.meals[0].strMeasure14 }
- ${ data.meals[0].strIngredient15 }${ data.meals[0].strMeasure15 }
- ${ data.meals[0].strIngredient16 }${ data.meals[0].strMeasure16 }
100 | - ${ data.meals[0].strIngredient17 }${ data.meals[0].strMeasure17 }
101 | - ${ data.meals[0].strIngredient18 }${ data.meals[0].strMeasure18 }
102 | - ${ data.meals[0].strIngredient19 }${ data.meals[0].strMeasure19 }
103 | - ${ data.meals[0].strIngredient20 }${ data.meals[0].strMeasure20 }
104 |
105 |
106 |
107 |
Instructions
108 |
109 | "${ data.meals[0].strInstructions }"
110 |
111 |
Buon Appetito!
112 |
113 |
114 |
115 |
116 |
117 | `
118 | } else {
119 | recipeSection.innerHTML = `
120 |
121 |
122 |
![${ data.meals[0].strMeal }](${ data.meals[0].strMealThumb })
123 |
124 |
125 |
126 | ${ data.meals[0].strMeal }
127 |
128 |
129 |
Category:
130 |
${ data.meals[0].strCategory }
131 |
132 |
133 |
Area:
134 |
${ data.meals[0].strArea }
135 |
136 |
137 |
138 |
139 |
140 |
Ingredients
141 |
142 | - ${ data.meals[0].strIngredient1 }${ data.meals[0].strMeasure1 }
143 | - ${ data.meals[0].strIngredient2 }${ data.meals[0].strMeasure2 }
144 | - ${ data.meals[0].strIngredient3 }${ data.meals[0].strMeasure3 }
145 | - ${ data.meals[0].strIngredient4 }${ data.meals[0].strMeasure4 }
146 | - ${ data.meals[0].strIngredient5 }${ data.meals[0].strMeasure5 }
147 | - ${ data.meals[0].strIngredient6 }${ data.meals[0].strMeasure6 }
148 | - ${ data.meals[0].strIngredient7 }${ data.meals[0].strMeasure7 }
149 | - ${ data.meals[0].strIngredient8 }${ data.meals[0].strMeasure8 }
150 | - ${ data.meals[0].strIngredient9 }${ data.meals[0].strMeasure9 }
151 | - ${ data.meals[0].strIngredient10 }${ data.meals[0].strMeasure10 }
152 | - ${ data.meals[0].strIngredient11 }${ data.meals[0].strMeasure11 }
153 | - ${ data.meals[0].strIngredient12 }${ data.meals[0].strMeasure12 }
154 | - ${ data.meals[0].strIngredient13 }${ data.meals[0].strMeasure13 }
155 | - ${ data.meals[0].strIngredient14 }${ data.meals[0].strMeasure14 }
- ${ data.meals[0].strIngredient15 }${ data.meals[0].strMeasure15 }
- ${ data.meals[0].strIngredient16 }${ data.meals[0].strMeasure16 }
156 | - ${ data.meals[0].strIngredient17 }${ data.meals[0].strMeasure17 }
157 | - ${ data.meals[0].strIngredient18 }${ data.meals[0].strMeasure18 }
158 | - ${ data.meals[0].strIngredient19 }${ data.meals[0].strMeasure19 }
159 | - ${ data.meals[0].strIngredient20 }${ data.meals[0].strMeasure20 }
160 |
161 |
162 |
163 |
Instructions
164 |
165 | "${ data.meals[0].strInstructions }"
166 |
167 |
Buon Appetito!
168 |
169 |
170 |
171 |
172 |
173 | `
174 | }
175 |
176 | }
177 |
178 | //create meal card
179 | static createMeals(data) {
180 | if (data.meals === null) {
181 | return
182 | } else {
183 | //create a new meal obj
184 | data.meals.forEach((m) => {
185 | const meal = new MealCard(m)
186 | let gridItem = document.createElement('div')
187 | gridItem.classList.add('grid-item')
188 | gridItem.classList.add('meal')
189 | gridItem.id = `${ meal.id }`
190 | gridItem.innerHTML = `
191 |
192 |

193 |
194 |
195 |
${ meal.name }
196 |
${ meal.type }
197 |
${ meal.area }
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 | `
206 | grid.appendChild(gridItem)
207 | //if id is in LS, add pink to heart
208 | const meals = Storage.getMealFromLS();
209 | if (meals.includes(gridItem.id)) {
210 | gridItem.innerHTML = `
211 |
212 |

213 |
214 |
215 |
${ meal.name }
216 |
${ meal.type }
217 |
${ meal.area }
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 | `
226 | }
227 | })
228 | }
229 |
230 | }
231 | }
232 |
233 | //HANDLES STORAGE
234 |
235 | class Storage {
236 |
237 | static getMealFromLS() {
238 | let meals;
239 | if (localStorage.getItem('meals') === null) {
240 | meals = [];
241 | } else {
242 | meals = JSON.parse(localStorage.getItem('meals'));
243 | }
244 |
245 | return meals;
246 | }
247 |
248 | static addMealToLS(meal) {
249 | const meals = Storage.getMealFromLS();
250 | //check if id is already in LS
251 | if (meals.includes(meal)) {
252 | return
253 | } else {
254 | meals.push(meal);
255 | localStorage.setItem('meals', JSON.stringify(meals));
256 | }
257 | }
258 |
259 | static removeMealFromLS(id) {
260 | const meals = Storage.getMealFromLS();
261 | meals.forEach((meal, index) => {
262 | if (meal === id) {
263 | meals.splice(index, 1);
264 | }
265 | });
266 | localStorage.setItem('meals', JSON.stringify(meals));
267 | }
268 | }
269 |
270 | // EVENT LISTENERS
271 |
272 | //heart listener
273 | body.addEventListener('click', (e) => {
274 | if (e.target.classList.contains('fa-heart') && e.target.classList.contains('fas') && e.target.parentElement.parentElement.parentElement.classList.contains('grid-item') === true) {
275 | //change heart color and animate it
276 | e.target.classList.add('pink')
277 | e.target.classList.toggle('animate-heart')
278 | //add/remove favourite meal to/from storage
279 | let id = e.target.parentElement.parentElement.parentElement.id
280 | if (Storage.getMealFromLS().includes(id)) {
281 | e.target.classList.remove('pink')
282 | Storage.removeMealFromLS(id)
283 | } else {
284 | Storage.addMealToLS(id)
285 | }
286 | }
287 | })
288 |
289 | //heart listener on RECIPE page
290 | body.addEventListener('click', (e) => {
291 | if (e.target.classList.contains('fa-heart') && e.target.classList.contains('fas') && e.target.parentElement.parentElement.parentElement.classList.contains('grid-item') === false) { // HERE **********
292 | //change heart color and animate it
293 | e.target.classList.add('pink')
294 | e.target.classList.toggle('animate-heart')
295 | //add/remove favourite meal to/from storage
296 | let id = e.target.parentElement.id
297 | if (Storage.getMealFromLS().includes(id)) { // HERE **********
298 | e.target.classList.remove('pink')
299 | Storage.removeMealFromLS(id)
300 | recipeSection.addEventListener('click', (e) => {
301 | if (e.target.classList.contains('fa-times')) {
302 | location.reload() // HERE **********
303 | }
304 | })
305 | } else {
306 | Storage.addMealToLS(id)
307 | recipeSection.addEventListener('click', (e) => {
308 | if (e.target.classList.contains('fa-times')) {
309 | location.reload() // HERE **********
310 | }
311 | })
312 | }
313 | }
314 | })
315 |
316 | //favourite button listener
317 | body.addEventListener('click', (e) => {
318 | if (e.target.classList.contains('fa-heart') && e.target.classList.contains('far')) {
319 | console.log('hi');
320 | }
321 | })
322 |
323 | //call fetchMealByName() if search input has value
324 | searchInput.addEventListener('input', () => {
325 | let result = searchInput.value
326 | if (result.length === 0) {
327 | grid.innerHTML = ''
328 | } else {
329 | grid.innerHTML = ''
330 | fetchMealByName(result)
331 | }
332 | })
333 |
334 | //get meal id on grid card click
335 | grid.addEventListener('click', (e) => {
336 | if (e.target.classList.contains('grid-item')) {
337 | //get recipe
338 | fetchRecipe(e.target.id)
339 | //remove none class from recipe section
340 | recipeSection.classList.remove('none')
341 | //add none class to meal categories section
342 | searchSection.classList.add('none')
343 | //scroll to top of recipe
344 | container.scrollTo(0, 0)
345 |
346 |
347 | } else if (e.target.parentElement.parentElement.classList.contains('grid-item')) {
348 | //get recipe
349 | fetchRecipe(e.target.parentElement.parentElement.id)
350 | //remove none class from recipe section
351 | recipeSection.classList.remove('none')
352 | //add none class to meal categories section
353 | searchSection.classList.add('none')
354 | //scroll to top of recipe
355 | container.scrollTo(0, 0)
356 |
357 | }
358 | })
359 |
360 | //close button listener
361 | recipeSection.addEventListener('click', (e) => {
362 | if (e.target.classList.contains('fa-times')) {
363 | recipeSection.classList.add('none')
364 | //remove none class to search section
365 | searchSection.classList.remove('none')
366 | }
367 | })
368 |
369 |
370 |
371 | //TO DO:
372 |
373 |
374 | //1. swap \r\ in recipe for
using regex?
--------------------------------------------------------------------------------
/country.js:
--------------------------------------------------------------------------------
1 | //API DATA HANDLING
2 |
3 | //list of areas data
4 | const areaURL = 'https://www.themealdb.com/api/json/v1/1/list.php?a=list';
5 |
6 | //all meals from areas
7 | const areaMealsBaseURL = 'https://www.themealdb.com/api/json/v1/1/filter.php?a=';
8 |
9 | const idBaseURL = 'https://www.themealdb.com/api/json/v1/1/lookup.php?i=';
10 |
11 |
12 | //fetch list of areas
13 | async function fetchAllAreas() {
14 | try {
15 | const res = await fetch(areaURL)
16 | const data = await res.json()
17 | UI.insertAreas(data)
18 | } catch (err) {
19 | console.log('There has been an error', err);
20 | }
21 | }
22 |
23 | //fetch a meal by its area
24 | async function fetchMealsByArea(area) {
25 | try {
26 | const res = await fetch(areaMealsBaseURL + area)
27 | const data = await res.json()
28 | //call the getmealsbyid()
29 | data.meals.forEach((meal) => {
30 | fetchMealById(meal.idMeal)
31 | })
32 | } catch (err) {
33 | console.log('There has been an error', err);
34 | }
35 | }
36 |
37 | //fetch a meal by its id
38 | async function fetchMealById(id) {
39 | try {
40 | const res = await fetch(idBaseURL + id)
41 | const data = await res.json()
42 | //find a way to return an array of objects
43 | //console.log(data);
44 | UI.createMeals(data)
45 | } catch (err) {
46 | console.log('There has been an error', err);
47 | }
48 | }
49 |
50 | //get recipe with id
51 | async function fetchRecipe(id) {
52 | try {
53 | const res = await fetch(idBaseURL + id)
54 | const data = await res.json()
55 | //find a way to return an array of objects
56 | //console.log(data);
57 | UI.createRecipe(data)
58 | return data
59 | } catch (err) {
60 | console.log(err);
61 | }
62 | }
63 |
64 |
65 | //LOAD FUNCTIONS ON WINDOW LOAD
66 | document.addEventListener('DOMContentLoaded', () => {
67 |
68 | //fetch all areas
69 | fetchAllAreas()
70 | //show default american area
71 | fetchMealsByArea('american')
72 | })
73 |
74 | //////////////////////////////////////////////
75 |
76 | //variables
77 | let randomMealSection = document.querySelector('.random-meal-section');
78 | let mealCategoriesSection = document.querySelector('.meal-categories-section')
79 | let recipeSection = document.querySelector('.recipe-section')
80 | let countrySection = document.querySelector('.country-section')
81 | let closeBtn = document.querySelector('.close-btn')
82 | let categories = document.querySelectorAll('.category');
83 | let grid = document.querySelector('.grid-country')
84 | let container = document.querySelector('.container')
85 | let countryContainer = document.querySelector('.country-container')
86 | let body = document.querySelector('body')
87 | let gridItems = document.querySelectorAll('.grid-item')
88 | let categoryValue;
89 | let idsArr = []
90 | let meals = []
91 |
92 |
93 | //REPRESENTS A MEAL
94 | class MealCard {
95 | constructor(data) {
96 | this.name = data.meals[0].strMeal
97 | this.type = data.meals[0].strCategory
98 | this.area = data.meals[0].strArea
99 | this.thumb = data.meals[0].strMealThumb
100 | this.id = data.meals[0].idMeal
101 | }
102 | }
103 |
104 | //HANDLES UI TASKS
105 | class UI {
106 |
107 | //create recipe
108 | static createRecipe(data) {
109 | const meals = Storage.getMealFromLS();
110 | if (meals.includes(data.meals[0].idMeal)) {
111 | recipeSection.innerHTML = `
112 |
118 |
119 |
![${ data.meals[0].strMeal }](${ data.meals[0].strMealThumb })
120 |
121 |
122 |
123 | ${ data.meals[0].strMeal }
124 |
125 |
126 |
Category:
127 |
${ data.meals[0].strCategory }
128 |
129 |
130 |
Area:
131 |
${ data.meals[0].strArea }
132 |
133 |
134 |
135 |
136 |
137 |
Ingredients
138 |
139 | - ${ data.meals[0].strIngredient1 }${ data.meals[0].strMeasure1 }
140 | - ${ data.meals[0].strIngredient2 }${ data.meals[0].strMeasure2 }
141 | - ${ data.meals[0].strIngredient3 }${ data.meals[0].strMeasure3 }
142 | - ${ data.meals[0].strIngredient4 }${ data.meals[0].strMeasure4 }
143 | - ${ data.meals[0].strIngredient5 }${ data.meals[0].strMeasure5 }
144 | - ${ data.meals[0].strIngredient6 }${ data.meals[0].strMeasure6 }
145 | - ${ data.meals[0].strIngredient7 }${ data.meals[0].strMeasure7 }
146 | - ${ data.meals[0].strIngredient8 }${ data.meals[0].strMeasure8 }
147 | - ${ data.meals[0].strIngredient9 }${ data.meals[0].strMeasure9 }
148 | - ${ data.meals[0].strIngredient10 }${ data.meals[0].strMeasure10 }
149 | - ${ data.meals[0].strIngredient11 }${ data.meals[0].strMeasure11 }
150 | - ${ data.meals[0].strIngredient12 }${ data.meals[0].strMeasure12 }
151 | - ${ data.meals[0].strIngredient13 }${ data.meals[0].strMeasure13 }
152 | - ${ data.meals[0].strIngredient14 }${ data.meals[0].strMeasure14 }
- ${ data.meals[0].strIngredient15 }${ data.meals[0].strMeasure15 }
- ${ data.meals[0].strIngredient16 }${ data.meals[0].strMeasure16 }
153 | - ${ data.meals[0].strIngredient17 }${ data.meals[0].strMeasure17 }
154 | - ${ data.meals[0].strIngredient18 }${ data.meals[0].strMeasure18 }
155 | - ${ data.meals[0].strIngredient19 }${ data.meals[0].strMeasure19 }
156 | - ${ data.meals[0].strIngredient20 }${ data.meals[0].strMeasure20 }
157 |
158 |
159 |
160 |
Instructions
161 |
162 | "${ data.meals[0].strInstructions }"
163 |
164 |
Buon Appetito!
165 |
166 |
167 |
168 |
169 |
170 | `
171 | } else {
172 | recipeSection.innerHTML = `
173 |
179 |
180 |
![${ data.meals[0].strMeal }](${ data.meals[0].strMealThumb })
181 |
182 |
183 |
184 | ${ data.meals[0].strMeal }
185 |
186 |
187 |
Category:
188 |
${ data.meals[0].strCategory }
189 |
190 |
191 |
Area:
192 |
${ data.meals[0].strArea }
193 |
194 |
195 |
196 |
197 |
198 |
Ingredients
199 |
200 | - ${ data.meals[0].strIngredient1 }${ data.meals[0].strMeasure1 }
201 | - ${ data.meals[0].strIngredient2 }${ data.meals[0].strMeasure2 }
202 | - ${ data.meals[0].strIngredient3 }${ data.meals[0].strMeasure3 }
203 | - ${ data.meals[0].strIngredient4 }${ data.meals[0].strMeasure4 }
204 | - ${ data.meals[0].strIngredient5 }${ data.meals[0].strMeasure5 }
205 | - ${ data.meals[0].strIngredient6 }${ data.meals[0].strMeasure6 }
206 | - ${ data.meals[0].strIngredient7 }${ data.meals[0].strMeasure7 }
207 | - ${ data.meals[0].strIngredient8 }${ data.meals[0].strMeasure8 }
208 | - ${ data.meals[0].strIngredient9 }${ data.meals[0].strMeasure9 }
209 | - ${ data.meals[0].strIngredient10 }${ data.meals[0].strMeasure10 }
210 | - ${ data.meals[0].strIngredient11 }${ data.meals[0].strMeasure11 }
211 | - ${ data.meals[0].strIngredient12 }${ data.meals[0].strMeasure12 }
212 | - ${ data.meals[0].strIngredient13 }${ data.meals[0].strMeasure13 }
213 | - ${ data.meals[0].strIngredient14 }${ data.meals[0].strMeasure14 }
- ${ data.meals[0].strIngredient15 }${ data.meals[0].strMeasure15 }
- ${ data.meals[0].strIngredient16 }${ data.meals[0].strMeasure16 }
214 | - ${ data.meals[0].strIngredient17 }${ data.meals[0].strMeasure17 }
215 | - ${ data.meals[0].strIngredient18 }${ data.meals[0].strMeasure18 }
216 | - ${ data.meals[0].strIngredient19 }${ data.meals[0].strMeasure19 }
217 | - ${ data.meals[0].strIngredient20 }${ data.meals[0].strMeasure20 }
218 |
219 |
220 |
221 |
Instructions
222 |
223 | "${ data.meals[0].strInstructions }"
224 |
225 |
Buon Appetito!
226 |
227 |
228 |
229 |
230 |
231 | `
232 | }
233 |
234 | }
235 |
236 | //changes category background color on click
237 | static changeAreaColor() {
238 | let countries = document.querySelectorAll('.country')
239 | let america = countries[0]
240 | america.classList.add('active-background')
241 | countries.forEach((country) => {
242 | country.addEventListener('click', () => {
243 | countries.forEach((country) => {
244 | country.classList.remove('active-background')
245 | })
246 | country.classList.add('active-background')
247 | })
248 | })
249 | }
250 |
251 | //create meal card
252 | static createMeals(data) {
253 | //create a new meal obj
254 | const meal = new MealCard(data)
255 | let gridItem = document.createElement('div')
256 | gridItem.classList.add('grid-item')
257 | gridItem.classList.add('meal')
258 | gridItem.id = `${ meal.id }`
259 | gridItem.innerHTML = `
260 |
261 |

262 |
263 |
264 |
${ meal.name }
265 |
${ meal.type }
266 |
${ meal.area }
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 | `
275 | grid.appendChild(gridItem)
276 | //if id is in LS, add pink to heart
277 | const meals = Storage.getMealFromLS();
278 | if (meals.includes(gridItem.id)) {
279 | gridItem.innerHTML = `
280 |
281 |

282 |
283 |
284 |
${ meal.name }
285 |
${ meal.type }
286 |
${ meal.area }
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 | `
295 | }
296 | }
297 |
298 | //insert areas list
299 | static insertAreas(areas) {
300 | areas.meals.forEach((meal) => {
301 | let country = document.createElement('div')
302 | country.classList.add('country')
303 | country.id = meal.strArea
304 | country.innerHTML = `
305 |
${ meal.strArea }
306 | `
307 | countryContainer.appendChild(country)
308 |
309 | })
310 | UI.changeAreaColor()
311 | //add event listener on each country
312 | let countries = document.querySelectorAll('.country')
313 | countries.forEach((c) => {
314 | c.addEventListener('click', () => {
315 | grid.innerHTML = ''
316 | fetchMealsByArea(c.id)
317 | })
318 | })
319 | }
320 | }
321 |
322 | //HANDLES STORAGE
323 |
324 | class Storage {
325 |
326 | static getMealFromLS() {
327 | let meals;
328 | if (localStorage.getItem('meals') === null) {
329 | meals = [];
330 | } else {
331 | meals = JSON.parse(localStorage.getItem('meals'));
332 | }
333 |
334 | return meals;
335 | }
336 |
337 | static addMealToLS(meal) {
338 | const meals = Storage.getMealFromLS();
339 | //check if id is already in LS
340 | if (meals.includes(meal)) {
341 | return
342 | } else {
343 | meals.push(meal);
344 | localStorage.setItem('meals', JSON.stringify(meals));
345 | }
346 | }
347 |
348 | static removeMealFromLS(id) {
349 | const meals = Storage.getMealFromLS();
350 | meals.forEach((meal, index) => {
351 | if (meal === id) {
352 | meals.splice(index, 1);
353 | }
354 | });
355 | localStorage.setItem('meals', JSON.stringify(meals));
356 | }
357 | }
358 |
359 |
360 |
361 |
362 |
363 | // EVENT LISTENERS
364 |
365 |
366 | //get meal id on grid card click
367 | grid.addEventListener('click', (e) => {
368 | if (e.target.classList.contains('grid-item')) {
369 | //get recipe
370 | fetchRecipe(e.target.id)
371 | //remove none class from recipe section
372 | recipeSection.classList.remove('none')
373 | //add none class to country section
374 | countrySection.classList.add('none')
375 | //add none class to grid
376 | grid.classList.add('none')
377 | //scroll to top of recipe
378 | container.scrollTo(0, 0)
379 |
380 | } else if (e.target.parentElement.parentElement.classList.contains('grid-item')) {
381 | //get recipe
382 | fetchRecipe(e.target.parentElement.parentElement.id)
383 | //remove none class from recipe section
384 | recipeSection.classList.remove('none')
385 | //add none class to country section
386 | countrySection.classList.add('none')
387 | //add none class to grid
388 | grid.classList.add('none')
389 | //scroll to top of recipe
390 | container.scrollTo(0, 0)
391 | }
392 | })
393 |
394 | //close button listener
395 | recipeSection.addEventListener('click', (e) => {
396 | if (e.target.classList.contains('fa-times')) {
397 | //add none class from recipe section
398 | recipeSection.classList.add('none')
399 | //remove none class to country section
400 | countrySection.classList.remove('none')
401 | //remove none class to grid
402 | grid.classList.remove('none')
403 | }
404 | })
405 |
406 | //heart listener
407 | body.addEventListener('click', (e) => {
408 | if (e.target.classList.contains('fa-heart') && e.target.classList.contains('fas') && e.target.parentElement.parentElement.parentElement.classList.contains('grid-item') === true) { // HERE **********
409 | //change heart color and animate it
410 | e.target.classList.add('pink')
411 | e.target.classList.toggle('animate-heart')
412 | //add/remove favourite meal to/from storage
413 | let id = e.target.parentElement.parentElement.parentElement.id
414 | if (Storage.getMealFromLS().includes(id)) {
415 | e.target.classList.remove('pink')
416 | Storage.removeMealFromLS(id)
417 | } else {
418 | Storage.addMealToLS(id)
419 | }
420 | }
421 | })
422 |
423 | //heart listener on RECIPE page
424 | body.addEventListener('click', (e) => {
425 | if (e.target.classList.contains('fa-heart') && e.target.classList.contains('fas') && e.target.parentElement.parentElement.parentElement.classList.contains('grid-item') === false) { // HERE **********
426 | //change heart color and animate it
427 | e.target.classList.add('pink')
428 | e.target.classList.toggle('animate-heart')
429 | //add/remove favourite meal to/from storage
430 | let id = e.target.parentElement.id
431 | if (Storage.getMealFromLS().includes(id)) { // HERE **********
432 | e.target.classList.remove('pink')
433 | Storage.removeMealFromLS(id)
434 | recipeSection.addEventListener('click', (e) => {
435 | if (e.target.classList.contains('fa-times')) {
436 | location.reload() // HERE **********
437 | }
438 | })
439 | } else {
440 | Storage.addMealToLS(id)
441 | recipeSection.addEventListener('click', (e) => {
442 | if (e.target.classList.contains('fa-times')) {
443 | location.reload() // HERE **********
444 | }
445 | })
446 | }
447 | }
448 | })
449 |
450 | //favourite button listener
451 | body.addEventListener('click', (e) => {
452 | if (e.target.classList.contains('fa-heart') && e.target.classList.contains('far')) {
453 | console.log('hi');
454 | }
455 | })
456 |
457 | //countries scroll x on mouse hold
458 | let isDown = false;
459 | let startX;
460 | let scrollLeft;
461 |
462 | countryContainer.addEventListener('mousedown', (e) => {
463 | isDown = true;
464 | startX = e.pageX - countryContainer.offsetLeft;
465 | scrollLeft = countryContainer.scrollLeft;
466 | });
467 | countryContainer.addEventListener('mouseleave', () => {
468 | isDown = false;
469 | });
470 | countryContainer.addEventListener('mouseup', () => {
471 | isDown = false;
472 | });
473 | countryContainer.addEventListener('mousemove', (e) => {
474 | if (!isDown) return;
475 | e.preventDefault();
476 | const x = e.pageX - countryContainer.offsetLeft;
477 | const walk = (x - startX) * 3; //scroll-fast
478 | countryContainer.scrollLeft = scrollLeft - walk;
479 | });
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 | //TO DO:
488 |
489 | //1. swap \r\ in recipe for
using regex?
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | //API DATA HANDLING
2 | const randomMealURL = 'https://www.themealdb.com/api/json/v1/1/random.php';
3 |
4 | const categoryBaseURL = 'https://www.themealdb.com/api/json/v1/1/filter.php?c=';
5 |
6 | const idBaseURL = 'https://www.themealdb.com/api/json/v1/1/lookup.php?i=';
7 |
8 | //fetch random meal
9 | async function fetchRandomMeal() {
10 | try {
11 | const res = await fetch(randomMealURL)
12 | const data = await res.json()
13 | // let meal = new MealCard(data)
14 | // return meal.createRandomMealCard()
15 | UI.createRandomMealCard(data)
16 | } catch (err) {
17 | console.log(err);
18 | }
19 |
20 | }
21 |
22 | //fetch all meals within a category
23 | async function fetchCategoryMeals(category) {
24 | try {
25 | const res = await fetch(categoryBaseURL + category)
26 | const data = await res.json()
27 | return data
28 | //UI.getMealIds(data)
29 | } catch (err) {
30 | console.log(err);
31 | }
32 |
33 | }
34 |
35 | //fetch a meal by its id
36 | async function fetchMealById(id) {
37 | try {
38 | const res = await fetch(idBaseURL + id)
39 | const data = await res.json()
40 | //find a way to return an array of objects
41 | //console.log(data);
42 | UI.createMeals(data)
43 | return data
44 | } catch (err) {
45 | console.log(err);
46 | }
47 | }
48 |
49 | //get recipe with id
50 | async function fetchRecipe(id) {
51 | try {
52 | const res = await fetch(idBaseURL + id)
53 | const data = await res.json()
54 | //find a way to return an array of objects
55 | //console.log(data);
56 | UI.createRecipe(data)
57 | return data
58 | } catch (err) {
59 | console.log(err);
60 | }
61 | }
62 |
63 |
64 | //LOAD FUNCTIONS ON WINDOW LOAD
65 | document.addEventListener('DOMContentLoaded', () => {
66 | //fetch random meal
67 | fetchRandomMeal()
68 | setInterval(() => {
69 | UI.removeRandomMeal()
70 | fetchRandomMeal()
71 | }, 8000)
72 | //fetchRandomMeal()
73 | //
74 | UI.changeCategoryColor()
75 | //show default beef category
76 | UI.getCategoryMeals('beef')
77 | //show favourite meals
78 | })
79 |
80 | //////////////////////////////////////////////
81 |
82 | //variables
83 | let randomMealSection = document.querySelector('.random-meal-section');
84 | let mealCategoriesSection = document.querySelector('.meal-categories-section')
85 | let recipeSection = document.querySelector('.recipe-section')
86 | let closeBtn = document.querySelector('.close-btn')
87 | let categories = document.querySelectorAll('.category');
88 | let grid = document.querySelector('.grid')
89 | let body = document.querySelector('body')
90 | let container = document.querySelector('.container')
91 | let gridItems = document.querySelectorAll('.grid-item')
92 | let categoryValue;
93 | let idsArr = []
94 | let meals = []
95 |
96 |
97 | //REPRESENTS A MEAL
98 | class MealCard {
99 | constructor(data) {
100 | this.name = data.meals[0].strMeal
101 | this.type = data.meals[0].strCategory
102 | this.area = data.meals[0].strArea
103 | this.thumb = data.meals[0].strMealThumb
104 | this.id = data.meals[0].idMeal
105 | }
106 | }
107 |
108 | //HANDLES UI TASKS
109 | class UI {
110 |
111 | //create recipe
112 | static createRecipe(data) {
113 | const meals = Storage.getMealFromLS();
114 | if (meals.includes(data.meals[0].idMeal)) {
115 | recipeSection.innerHTML = `
116 |
122 |
![${ data.meals[0].strMeal }](${ data.meals[0].strMealThumb })
123 |
124 |