├── 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 | 5 | 6 | 7 | 8 | 9 | 10 | My Foods | Favourites 11 | 12 | 13 |
14 | 20 | 21 |
22 | 23 |
24 | 25 | 26 |
27 | 28 |
29 |
30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /country.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | My Foods | Country 11 | 12 | 13 |
14 | 15 | 21 | 22 |
23 | 24 |
25 |
26 |

Filter By Country

27 |
28 | 29 |
30 |
31 |
32 | 33 |
34 |
35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | My Foods | Search 11 | 12 | 13 |
14 | 20 | 21 |
22 | 23 |
24 |
25 |

Search

26 |
27 | 28 |
29 |
30 | 31 |
32 |
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 |
36 | 37 | 38 |
39 | 40 | 46 | 47 |
48 | 49 |
50 | 51 |
52 | 53 |
54 |
55 | 56 |
57 | 61 | 62 |
63 | 64 |
65 | 66 |
67 |
68 |
69 |

Category

70 | See All 71 |
72 |
73 |
74 |

Beef

75 |
76 |
77 |

Chicken

78 |
79 |
80 |

Dessert

81 |
82 |
83 |

Lamb

84 |
85 |
86 |

Misc

87 |
88 |
89 |

Pasta

90 |
91 |
92 |

Pork

93 |
94 |
95 |

Seafood

96 |
97 |
98 |

Side

99 |
100 |
101 |

Starter

102 |
103 |
104 |

Vegan

105 |
106 |
107 |

Veggies

108 |
109 |
110 |

Morning

111 |
112 |
113 |

Goat

114 |
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 |
98 |
99 |
100 | 101 |
102 |
103 |
104 | ${ data.meals[0].strMeal } 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 | meal 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 | meal 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 } 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 } 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 | meal 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 | meal 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 |
113 |
114 |
115 | 116 |
117 |
118 |
119 | ${ data.meals[0].strMeal } 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 |
174 |
175 |
176 | 177 |
178 |
179 |
180 | ${ data.meals[0].strMeal } 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 | meal 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 | meal 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 |
117 |
118 |
119 | 120 |
121 |
122 | ${ data.meals[0].strMeal } 123 |
124 |

125 | ${ data.meals[0].strMeal } 126 |

127 | 128 |
129 | 130 |

Category:

131 |

${ data.meals[0].strCategory }

132 |
133 |
134 |

Area:

135 |

${ data.meals[0].strArea }

136 |
137 |
138 | 139 |
140 |
141 |

Ingredients

142 | 162 |
163 |
164 |
Instructions
165 |

166 | "${ data.meals[0].strInstructions }" 167 |

168 |

Buon Appetito!

169 |
170 | 171 |
172 |
173 | 174 | ` 175 | } else { 176 | recipeSection.innerHTML = ` 177 |
178 |
179 |
180 | 181 |
182 |
183 | 184 | 185 |
186 |
187 | ${ data.meals[0].strMeal } 188 |
189 |
190 |

191 | ${ data.meals[0].strMeal } 192 |

193 |
194 |

Category:

195 |

${ data.meals[0].strCategory }

196 |
197 |
198 |

Area:

199 |

${ data.meals[0].strArea }

200 |
201 |
202 | 203 |
204 |
205 |

Ingredients

