[SOLVED] WEB422 Assignment 5

75.00 $

Category: Tags: ,
Click Category Button to View Your Next Assignment | Homework

You will receive the following solution file(s) instantly after successful payment:

zip file icon Web422-Assignment4-main-iob75e.zip (107.7 KB)
Assignment Instructions Updated Recently? Submit Below and we will provide new Solution!
Submit New Instructions
πŸ”’ Securely Powered by:
Secure Checkout
5/5 - (5 votes)

Objective:

For this assignment, we will continue our development effort from Assignment 4.Β  Before you begin, make a copy of your assignment 4 directory and begin working from there.

Β 

Note: If you require a working version of assignment 4 to continue with this assignment, please email your professor.

Β 

The main focus for this assignment will be to leverage our knowledge of Jotai to add additional features to our β€œMet Artwork” application created in Assignment 4.Β  This will primarily consist of β€œFavourites” and β€œSearch History” functionality.Β  We will also address some issues around data integrity / usability from Assignment 4, including the issue of certain search queries returning invalid object ID’s (430610, 430542, 430524, etc) and the navigation menu not highlighting / collapsing when browsing the site on smaller devices.

Sample Solution:

https://web422–a5–fall–2022.vercel.app/

Β 

Please refer to the sample solution regularly to help you design your components and check your solution.Β Β  It is there to help supplement the following requirements.

Β 

Step 1: Solving the invalid objectID problem

Β 

As you have seen from your own testing of Assignment 4, there are some search queries that result in invalid objectID’s from the collectionapi.metmuseum.org/public/collection/v1 API.Β  Fortunately, in addition to querying the API for a subset of artwork (ie: objectID’s), the API also has another route available that provides a list of all 480000+ valid objectID’s, via the following:

Β 

https://collectionapi.metmuseum.org/public/collection/v1/objects

Β 

For us to solve the invalid objectID issue, we must make sure our system is aware of all of the valid object ID values so that we may compute the intersection of our search result list and the valid objectID list.Β  This will ensure that any invalid objectID’s are excluded from the UI.

Β 

Since there is so much data returned from the above URL and it’s not likely to change frequently, the fastest way for us to use it (ie: not slow down our searches) is to download it ahead of time and include it in our solution as a .json file.

Β 

Β 

Β 

Β 

To do this, you must:

Β 

  • Create a β€œdata” folder in your app’s β€œpublic” folder, for example β€œ/WEB422-A5/my-app/public/data”
  • Visit the https://collectionapi.metmuseum.org/public/collection/v1/objects route in a web browser
  • (in Chrome / Firefox) Choose β€œFile” > β€œSave Page As” (it should show the filename as β€œobjects.json”)
  • Navigate to your newly created β€œpublic/data” folder for your app and save the file as: json

With validObjectIDList.json now in your β€œpublic” folder, we can now update our β€œ/pages/artwork/index.js” page to use it in our searches:

Β 

  • Near the top of the file, add the line to import the json file from the β€œdata” folder:

import validObjectIDList from β€˜../../public/data/validObjectIDList.json’

Β 

  • Next, we must use the β€œobjectIDs” property from the β€œvalidObjectIDList” to help filter our search results (recall: this is the array containing over 480000 objectIDs). This must be done in the β€œuseEffect” hook, before calculating the β€œresults” array and updating the β€œartworkList” state value.Β  This can be done using the following code (NOTE: Please feel free to calculate β€œfilteredResults” using an alternative strategy, if you feel that it yields faster results):

Β 

let filteredResults = validObjectIDList.objectIDs.filter(x => data.objectIDs?.includes(x));

Β 

Essentially, we filter all objectID values in the validObjectIDList such that it only contains values that are also returned from our search.Β  This has the effect of eliminating objectIDs from our search results that are not in the validObjectIDList.

Β 

  • Now, you can use the β€œfilteredResults” value in your loop to build the results array instead ofobjectIDs, ie:

Β 

for (let i = 0; i < filteredResults.length; i += PER_PAGE) {Β Β Β Β  const chunk = filteredResults.slice(i, i + PER_PAGE);Β Β Β Β  results.push(chunk);

}

