diff --git a/README.md b/README.md index 696ce0c..3af2718 100644 --- a/README.md +++ b/README.md @@ -1 +1,11 @@ -# Lab8-Starter \ No newline at end of file +# Lab8-Starter + +## Lab Partner +Samson + +## Deployed GitHub Pages URL +[http://127.0.0.1:5500/index.html](http://127.0.0.1:5500/index.html) + +## Graceful Degradation and Service Workers + +Graceful degradation means building a web app that still works even when some features fail or are not supported. In this project, if the user loses internet, the app still works because recipes are cached using the service worker and stored in localStorage. This keeps the app functional even when offline. diff --git a/assets/scripts/main.js b/assets/scripts/main.js index 144a28a..12fd217 100644 --- a/assets/scripts/main.js +++ b/assets/scripts/main.js @@ -2,12 +2,12 @@ // CONSTANTS const RECIPE_URLS = [ - 'https://adarsh249.github.io/Lab8-Starter/recipes/1_50-thanksgiving-side-dishes.json', - 'https://adarsh249.github.io/Lab8-Starter/recipes/2_roasting-turkey-breast-with-stuffing.json', - 'https://adarsh249.github.io/Lab8-Starter/recipes/3_moms-cornbread-stuffing.json', - 'https://adarsh249.github.io/Lab8-Starter/recipes/4_50-indulgent-thanksgiving-side-dishes-for-any-holiday-gathering.json', - 'https://adarsh249.github.io/Lab8-Starter/recipes/5_healthy-thanksgiving-recipe-crockpot-turkey-breast.json', - 'https://adarsh249.github.io/Lab8-Starter/recipes/6_one-pot-thanksgiving-dinner.json', + 'https://adarsh249.github.io/Lab8-Starter/recipes/1_50-thanksgiving-side-dishes.json', + 'https://adarsh249.github.io/Lab8-Starter/recipes/2_roasting-turkey-breast-with-stuffing.json', + 'https://adarsh249.github.io/Lab8-Starter/recipes/3_moms-cornbread-stuffing.json', + 'https://adarsh249.github.io/Lab8-Starter/recipes/4_50-indulgent-thanksgiving-side-dishes-for-any-holiday-gathering.json', + 'https://adarsh249.github.io/Lab8-Starter/recipes/5_healthy-thanksgiving-recipe-crockpot-turkey-breast.json', + 'https://adarsh249.github.io/Lab8-Starter/recipes/6_one-pot-thanksgiving-dinner.json', ]; // Run the init() function when the page has loaded @@ -15,115 +15,115 @@ window.addEventListener('DOMContentLoaded', init); // Starts the program, all function calls trace back here async function init() { - // initialize ServiceWorker - initializeServiceWorker(); - // Get the recipes from localStorage - let recipes; - try { - recipes = await getRecipes(); - } catch (err) { - console.error(err); - } - // Add each recipe to the
element - addRecipesToDocument(recipes); +// initialize ServiceWorker +initializeServiceWorker(); +// Get the recipes from localStorage +let recipes; +try { + recipes = await getRecipes(); +} catch (err) { + console.error(err); +} +// Add each recipe to the
element +addRecipesToDocument(recipes); } /** - * Detects if there's a service worker, then loads it and begins the process - * of installing it and getting it running - */ +* Detects if there's a service worker, then loads it and begins the process +* of installing it and getting it running +*/ function initializeServiceWorker() { - // EXPLORE - START (All explore numbers start with B) - /*******************/ - // ServiceWorkers have many uses, the most common of which is to manage - // local caches, intercept network requests, and conditionally serve from - // those local caches. This increases performance since users aren't - // re-downloading the same resources every single page visit. This also allows - // websites to have some (if not all) functionality offline! I highly - // recommend reading up on ServiceWorkers on MDN before continuing. - /*******************/ - // We first must register our ServiceWorker here before any of the code in - // sw.js is executed. - // B1. TODO - Check if 'serviceWorker' is supported in the current browser - // B2. TODO - Listen for the 'load' event on the window object. - // Steps B3-B6 will be *inside* the event listener's function created in B2 - // B3. TODO - Register './sw.js' as a service worker (The MDN article - // "Using Service Workers" will help you here) - // B4. TODO - Once the service worker has been successfully registered, console - // log that it was successful. - // B5. TODO - In the event that the service worker registration fails, console - // log that it has failed. - // STEPS B6 ONWARDS WILL BE IN /sw.js +// EXPLORE - START (All explore numbers start with B) +if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('./sw.js') + .then((registration) => { + console.log('Service Worker registered with scope:', registration.scope); + }) + .catch((error) => { + console.error('Service Worker registration failed:', error); + }); + }); +} } /** - * Reads 'recipes' from localStorage and returns an array of - * all of the recipes found (parsed, not in string form). If - * nothing is found in localStorage, network requests are made to all - * of the URLs in RECIPE_URLs, an array is made from those recipes, that - * array is saved to localStorage, and then the array is returned. - * @returns {Array} An array of recipes found in localStorage - */ +* Reads 'recipes' from localStorage and returns an array of +* all of the recipes found (parsed, not in string form). If +* nothing is found in localStorage, network requests are made to all +* of the URLs in RECIPE_URLs, an array is made from those recipes, that +* array is saved to localStorage, and then the array is returned. +* @returns {Array} An array of recipes found in localStorage +*/ async function getRecipes() { - // EXPOSE - START (All expose numbers start with A) - // A1. TODO - Check local storage to see if there are any recipes. - // If there are recipes, return them. - /**************************/ - // The rest of this method will be concerned with requesting the recipes - // from the network - // A2. TODO - Create an empty array to hold the recipes that you will fetch - // A3. TODO - Return a new Promise. If you are unfamiliar with promises, MDN - // has a great article on them. A promise takes one parameter - A - // function (we call these callback functions). That function will - // take two parameters - resolve, and reject. These are functions - // you can call to either resolve the Promise or Reject it. - /**************************/ - // A4-A11 will all be *inside* the callback function we passed to the Promise - // we're returning - /**************************/ +// EXPOSE - START (All expose numbers start with A) +// A1. TODO - Check local storage to see if there are any recipes. +// If there are recipes, return them. +const localData = localStorage.getItem('recipes'); +if (localData) { + return JSON.parse(localData); +} + +// A2. TODO - Create an empty array to hold the recipes that you will fetch +const recipes = []; + +// A3. TODO - Return a new Promise. If you are unfamiliar with promises, MDN +// has a great article on them. A promise takes one parameter - A +// function (we call these callback functions). That function will +// take two parameters - resolve, and reject. These are functions +// you can call to either resolve the Promise or Reject it. +return new Promise(async (resolve, reject) => { // A4. TODO - Loop through each recipe in the RECIPE_URLS array constant - // declared above - // A5. TODO - Since we are going to be dealing with asynchronous code, create - // a try / catch block. A6-A9 will be in the try portion, A10-A11 - // will be in the catch portion. - // A6. TODO - For each URL in that array, fetch the URL - MDN also has a great - // article on fetch(). NOTE: Fetches are ASYNCHRONOUS, meaning that - // you must either use "await fetch(...)" or "fetch.then(...)". This - // function is using the async keyword so we recommend "await" - // A7. TODO - For each fetch response, retrieve the JSON from it using .json(). - // NOTE: .json() is ALSO asynchronous, so you will need to use - // "await" again - // A8. TODO - Add the new recipe to the recipes array - // A9. TODO - Check to see if you have finished retrieving all of the recipes, - // if you have, then save the recipes to storage using the function - // we have provided. Then, pass the recipes array to the Promise's - // resolve() method. - // A10. TODO - Log any errors from catch using console.error - // A11. TODO - Pass any errors to the Promise's reject() function + for (let i = 0; i < RECIPE_URLS.length; i++) { + const url = RECIPE_URLS[i]; + // A5. TODO - Since we are going to be dealing with asynchronous code, create + // a try / catch block. A6-A9 will be in the try portion, A10-A11 + // will be in the catch portion. + try { + // A6. TODO - For each URL in that array, fetch the URL + const response = await fetch(url); + // A7. TODO - For each fetch response, retrieve the JSON from it using .json() + const recipeData = await response.json(); + // A8. TODO - Add the new recipe to the recipes array + recipes.push(recipeData); + // A9. TODO - Check to see if you have finished retrieving all of the recipes, + if (recipes.length === RECIPE_URLS.length) { + // Save to storage and resolve + saveRecipesToStorage(recipes); + resolve(recipes); + } + } catch (error) { + // A10. TODO - Log any errors from catch using console.error + console.error(error); + // A11. TODO - Pass any errors to the Promise's reject() function + reject(error); + } + } +}); } /** - * Takes in an array of recipes, converts it to a string, and then - * saves that string to 'recipes' in localStorage - * @param {Array} recipes An array of recipes - */ +* Takes in an array of recipes, converts it to a string, and then +* saves that string to 'recipes' in localStorage +* @param {Array} recipes An array of recipes +*/ function saveRecipesToStorage(recipes) { - localStorage.setItem('recipes', JSON.stringify(recipes)); +localStorage.setItem('recipes', JSON.stringify(recipes)); } /** - * Takes in an array of recipes and for each recipe creates a - * new element, adds the recipe data to that card - * using element.data = {...}, and then appends that new recipe - * to
- * @param {Array} recipes An array of recipes - */ +* Takes in an array of recipes and for each recipe creates a +* new element, adds the recipe data to that card +* using element.data = {...}, and then appends that new recipe +* to
+* @param {Array} recipes An array of recipes +*/ function addRecipesToDocument(recipes) { - if (!recipes) return; - let main = document.querySelector('main'); - recipes.forEach((recipe) => { - let recipeCard = document.createElement('recipe-card'); - recipeCard.data = recipe; - main.append(recipeCard); - }); -} \ No newline at end of file +if (!recipes) return; +let main = document.querySelector('main'); +recipes.forEach((recipe) => { + let recipeCard = document.createElement('recipe-card'); + recipeCard.data = recipe; + main.append(recipeCard); +}); +} diff --git a/index.html b/index.html index 79f4c26..c46d144 100644 --- a/index.html +++ b/index.html @@ -4,6 +4,7 @@ + Lab 8 - Network & ServiceWorkers diff --git a/manifest.json b/manifest.json index 0e9b6c7..c923905 100644 --- a/manifest.json +++ b/manifest.json @@ -1,17 +1,21 @@ { - "name": "", - "short_name": "", - "description": "", - "start_url": "", - "display": "", - "background_color": "", - "theme_color": "", - "icons": [ - { - "src": "/assets/images/icons/icon-192x192.png", - "sizes": "192x192", - "type": "image/png" - } - ] - } - \ No newline at end of file + "name": "Lab8-Starter", + "short_name": "Samson", + "description": "A recipe PWA that works offline", + "start_url": "/index.html", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#4CAF50", + "icons": [ + { + "src": "/assets/images/icons/icon-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/assets/images/icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/pwp.png b/pwp.png new file mode 100644 index 0000000..430fb3a Binary files /dev/null and b/pwp.png differ diff --git a/sw.js b/sw.js index 6e0a343..5a028f6 100644 --- a/sw.js +++ b/sw.js @@ -9,7 +9,25 @@ self.addEventListener('install', function (event) { caches.open(CACHE_NAME).then(function (cache) { // B6. TODO - Add all of the URLs from RECIPE_URLs here so that they are // added to the cache when the ServiceWorker is installed - return cache.addAll([]); + return cache.addAll([ + '/', + '/index.html', + '/assets/scripts/main.js', + '/assets/scripts/RecipeCard.js', + '/assets/styles/main.css', + '/assets/images/icons/0-star.svg', + '/assets/images/icons/1-star.svg', + '/assets/images/icons/2-star.svg', + '/assets/images/icons/3-star.svg', + '/assets/images/icons/4-star.svg', + '/assets/images/icons/5-star.svg', + '/recipes/1_50-thanksgiving-side-dishes.json', + '/recipes/2_roasting-turkey-breast-with-stuffing.json', + '/recipes/3_moms-cornbread-stuffing.json', + '/recipes/4_50-indulgent-thanksgiving-side-dishes-for-any-holiday-gathering.json', + '/recipes/5_healthy-thanksgiving-recipe-crockpot-turkey-breast.json', + '/recipes/6_one-pot-thanksgiving-dinner.json' + ]); }) ); }); @@ -37,4 +55,17 @@ self.addEventListener('fetch', function (event) { // B8. TODO - If the request is in the cache, return with the cached version. // Otherwise fetch the resource, add it to the cache, and return // network response. -}); \ No newline at end of file + event.respondWith( + caches.open(CACHE_NAME).then(function (cache) { + return cache.match(event.request).then(function (cachedResponse) { + if (cachedResponse) { + return cachedResponse; + } + return fetch(event.request).then(function (networkResponse) { + cache.put(event.request, networkResponse.clone()); + return networkResponse; + }); + }); + }) + ); +});