206 |
    207 |
  • ${ data.meals[0].strIngredient1 }${ data.meals[0].strMeasure1 }
  • 208 |
  • ${ data.meals[0].strIngredient2 }${ data.meals[0].strMeasure2 }
  • 209 |
  • ${ data.meals[0].strIngredient3 }${ data.meals[0].strMeasure3 }
  • 210 |
  • ${ data.meals[0].strIngredient4 }${ data.meals[0].strMeasure4 }
  • 211 |
  • ${ data.meals[0].strIngredient5 }${ data.meals[0].strMeasure5 }
  • 212 |
  • ${ data.meals[0].strIngredient6 }${ data.meals[0].strMeasure6 }
  • 213 |
  • ${ data.meals[0].strIngredient7 }${ data.meals[0].strMeasure7 }
  • 214 |
  • ${ data.meals[0].strIngredient8 }${ data.meals[0].strMeasure8 }
  • 215 |
  • ${ data.meals[0].strIngredient9 }${ data.meals[0].strMeasure9 }
  • 216 |
  • ${ data.meals[0].strIngredient10 }${ data.meals[0].strMeasure10 }
  • 217 |
  • ${ data.meals[0].strIngredient11 }${ data.meals[0].strMeasure11 }
  • 218 |
  • ${ data.meals[0].strIngredient12 }${ data.meals[0].strMeasure12 }
  • 219 |
  • ${ data.meals[0].strIngredient13 }${ data.meals[0].strMeasure13 }
  • 220 |
  • ${ data.meals[0].strIngredient14 }${ data.meals[0].strMeasure14 }
  • ${ data.meals[0].strIngredient15 }${ data.meals[0].strMeasure15 }
  • ${ data.meals[0].strIngredient16 }${ data.meals[0].strMeasure16 }
  • 221 |
  • ${ data.meals[0].strIngredient17 }${ data.meals[0].strMeasure17 }
  • 222 |
  • ${ data.meals[0].strIngredient18 }${ data.meals[0].strMeasure18 }
  • 223 |
  • ${ data.meals[0].strIngredient19 }${ data.meals[0].strMeasure19 }
  • 224 |
  • ${ data.meals[0].strIngredient20 }${ data.meals[0].strMeasure20 }
  • 225 |
226 |
227 |
228 |
Instructions
229 |

230 | "${ data.meals[0].strInstructions }" 231 |

232 |
233 | 234 |
235 |

Buon Appetito!

236 |
237 | 238 |
239 |
240 |
241 |
242 | ` 243 | } 244 | } 245 | 246 | //creates a random meal card 247 | static createRandomMealCard(data) { 248 | //create a new meal obj 249 | const meal = new MealCard(data) 250 | //create new div element 251 | let card = document.createElement('div'); 252 | //insert obj data in new div 253 | card.innerHTML = ` 254 |

255 |
256 |
257 |

${ meal.name }

258 |

${ meal.type }

259 |

${ meal.area }

260 | 261 |
262 |
263 | random meal 264 |
265 |
266 | ` 267 | //append div to its parent element 268 | randomMealSection.appendChild(card) 269 | 270 | const meals = Storage.getMealFromLS(); 271 | if (meals.includes(meal.id)) { 272 | card.innerHTML = ` 273 |

Random Meal

274 |
275 |
276 |

${ meal.name }

277 |

${ meal.type }

278 |

${ meal.area }

279 |
280 | 281 |
282 |
283 |
284 | random meal 285 |
286 |
287 | ` 288 | } 289 | } 290 | 291 | //changes category background color on click 292 | static changeCategoryColor() { 293 | categories.forEach((category) => { 294 | category.addEventListener('click', () => { 295 | categories.forEach((category) => { 296 | category.classList.remove('active-background') 297 | }) 298 | category.classList.add('active-background') 299 | UI.getCategoryMeals(category.id) 300 | //UI.showCategoryMeals(category) 301 | }) 302 | }) 303 | } 304 | 305 | //fetch all category ids and all meal by those ids 306 | static getCategoryMeals(cat) { 307 | fetchCategoryMeals(cat).then((res) => { 308 | for (let i = 0; i < res.meals.length; i++) { 309 | fetchMealById(res.meals[i].idMeal) 310 | } 311 | if (grid === null) { 312 | return 313 | } else { 314 | grid.innerHTML = '' 315 | } 316 | }) 317 | } 318 | 319 | //create meal card 320 | static createMeals(data) { 321 | //create a new meal obj 322 | const meal = new MealCard(data) 323 | let gridItem = document.createElement('div') 324 | gridItem.classList.add('grid-item') 325 | gridItem.classList.add('meal') 326 | gridItem.id = `${ meal.id }` 327 | gridItem.innerHTML = ` 328 |
329 | meal 330 |
331 |
332 |

${ meal.name }

333 |

${ meal.type }

334 |

${ meal.area }

335 |
336 |
337 |
338 | 339 |
340 | 341 |
342 | ` 343 | grid.appendChild(gridItem) 344 | //if id is in LS, add pink to heart 345 | const meals = Storage.getMealFromLS(); 346 | if (meals.includes(gridItem.id)) { 347 | gridItem.innerHTML = ` 348 |
349 | meal 350 |
351 |
352 |

${ meal.name }

353 |

${ meal.type }

354 |

${ meal.area }