Β 

If you try searching for results now, you should see that the β€œ404” errors are eliminated and only valid results are rendered.

Β 

Step 2: Automatically Hiding the Expanded Navbar (Mobile / Small Screens)

Β 

The second issue has more to do with usability.Β  If you try to navigate the site when viewing it on a mobile device (or any screen where the β€œcollapsed” menu bar is shown), you will notice that the navbar does not close automatically.Β  This causes a large fraction of the page to be obscured when navigating the site, unless the user manually clicks the β€œhamburger” icon.

Β 

Instead, we would like any action taken in the navbar (clicking a link, searching for artwork) to automatically close the navbar.Β  To achieve this, we must update our β€œMainNav” component according to the following specification:

Β 

  • Set an β€œisExpanded” value in the state, with a default value of false

Β 

  • Add an β€œexpanded” property to the <Navbar> component with the value β€œisExpanded”, ie:

expanded={isExpanded}

Β 

  • When the user searches for art (ie: when the form is submitted), set the β€œisExpanded” value in the state to false

Β 

  • When the user clicks the <Navbar.Toggle> Component, toggle the β€œisExpanded” value (ie: if it’s true, set it to false and vice versa) HINT: you can β€œtoggle” a Boolean value in JS using someBoolean = !someBoolean.

Β 

  • Ensure that every <Nav.Link> element will set the β€œisExpanded” value to false

Β 

NOTE: To help space out the elements in the navbar when collapsed, you can place &nbsp; characters before and after the <Form></Form> element, ie &nbsp;<Form>…</Form>&nbsp;

Β 

Β 

Step 3: Adding β€œFavourites” Functionality (Part 1)

Β 

For this assignment, we would like users to be able to add specific artwork to a β€œfavourites” list.Β  This will involve:

Β 

  • Creating a store.js file in the root of your application (ie: β€œmy-app/store.js”)
  • Using Jotai to create a β€œfavouritesAtom” with the default value of [] (an empty array), defined within the store.js

file

Β 

With our β€œatom” in place, we must now update our β€œArtworkCardDetail” component to use it, and provide a mechanism (button) to allow the user to specify a piece of artwork as one of their β€œfavourites” and add it to the list:

Β 

  • In the β€œArtworkCardDetail” component, import both the β€œuseAtom” hook, and the β€œfavouritesAtom” We will also need the β€œuseState” hook

Β 

With the imports in place, we can now focus on the additional logic required for this component, specifically:

Β 

  • Get a reference to the β€œfavouritesList” from the β€œfavouritesAtom” (HINT: this can be done using the β€œuseAtom” hook)

Β 

  • Add a β€œshowAdded” value to the state (this will control how the button (defined below) is displayed) with a default value of true if the β€œfavouritesList” includes the objectID (passed in β€œprops”) and false if it does not

Β 

  • Add a β€œfavouritesClicked” function (to be invoked when the button (defined below) is clicked), according to the following specification:

Β 

  • If the β€œshowAdded” value in the state is true, then we must remove the objectID (passed in β€œprops”) from the β€œfavouritesList” (effectively removing this piece of artwork from the favourites list). This can be done using the code:

Β 

setFavouritesList(current => current.filter(fav => fav != objectID));

Β 

(assuming that you have defined the function β€œsetFavouritesList” when using the β€œuseAtom” hook with the β€œfavouritesAtom”)

Β 

We must also set the β€œshowAdded” value in the state to false (since the artwork is no longer in the favourites list)

Β 

  • If the β€œshowAdded” value in the state is false, then we must add the objectID (passed in β€œprops”) to the β€œfavouritesList” (effectively adding this piece of artwork to the favourites list). This can be done using the code:

Β 

setFavouritesList(current => […current, objectID]);

Β 

(assuming that you have defined the function β€œsetFavouritesList” when using the β€œuseAtom” hook with the β€œfavouritesAtom”)

Β 

We must also set the β€œshowAdded” value in the state to true (since the artwork is now in the favourites list)

