Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
# Lab8-Starter
# 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.
202 changes: 101 additions & 101 deletions assets/scripts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,128 +2,128 @@

// 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
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 <main> 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 <main> 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<Object>} 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<Object>} 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<Object>} 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<Object>} 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 <recipe-card> element, adds the recipe data to that card
* using element.data = {...}, and then appends that new recipe
* to <main>
* @param {Array<Object>} recipes An array of recipes
*/
* Takes in an array of recipes and for each recipe creates a
* new <recipe-card> element, adds the recipe data to that card
* using element.data = {...}, and then appends that new recipe
* to <main>
* @param {Array<Object>} 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);
});
}
if (!recipes) return;
let main = document.querySelector('main');
recipes.forEach((recipe) => {
let recipeCard = document.createElement('recipe-card');
recipeCard.data = recipe;
main.append(recipeCard);
});
}
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="manifest" href="manifest.json" />
<title>Lab 8 - Network & ServiceWorkers</title>

<!-- Turkey Favicon -->
Expand Down
36 changes: 20 additions & 16 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}

"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"
}
]
}
Binary file added pwp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 33 additions & 2 deletions sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
]);
})
);
});
Expand Down Expand Up @@ -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.
});
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;
});
});
})
);
});