355 |
356 |
357 |
358 | 359 |
360 | 361 |
362 | ` 363 | } 364 | } 365 | 366 | 367 | static removeRandomMeal() { 368 | randomMealSection.innerHTML = '' 369 | } 370 | 371 | 372 | } 373 | 374 | 375 | 376 | //HANDLES STORAGE 377 | 378 | class Storage { 379 | 380 | static getMealFromLS() { 381 | let meals; 382 | if (localStorage.getItem('meals') === null) { 383 | meals = []; 384 | } else { 385 | meals = JSON.parse(localStorage.getItem('meals')); 386 | } 387 | 388 | return meals; 389 | } 390 | 391 | static addMealToLS(meal) { 392 | const meals = Storage.getMealFromLS(); 393 | //check if id is already in LS 394 | if (meals.includes(meal)) { 395 | return 396 | } else { 397 | meals.push(meal); 398 | localStorage.setItem('meals', JSON.stringify(meals)); 399 | } 400 | } 401 | 402 | static removeMealFromLS(id) { 403 | const meals = Storage.getMealFromLS(); 404 | meals.forEach((meal, index) => { 405 | if (meal === id) { 406 | meals.splice(index, 1); 407 | } 408 | }); 409 | localStorage.setItem('meals', JSON.stringify(meals)); 410 | } 411 | } 412 | 413 | 414 | 415 | 416 | 417 | // EVENT LISTENERS 418 | 419 | //get meal id on random meal card click 420 | randomMealSection.addEventListener('click', (e) => { 421 | if (e.target.parentElement.classList.contains('random-meal-container')) { 422 | //fecth recipe 423 | fetchRecipe(e.target.parentElement.id) 424 | //remove none class from recipe section 425 | recipeSection.classList.remove('none') 426 | //add none class to random meal section 427 | randomMealSection.classList.add('none') 428 | //add none class to meal categories section 429 | mealCategoriesSection.classList.add('none') 430 | } else if (e.target.parentElement.parentElement.classList.contains('random-meal-container')) { 431 | //fetch recipe 432 | fetchRecipe(e.target.parentElement.parentElement.id) 433 | //remove none class from recipe section 434 | recipeSection.classList.remove('none') 435 | //add none class to random meal section 436 | randomMealSection.classList.add('none') 437 | //add none class to meal categories section 438 | mealCategoriesSection.classList.add('none') 439 | } 440 | }) 441 | 442 | //get meal id on grid card click 443 | grid.addEventListener('click', (e) => { 444 | if (e.target.classList.contains('grid-item')) { 445 | //get recipe 446 | fetchRecipe(e.target.id) 447 | //remove none class from recipe section 448 | recipeSection.classList.remove('none') 449 | //add none class to random meal section 450 | randomMealSection.classList.add('none') 451 | //add none class to meal categories section 452 | mealCategoriesSection.classList.add('none') 453 | //scroll to top of recipe 454 | container.scrollTo(0, 0) 455 | 456 | } else if (e.target.parentElement.parentElement.classList.contains('grid-item')) { 457 | //get recipe 458 | fetchRecipe(e.target.parentElement.parentElement.id) 459 | //remove none class from recipe section 460 | recipeSection.classList.remove('none') 461 | //add none class to random meal section 462 | randomMealSection.classList.add('none') 463 | //add none class to meal categories section 464 | mealCategoriesSection.classList.add('none') 465 | //scroll to top of recipe 466 | container.scrollTo(0, 0) 467 | } 468 | }) 469 | 470 | //close button listener 471 | recipeSection.addEventListener('click', (e) => { 472 | if (e.target.classList.contains('fa-times')) { 473 | recipeSection.classList.add('none') 474 | //remove none class to random meal section 475 | randomMealSection.classList.remove('none') 476 | //remove none class to meal categories section 477 | mealCategoriesSection.classList.remove('none') 478 | } 479 | }) 480 | 481 | /////////////////// MEAL CARD HEART /////////////////////// 482 | 483 | //heart listener 484 | body.addEventListener('click', (e) => { 485 | if (e.target.classList.contains('fa-heart') && e.target.classList.contains('fas') && e.target.parentElement.parentElement.parentElement.classList.contains('grid-item') === true) { 486 | //change heart color and animate it 487 | e.target.classList.add('pink') 488 | e.target.classList.toggle('animate-heart') 489 | //add/remove favourite meal to/from storage 490 | let id = e.target.parentElement.parentElement.parentElement.id 491 | if (Storage.getMealFromLS().includes(id)) { 492 | e.target.classList.remove('pink') 493 | Storage.removeMealFromLS(id) 494 | } else { 495 | Storage.addMealToLS(id) 496 | } 497 | } 498 | }) 499 | 500 | //heart listener on RECIPE page 501 | body.addEventListener('click', (e) => { 502 | if (e.target.classList.contains('fa-heart') && e.target.classList.contains('fas') && e.target.parentElement.parentElement.parentElement.classList.contains('grid-item') === false && e.target.parentElement.parentElement.parentElement.classList.contains('random-meal-container') === false) { // HERE ********** 503 | //change heart color and animate it 504 | e.target.classList.add('pink') 505 | e.target.classList.toggle('animate-heart') 506 | //add/remove favourite meal to/from storage 507 | let id = e.target.parentElement.id 508 | if (Storage.getMealFromLS().includes(id)) { // HERE ********** 509 | e.target.classList.remove('pink') 510 | Storage.removeMealFromLS(id) 511 | recipeSection.addEventListener('click', (e) => { 512 | if (e.target.classList.contains('fa-times')) { 513 | location.reload() // HERE ********** 514 | } 515 | }) 516 | } else { 517 | Storage.addMealToLS(id) 518 | recipeSection.addEventListener('click', (e) => { 519 | if (e.target.classList.contains('fa-times')) { 520 | location.reload() // HERE ********** 521 | } 522 | }) 523 | } 524 | } 525 | }) 526 | 527 | 528 | /////////////////// RANDOM CARD HEART /////////////////////// 529 | 530 | //heart listener 531 | body.addEventListener('click', (e) => { 532 | if (e.target.classList.contains('fa-heart') && e.target.classList.contains('fas') && e.target.parentElement.parentElement.parentElement.classList.contains('random-meal-container') === true) { // HERE ********** 533 | //change heart color and animate it 534 | e.target.classList.add('pink') 535 | e.target.classList.toggle('animate-heart') 536 | //add/remove favourite meal to/from storage 537 | let id = e.target.parentElement.parentElement.parentElement.id 538 | if (Storage.getMealFromLS().includes(id)) { 539 | e.target.classList.remove('pink') 540 | Storage.removeMealFromLS(id) 541 | } else { 542 | Storage.addMealToLS(id) 543 | } 544 | } 545 | }) 546 | 547 | //heart listener on RECIPE page - not needed? 548 | 549 | //categories scroll x on mouse hold 550 | let categoriesContainer = document.querySelector('.categories-container') 551 | let isDown = false; 552 | let startX; 553 | let scrollLeft; 554 | 555 | categoriesContainer.addEventListener('mousedown', (e) => { 556 | isDown = true; 557 | startX = e.pageX - categoriesContainer.offsetLeft; 558 | scrollLeft = categoriesContainer.scrollLeft; 559 | }); 560 | categoriesContainer.addEventListener('mouseleave', () => { 561 | isDown = false; 562 | }); 563 | categoriesContainer.addEventListener('mouseup', () => { 564 | isDown = false; 565 | }); 566 | categoriesContainer.addEventListener('mousemove', (e) => { 567 | if (!isDown) return; 568 | e.preventDefault(); 569 | const x = e.pageX - categoriesContainer.offsetLeft; 570 | const walk = (x - startX) * 3; //scroll-fast 571 | categoriesContainer.scrollLeft = scrollLeft - walk; 572 | 573 | }); 574 | 575 | 576 | 577 | 578 | // Fungsi untuk menutup pemberitahuan 579 | function closeNotification() { 580 | document.getElementById("notification").style.display = "none"; 581 | // Simpan informasi penutupan pemberitahuan ke localStorage 582 | localStorage.setItem("notificationClosed", "true"); 583 | } 584 | 585 | // Cek apakah pemberitahuan sudah ditutup sebelumnya 586 | var isClosed = localStorage.getItem("notificationClosed"); 587 | if (!isClosed) { 588 | // Jika belum ditutup, tampilkan pemberitahuan 589 | document.getElementById("notification").style.display = "block"; 590 | } 591 | 592 | 593 | 594 | 595 | //TO DO: 596 | 597 | //1. on favourite page make the category filter work 598 | //2. swap \r\ or "." in recipe instructions for
using regex? --------------------------------------------------------------------------------