Β 

  • Add a <Button> Element after the β€œDimensions” value in the β€œCard” element according to the following:

Β 

  • Has a β€œvariant” attribute set to β€œprimary” if the β€œshowAdded” value in the state is true, or β€œoutlineprimary” if the β€œshowAdded” value in the state is false

Β 

  • When the button is β€œClicked” invoke the β€œfavouritesClicked” function (defined above)

Β 

  • Shows the text β€œ+ Favourite (added)” if the β€œshowAdded” value in the state is set to true, or just β€œ+ Favourite” if the β€œshowAdded value in the state is set to false

Β 

NOTE: To ensure that SWR only starts the request for β€œDetailed Artwork Data” if it has an β€œobjectID” value, you can update the request to use β€œConditional Fetchingβ€œ. For example,Β  instead of executing the SWR function as:

Β 

  • const { data, error } = useSWR(`someUrl/${objectID}`);

You can instead use:

Β 

  • const { data, error } = useSWR(objectID ? `someUrl/${objectID}` : null);

Β 

Step 4: Adding β€œFavourites” Functionality (Part 2)

Β 

If you test the UI now, you will see that you can toggle the β€œ+ Favourite” button for an individual piece of artwork to add / remove it as a β€œfavourite”.Β  However, there doesn’t exist a page for us to view all of the favourites added.Β  To remedy this, we must add a β€œFavourites” component in the β€œpages” directory using file name (favourites.js), such that:

Β 

  • It gets a reference to the β€œfavouritesList” from the β€œfavouritesAtom” (HINT: this can be done using the

β€œuseAtom” hook)

Β 

  • It renders all of the items in the β€œfavouritsList” in exactly the same way as the component in

β€œ/pages/artwork/index.js”, ie: shows an β€œArtworkCard” component for every element in the β€œfavouritesList” using the same React-Bootstrap elements (ie: β€œRow”, β€œCol”, etc).

Β 

  • Also like the component in β€œ/pages/artwork/index.js”, if the β€œfavouritesList” is empty, a message must be shown to the user – in this case, something along the lines of: β€œNothing Here Try adding some new artwork to the list.”

Β 

  • Important Note: Pagination is not required for this component

Β 

Step 5: Adding β€œFavourites” Functionality (Part 3)

Β 

To truly test whether or not our new β€œ/favourites” page works correctly, we will need to provide a link to the page somewhere in our application.Β  Since β€œfavourites” will eventually be stored for each individual user, it makes sense to introduce a β€œUser Name” dropdown menu to the β€œMainNav” component, and place it there:

Β 

  • To get started, add a new <Nav>…</Nav> element below the </form>&nbsp; content

Β 

  • Next, use the code from: the documentation for Navbar to copy the β€œNavDropdown” component and paste it into our new <Nav>…</Nav> element, changing the β€œtitle” attribute from β€œDropdown” to β€œUser Name”

Β 

  • Modify it so that it only has a single <NavDropdown.Item> component that links to β€œ/favourites” with the text β€œFavourites”

Β 

  • Important Note: Do not forget to wrap your <NavDropdown.Item> component in a <Link>…</Link> tag (follow the same code that you used for your other <Nav.Link> components). If the page refreshes when navigating to β€œ/favourites”, then we will not see any of our favourites, since they’re currently only persisted in memory

Β 

  • Finally, ensure that when the user clicks on the <NavDropdown.Item> component, the β€œisExpanded” value in the state to false (this is the same logic as your other <Nav.Link> components)

Β 

If you test out your app now, you should see a β€œUser Name” drop down to the right of the search form in the MainNav component with the β€œFavourites” link.Β  Try clicking β€œ+ favourite” for some artwork, then navigating to β€œ/favourites” via the navbar to test the add / remove functionality.

Β 

Β 

Step 6: Adding β€œHistory” Functionality (Part 1)

Β 

The next major piece of functionality that we will add is a β€œSearch History” mechanism to allow users to keep track of previous searches and re-run / delete them if they wish.Β  Like the β€œfavourites” functionality, we are going to use Jotai to store the list of previous searches so that we can update / read it from anywhere within our app:

