Lab 2: Anchors and Links
Released: September 30th, 4:00pm ET
Due: October 14th 11:59pm ET
Introduction
Now, we want to have more in-depth ways of interacting with
our file system. One of the inherent features of all
hypertext systems is the ability to create anchors and
links. As with the Node objects you created in the last
assignment, each part of the stack will have a way of
interacting with these new objects. As a result, your
completed assignment will contain the following for both
Anchors and Links: Database Connections, Gateways, Routers,
and Frontend Components.
In this lab we are laying out the foundations as we get
ready to implement linking and anchoring. Now that you have
all completed a basic file system and gone through the
complexities of understanding the backend structure for our
nodes, this unit will be focused on frontend, and will
involve far more design decisions! As always, if you have
any questions about anything please reach out to a TA!
Objective:
Introduce you to the new types and some techniques for
frontend that you will be working with for this
assignment.
Important note on Assignment 2:
Links & Anchors will be released on Monday at 9am,
rather than this Thursday at 6pm. This allow TAs to have
more time to make sure the codebase is fully functional
and has no issues! We apologize for this inconvenience
and will be extending accordingly!
Checklist
Reflecting on Assignment #1
You successfully prepped your donut/doughnut! Now it’s time
for looking over the recipe to see where you’re at. Please
complete the short and easy feedback form
for Assignment #1! We want to get a good sense of what has
gone well and what has not, so we can improve for the coming
assignments.
Cloning the GitHub Repo
Unlike Unit 1, we will not be using the same repository
for lab and the assignment. This is so that you all have
the same starting point for the assignment as some of
you may implement this lab differently!
You can clone the repository here, eveything else in terms of setup should be the same as
Unit 1 once you have it stored locally!
Always remember to run yarn install
in both the frontend and backend portions of the code.
New Types - ILink & IAnchor
We highly recommend that you look over the files in server/src/types
as all the following types are defined there, doing so will
make your life much easier when you are looking for helper
functions in the future:
IServiceResponse
INode
INodeProperties
IFilePath
TreeWrapper
IAnchor
Extent
ILink
We also provide and define useful helper methods related to
these types in each of the files. Again, we highly recommend
that you familiarize yourself with each of these types
before coding.
Anchors
In the previous assignment, you implemented the file system
for nodes, but didn’t actually implement anything regarding
selecting content on a node. In order for anchors to work
properly, you need to be able to make selections on the
content of a node (whether that node is a text document, an
image, or a video). Conceptually, you can think about
implementing anchors via two distinct components:
-
a generic component that manages all types of anchors
and stores the associated node and other metadata about
the anchor (
IAnchor
), and
-
specific implementations of anchors that define the
location of that anchor for the given node type (
Extent
).
We have defined a schema for anchors as the IAnchor
interface:
export interface IAnchor {
anchorId: string
nodeId: string
extent: Extent | null
}
The new Extent
interfaces:
export type Extent = ITextExtent | IImageExtent
export interface ITextExtent {
type: 'text'
text: string
startCharacter: number
endCharacter: number
}
export interface IImageExtent {
type: 'image'
left: number
top: number
width: number
height: number
}
Links
Once we have a way of creating anchors on our nodes, we
need an intuitive way to navigate between them. This is
where links come in! Whenever we place an anchor on the
content of a node, we want to have the ability to link this
anchor to another location. For example, if there is a
statistic or quote within the content of a specific node, we
want to have the ability to create an anchor on this
section, and then link it to an anchor on the node from
where this information comes.
The new ILink
interface:
export interface ILink {
linkId: string
explainer: string
title: string
dateCreated?: Date
anchor1Id: string
anchor2Id: string
}
Backend
Environment Variables
You should know how to do this by now! Use the same .env
file that you did for your own MongoDb connection in Unit 1
and add it into your server
folder.
DB_URI = <YOUR OWN URI>
PORT=5000
TSC_COMPILE_ON_ERROR=true
ESLINT_NO_DEV_ERRORS=true
MongoDB Queries
In Unit 1 you got your hands dirty with NodeGateway.ts
methods. In Unit 2, you will familiarize yourself with AnchorCollectionConnection.ts
and LinkCollectionConnection.ts
. These collection connection classes use the mongodb
node package to interact with the MongoDB database we
created in Assignment 1.
But first, what exactly is MongoDB?
MongoDB is a cross-platform (runs on multiple operating
systems), document-oriented database management system
(DBMS). MongoDB is also a non-relational, NoSQL database.
(SQL is a query language for relational databases, not a
database architecture itself, so the NoSQL name is
confusing, arguably the category should have been called
non-relational.)
It is important to know at a high-level how this type of
database operates, as the structure of a MongoDB database is
inherently different from relational databases that use SQL.
Traditional relational databases that use SQL to perform
operations store data with predefined relationships. This is
why these types of databases consist of a set of tables with
columns and rows - to organize data points using defined
relationships for easy access.
Don’t skip this paragraph!
A non-relational, NoSQL database such as Mongo is
different. Non-relational databases do not use the tabular
schema of rows and columns found in most traditional
database systems. Instead, data is stored as JSON-like
objects with indexes that allow for constant lookup for
certain fields. As a result, these types of databases do not
use SQL to perform operations and tend to be more flexible
by allowing data to be stored in myriad ways.
MongoDB uses documents that are in JSON-like format, known
as BSON, which is the binary encoding of JSON. Node.js and
MongoDB work very well together, in part because Mongo uses
a JavaScript engine built into the database as JavaScript is
good at handling JSON objects.
With Mongo being a non-relational database, it has its own
way of storing data. Here are some of the constructs that
make up the database structure:
-
Database: The container that holds a set of collections.
-
Collection: A set of documents. This is similar to a table in a
SQL database. However, unlike a SQL database, a
collection does not have a set structure or
pre-configured data types.
-
Documents: A JSON-like object with a unique identifier that
contains fields, which can store anything from strings
and ints to arrays and complex objects. You can tell
MongoDB which fields of a document to index for
constant-time lookup and range queries. These documents
also do not have to have the same fields as one another,
which is an advantage that these databases have in
flexibility over traditional, relational databases. [As
Norm has said, the word “document” is confusing, since
it has nothing to do with the types of documents (text,
spreadsheets, etc.) that people are used to. We will
continue to call them documents, but think of them as
JSON-like objects.]
-
_id: Mandatory unique field in every document. It
separates one document from another, so we can identify
each document independently. If this value is not
provided, MongoDB automatically assigns a random value
for the field.
Task 1: First, navigate to server/src/app.ts
. This file is what runs when you yarn start
. As you see, we configure a MongoClient
in this file as so:
const uri = process.env.DB_URI
const mongoClient = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
mongoClient.connect()
A few lines below this, we pass this mongoClient
into each of our routers:
const myNodeRouter = new NodeRouter(mongoClient)
app.use('/node', myNodeRouter.getExpressRouter())
This mongoClient
is then passed down from the router
> gateway
> collectionConnection
via their respective constructors. For example for
links:
constructor(mongoClient: MongoClient) {
this.linkGateway = new LinkGateway(mongoClient)
}
constructor(mongoClient: MongoClient, collectionName?: string) {
this.linkCollectionConnection = new LinkCollectionConnection(
mongoClient,
collectionName ?? 'links'
)
}
constructor(mongoClient: MongoClient, collectionName?: string) {
this.client = mongoClient
this.collectionName = collectionName ?? 'links'
}
Now that we have the MongoClient
, let’s try to write LinkCollectionConnection.deleteLinks()
.
Task 2: Implement LinkCollectionConnection.deleteLinks()
First, we need to get the links
collection:
async deleteLinks(linkIds: string[]): Promise<IServiceResponse<{}>> {
const collection = await this.client.db().collection(this.collectionName)
}
Next, we need to form a MongoDB query.
async deleteLinks(linkIds: string[]): Promise<IServiceResponse<{}>> {
const collection = await this.client.db().collection(this.collectionName)
const myQuery = { linkId: { $in: linkIds } }
}
Learning MongoDB queries
Over the duration of the semester, you will become
familiar with MongoDB queries and will hopefully be able
to form basic ones on your own. Here are some basic ones
to get you started, but feel free to consult Google at
anytime!
Some Basic MongoDB Query Functions
The stencil code already calls getCollection
and stores the response in a variable. Below are some of
the functions that you can call on the collection
variable to interact with the MongoDB database. Feel
free to reference the previous assignment’s backend NodeCollectionConnection.ts
file to help with your implementation.
collection.findOne(query)
- Docs
collection.find(query)
- Docs
collection.deleteOne(query)
- Docs
collection.deleteMany(query)
- Docs
collection.insertMany(query)
- Docs
collection.insertOne(query)
- Docs
Next, we need to call a MongoDB API call with our newly
formed query. Notice that we call await
on this method because we are sending a request the MongoDB
server that we connected to using mongoClient.connect()
. Because they are hosted on a remote server, we must then
await a response from the MongoDB server before continuing
our program.
async deleteLinks(linkIds: string[]): Promise<IServiceResponse<{}>> {
const collection = await this.client.db().collection(this.collectionName)
const myQuery = { linkId: { $in: linkIds } }
const deleteResponse = await collection.deleteMany(myQuery)
}
Now that we have stored the deleteResponse
, we have to verify whether or not the deletion was
successful and return an IServiceResponse accordingly.
async deleteLinks(linkIds: string[]): Promise<IServiceResponse<{}>> {
const collection = await this.client.db().collection(this.collectionName)
const myQuery = { linkId: { $in: linkIds } }
const deleteResponse = await collection.deleteMany(myQuery)
if (deleteResponse.result.ok) {
return successfulServiceResponse({})
}
return failureServiceResponse('Failed to delete links')
}
Error checking MongoDB responses
Each MongoDB method call will return a differently
shaped response. This means, that the method for
error checking a deleteMany
reponse may be different to the method for error
checking a findOneAndUpdate
method. For example, this is how we error check findOneAndUpdate
in NodeCollectionConnection.updateNode()
:
async updateNode(
nodeId: string,
updatedProperties: Object
): Promise<IServiceResponse<INode>> {
const collection = await this.client.db().collection(this.collectionName)
const updateResponse = collection.findOneAndUpdate(
{ nodeId: nodeId }, { $set: updatedProperties })
if (updateResponse.ok && updateResponse.lastErrorObject.n) {
return successfulServiceResponse(updateResponse.value)
}
return failureServiceResponse(
'Failed to update node, lastErrorObject: ' +
updateResponse.lastErrorObject.toString()
)
}
We highly recommend looking at NodeCollectionConnection.ts
for tips on how to form MongoDB queries. E.g. how to
query a nested field, how to update documents,
etc.
Frontend
Environment Variables
Please add the following to your frontend’s .env
file (client/src/.env
).
TSC_COMPILE_ON_ERROR=true
ESLINT_NO_DEV_ERRORS=true
In this section, we will introduce a two more built-in
React hooks that help writing React code to reduce
unnecessary rerendering. They essentially memoize your
functions (useCallback
) or your values (useMemo
).
There are also a few “Code Review”
info blocks that give software engineering tips and best
practices, sort of like what you might receive from a code
review at a company.
You may remember that a callback is simply a function
passed as an argument. Whenever you pass a function as an
argument, it’s best to wrap it in a useCallback
to make sure it only updates when needed.
const MyParentComponent = () => {
const [selectedNode, setSelectedNode] = useState<INode>(...);
const handleNodeClick = (node: INode) => {
setSelectedNode(node)
}
return (
<MyChildComponent onNodeClick={handleNodeClick} />
)
}
const MyParentComponent = () => {
const [selectedNode, setSelectedNode] = useState<INode>(...);
const handleNodeClick = useCallback((node: INode) => {
setSelectedNode(node)
}, [setSelectedNode])
return (
<MyChildComponent onNodeClick={handleNodeClick} />
)
}
In the first example, a new reference to handleNodeClick
is created every time MyParentComponent
rerenders. Since we pass handleNodeClick
as a prop to MyChildComponent
, we will also make MyChildComponent
rerender, which is inefficient. That’s where useCallback
comes in!
The first argument to useCallback
is the function you want to wrap. The second argument is the
dependency array, which is important and required. In the
example above, the dependency array tells React to only
change handleNodeClick
if setSelectedNode
changes.
Code Review:
The example above also illustrates a common naming
pattern in React. Our child component has a prop with a
name like onNounVerb
that will be called when some event happens in the child
component (like a button click). We then write a
function with a name like handleNounVerb
that handles the event, which we pass into the child
component.
useMemo
If we want to memoize a value instead of a callback
function, we can use useMemo
. It has a similar interface, including the dependency
array:
const selectedNodeTitle: string = useMemo(() => {
return selectedNode.title;
}, [selectedNode])
In this example, selectedNode
is a state variable, and we only want selectedNodeTitle
to change when selectedNode
changes. Often times, we will use useMemo
to store values that we derive from state variables.
Code Review:
As a rule of thumb, you should store as little data as
possible in state variables, and derive your other data
from the state. For instance, in the above example, we
wouldn’t want to store selectedNodeTitle
as its own state variable, since we’d then have to
remember to update it.
Dependency arrays are very important! We have enabled a
linter rule called exhaustive-deps
that will make sure that every value that’s used in the
function is included in the dependency array, preventing a
wide array of bugs.
For example, the following code would error:
const selectedNodeTitle: string = useMemo(() => {
return selectedNode.title;
}, [])
To what extent is an Extent
…
When we make a link from a document, we don’t
necessarily always want to link from the entire
document. For example if we are linking from a PVDonut’s
menu, we may want to link from just one particular donut
- rather than the entire menu, for that we need to
create extents.
In this lab you will be implementing selecting an
extent on both text and image nodes.
In order to create links, you need to know how to create
the anchor that you are going to link from!
useRef React Hook
useRef returns a mutable ref object whose .current property
is initialized to the passed argument (initialValue
in the example below). The returned object will persist for
the full lifetime of the component.
const refDonut = useRef(initialValue);
Note that the initial value we pass into useRef
is often null
.
Here’s a common use case for use ref, which you be using in
later steps of this lab:
function ChocolateDonut() {
const refDonut = useRef(null);
const handleMakeChocolateClick = () => {
refDonut.current.style.backgroundColor = "chocolate"
};
return (
<>
<div className="donut" ref={refDonut}/>
<Button icon={<fa.FaDonut/>} onClick={onMakeChocolateDonut}>
Focus the input
</Button>
</>
);
}
Essentially, useRef is like a “box” that can hold a mutable
value in its .current
property.
You might be familiar with refs primarily as a way to
access the DOM. If you pass a ref object to React with <div ref={myRef} />
, React will set its .current property to the corresponding
DOM node whenever that node changes.
Extents in Images: IImageExtent
An extent is basically the terminology that we use to say
we want to specify this part
of a node. For an image node, that could be a selection of a
particular person. For example we can see in the image below
we have a selection over the two table tennis tables. These are anchors, but within anchors the extent is
what handles where exactly on the image the anchor
should be!
Given a list of anchors, you will be generating these
visual indicators of where exactly the anchor is. Note that if extent is null, then we are linking from
the entire image.
export interface IImageExtent {
type: 'image'
left: number
top: number
width: number
height: number
}
Since anchor.extent can be both an image or text type,
we want to check that it is the correct type before we
access the properties (eg. top, width) which will exist
on IImageExtent but not ITextExtent. We can do that with
the following condition:
if (anchor.extent?.type == 'image')
Here is an example of what we might want to add into the anchorElementList
array. Feel free to edit this!
<div
key={'image.' + anchor.anchorId}
className="image-anchor"
style={{
width: anchor.extent.width,
height: anchor.extent.height,
top: anchor.extent.top,
left: anchor.extent.left,
}}
/>
Notice that we have a key property, this is because when we
render the same property over again in our HTML DOM, it needs to have a unique key.
Task 3:
Load the existing randomly generated anchors onto your
image using the extent
!
Note: If selecting a region on an image does not work, then
change the divider
in the onMove
method. This is annoyance of having screens with
different resolutions!
Extents in Text: ITextExtent
First let us look at what an ITextExtent
object looks like.
export interface ITextExtent {
type: 'text'
startCharacter: number
endCharacter: number
text?: string
}
Imagine we have the following piece of text from PVDonuts.
As you can see there are already existing links that are underlined and blue. In our implementation, we would store this as an anchor
with the extent defined as ITextExtent
. This has a startCharacter
and an endCharacter
relative to paragraph. We want our anchors to have access to
that information so that when we load our text we can
visually show where our anchors are.
We’re not your typical donut shop. We’re a bit over the
top.
We officially hit the scene when we opened our doors in 2016, but we’ve been experimenting since 2014. We’re most
popular for introducing brioche style donuts to the New
England area, and they’re what makes us unique. We also
offer old fashioned, filled brioche, cake, crullers, fritters, and more. All of our
signature styles are made, rolled, cut, dipped and
decorated by hand each day.
Task 4: Go to TextContent.tsx
, and read the to-dos for more information on each
step. You can refer to ImageContent.tsx
if you are in doubt or ask a TA!
Manage the extent for when we want to create a new
anchor - which would be when we click Start Link
. To check your implementation is correct click Start Link
and check that the alert that pops up is the expected Extent
(Note: It should not be undefined
)
Here are the TODOs:
-
Add a use ref so that we can keep track of the text
content
-
Add an onPointerUp method which updates the
selected extent
-
Set the selected extent to null when this component
loads
-
Update the textContent-wrapper HTML
Element so that it works with useRef and
onPointerUp
[Optional] Tricky Optional Task 4.1:
Display the anchors in the text node. You do not need
to worry about our anchors being clickable yet (that
will come in the assignment itself)
-
Write a method where we display the existing
anchors.
Extents on other document types
Task 5: Discuss these questions with a partner, try and write
out an interface for each of these 2 of the
following:
-
What does an extent look like in a PDF? (Maybe we
would want to combine image extents and text
extents?)
-
What would an extent look like in an audio
recording?
-
What would an extent look like for a video?
- What about a webpage?
Styling
Just a note that there are other ways to be stylish
when writing HTML 😎 - feel free to use any you’d like!
SCSS
This is what you have used so far - but there are many
other ways to style - feel free to switch it up, or continue
using scss
. We just want to introduce you to some other ways of
styling your web app!
If you decide to use an SCSS alternative, you are responsible for converting all .sass
files to the file format of your choice.
Styling with Bootstrap
Sometimes, we want to be able to style our elements without
creating an entire new .scss
file. For many common styles like padding, margin, and flex-box, you can just add a Bootstrap utility class.
We’ve added Bootstrap to our assignment repos, so the
classes are ready to use! For padding and margin, there are
defined levels (i.e. 1, 2, 3, 4, 5) that correspond to
consistent values (i.e. 4px, 8px, 16px, 24px, 32px).
Having consistently scaling spacing values is generally
good practice in design!
Padding
<div className="p-3">Hello!</div>
<div className="pl-2">Hello!</div>
<div className="pr-4">Hello!</div>
<div className="px-1">Hello!</div>
<div className="py-1">Hello!</div>
Margin
<div className="m-3">Hello!</div>
<div className="ml-2">Hello!</div>
<div className="mr-4">Hello!</div>
<div className="mx-1">Hello!</div>
<div className="my-1">Hello!</div>
Flexbox
Flexbox allows you to create responsive containers that
nicely lay out your elements. You can create flexboxs that
are horizontal (flex-row
, the default) or vertical (flex-column
).
For a great explanation and resource on flexbox, see this
guide:
https://css-tricks.com/snippets/css/a-guide-to-flexbox/
Bootstrap flexbox classnames are pretty straightforward;
here’s some common ones:
d-flex (this means display: flex)
flex-row
flex-column
align-items-center
justify-content-center
Checkoff
-
Show a TA your
linkCollectionConnection.deleteLinks()
-
Show a TA that you have implemented text extent and
image extent!
- Have the best day ever 😎
Lab 2: Anchors and Links
Released: September 30th, 4:00pm ET
Due: October 14th 11:59pm ET
Introduction
Now, we want to have more in-depth ways of interacting with our file system. One of the inherent features of all hypertext systems is the ability to create anchors and links. As with the Node objects you created in the last assignment, each part of the stack will have a way of interacting with these new objects. As a result, your completed assignment will contain the following for both Anchors and Links: Database Connections, Gateways, Routers, and Frontend Components.
In this lab we are laying out the foundations as we get ready to implement linking and anchoring. Now that you have all completed a basic file system and gone through the complexities of understanding the backend structure for our nodes, this unit will be focused on frontend, and will involve far more design decisions! As always, if you have any questions about anything please reach out to a TA!
Objective: Introduce you to the new types and some techniques for frontend that you will be working with for this assignment.
Important note on Assignment 2: Links & Anchors will be released on Monday at 9am, rather than this Thursday at 6pm. This allow TAs to have more time to make sure the codebase is fully functional and has no issues! We apologize for this inconvenience and will be extending accordingly!
Checklist
linkCollectionConnection.deleteLinks()
extents
!Reflecting on Assignment #1
You successfully prepped your donut/doughnut! Now it’s time for looking over the recipe to see where you’re at. Please complete the short and easy feedback form for Assignment #1! We want to get a good sense of what has gone well and what has not, so we can improve for the coming assignments.
Cloning the GitHub Repo
Unlike Unit 1, we will not be using the same repository for lab and the assignment. This is so that you all have the same starting point for the assignment as some of you may implement this lab differently!
You can clone the repository here, eveything else in terms of setup should be the same as Unit 1 once you have it stored locally!
Always remember to run
yarn install
in both the frontend and backend portions of the code.New Types - ILink & IAnchor
We highly recommend that you look over the files in
server/src/types
as all the following types are defined there, doing so will make your life much easier when you are looking for helper functions in the future:IServiceResponse
INode
INodeProperties
IFilePath
TreeWrapper
IAnchor
Extent
ILink
We also provide and define useful helper methods related to these types in each of the files. Again, we highly recommend that you familiarize yourself with each of these types before coding.
Anchors
In the previous assignment, you implemented the file system for nodes, but didn’t actually implement anything regarding selecting content on a node. In order for anchors to work properly, you need to be able to make selections on the content of a node (whether that node is a text document, an image, or a video). Conceptually, you can think about implementing anchors via two distinct components:
IAnchor
), andExtent
).We have defined a schema for anchors as the
IAnchor
interface:The new
Extent
interfaces:Links
Once we have a way of creating anchors on our nodes, we need an intuitive way to navigate between them. This is where links come in! Whenever we place an anchor on the content of a node, we want to have the ability to link this anchor to another location. For example, if there is a statistic or quote within the content of a specific node, we want to have the ability to create an anchor on this section, and then link it to an anchor on the node from where this information comes.
The new
ILink
interface:Backend
Environment Variables
You should know how to do this by now! Use the same
.env
file that you did for your own MongoDb connection in Unit 1 and add it into yourserver
folder.MongoDB Queries
In Unit 1 you got your hands dirty with
NodeGateway.ts
methods. In Unit 2, you will familiarize yourself withAnchorCollectionConnection.ts
andLinkCollectionConnection.ts
. These collection connection classes use themongodb
node package to interact with the MongoDB database we created in Assignment 1.But first, what exactly is MongoDB?
MongoDB is a cross-platform (runs on multiple operating systems), document-oriented database management system (DBMS). MongoDB is also a non-relational, NoSQL database. (SQL is a query language for relational databases, not a database architecture itself, so the NoSQL name is confusing, arguably the category should have been called non-relational.)
It is important to know at a high-level how this type of database operates, as the structure of a MongoDB database is inherently different from relational databases that use SQL. Traditional relational databases that use SQL to perform operations store data with predefined relationships. This is why these types of databases consist of a set of tables with columns and rows - to organize data points using defined relationships for easy access.
Don’t skip this paragraph!
A non-relational, NoSQL database such as Mongo is different. Non-relational databases do not use the tabular schema of rows and columns found in most traditional database systems. Instead, data is stored as JSON-like objects with indexes that allow for constant lookup for certain fields. As a result, these types of databases do not use SQL to perform operations and tend to be more flexible by allowing data to be stored in myriad ways.
MongoDB uses documents that are in JSON-like format, known as BSON, which is the binary encoding of JSON. Node.js and MongoDB work very well together, in part because Mongo uses a JavaScript engine built into the database as JavaScript is good at handling JSON objects.
With Mongo being a non-relational database, it has its own way of storing data. Here are some of the constructs that make up the database structure:
Task 1: First, navigate to
server/src/app.ts
. This file is what runs when youyarn start
. As you see, we configure aMongoClient
in this file as so:A few lines below this, we pass this
mongoClient
into each of our routers:This
mongoClient
is then passed down from therouter
>gateway
>collectionConnection
via their respective constructors. For example for links:Now that we have the
MongoClient
, let’s try to writeLinkCollectionConnection.deleteLinks()
.Task 2: Implement
LinkCollectionConnection.deleteLinks()
First, we need to get the
links
collection:Next, we need to form a MongoDB query.
Learning MongoDB queries
Over the duration of the semester, you will become familiar with MongoDB queries and will hopefully be able to form basic ones on your own. Here are some basic ones to get you started, but feel free to consult Google at anytime!
Some Basic MongoDB Query Functions
The stencil code already calls
getCollection
and stores the response in a variable. Below are some of the functions that you can call on the collection variable to interact with the MongoDB database. Feel free to reference the previous assignment’s backendNodeCollectionConnection.ts
file to help with your implementation.collection.findOne(query)
- Docscollection.find(query)
- Docscollection.deleteOne(query)
- Docscollection.deleteMany(query)
- Docscollection.insertMany(query)
- Docscollection.insertOne(query)
- DocsNext, we need to call a MongoDB API call with our newly formed query. Notice that we call
await
on this method because we are sending a request the MongoDB server that we connected to usingmongoClient.connect()
. Because they are hosted on a remote server, we must then await a response from the MongoDB server before continuing our program.Now that we have stored the
deleteResponse
, we have to verify whether or not the deletion was successful and return an IServiceResponse accordingly.Error checking MongoDB responses
Each MongoDB method call will return a differently shaped response. This means, that the method for error checking a
deleteMany
reponse may be different to the method for error checking afindOneAndUpdate
method. For example, this is how we error checkfindOneAndUpdate
inNodeCollectionConnection.updateNode()
:We highly recommend looking at
NodeCollectionConnection.ts
for tips on how to form MongoDB queries. E.g. how to query a nested field, how to update documents, etc.Frontend
Environment Variables
Please add the following to your frontend’s
.env
file (client/src/.env
).Improving performance in React
In this section, we will introduce a two more built-in React hooks that help writing React code to reduce unnecessary rerendering. They essentially memoize your functions (
useCallback
) or your values (useMemo
).There are also a few “Code Review” info blocks that give software engineering tips and best practices, sort of like what you might receive from a code review at a company.
useCallback
You may remember that a callback is simply a function passed as an argument. Whenever you pass a function as an argument, it’s best to wrap it in a
useCallback
to make sure it only updates when needed.In the first example, a new reference to
handleNodeClick
is created every timeMyParentComponent
rerenders. Since we passhandleNodeClick
as a prop toMyChildComponent
, we will also makeMyChildComponent
rerender, which is inefficient. That’s whereuseCallback
comes in!The first argument to
useCallback
is the function you want to wrap. The second argument is the dependency array, which is important and required. In the example above, the dependency array tells React to only changehandleNodeClick
ifsetSelectedNode
changes.Code Review: The example above also illustrates a common naming pattern in React. Our child component has a prop with a name like
onNounVerb
that will be called when some event happens in the child component (like a button click). We then write a function with a name likehandleNounVerb
that handles the event, which we pass into the child component.useMemo
If we want to memoize a value instead of a callback function, we can use
useMemo
. It has a similar interface, including the dependency array:In this example,
selectedNode
is a state variable, and we only wantselectedNodeTitle
to change whenselectedNode
changes. Often times, we will useuseMemo
to store values that we derive from state variables.Code Review: As a rule of thumb, you should store as little data as possible in state variables, and derive your other data from the state. For instance, in the above example, we wouldn’t want to store
selectedNodeTitle
as its own state variable, since we’d then have to remember to update it.Dependency arrays are very important! We have enabled a linter rule called
exhaustive-deps
that will make sure that every value that’s used in the function is included in the dependency array, preventing a wide array of bugs.For example, the following code would error:
To what extent is an
Extent
…When we make a link from a document, we don’t necessarily always want to link from the entire document. For example if we are linking from a PVDonut’s menu, we may want to link from just one particular donut - rather than the entire menu, for that we need to create extents.
In this lab you will be implementing selecting an extent on both text and image nodes. In order to create links, you need to know how to create the anchor that you are going to link from!
useRef React Hook
useRef returns a mutable ref object whose .current property is initialized to the passed argument (
initialValue
in the example below). The returned object will persist for the full lifetime of the component.const refDonut = useRef(initialValue);
Note that the initial value we pass into
useRef
is oftennull
.Here’s a common use case for use ref, which you be using in later steps of this lab:
Essentially, useRef is like a “box” that can hold a mutable value in its
.current
property.You might be familiar with refs primarily as a way to access the DOM. If you pass a ref object to React with
<div ref={myRef} />
, React will set its .current property to the corresponding DOM node whenever that node changes.Extents in Images:
IImageExtent
An extent is basically the terminology that we use to say we want to specify this part of a node. For an image node, that could be a selection of a particular person. For example we can see in the image below we have a selection over the two table tennis tables. These are anchors, but within anchors the extent is what handles where exactly on the image the anchor should be!
Given a list of anchors, you will be generating these visual indicators of where exactly the anchor is. Note that if extent is null, then we are linking from the entire image.
Since anchor.extent can be both an image or text type, we want to check that it is the correct type before we access the properties (eg. top, width) which will exist on IImageExtent but not ITextExtent. We can do that with the following condition:
if (anchor.extent?.type == 'image')
Here is an example of what we might want to add into the
anchorElementList
array. Feel free to edit this!Notice that we have a key property, this is because when we render the same property over again in our HTML DOM, it needs to have a unique key.
Task 3: Load the existing randomly generated anchors onto your image using the
extent
!Note: If selecting a region on an image does not work, then change the
divider
in theonMove
method. This is annoyance of having screens with different resolutions!Extents in Text:
ITextExtent
First let us look at what an
ITextExtent
object looks like.Imagine we have the following piece of text from PVDonuts. As you can see there are already existing links that are underlined and blue. In our implementation, we would store this as an anchor with the extent defined as
ITextExtent
. This has astartCharacter
and anendCharacter
relative to paragraph. We want our anchors to have access to that information so that when we load our text we can visually show where our anchors are.Task 4: Go to
TextContent.tsx
, and read the to-dos for more information on each step. You can refer toImageContent.tsx
if you are in doubt or ask a TA!Manage the extent for when we want to create a new anchor - which would be when we click
Start Link
. To check your implementation is correct clickStart Link
and check that the alert that pops up is the expectedExtent
(Note: It should not beundefined
)Here are the TODOs:
[Optional] Tricky Optional Task 4.1:
Display the anchors in the text node. You do not need to worry about our anchors being clickable yet (that will come in the assignment itself)
Extents on other document types
Task 5: Discuss these questions with a partner, try and write out an interface for each of these 2 of the following:
Styling
Just a note that there are other ways to be
stylish
when writing HTML 😎 - feel free to use any you’d like!SCSS
This is what you have used so far - but there are many other ways to style - feel free to switch it up, or continue using
scss
. We just want to introduce you to some other ways of styling your web app!If you decide to use an SCSS alternative, you are responsible for converting all
.sass
files to the file format of your choice.Styling with Bootstrap
Sometimes, we want to be able to style our elements without creating an entire new
.scss
file. For many common styles like padding, margin, and flex-box, you can just add a Bootstrap utility class.We’ve added Bootstrap to our assignment repos, so the classes are ready to use! For padding and margin, there are defined levels (i.e. 1, 2, 3, 4, 5) that correspond to consistent values (i.e. 4px, 8px, 16px, 24px, 32px).
Having consistently scaling spacing values is generally good practice in design!
Padding
Margin
Flexbox
Flexbox allows you to create responsive containers that nicely lay out your elements. You can create flexboxs that are horizontal (
flex-row
, the default) or vertical (flex-column
).For a great explanation and resource on flexbox, see this guide:
https://css-tricks.com/snippets/css/a-guide-to-flexbox/
Bootstrap flexbox classnames are pretty straightforward; here’s some common ones:
Checkoff
linkCollectionConnection.deleteLinks()