From 7da31bd180f7d339f84214332834774071fc8fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Mon, 15 Dec 2025 21:26:20 +0100 Subject: [PATCH 01/21] feat: add prototype for Nutrient Tracker Dictionary lab prototype This adds a prototype for the Nutrient Tracker Dictionary lab. Currently, the prototype uses "for()" loops and ".sort()" method. Couldn't figure out how to use "Object.values()" in the functions. --- .../lab-nutrient-tracker-dictionary/script.js | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 fullstack-cert/js-projects/lab-nutrient-tracker-dictionary/script.js diff --git a/fullstack-cert/js-projects/lab-nutrient-tracker-dictionary/script.js b/fullstack-cert/js-projects/lab-nutrient-tracker-dictionary/script.js new file mode 100644 index 000000000..89b64e8a4 --- /dev/null +++ b/fullstack-cert/js-projects/lab-nutrient-tracker-dictionary/script.js @@ -0,0 +1,103 @@ +const chilli = { + "red beans": { + calories: 49, + protein: 3, + carbs: 9, + fat: 0.2, + }, + "brown rice": { + calories: 444, + protein: 10, + carbs: 93, + fat: 4, + }, + tomatoes: { + calories: 32, + protein: 2, + carbs: 7, + fat: 0.3, + }, + "sweet peppers": { + calories: 25, + protein: 1, + carbs: 6, + fat: 0.2, + }, + onions: { + calories: 44, + protein: 1, + carbs: 10, + fat: 0.1, + }, + corn: { + calories: 67, + protein: 2, + carbs: 15, + fat: 1, + }, + chickpeas: { + calories: 106, + protein: 6, + carbs: 16, + fat: 2, + }, + garlic: { + calories: 9, + protein: 1.44, + carbs: 7.38, + fat: 0.27, + }, +}; + +function cloneDictionary(dictionary) { + return { ...dictionary }; +} + +function addMealEntry(dictionary, entry) { + const macroKeys = ["calories", "protein", "carbs", "fat"]; + + if ( + !Object.keys(entry).includes("ingredient") || + typeof entry["ingredient"] !== "string" + ) { + return 'Entry must include a "ingredient" key, with a "string" value.'; + } + for (const macroKey of macroKeys) { + if ( + !Object.keys(entry).includes(macroKey) || + typeof entry[macroKey] !== "number" + ) { + return `Entry must include a "${macroKey}" key, with a "number" value.`; + } + } + + const clonedDictionary = cloneDictionary(dictionary); + const { ingredient } = entry; + const { calories, protein, carbs, fat } = entry; + + if (clonedDictionary[ingredient] === undefined) { + clonedDictionary[ingredient] = { calories, protein, carbs, fat }; + } + dictionary = clonedDictionary; + return clonedDictionary; +} + +function getIngredientTotals(dictionary, ingredient) { + if (!Object.keys(dictionary).includes(ingredient)) { + return `"${ingredient}" not found in the dictionary.`; + } else { + for (const clonedIngredient of Object.entries(dictionary)) { + if (clonedIngredient[0] === ingredient) { + return clonedIngredient; + } + } + } +} + +function listTopIngredients(dictionary, key, limit) { + const clonedDict = cloneDictionary(dictionary); + const ingredientsSortedPerMacro = Object.entries(clonedDict); + + ingredientsSortedPerMacro.sort((a, b) => b[1][key] - a[1][key]); + return ingredientsSortedPerMacro.slice(0, limit); +} From c2ca3e7efd4cf4050e2a3024e110158ac8fb53ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Tue, 16 Dec 2025 15:03:25 +0100 Subject: [PATCH 02/21] feat: add user-stories.md to the nutrient tracker prototype --- .../lab-nutrient-tracker-dictionary/user-stories.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 fullstack-cert/js-projects/lab-nutrient-tracker-dictionary/user-stories.md diff --git a/fullstack-cert/js-projects/lab-nutrient-tracker-dictionary/user-stories.md b/fullstack-cert/js-projects/lab-nutrient-tracker-dictionary/user-stories.md new file mode 100644 index 000000000..4db95a54f --- /dev/null +++ b/fullstack-cert/js-projects/lab-nutrient-tracker-dictionary/user-stories.md @@ -0,0 +1,13 @@ +In this lab, you will aggregate nutritional macros per ingredient inside nested dictionaries. + +**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab. + +**User Stories:** + +1. You should accept meal entries shaped like `{ ingredient, calories, protein, carbs, fat }`. +2. You should implement `addMealEntry(dictionary, entry)` that clones and updates cumulative macros per ingredient. +3. You should implement `getIngredientTotals(dictionary, ingredient)` with guard clauses for unknown ingredients. +4. You should implement `listTopIngredients(dictionary, key, limit)` that sorts descending by a macro. +5. You should implement `cloneDictionary(dictionary)` to protect against accidental mutations. + +Note: Use `Object.keys`, `Object.values`, and `Object.entries`. Include tests for aggregation accuracy and cloning behavior. From fba7edb468e13da4050cbbaafb0fd069c192c54e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Sat, 20 Dec 2025 00:44:44 +0100 Subject: [PATCH 03/21] chore: renamed "lab-nutrient-tracker-dictionary" directory Changed "lab-nutrient-tracker-dictionary" to "lab-guild-loot-tracker" to fit the new topic for the Lab. --- .../script.js | 0 .../user-stories.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename fullstack-cert/js-projects/{lab-nutrient-tracker-dictionary => lab-guild-loot-tracker}/script.js (100%) rename fullstack-cert/js-projects/{lab-nutrient-tracker-dictionary => lab-guild-loot-tracker}/user-stories.md (100%) diff --git a/fullstack-cert/js-projects/lab-nutrient-tracker-dictionary/script.js b/fullstack-cert/js-projects/lab-guild-loot-tracker/script.js similarity index 100% rename from fullstack-cert/js-projects/lab-nutrient-tracker-dictionary/script.js rename to fullstack-cert/js-projects/lab-guild-loot-tracker/script.js diff --git a/fullstack-cert/js-projects/lab-nutrient-tracker-dictionary/user-stories.md b/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md similarity index 100% rename from fullstack-cert/js-projects/lab-nutrient-tracker-dictionary/user-stories.md rename to fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md From 492458a4829d1f2f07d78d2d50538dd1909cbdf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Sat, 20 Dec 2025 00:54:06 +0100 Subject: [PATCH 04/21] docs: change "nutrient tracker" references in lab's "user-stories.md" Changes all references in "user-stories.md" from nutrient tracking to guild loot management, and "dictionaries" to "objects". --- .../lab-guild-loot-tracker/user-stories.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md b/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md index 4db95a54f..90ead6d52 100644 --- a/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md +++ b/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md @@ -1,13 +1,13 @@ -In this lab, you will aggregate nutritional macros per ingredient inside nested dictionaries. +In this lab, you will aggregate resource totals per guild member inside nested objects. **Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab. **User Stories:** -1. You should accept meal entries shaped like `{ ingredient, calories, protein, carbs, fat }`. -2. You should implement `addMealEntry(dictionary, entry)` that clones and updates cumulative macros per ingredient. -3. You should implement `getIngredientTotals(dictionary, ingredient)` with guard clauses for unknown ingredients. -4. You should implement `listTopIngredients(dictionary, key, limit)` that sorts descending by a macro. -5. You should implement `cloneDictionary(dictionary)` to protect against accidental mutations. +1. You should accept meal entries shaped like `{ member, gold, silver, reputation, experience }`. +2. You should implement `addLootEntry(object, entry)` that clones and updates cumulative resources per member. +3. You should implement `getMemberTotals(object, member)` with guard clauses for unknown members. +4. You should implement `listTopMembers(object, key, limit)` that sorts descending by a specific resource type (e.g., finding the member with the most `gold`). +5. You should implement `cloneGuildData(object)` to protect against accidental mutations. Note: Use `Object.keys`, `Object.values`, and `Object.entries`. Include tests for aggregation accuracy and cloning behavior. From d790e41c9a71d84f3cf70d96bf6ad3ed4d2adae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Sat, 20 Dec 2025 03:00:39 +0100 Subject: [PATCH 05/21] refactor: rewrite of "nutrient tracker" functions Changed references "nutrient tracking" references in "script.js" to tracking guild loot management. --- .../lab-guild-loot-tracker/script.js | 136 ++++++++---------- 1 file changed, 61 insertions(+), 75 deletions(-) diff --git a/fullstack-cert/js-projects/lab-guild-loot-tracker/script.js b/fullstack-cert/js-projects/lab-guild-loot-tracker/script.js index 89b64e8a4..c122cffd2 100644 --- a/fullstack-cert/js-projects/lab-guild-loot-tracker/script.js +++ b/fullstack-cert/js-projects/lab-guild-loot-tracker/script.js @@ -1,103 +1,89 @@ -const chilli = { - "red beans": { - calories: 49, - protein: 3, - carbs: 9, - fat: 0.2, +const guild = { + nemo: { + gold: 31, + silver: 48, + reputation: 9, + experience: 198, }, - "brown rice": { - calories: 444, - protein: 10, - carbs: 93, - fat: 4, + shamin: { + gold: 78, + silver: 64, + reputation: 12, + experience: 111, }, - tomatoes: { - calories: 32, - protein: 2, - carbs: 7, - fat: 0.3, + ahlerich: { + gold: 41, + silver: 7, + reputation: 7, + experience: 70, }, - "sweet peppers": { - calories: 25, - protein: 1, - carbs: 6, - fat: 0.2, + corlandus: { + gold: 81, + silver: 2, + reputation: 20, + experience: 220, }, - onions: { - calories: 44, - protein: 1, - carbs: 10, - fat: 0.1, + pedro: { + gold: 34, + silver: 28, + reputation: 10, + experience: 179, }, - corn: { - calories: 67, - protein: 2, - carbs: 15, - fat: 1, - }, - chickpeas: { - calories: 106, - protein: 6, - carbs: 16, - fat: 2, - }, - garlic: { - calories: 9, - protein: 1.44, - carbs: 7.38, - fat: 0.27, + morgat: { + gold: 36, + silver: 81, + reputation: 12, + experience: 82, }, }; -function cloneDictionary(dictionary) { - return { ...dictionary }; +function cloneGuildData(object) { + return { ...object }; } -function addMealEntry(dictionary, entry) { - const macroKeys = ["calories", "protein", "carbs", "fat"]; +function addLootEntry(object, entry) { + const memberData = ["gold", "silver", "reputation", "experience"]; if ( - !Object.keys(entry).includes("ingredient") || - typeof entry["ingredient"] !== "string" + !Object.keys(entry).includes("member") || + typeof entry["member"] !== "string" ) { - return 'Entry must include a "ingredient" key, with a "string" value.'; + return 'Entry must include a "member" key, with a "string" value (the guild member name).'; } - for (const macroKey of macroKeys) { + + for (const value of memberData) { if ( - !Object.keys(entry).includes(macroKey) || - typeof entry[macroKey] !== "number" + !Object.keys(entry).includes(value) || + typeof entry[value] !== "number" ) { - return `Entry must include a "${macroKey}" key, with a "number" value.`; + return `Entry must include a "${value}" key, with a "number" value.`; } } - const clonedDictionary = cloneDictionary(dictionary); - const { ingredient } = entry; - const { calories, protein, carbs, fat } = entry; + const clonedGuildData = cloneGuildData(object); + const { member } = entry; + const { gold, silver, reputation, experience } = entry; - if (clonedDictionary[ingredient] === undefined) { - clonedDictionary[ingredient] = { calories, protein, carbs, fat }; - } - dictionary = clonedDictionary; - return clonedDictionary; + clonedGuildData[member] = { gold, silver, reputation, experience }; + + return clonedGuildData; } -function getIngredientTotals(dictionary, ingredient) { - if (!Object.keys(dictionary).includes(ingredient)) { - return `"${ingredient}" not found in the dictionary.`; +function getMemberTotals(object, member) { + if (!Object.keys(object).includes(member)) { + return `"${ingredient}" not found in the guild roster.`; } else { - for (const clonedIngredient of Object.entries(dictionary)) { - if (clonedIngredient[0] === ingredient) { - return clonedIngredient; - } - } + return `${member}'s totals - gold: ${Object.values(object[member])[0]}, silver: ${Object.values(object[member])[1]} reputation: ${Object.values(object[member])[2]}, experience: ${Object.values(object[member])[3]}`; } } -function listTopIngredients(dictionary, key, limit) { - const clonedDict = cloneDictionary(dictionary); - const ingredientsSortedPerMacro = Object.entries(clonedDict); +function listTopMembers(object, key, limit) { + const membersSortedByValue = Object.entries(object); + let returnObject = {}; - ingredientsSortedPerMacro.sort((a, b) => b[1][key] - a[1][key]); - return ingredientsSortedPerMacro.slice(0, limit); + membersSortedByValue.sort((a, b) => b[1][key] - a[1][key]); + for (let member of membersSortedByValue.slice(0, limit)) { + returnObject[member[0]] = member[1]; + } + return returnObject; } From d510dcbf9d86f75fbeebb009ff88a185d0c4b416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Tue, 30 Dec 2025 03:06:47 +0100 Subject: [PATCH 06/21] docs: changed the lab's summary section to be more descriptive --- .../lab-guild-loot-tracker/user-stories.md | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md b/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md index 90ead6d52..749678451 100644 --- a/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md +++ b/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md @@ -1,13 +1,29 @@ -In this lab, you will aggregate resource totals per guild member inside nested objects. +You are creating a set of functions that help a group of role-playing game players (a guild) keep track of, and update their character's resources. The guild is represented as an object containing entries with each of the guild members' resources. These entries are also objects, with properties describing amounts of gold, silver, reputation, and experience belonging to that member. + +All functions take a `guild` object containing records of the guild member's resources as their first parameter. + +The `cloneGuildData` function returns a copy of the object passed into it as its parameter. It is used in the other functions to prevent accidentally overwriting values in the original object. + +The `addLootEntry` function takes an object shaped like `{ member, gold, silver, reputation, experience }` as its second parameter. + +- If the `guild` object contains an entry with the same name as the `member` value in its second parameter, it updates the resources of that member. +- If it does not contain that entry, it adds the member entry to the object. + +The `getMemberTotals` function takes a `member` string with a name of one of the guild members as the second parameter. + +- If the `member` is not listed in the guild, the function should return a message: `[member] is not a member of the guild` +- Otherwise, the function should return the `guild[member]` object + +The `listTopMembers` function takes `key` as the second, and `limit` as the third parameter. It returns the `guild` object, with keys sorted in descending order by a specific resource (for example, by the amount of `gold` the characters have). The `limit` parameter describes how many keys should the returned object contain. **Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab. **User Stories:** -1. You should accept meal entries shaped like `{ member, gold, silver, reputation, experience }`. +1. You should accept loot entries shaped like `{ member, gold, silver, reputation, experience }`. 2. You should implement `addLootEntry(object, entry)` that clones and updates cumulative resources per member. 3. You should implement `getMemberTotals(object, member)` with guard clauses for unknown members. 4. You should implement `listTopMembers(object, key, limit)` that sorts descending by a specific resource type (e.g., finding the member with the most `gold`). -5. You should implement `cloneGuildData(object)` to protect against accidental mutations. +5. You should implement `cloneGuildData(object)` to protect against accidental mutations by creating a copy of the object, and making changes only to that copy. Note: Use `Object.keys`, `Object.values`, and `Object.entries`. Include tests for aggregation accuracy and cloning behavior. From b59a232344d819123a96622ac5f6e91ca359acb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Mon, 9 Feb 2026 01:46:20 +0100 Subject: [PATCH 07/21] refactor: change "listTopMembers" function to "listMembers" Removed the "listTopMembers" function and added "listMembers", which logs the input object entries to the console. --- .../js-projects/lab-guild-loot-tracker/script.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/fullstack-cert/js-projects/lab-guild-loot-tracker/script.js b/fullstack-cert/js-projects/lab-guild-loot-tracker/script.js index c122cffd2..96a7645df 100644 --- a/fullstack-cert/js-projects/lab-guild-loot-tracker/script.js +++ b/fullstack-cert/js-projects/lab-guild-loot-tracker/script.js @@ -77,13 +77,8 @@ function getMemberTotals(object, member) { } } -function listTopMembers(object, key, limit) { - const membersSortedByValue = Object.entries(object); - let returnObject = {}; - - membersSortedByValue.sort((a, b) => b[1][key] - a[1][key]); - for (let member of membersSortedByValue.slice(0, limit)) { - returnObject[member[0]] = member[1]; - } - return returnObject; +function listMembers(object) { + console.log(Object.entries(object)); } + +listMembers(guild); \ No newline at end of file From 789a19fef6738e82e52f7a7ed0e26df16c9a2d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Mon, 9 Feb 2026 01:50:05 +0100 Subject: [PATCH 08/21] docs: remove the "listTopMembers" mentions from "user-stories.md", add description of "listMembers" Replaces description of the removed "listTopMembers" function with one for "listMembers". --- .../js-projects/lab-guild-loot-tracker/user-stories.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md b/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md index 749678451..786bdbe18 100644 --- a/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md +++ b/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md @@ -9,21 +9,21 @@ The `addLootEntry` function takes an object shaped like `{ member, gold, silver, - If the `guild` object contains an entry with the same name as the `member` value in its second parameter, it updates the resources of that member. - If it does not contain that entry, it adds the member entry to the object. +The `listMembers` function takes only one parameter (the `guild` object) It prints out the object entries to the console. + The `getMemberTotals` function takes a `member` string with a name of one of the guild members as the second parameter. - If the `member` is not listed in the guild, the function should return a message: `[member] is not a member of the guild` - Otherwise, the function should return the `guild[member]` object -The `listTopMembers` function takes `key` as the second, and `limit` as the third parameter. It returns the `guild` object, with keys sorted in descending order by a specific resource (for example, by the amount of `gold` the characters have). The `limit` parameter describes how many keys should the returned object contain. - **Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab. **User Stories:** 1. You should accept loot entries shaped like `{ member, gold, silver, reputation, experience }`. 2. You should implement `addLootEntry(object, entry)` that clones and updates cumulative resources per member. -3. You should implement `getMemberTotals(object, member)` with guard clauses for unknown members. -4. You should implement `listTopMembers(object, key, limit)` that sorts descending by a specific resource type (e.g., finding the member with the most `gold`). +3. You should implement `listMembers(object)` that prints out a list of guild members. +4. You should implement `getMemberTotals(object, member)` with guard clauses for unknown members. 5. You should implement `cloneGuildData(object)` to protect against accidental mutations by creating a copy of the object, and making changes only to that copy. Note: Use `Object.keys`, `Object.values`, and `Object.entries`. Include tests for aggregation accuracy and cloning behavior. From 615c8a5e93953d9da7838f181d29dbb3ed1264dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Thu, 19 Mar 2026 03:26:17 +0100 Subject: [PATCH 09/21] refactor: removed users stories for Guild Loot Tracker This removes user stories from the prototype - Guild Loot Tracker is supposed to be a workshop, and workshops don't use them. --- .../lab-guild-loot-tracker/user-stories.md | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md diff --git a/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md b/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md deleted file mode 100644 index 786bdbe18..000000000 --- a/fullstack-cert/js-projects/lab-guild-loot-tracker/user-stories.md +++ /dev/null @@ -1,29 +0,0 @@ -You are creating a set of functions that help a group of role-playing game players (a guild) keep track of, and update their character's resources. The guild is represented as an object containing entries with each of the guild members' resources. These entries are also objects, with properties describing amounts of gold, silver, reputation, and experience belonging to that member. - -All functions take a `guild` object containing records of the guild member's resources as their first parameter. - -The `cloneGuildData` function returns a copy of the object passed into it as its parameter. It is used in the other functions to prevent accidentally overwriting values in the original object. - -The `addLootEntry` function takes an object shaped like `{ member, gold, silver, reputation, experience }` as its second parameter. - -- If the `guild` object contains an entry with the same name as the `member` value in its second parameter, it updates the resources of that member. -- If it does not contain that entry, it adds the member entry to the object. - -The `listMembers` function takes only one parameter (the `guild` object) It prints out the object entries to the console. - -The `getMemberTotals` function takes a `member` string with a name of one of the guild members as the second parameter. - -- If the `member` is not listed in the guild, the function should return a message: `[member] is not a member of the guild` -- Otherwise, the function should return the `guild[member]` object - -**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab. - -**User Stories:** - -1. You should accept loot entries shaped like `{ member, gold, silver, reputation, experience }`. -2. You should implement `addLootEntry(object, entry)` that clones and updates cumulative resources per member. -3. You should implement `listMembers(object)` that prints out a list of guild members. -4. You should implement `getMemberTotals(object, member)` with guard clauses for unknown members. -5. You should implement `cloneGuildData(object)` to protect against accidental mutations by creating a copy of the object, and making changes only to that copy. - -Note: Use `Object.keys`, `Object.values`, and `Object.entries`. Include tests for aggregation accuracy and cloning behavior. From c1777d2300bd472fb20800f45f4a6eff291554bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Thu, 19 Mar 2026 03:29:30 +0100 Subject: [PATCH 10/21] refactor: restructured Guild Loot Tracker prototype This changes the structure of the Guild Loot Tracker to fit better as the workshop. --- .../lab-guild-loot-tracker/script.js | 104 ++++++++++++------ 1 file changed, 71 insertions(+), 33 deletions(-) diff --git a/fullstack-cert/js-projects/lab-guild-loot-tracker/script.js b/fullstack-cert/js-projects/lab-guild-loot-tracker/script.js index 96a7645df..182afcaf7 100644 --- a/fullstack-cert/js-projects/lab-guild-loot-tracker/script.js +++ b/fullstack-cert/js-projects/lab-guild-loot-tracker/script.js @@ -1,4 +1,5 @@ -const guild = { +// Creating the "guild" object could be 2 or 3 Steps (creating "guild" object and adding the first "guild member" object as the first property - rest of the "guild members" can be filled out between the next steps) +let guild = { nemo: { gold: 31, silver: 48, @@ -37,48 +38,85 @@ const guild = { }, }; -function cloneGuildData(object) { - return { ...object }; +// `listMembers()` introduces `Object.entries()` static method (possibly also `Object.keys()` and `Object.values()`, depending on how long the description for each step is supposed to be) +function listMembers(guildObject) { + const guildEntries = Object.entries(guildObject); + + console.log(guildEntries); } -function addLootEntry(object, entry) { - const memberData = ["gold", "silver", "reputation", "experience"]; +// 'getMemberTotals()` can be 4 steps (introduce `Object.keys()` method, write a guard clause using `if()/else` and `.includes()`, introduce `Object.values()`, return a string literal with the result) - +function getMemberTotals(guildObject, member) { + const objectKeys = Object.keys(guildObject); - if ( - !Object.keys(entry).includes("member") || - typeof entry["member"] !== "string" - ) { - return 'Entry must include a "member" key, with a "string" value (the guild member name).'; + if (!objectKeys.includes(member)) { + return `"${member}" not found in the guild roster.`; + } else { + return `${member}'s totals\n gold: ${Object.values(guildObject[member])[0]},\n silver: ${Object.values(guildObject[member])[1]},\n reputation: ${Object.values(guildObject[member])[2]},\n experience: ${Object.values(guildObject[member])[3]}`; } +} - for (const value of memberData) { - if ( - !Object.keys(entry).includes(value) || - typeof entry[value] !== "number" - ) { - return `Entry must include a "${value}" key, with a "number" value.`; - } +// "cloneGuildData" can be broken into multiple (3?) steps to show that using the spread syntax creates a shallow copy of the object, for example #3 write the function and log it #4 assign the returned data to a variable, and change some of its values, then log both original and the copy to the console, #5 remove the variable and console.log() calls to continue +function cloneGuildData(guildObject) { + return { ...guildObject }; +} + +function addLootEntry(guildObject, entry) { + const clonedGuildData = cloneGuildData(guildObject); + const { member, ...memberData } = entry; + + if (typeof member !== "string") { + console.log(`Entry must include a "gold" key, with a "number" value.`); + return; } - const clonedGuildData = cloneGuildData(object); - const { member } = entry; - const { gold, silver, reputation, experience } = entry; + if ( + !Object.keys(memberData).includes("gold") || + typeof memberData["gold"] !== "number" + ) { + console.log('object must have a "gold" key!'); + } else if ( + !Object.keys(memberData).includes("silver") || + typeof memberData["silver"] !== "number" + ) { + console.log(`Entry must include a "silver" key, with a "number" value.`); + return; + } else if ( + !Object.keys(memberData).includes("reputation") || + typeof memberData["reputation"] !== "number" + ) { + console.log( + `Entry must include a "reputation" key, with a "number" value.`, + ); + return; + } else if ( + !Object.keys(memberData).includes("experience") || + typeof memberData["experience"] !== "number" + ) { + console.log( + `Entry must include a "experience" key, with a "number" value.`, + ); + return; + } - clonedGuildData[member] = { gold, silver, reputation, experience }; + clonedGuildData[member] = memberData; - return clonedGuildData; + guild = clonedGuildData; + console.log("Guild roster updated"); + return; } -function getMemberTotals(object, member) { - if (!Object.keys(object).includes(member)) { - return `"${ingredient}" not found in the guild roster.`; - } else { - return `${member}'s totals - gold: ${Object.values(object[member])[0]}, silver: ${Object.values(object[member])[1]} reputation: ${Object.values(object[member])[2]}, experience: ${Object.values(object[member])[3]}`; - } -} +//Test listMembers +listMembers(guild); -function listMembers(object) { - console.log(Object.entries(object)); -} +console.log(getMemberTotals(guild, "morgat")); + +addLootEntry(guild, { + member: "morgat", + gold: 1, + silver: 2, + reputation: 3, + experience: 4, +}); -listMembers(guild); \ No newline at end of file +console.log(getMemberTotals(guild, "morgat")); From 686091eb029f8fbbe2922ffd85a8faf914b0f036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Thu, 23 Apr 2026 22:11:19 +0200 Subject: [PATCH 11/21] chore: remove the "lab-" prefix from the prototype's directory name This renames the directory the code is contained in. It's done to avoid potential confusion, since the purpose of this prototype changed from a Lab to a Workshop. --- .../{lab-guild-loot-tracker => guild-loot-tracker}/script.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename fullstack-cert/js-projects/{lab-guild-loot-tracker => guild-loot-tracker}/script.js (100%) diff --git a/fullstack-cert/js-projects/lab-guild-loot-tracker/script.js b/fullstack-cert/js-projects/guild-loot-tracker/script.js similarity index 100% rename from fullstack-cert/js-projects/lab-guild-loot-tracker/script.js rename to fullstack-cert/js-projects/guild-loot-tracker/script.js From 3b7695f0fa90b0aa8d675b18f35f40560de771b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Thu, 23 Apr 2026 23:49:27 +0200 Subject: [PATCH 12/21] feat: add listMemberNames() function that introduces Object.keys() method --- .../js-projects/guild-loot-tracker/script.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/fullstack-cert/js-projects/guild-loot-tracker/script.js b/fullstack-cert/js-projects/guild-loot-tracker/script.js index 182afcaf7..5f099f2a4 100644 --- a/fullstack-cert/js-projects/guild-loot-tracker/script.js +++ b/fullstack-cert/js-projects/guild-loot-tracker/script.js @@ -1,4 +1,6 @@ -// Creating the "guild" object could be 2 or 3 Steps (creating "guild" object and adding the first "guild member" object as the first property - rest of the "guild members" can be filled out between the next steps) +// This will be a workshop for the Loops section, probably after the "Build a Profile Lookup" lab. + +// Creating the "guild" object could be 2 or 3 Steps (creating "guild" object itself and adding the first "guild member" object as the first property - rest of the "guild members" can be filled out somewhere between the next steps to avoid too much repetition). let guild = { nemo: { gold: 31, @@ -38,6 +40,17 @@ let guild = { }, }; +// 'listMemberNames()` introduces `Object.keys()` static method, and provides a list of current members of the guild. Could be 3 steps (1. create a function, 2. assign array returned by the `Object.keys()` to a variable + explanation of the method, ) +function listMemberNames(guildObject) { + console.log("Current Guild Members:"); + const memberNames = Object.keys(guildObject); + for (let i = 0; i < memberNames.length; i++) { + console.log(`${i + 1}. ${memberNames[i]}`); + } +} + +listMemberNames(guild); + // `listMembers()` introduces `Object.entries()` static method (possibly also `Object.keys()` and `Object.values()`, depending on how long the description for each step is supposed to be) function listMembers(guildObject) { const guildEntries = Object.entries(guildObject); From cb705aeb2c4d581f034ee4759a8de9765a8e0729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Fri, 24 Apr 2026 00:35:51 +0200 Subject: [PATCH 13/21] refactor: rewrite getMemberTotal() to return an object or `false` This changes the getMemberTotals() so it either return an object including the member's resources or `false` if the guild object doesn't include that member. --- .../js-projects/guild-loot-tracker/script.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/fullstack-cert/js-projects/guild-loot-tracker/script.js b/fullstack-cert/js-projects/guild-loot-tracker/script.js index 5f099f2a4..eb28b5bd3 100644 --- a/fullstack-cert/js-projects/guild-loot-tracker/script.js +++ b/fullstack-cert/js-projects/guild-loot-tracker/script.js @@ -61,14 +61,17 @@ function listMembers(guildObject) { // 'getMemberTotals()` can be 4 steps (introduce `Object.keys()` method, write a guard clause using `if()/else` and `.includes()`, introduce `Object.values()`, return a string literal with the result) - function getMemberTotals(guildObject, member) { const objectKeys = Object.keys(guildObject); - - if (!objectKeys.includes(member)) { - return `"${member}" not found in the guild roster.`; - } else { - return `${member}'s totals\n gold: ${Object.values(guildObject[member])[0]},\n silver: ${Object.values(guildObject[member])[1]},\n reputation: ${Object.values(guildObject[member])[2]},\n experience: ${Object.values(guildObject[member])[3]}`; + for (let i = 0; i < objectKeys.length; i++) { + if (objectKeys[i] === member) { + return guildObject[member]; + } } + return false; } +console.log(getMemberTotals(guild, "morgat")); +console.log(getMemberTotals(guild, "francis")); + // "cloneGuildData" can be broken into multiple (3?) steps to show that using the spread syntax creates a shallow copy of the object, for example #3 write the function and log it #4 assign the returned data to a variable, and change some of its values, then log both original and the copy to the console, #5 remove the variable and console.log() calls to continue function cloneGuildData(guildObject) { return { ...guildObject }; @@ -132,4 +135,3 @@ addLootEntry(guild, { experience: 4, }); -console.log(getMemberTotals(guild, "morgat")); From 77284daee07cbea5a026a4abf1c134369e9be3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Tue, 28 Apr 2026 00:54:38 +0200 Subject: [PATCH 14/21] refactor: rewrite 'listMembers', 'getMemberTotals', and 'addLootEntry', add coments This changes the way the "main" functions of the script work., and adds comments to make describing the workshop's steps easier. --- .../js-projects/guild-loot-tracker/script.js | 120 ++++++++++-------- 1 file changed, 70 insertions(+), 50 deletions(-) diff --git a/fullstack-cert/js-projects/guild-loot-tracker/script.js b/fullstack-cert/js-projects/guild-loot-tracker/script.js index eb28b5bd3..2a3689948 100644 --- a/fullstack-cert/js-projects/guild-loot-tracker/script.js +++ b/fullstack-cert/js-projects/guild-loot-tracker/script.js @@ -40,6 +40,9 @@ let guild = { }, }; +/* NOTE: +This method seemed like a good addition before I refactored `listMembers()` - it still might be a good(?), isolated example of `Object.keys()` use, but otherwise seems slightly redundant. +*/ // 'listMemberNames()` introduces `Object.keys()` static method, and provides a list of current members of the guild. Could be 3 steps (1. create a function, 2. assign array returned by the `Object.keys()` to a variable + explanation of the method, ) function listMemberNames(guildObject) { console.log("Current Guild Members:"); @@ -53,22 +56,34 @@ listMemberNames(guild); // `listMembers()` introduces `Object.entries()` static method (possibly also `Object.keys()` and `Object.values()`, depending on how long the description for each step is supposed to be) function listMembers(guildObject) { - const guildEntries = Object.entries(guildObject); - - console.log(guildEntries); + console.log("Guild Member's Resources:"); + // TODO: Simplify the loop iteration + for(const member in guildObject) { + // Use Object.keys() and Object.resources() to get a list of resources of each guild member + const resourceNames = Object.keys(guildObject[member]); + const resourceValues = Object.values(guildObject[member]); + console.log(`${member}:`); + console.log(`${resourceNames[0]}\t${resourceNames[1]}\t${resourceNames[2]}\t${resourceNames[3]}`) + console.log(`${resourceValues[0]}\t${resourceValues[1]}\t${resourceValues[2]}\t\t${resourceValues[3]}`) + } } +listMembers(guild); + +//TODO: bring the listTopMembers method back (now that the workshop is in the Loops section, sorting should be viable. In Theory. need to double-check that.) + // 'getMemberTotals()` can be 4 steps (introduce `Object.keys()` method, write a guard clause using `if()/else` and `.includes()`, introduce `Object.values()`, return a string literal with the result) - function getMemberTotals(guildObject, member) { - const objectKeys = Object.keys(guildObject); - for (let i = 0; i < objectKeys.length; i++) { - if (objectKeys[i] === member) { + const memberNames = Object.keys(guildObject); + // TODO: Simplify the loop iteration + for (const name in memberNames) { + if (memberNames[name] === member) { return guildObject[member]; } } return false; } - +// Checks if `getMemberTotals()` works correctly - can be one of the steps and removed later console.log(getMemberTotals(guild, "morgat")); console.log(getMemberTotals(guild, "francis")); @@ -78,55 +93,58 @@ function cloneGuildData(guildObject) { } function addLootEntry(guildObject, entry) { - const clonedGuildData = cloneGuildData(guildObject); - const { member, ...memberData } = entry; - - if (typeof member !== "string") { - console.log(`Entry must include a "gold" key, with a "number" value.`); - return; - } - - if ( - !Object.keys(memberData).includes("gold") || - typeof memberData["gold"] !== "number" - ) { - console.log('object must have a "gold" key!'); - } else if ( - !Object.keys(memberData).includes("silver") || - typeof memberData["silver"] !== "number" - ) { - console.log(`Entry must include a "silver" key, with a "number" value.`); + // Use cloneGuildData() to prevent accidental mutations before committing the changes to the 'guild object' + const clonedGuild = cloneGuildData(guildObject); + // Use `Object.keys() to get the guild member's + const guildMemberNames = Object.keys(guildObject); + // Use spread operator to get the member's name and resources out of the "entry" argument + const { member: memberName, ...newMemberTotals} = entry; + // Check if the member's name exists, and is a string + if (!memberName) { + console.log("New entry must include a name of the guild member!"); return; - } else if ( - !Object.keys(memberData).includes("reputation") || - typeof memberData["reputation"] !== "number" - ) { - console.log( - `Entry must include a "reputation" key, with a "number" value.`, - ); - return; - } else if ( - !Object.keys(memberData).includes("experience") || - typeof memberData["experience"] !== "number" - ) { - console.log( - `Entry must include a "experience" key, with a "number" value.`, - ); + } else if (typeof memberName !== "string") { + console.log("Member name must be a string!"); return; } - - clonedGuildData[member] = memberData; - - guild = clonedGuildData; - console.log("Guild roster updated"); + // Check if the guildObject includes a record of the provided member + for (let i = 0; i < guildMemberNames.length; i++) { + if (guildMemberNames[i] === memberName) { + // Now, we assign the member from the cloned data to a variable to make the loops more readable (we're not making a copy of the object! it could be worth pointing out the difference in the steps). + const currentMemberTotals = getMemberTotals(clonedGuild, memberName); + // We could check if the entry includes all of the required fields (the same as the existing member) + // First we check if the new entry has all of the required resources: + for (const resource in currentMemberTotals) { + // TODO: replace `Object.hasOwn() with a different method (going over the Object.keys() for example) + if (!Object.hasOwn(newMemberTotals, resource)) { + console.log(`Updated data is missing an entry for "${resource}"!`); + return; + } else if ( typeof newMemberTotals[resource] !== typeof currentMemberTotals[resource]){ + console.log(`"${resource}" must be a "${typeof currentMemberTotals[resource]}" variable!`); + return; + } + // Assigning new values could be done outside of the loop, but on the other hand, it might be how we can show how cloning the current data can prevent against accidental mutation on case where someone provides incorrect input data more clearly (maybe?). + currentMemberTotals[resource] = newMemberTotals[resource]; + } + // Then, we check if the new entry has ONLY the required resources NOTE: (this one is probably unnecessary, I'm not sure if ALL of the resources should actually be required to make an update): + // TODO: Simplify the loop iteration + for (const resource in currentMemberTotals) { + if (!Object.hasOwn(currentMemberTotals, resource)) { + console.log(`Updated data has an unknown entry "${resource}!`); + return; + } + } + // Finally, now we're sure our input was correct, we can overwrite the old data. + guild = clonedGuild; + console.log("Guild roster updated."); + return; + } + } + // In the case that the guildObject doesn't include a member with the given name, inform the user and return + console.log(`${memberName} is not a member of this guild!`); return; } -//Test listMembers -listMembers(guild); - -console.log(getMemberTotals(guild, "morgat")); - addLootEntry(guild, { member: "morgat", gold: 1, @@ -135,3 +153,5 @@ addLootEntry(guild, { experience: 4, }); +// Using `listMembers()` to check if the data has been updated +listMembers(guild); From 16cace1161dc2c97ec438f5482819b7f85e74cf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Fri, 1 May 2026 15:29:14 +0200 Subject: [PATCH 15/21] refactor: change the guild members' names This changes the names used in the workshop to less cunfusing ones (Thank you Jessica for the list!). --- .../js-projects/guild-loot-tracker/script.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fullstack-cert/js-projects/guild-loot-tracker/script.js b/fullstack-cert/js-projects/guild-loot-tracker/script.js index 2a3689948..2a25af006 100644 --- a/fullstack-cert/js-projects/guild-loot-tracker/script.js +++ b/fullstack-cert/js-projects/guild-loot-tracker/script.js @@ -2,37 +2,37 @@ // Creating the "guild" object could be 2 or 3 Steps (creating "guild" object itself and adding the first "guild member" object as the first property - rest of the "guild members" can be filled out somewhere between the next steps to avoid too much repetition). let guild = { - nemo: { + ethan: { gold: 31, silver: 48, reputation: 9, experience: 198, }, - shamin: { + elara: { gold: 78, silver: 64, reputation: 12, experience: 111, }, - ahlerich: { + brandon: { gold: 41, silver: 7, reputation: 7, experience: 70, }, - corlandus: { + dylan: { gold: 81, silver: 2, reputation: 20, experience: 220, }, - pedro: { + lucas: { gold: 34, silver: 28, reputation: 10, experience: 179, }, - morgat: { + natalie: { gold: 36, silver: 81, reputation: 12, @@ -84,7 +84,7 @@ function getMemberTotals(guildObject, member) { return false; } // Checks if `getMemberTotals()` works correctly - can be one of the steps and removed later -console.log(getMemberTotals(guild, "morgat")); +console.log(getMemberTotals(guild, "natalie")); console.log(getMemberTotals(guild, "francis")); // "cloneGuildData" can be broken into multiple (3?) steps to show that using the spread syntax creates a shallow copy of the object, for example #3 write the function and log it #4 assign the returned data to a variable, and change some of its values, then log both original and the copy to the console, #5 remove the variable and console.log() calls to continue @@ -146,7 +146,7 @@ function addLootEntry(guildObject, entry) { } addLootEntry(guild, { - member: "morgat", + member: "natalie", gold: 1, silver: 2, reputation: 3, From 529235e5b4f78ee6b1f98587f9aaa8f1dd2f0a86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Sat, 2 May 2026 13:51:44 +0200 Subject: [PATCH 16/21] refactor: changes the loop in listMemberNames function to 'for...in' for consistency This changes the 'for' loop in listMemberNames() to a 'for...in' loop to keep the used loops consistent, and adds the use of 'parseInt' to that loop. Also, adds missing semicolons to listMembers() function (probably should've made it into a separate commit). --- .../js-projects/guild-loot-tracker/script.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/fullstack-cert/js-projects/guild-loot-tracker/script.js b/fullstack-cert/js-projects/guild-loot-tracker/script.js index 2a25af006..41e572308 100644 --- a/fullstack-cert/js-projects/guild-loot-tracker/script.js +++ b/fullstack-cert/js-projects/guild-loot-tracker/script.js @@ -43,12 +43,12 @@ let guild = { /* NOTE: This method seemed like a good addition before I refactored `listMembers()` - it still might be a good(?), isolated example of `Object.keys()` use, but otherwise seems slightly redundant. */ -// 'listMemberNames()` introduces `Object.keys()` static method, and provides a list of current members of the guild. Could be 3 steps (1. create a function, 2. assign array returned by the `Object.keys()` to a variable + explanation of the method, ) +// 'listMemberNames()` introduces `Object.keys()` static method, and provides a list of current members of the guild. Could be 3 steps (1. create a function, 2. assign the array returned by the `Object.keys()` to a variable + explanation of the method, 3. for loop and `console.log()`s) function listMemberNames(guildObject) { console.log("Current Guild Members:"); - const memberNames = Object.keys(guildObject); - for (let i = 0; i < memberNames.length; i++) { - console.log(`${i + 1}. ${memberNames[i]}`); + const guildMembers = Object.keys(guildObject); + for (const member in guildMembers) { + console.log(`${parseInt(member) + 1}. ${guildMembers[member]}`); } } @@ -57,14 +57,13 @@ listMemberNames(guild); // `listMembers()` introduces `Object.entries()` static method (possibly also `Object.keys()` and `Object.values()`, depending on how long the description for each step is supposed to be) function listMembers(guildObject) { console.log("Guild Member's Resources:"); - // TODO: Simplify the loop iteration for(const member in guildObject) { // Use Object.keys() and Object.resources() to get a list of resources of each guild member const resourceNames = Object.keys(guildObject[member]); const resourceValues = Object.values(guildObject[member]); console.log(`${member}:`); - console.log(`${resourceNames[0]}\t${resourceNames[1]}\t${resourceNames[2]}\t${resourceNames[3]}`) - console.log(`${resourceValues[0]}\t${resourceValues[1]}\t${resourceValues[2]}\t\t${resourceValues[3]}`) + console.log(`${resourceNames[0]}\t${resourceNames[1]}\t${resourceNames[2]}\t${resourceNames[3]}`); + console.log(`${resourceValues[0]}\t${resourceValues[1]}\t${resourceValues[2]}\t\t${resourceValues[3]}`); } } From fd83374dccde06bb986b103d5ea6303c589b6890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Sun, 3 May 2026 14:50:35 +0200 Subject: [PATCH 17/21] refactor: change loops in addLootEntry() to 'for...in', fix the order of checks This changes the loop used for iteration over guild member's names to 'for...in' loop (for consistency), and moves the check if the new entry before updating the member data. --- .../js-projects/guild-loot-tracker/script.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/fullstack-cert/js-projects/guild-loot-tracker/script.js b/fullstack-cert/js-projects/guild-loot-tracker/script.js index 41e572308..c5f5191d9 100644 --- a/fullstack-cert/js-projects/guild-loot-tracker/script.js +++ b/fullstack-cert/js-projects/guild-loot-tracker/script.js @@ -107,32 +107,30 @@ function addLootEntry(guildObject, entry) { return; } // Check if the guildObject includes a record of the provided member - for (let i = 0; i < guildMemberNames.length; i++) { - if (guildMemberNames[i] === memberName) { + for (const guildMember in guildMemberNames) { + if (guildMemberNames[guildMember] === memberName) { // Now, we assign the member from the cloned data to a variable to make the loops more readable (we're not making a copy of the object! it could be worth pointing out the difference in the steps). const currentMemberTotals = getMemberTotals(clonedGuild, memberName); // We could check if the entry includes all of the required fields (the same as the existing member) // First we check if the new entry has all of the required resources: for (const resource in currentMemberTotals) { - // TODO: replace `Object.hasOwn() with a different method (going over the Object.keys() for example) if (!Object.hasOwn(newMemberTotals, resource)) { console.log(`Updated data is missing an entry for "${resource}"!`); return; } else if ( typeof newMemberTotals[resource] !== typeof currentMemberTotals[resource]){ - console.log(`"${resource}" must be a "${typeof currentMemberTotals[resource]}" variable!`); + console.log(`"${resource}" must be a ${typeof currentMemberTotals[resource]}!`); return; } - // Assigning new values could be done outside of the loop, but on the other hand, it might be how we can show how cloning the current data can prevent against accidental mutation on case where someone provides incorrect input data more clearly (maybe?). - currentMemberTotals[resource] = newMemberTotals[resource]; - } // Then, we check if the new entry has ONLY the required resources NOTE: (this one is probably unnecessary, I'm not sure if ALL of the resources should actually be required to make an update): - // TODO: Simplify the loop iteration - for (const resource in currentMemberTotals) { + for (const resource in newMemberTotals) { if (!Object.hasOwn(currentMemberTotals, resource)) { console.log(`Updated data has an unknown entry "${resource}!`); return; } } + // Assigning new values could be done outside of the loop, but on the other hand, it might be how we can show how cloning the current data can prevent against accidental mutation on case where someone provides incorrect input data more clearly (maybe?). + currentMemberTotals[resource] = newMemberTotals[resource]; + } // Finally, now we're sure our input was correct, we can overwrite the old data. guild = clonedGuild; console.log("Guild roster updated."); From 2a4c46ee2ac3856139284f0ecdc8e89e0d861d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Tue, 12 May 2026 11:12:33 +0200 Subject: [PATCH 18/21] feat: use 'getMemberTotals()' function to introduce 'Object.entries()' method --- .../js-projects/guild-loot-tracker/script.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/fullstack-cert/js-projects/guild-loot-tracker/script.js b/fullstack-cert/js-projects/guild-loot-tracker/script.js index c5f5191d9..900189155 100644 --- a/fullstack-cert/js-projects/guild-loot-tracker/script.js +++ b/fullstack-cert/js-projects/guild-loot-tracker/script.js @@ -69,15 +69,15 @@ function listMembers(guildObject) { listMembers(guild); -//TODO: bring the listTopMembers method back (now that the workshop is in the Loops section, sorting should be viable. In Theory. need to double-check that.) -// 'getMemberTotals()` can be 4 steps (introduce `Object.keys()` method, write a guard clause using `if()/else` and `.includes()`, introduce `Object.values()`, return a string literal with the result) - +// 'getMemberTotals()` introduces `Object.entries() method function getMemberTotals(guildObject, member) { - const memberNames = Object.keys(guildObject); - // TODO: Simplify the loop iteration - for (const name in memberNames) { - if (memberNames[name] === member) { - return guildObject[member]; + const guildMembers = Object.entries(guildObject); + + for (const guildMember of guildMembers) { + const [name, entry] = guildMember; + if (member === name) { + return entry; } } return false; From e15cac63ba9b9ebe9d21b77b295c47adbb1dbeb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Tue, 12 May 2026 14:54:15 +0200 Subject: [PATCH 19/21] refactor: use 'listMembers()' to introduce only the 'Object.values()' method --- .../js-projects/guild-loot-tracker/script.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fullstack-cert/js-projects/guild-loot-tracker/script.js b/fullstack-cert/js-projects/guild-loot-tracker/script.js index 900189155..44fafba42 100644 --- a/fullstack-cert/js-projects/guild-loot-tracker/script.js +++ b/fullstack-cert/js-projects/guild-loot-tracker/script.js @@ -54,16 +54,16 @@ function listMemberNames(guildObject) { listMemberNames(guild); -// `listMembers()` introduces `Object.entries()` static method (possibly also `Object.keys()` and `Object.values()`, depending on how long the description for each step is supposed to be) +// `listMembers()` introduces `Object.values()` function listMembers(guildObject) { console.log("Guild Member's Resources:"); for(const member in guildObject) { // Use Object.keys() and Object.resources() to get a list of resources of each guild member - const resourceNames = Object.keys(guildObject[member]); - const resourceValues = Object.values(guildObject[member]); + const [gold, silver, reputation, experience] = Object.values(guildObject[member]); + console.log(`${member}:`); - console.log(`${resourceNames[0]}\t${resourceNames[1]}\t${resourceNames[2]}\t${resourceNames[3]}`); - console.log(`${resourceValues[0]}\t${resourceValues[1]}\t${resourceValues[2]}\t\t${resourceValues[3]}`); + console.log("gold\tsilver\treputation\texperience"); + console.log(`${gold}\t${silver}\t${reputation}\t\t${experience}`); } } From ad044a54525c135d9cbc7aeb39399b8517f1e2f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Tue, 12 May 2026 15:08:38 +0200 Subject: [PATCH 20/21] style: format the script for consistency and readability --- .../js-projects/guild-loot-tracker/script.js | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/fullstack-cert/js-projects/guild-loot-tracker/script.js b/fullstack-cert/js-projects/guild-loot-tracker/script.js index 44fafba42..2f2844e4d 100644 --- a/fullstack-cert/js-projects/guild-loot-tracker/script.js +++ b/fullstack-cert/js-projects/guild-loot-tracker/script.js @@ -1,5 +1,5 @@ // This will be a workshop for the Loops section, probably after the "Build a Profile Lookup" lab. - + // Creating the "guild" object could be 2 or 3 Steps (creating "guild" object itself and adding the first "guild member" object as the first property - rest of the "guild members" can be filled out somewhere between the next steps to avoid too much repetition). let guild = { ethan: { @@ -57,10 +57,12 @@ listMemberNames(guild); // `listMembers()` introduces `Object.values()` function listMembers(guildObject) { console.log("Guild Member's Resources:"); - for(const member in guildObject) { + for (const member in guildObject) { // Use Object.keys() and Object.resources() to get a list of resources of each guild member - const [gold, silver, reputation, experience] = Object.values(guildObject[member]); - + const [gold, silver, reputation, experience] = Object.values( + guildObject[member], + ); + console.log(`${member}:`); console.log("gold\tsilver\treputation\texperience"); console.log(`${gold}\t${silver}\t${reputation}\t\t${experience}`); @@ -69,7 +71,6 @@ function listMembers(guildObject) { listMembers(guild); - // 'getMemberTotals()` introduces `Object.entries() method function getMemberTotals(guildObject, member) { const guildMembers = Object.entries(guildObject); @@ -94,11 +95,11 @@ function cloneGuildData(guildObject) { function addLootEntry(guildObject, entry) { // Use cloneGuildData() to prevent accidental mutations before committing the changes to the 'guild object' const clonedGuild = cloneGuildData(guildObject); - // Use `Object.keys() to get the guild member's + // Use `Object.keys() to get the guild member's const guildMemberNames = Object.keys(guildObject); // Use spread operator to get the member's name and resources out of the "entry" argument - const { member: memberName, ...newMemberTotals} = entry; - // Check if the member's name exists, and is a string + const { member: memberName, ...newMemberTotals } = entry; + // Check if the member's name exists, and is a string if (!memberName) { console.log("New entry must include a name of the guild member!"); return; @@ -109,27 +110,32 @@ function addLootEntry(guildObject, entry) { // Check if the guildObject includes a record of the provided member for (const guildMember in guildMemberNames) { if (guildMemberNames[guildMember] === memberName) { - // Now, we assign the member from the cloned data to a variable to make the loops more readable (we're not making a copy of the object! it could be worth pointing out the difference in the steps). + // Now, we assign the member from the cloned data to a variable to make the loops more readable (we're not making a copy of the object! it could be worth pointing out the difference in the steps). const currentMemberTotals = getMemberTotals(clonedGuild, memberName); // We could check if the entry includes all of the required fields (the same as the existing member) // First we check if the new entry has all of the required resources: for (const resource in currentMemberTotals) { - if (!Object.hasOwn(newMemberTotals, resource)) { - console.log(`Updated data is missing an entry for "${resource}"!`); - return; - } else if ( typeof newMemberTotals[resource] !== typeof currentMemberTotals[resource]){ - console.log(`"${resource}" must be a ${typeof currentMemberTotals[resource]}!`); - return; - } - // Then, we check if the new entry has ONLY the required resources NOTE: (this one is probably unnecessary, I'm not sure if ALL of the resources should actually be required to make an update): - for (const resource in newMemberTotals) { - if (!Object.hasOwn(currentMemberTotals, resource)) { - console.log(`Updated data has an unknown entry "${resource}!`); + if (!Object.hasOwn(newMemberTotals, resource)) { + console.log(`Updated data is missing an entry for "${resource}"!`); + return; + } else if ( + typeof newMemberTotals[resource] !== + typeof currentMemberTotals[resource] + ) { + console.log( + `"${resource}" must be a ${typeof currentMemberTotals[resource]}!`, + ); return; } - } - // Assigning new values could be done outside of the loop, but on the other hand, it might be how we can show how cloning the current data can prevent against accidental mutation on case where someone provides incorrect input data more clearly (maybe?). - currentMemberTotals[resource] = newMemberTotals[resource]; + // Then, we check if the new entry has ONLY the required resources NOTE: (this one is probably unnecessary, I'm not sure if ALL of the resources should actually be required to make an update): + for (const resource in newMemberTotals) { + if (!Object.hasOwn(currentMemberTotals, resource)) { + console.log(`Updated data has an unknown entry "${resource}!`); + return; + } + } + // Assigning new values could be done outside of the loop, but on the other hand, it might be how we can show how cloning the current data can prevent against accidental mutation on case where someone provides incorrect input data more clearly (maybe?). + currentMemberTotals[resource] = newMemberTotals[resource]; } // Finally, now we're sure our input was correct, we can overwrite the old data. guild = clonedGuild; From 4f63860a51f0898a1c729fcede482ecad65845f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C5=82odarczyk?= Date: Mon, 25 May 2026 01:06:45 +0200 Subject: [PATCH 21/21] chore: update comments to reflect steps of the workshop This updates the overview of how "Guild Loot Tracker" workshop should be broken down into steps. --- .../js-projects/guild-loot-tracker/script.js | 56 ++++++++++++------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/fullstack-cert/js-projects/guild-loot-tracker/script.js b/fullstack-cert/js-projects/guild-loot-tracker/script.js index 2f2844e4d..dae83ddc5 100644 --- a/fullstack-cert/js-projects/guild-loot-tracker/script.js +++ b/fullstack-cert/js-projects/guild-loot-tracker/script.js @@ -1,6 +1,6 @@ -// This will be a workshop for the Loops section, probably after the "Build a Profile Lookup" lab. +// This will be a workshop for the Loops section, probably after the "Build a Profile Lookup" lab. Probably around 32 steps. -// Creating the "guild" object could be 2 or 3 Steps (creating "guild" object itself and adding the first "guild member" object as the first property - rest of the "guild members" can be filled out somewhere between the next steps to avoid too much repetition). +// The "guild" object could be already provided, or created over 3 or so steps (creating "guild" object itself and adding the first "guild member" object as the first property - rest of the "guild members" can be filled out somewhere between the next steps to avoid too much repetition). let guild = { ethan: { gold: 31, @@ -43,7 +43,10 @@ let guild = { /* NOTE: This method seemed like a good addition before I refactored `listMembers()` - it still might be a good(?), isolated example of `Object.keys()` use, but otherwise seems slightly redundant. */ -// 'listMemberNames()` introduces `Object.keys()` static method, and provides a list of current members of the guild. Could be 3 steps (1. create a function, 2. assign the array returned by the `Object.keys()` to a variable + explanation of the method, 3. for loop and `console.log()`s) +// 'listMemberNames()` introduces `Object.keys()` static method, and provides a list of current members of the guild. Could be 3 steps: +// 1. create a function, +// 2. assign the array returned by the `Object.keys()` to a variable + explanation of the method, +// 3. for loop and `console.log()` methods for the name, function listMemberNames(guildObject) { console.log("Current Guild Members:"); const guildMembers = Object.keys(guildObject); @@ -54,11 +57,14 @@ function listMemberNames(guildObject) { listMemberNames(guild); -// `listMembers()` introduces `Object.values()` +// `listMembers()` introduces `Object.values()`. Probably good for about 4 steps: +// 1. Create a new function and add a `console.log()` with the function's description, +// 2. Create a `for()` loop, and log the `Object.values()`, +// 3. Replace the `console.log()` for the `Object.values()` with destructuring, +// 4. add the 3 `console.log()` for the member's name, value names and the destructured values function listMembers(guildObject) { console.log("Guild Member's Resources:"); for (const member in guildObject) { - // Use Object.keys() and Object.resources() to get a list of resources of each guild member const [gold, silver, reputation, experience] = Object.values( guildObject[member], ); @@ -71,7 +77,11 @@ function listMembers(guildObject) { listMembers(guild); -// 'getMemberTotals()` introduces `Object.entries() method +// 'getMemberTotals()` introduces `Object.entries() method. +// 1. Create a function and assign `Object.entries()` to a variable, +// 2. Use `console.log()` to see what has been assigned to the variable +// 3. Remove the `console.log()` and create a `for()` loop, add `return false` statement, +// 4. Destructure `guildMember` inside the `if()` conditional, add `return entry` function getMemberTotals(guildObject, member) { const guildMembers = Object.entries(guildObject); @@ -83,23 +93,27 @@ function getMemberTotals(guildObject, member) { } return false; } -// Checks if `getMemberTotals()` works correctly - can be one of the steps and removed later + +// Checks for validating `getMemberTotals()` is working correctly - can be one of the steps and removed later console.log(getMemberTotals(guild, "natalie")); console.log(getMemberTotals(guild, "francis")); -// "cloneGuildData" can be broken into multiple (3?) steps to show that using the spread syntax creates a shallow copy of the object, for example #3 write the function and log it #4 assign the returned data to a variable, and change some of its values, then log both original and the copy to the console, #5 remove the variable and console.log() calls to continue +// "cloneGuildData()" can be broken into multiple (4?) steps, showing that using the spread syntax creates a shallow copy of the object (and explaining how it prevents accidental mutations) for example: +// 1. Write the function and assign the input parameter to one variable directly, and via the spread operator to a second variable, then call the function, +// 2. Modify the content of the first variable, then use `console.log()` to compare the original input parameter, and both variables, showing that modifying the direct copy modified the original as well, +// 3. Modify the shallow copy to show it didn't affect the original +// 4. Remove the variables and the function call, write the final version of the function. function cloneGuildData(guildObject) { return { ...guildObject }; } function addLootEntry(guildObject, entry) { - // Use cloneGuildData() to prevent accidental mutations before committing the changes to the 'guild object' + // 1. Use cloneGuildData() to get a copy of the first input parameter, const clonedGuild = cloneGuildData(guildObject); - // Use `Object.keys() to get the guild member's + // 2. Use `Object.keys() to get a list of guild member's names, and use the spread operator to separate data from the second parameter const guildMemberNames = Object.keys(guildObject); - // Use spread operator to get the member's name and resources out of the "entry" argument const { member: memberName, ...newMemberTotals } = entry; - // Check if the member's name exists, and is a string + // 3, 4. Write conditional checking to validate the member's name exists, and is a string if (!memberName) { console.log("New entry must include a name of the guild member!"); return; @@ -107,18 +121,19 @@ function addLootEntry(guildObject, entry) { console.log("Member name must be a string!"); return; } - // Check if the guildObject includes a record of the provided member + // 5. Add a `for()` loop iterating over the guild member's names, add the `console.log()` and a return statement after it (I just realized that `return` is kind of pointless...), Check if the guildObject includes a record of the provided member, for (const guildMember in guildMemberNames) { + // 6. Write a `if()` statement for finding the name provided in the second input parameter, then get this guild member's data with `getMemberTotals()` and assign it to a variable for readability (we're not making a copy of the object! it could be worth pointing out the difference in the steps), if (guildMemberNames[guildMember] === memberName) { - // Now, we assign the member from the cloned data to a variable to make the loops more readable (we're not making a copy of the object! it could be worth pointing out the difference in the steps). const currentMemberTotals = getMemberTotals(clonedGuild, memberName); - // We could check if the entry includes all of the required fields (the same as the existing member) - // First we check if the new entry has all of the required resources: + // 7. Add a `for()` loop to iterate over the member's resources, for (const resource in currentMemberTotals) { + // 8. Add a `if()` statement to check if the new data is missing one of the values - if it does, end the function execution if it does, if (!Object.hasOwn(newMemberTotals, resource)) { console.log(`Updated data is missing an entry for "${resource}"!`); return; } else if ( + // 9. Add the else if statement to check if the new value is of the same type as the current one, return early if it isn't typeof newMemberTotals[resource] !== typeof currentMemberTotals[resource] ) { @@ -127,27 +142,27 @@ function addLootEntry(guildObject, entry) { ); return; } - // Then, we check if the new entry has ONLY the required resources NOTE: (this one is probably unnecessary, I'm not sure if ALL of the resources should actually be required to make an update): + // 10., 11. Then, check if the new entry has ONLY the required resources (could be just one step, but it might be a bit too confusing), for (const resource in newMemberTotals) { if (!Object.hasOwn(currentMemberTotals, resource)) { console.log(`Updated data has an unknown entry "${resource}!`); return; } } - // Assigning new values could be done outside of the loop, but on the other hand, it might be how we can show how cloning the current data can prevent against accidental mutation on case where someone provides incorrect input data more clearly (maybe?). + // 12. Since at this point the new data passed all of the guard clauses, we can overwrite the old data currentMemberTotals[resource] = newMemberTotals[resource]; } - // Finally, now we're sure our input was correct, we can overwrite the old data. + // 13. Finally, now we're sure our input was correct, we can overwrite the old data, inform the user the operation was successful, and exit the function. guild = clonedGuild; console.log("Guild roster updated."); return; } } - // In the case that the guildObject doesn't include a member with the given name, inform the user and return console.log(`${memberName} is not a member of this guild!`); return; } +// Last step: use `AddLootEntry() to change the guild member's resources, and use `listMembers()` to see if it worked. addLootEntry(guild, { member: "natalie", gold: 1, @@ -156,5 +171,4 @@ addLootEntry(guild, { experience: 4, }); -// Using `listMembers()` to check if the data has been updated listMembers(guild);