Β 

  • In the store.js file, add a β€œsearchHistoryAtom” with a default value of [] (empty array)

Β 

  • Next, in the β€œsearch.js” file (ie: the β€œAdvancedSearch” component), get a reference to the β€œsearchHistory” from the β€œsearchHistoryAtom” (HINT: this can be done using the β€œuseAtom” hook)

Β 

  • In the β€œsubmitForm” function (ie: once the search form has been submitted), add the computed queryString value to the searchHistory. This can be done using the code:

Β 

setSearchHistory(current => […current, queryString]);

Β 

(assuming that you have defined the function β€œsetSearchHistory” when using the β€œuseAtom” hook with the β€œsearchHistoryAtom”)

Β 

  • In the MainNav component, we must also get a reference to the β€œsearchHistory” from the β€œsearchHistoryAtom”, since it is also possible to search from artwork with the β€œsearch” form in the navbar.

Β 

  • In the β€œsubmitForm” function (ie: once the search form in the navbar has been submitted), once again add the computed queryString value to the searchHistory (this should be β€œtitle=true&q=searchValue” where searchValue is the value from the search field). This can once again be done using the code:

Β 

setSearchHistory(current => […current, queryString]);

Β 

(assuming that you have defined the function β€œsetSearchHistory” when using the β€œuseAtom” hook with the β€œsearchHistoryAtom”)

Β 

Step 7: Adding β€œHistory” Functionality (Part 2)

Β 

With both search possibilities now adding to the β€œsearchHistory” array, we can concentrate on writing the page responsible for showing the values.Β  This will be a β€œHistory” component in the β€œpages” directory using file name (history.js), with the following logic:

Β 

  • The component must get a reference to the β€œsearchHistory” from the β€œsearchHistoryAtom” (HINT: this can be done using the β€œuseAtom” hook).

Β 

  • In the body of the function, loop through the β€œsearchHistory” list to generate a list of β€œparsed” search queries, ie:

Β 

let parsedHistory = [];

Β 

searchHistory.forEach(h => {

let params = new URLSearchParams(h);

let entries = params.entries();

parsedHistory.push(Object.fromEntries(entries));

});

Β 

  • The component must have a function called β€œhistoryClicked” that takes two parameters: e (the event) and index (a number). This function must cause the user to navigate (using the β€œuseRouter” hook) to the page

Β 

/artwork?searchHistory[index]

Β 

where index is the number passed to the function and searchHistory is the β€œsearchHistory” list from the β€œsearchHistoryAtom”

Β 

  • The component must have a second function called β€œremoveHistoryClicked” that also takes two parameters:

e (the event) and index (a number).Β  The purpose of this function is to remove an element from the β€œsearchHistory” list.Β  This can be done using the following logic:

Β 

e.stopPropagation(); // stop the event from trigging other events setSearchHistory(current => {Β Β Β Β  let x = […current];Β Β Β Β  x.splice(index, 1)Β Β Β Β  return x;

});

Β 

  • The component must render the β€œparsedHistory” (created above) in the component according to the following specification:

Β 

  • If the β€œparsedHistory” array is empty, render a message, ie: β€œNothing Here Try searching for some artwork”. To match the style used in the example, place this text in a <Card> Component

Β 

  • If the β€œparsedHistory” array is not empty, render each β€œparsedHistory” object from the array inside a

<ListGroup.Item> component.Β  Each of these components will be contained within a single, parent <ListGroup> component (see the documentation for the <ListGroup> Component from reactbootstrap)

Β 

The contents of each <ListGroup.Item> component should be a list of all properties present in the current β€œparsedHistory” object, for example:

Β 

{Object.keys(historyItem).map(key => (<>{key}: <strong>{historyItem[key]}</strong>&nbsp;</>))}

Β 

where historyItem is the current β€œparsedHistory” object

Β 

  • Additionally, each <ListGroup.Item> component must itself have a β€œclick” event that invokes the

β€œhistoryClicked” function with the event object and current index of the array (Recall: When using Array.map(), the callback function can take two parameters, the current element and the array index, ie: parsedHistory.map((historyItem, index) => ( … ))

Β 

  • Finally, each <ListGroup.Item> component must also include a means to remove the current element from the β€œsearchHistory” list. This can be done with the following button that invokes β€œremoveHistoryClicked” with the current index of the array:

Β 

<Button className=”float-end” variant=”danger” size=”sm”  onClick={e => removeHistoryClicked(e, index)}>&times;</Button>

Β 

  • To ensure that users are aware that they can click on each <ListGroup.Item> element to re-run the search, we should add some CSS to highlight the component, as well as change the cursor to β€œpointer”. Here, we will use the CSS Module support from Next.js:

Β 

  • Create a β€œHistory.module.css” file within the β€œstyles” folder (ie: my-app/styles), with the content:

Β 

.historyListItem:hover{

background-color: rgb(244, 244, 244);Β Β Β Β  cursor:pointer;

  • }

Β 

NOTE: The above background-color is just a suggestion – please feel free to use different colors / styles for your own application to match your design

Β 

  • Import the module in the history.js file using the code:

Β 

import styles from β€˜../styles/History.module.css’;

Β 

  • Finally, ensure that the <ListGroup.Item> elements have the className value: {styles.historyListItem}

Β 

Step 8: Adding β€œHistory” Functionality (Part 3)

Β 

Once again, to test whether or not our new β€œ/history” page works correctly, we will need to provide a link to the page somewhere in our application.Β  Since β€œhistory” will also eventually be stored for each individual user, it makes sense to place it within the β€œUser Name” dropdown menu in the β€œMainNav” component.

Β 

To accomplish this, you may use the exact same <Link> component that was used for β€œfavourites”, except it must link to β€œ/history” and show the text β€œSearch History”.

Β 

If you test the app now, you should be able to make searches via either the nav bar or advanced search page and see the searches listed on the β€œ/history” page.

Β 

Step 9: Final β€œBug” fix (Highlighting Navbar Items)

Β 

You will have noticed that when navigating the site, the menu items do not necessarily highlight / remain highlighted on the current page that we are on.Β  For example, when we first visit the site, β€œHome” is not highlighted and when a page is refreshed, none of the navbar text is highlighted.

Β 

Fortunately, this is a quick fix for us to implement:

Β 

  • Ensure that each <Nav.Link> and <NavDropdown.Item> element in the MainNav component correctly sets the β€œactive” attribute, using the current path, ie:

Β 

  • <Nav.Link active={router.pathname === β€œ/search”}>Advanced Search</Nav.Link
  • <NavDropdown.Item active={router.pathname === β€œ/history”>Search History</NavDropdown.Item> o

Β 

Β 

Β 

Β 

Β 

Β 

Β 

Β 

Β 

Β 

Β 

Β 

Β 

Β 

Β 

Β 

Β 

Β 

Assignment Submission:

  • For this assignment, you will once again be required to build your assignment before submitting. This will involve running the command:

o npm run build

to create a production build in the β€œ.next” folder to be included in your submission.Β  Please Note: you will be required to fix any errors that are identified during the build process.

  • Next, add the following declaration at the top of your index.js file

Β 

/********************************************************************************* *Β  WEB422 – Assignment 5

  • I declare that this assignment is my own work in accordance with Seneca Academic Policy. *Β  No part of this assignment has been copied manually or electronically from any other source *Β  (including web sites) or distributed to other students.

*

  • Name: ______________________ Student ID: __________________ Date: ____________________

*

*

********************************************************************************/

  • Compress (.zip) the files in your Visual Studio working directory without node_modules (this is the folder that you opened in Visual Studio to create your client side code).

Β 

Important Note:

  • NO LATE SUBMISSIONS for assignments. Late assignment submissions will not be accepted and will receive a grade of zero (0).
  • After the end (11:59PM) of the due date, the assignment submission link on My.Seneca will no longer be available.
  • Submitted assignments must run locally, ie: start up errors causing the assignment/app to fail on startup will result in a grade of zero (0) for the assignment.

1 file Β· 107.7 KB

  • Web422-Assignment4-main-iob75e.zip 107.7 KB