---
title: Lab 3 | Editable Nodes
tags: lab
---
<span style="font-size: 50px;">**Lab 3: Editable Nodes**</span>
:::info
**Released: October 12th, 2023**
**Due: October 18th, 2023 at 11:59 PM ET**
To get checked off for this lab, meet with a TA at your lab section or TA hours
:::
:::danger
⚠️ **Do not clone the Assignment 3 stencil code until you have submitted Assignment 2 for the last time to Gradescope**. We will be checking to make sure you do not submit Assignment 2 after cloning Assignment 3. ⚠️
:::
<!-- INFO BOXES
:::success GREEN
This is for any step by step instructions that students should follow.
:::
:::info BLUE
This is for important assignment information throughout the assignment:
**Released: September 8th, 6:00pm ET**
**Due: September 15th, 11:59pm ET**
:::
:::warning YELLOW
Use this info box for disclaimers.
:::
:::danger
Use this info box for important points that students should not miss!
:::
:::spoiler Dropdown list
Use this
:::
-->
<!-- TYPESCRIPT CODE BLOCKS
```typescript
const list = [10, 20];
console.log(list.map(x => (x * x)))
```
-->
<!-- HOW TO CHANGE COLOR IN MARKDOWN
<span style="background:aliceblue">some text with a **lightblue** background</span>
<span style="color:red">some **red** text</span>
-->
<!-- These are a list of shortcuts available. -->
*[HTML]: Hyper Text Markup Language
*[W3C]: World Wide Web Consortium
*[NPM]: Node Package Manager
*[IDE]: Integrated Development Environment
*[MERN]: MongoDB, Express, React, NodeJS
*[Yarn]: Yet Another Resource Negotiator
# **Introduction**
Congratulations ~ you have successfully created and deployed your very own hypermedia system! You can create anchors and links between nodes, within the same node, and follow links, but there is so much more that you could be able to do in a hypermedia system!
In this unit, you will be turning the system from a static, view-only hypermedia system to one that is interactive and usable.
In this lab we are laying out the foundation as we get ready to implement editing the properties of the `INode`s that we have created. As always, if you have any questions about anything, please reach out to a TA or message us on Slack!
:::success
**Objective:** Introduce you to `NPM` packages, `Prosemirror`, `Tiptap`, and how to make your content editable!
:::
## **Checklist**
- [ ] Complete Assignment 2 feedback form and student information form
- [ ] Brainstorm ideas for your final project
- [ ] Clone the repo for the lab
- [ ] Get setup in your codebase with `tiptap`
- [ ] Implement different ways of editing a node's title
- Double click
- Context menu
- Keyboard shortcuts
# Reflecting on Assignment 2
Great job on a tough assignment! Please let us know what went well and what didn't by completing this anonymous [feedback form](https://forms.gle/Vbu1bNj8x9jHfHB76) for Assignment 2.
# Student Information Form
We need to link all of your lab checkoffs, hypothesis annotations, and assignment submissions in order to calculate midpoint grade reports. Please fill out [this form](https://forms.gle/QdB6Uf8iyV71KU4C8) so we can get all the necessary information.
# Beginning to Brainstorm for your Final Project
<div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:200px" src="https://c.tenor.com/OrKCLkgfSQMAAAAM/kstr-kochstrasse.gif" /></div>
For the final project, you will be working in groups of 3 to build your very own `hypermedia system`! As you are already aware, the scope of a hypermedia system is extremely broad – it can range from a system that focuses on `temporal media` to one that focuses on `visualisations`, or even just a system like `Wikipedia` that is primarily textual.
We want to get you thinking about what type of hypermedia system you want to work on for your final project **early**, because in **Unit 4: Additional Hypertext Features** you will need to begin making choices relevant to what hypertext system you want to build.
:::success
**Task 1:** Write out a list of areas that you would be interested in exploring. This can range from as broad as just `text nodes` to a particular technology you would like to implement like a `motion tracker`. This task is merely to get you thinking so don't worry if you can't come up with much!
Feel free to discuss this with a partner!
:::
# **Demo**
:::warning
Note: The demo may take up to 30 seconds to load due to the free backend we used for hosting. Additionally, you might notice some strange behavior if there are multiple students interacting with the demo at the same time.
:::
You can find a demo of the MyHypermedia application [**here**](https://assignment-three-demo.vercel.app/).
# Cloning the GitHub Repo
You can clone the repository [here](https://classroom.github.com/a/G1bzI2ur).
:::success
The same repo will be used for Lab 3 and Assignment 3!
:::
Always remember to run `npm install` in both the frontend and backend folder as well as adding your `.env` file with the appropriate environment variables to the `server` folder. Let us know if you have any questions!
# Using `npm` modules from the `npm` registry
As a `npm` user, you can create and publish public packages that anyone can download and use in their own projects. As a developer, writing your own code for everything can become tedious and challenging, and therefore the `npm registry` provides free open source code that you can use for your projects.
It is vital that you have an understanding of how to install new `npm` modules so we will be walking you through an example of how to install one and also giving import tips along the way to explain how you can ensure that you have a well-managed codebase.
Web applications usually use a great number of third-party packages and libraries so we don’t have to write everything on our own. Instead of manually installing the packages one-by-one, we use the package manager to automate this process. It’s really quick!
## Dependencies
Project dependencies are the packages that are being used in the code of the project. We have separated our frontend and backend into two seperate folders, each with their own project dependencies. There are multiple types of project dependencies, which are outlined below.
:::success
**Task 2:** Look at the `package.json` file in the `unit3-editable-nodes/client` folder. Notice how there are seperate lists for`dependencies` and `devDependencies`? Each dependency has the name in the `npm` registry and the version that your current project is compatible with.
:::
### Production Dependencies
These are the dependencies that our web application needs to run. For example since our web application is working with React we need a `react` node package in our application. These dependencies are specified under the key “dependencies” in `package.json` file.
### Development Dependencies
These are those dependencies which are needed at the time of development but are not responsible for working of the application i.e. even if we skip these dependency our application will work just fine. An example of a dependency that would go into this list is `ESLint`, which checks the Java/TypeScript code we write for common problems, such as syntax errors, formatting issues, code style violations, and potential bugs.
### Optional Dependencies
Our project has no optional dependencies, but as the name suggests, these dependencies are optional. If they fail to install, `npm` will still say the install process was successful. This is useful for dependencies that won’t necessarily work on every machine and you have a fallback plan in case they are not installed.
### Peer Dependencies
Peer dependencies are primarily used when you are developing a plugin/package for a host tool or package. That means you expect the user of the plugin/package to have these dependencies installed while not necessarily using these dependencies in your plugin/package.
You won't have to worry about including peer dependencies for this web application, but you may come across the message: `"some_dependency requires a peer of <another_dependency> but none is installed. You must install peer dependencies yourself"`.
If you see an error like this, then you should install `<another_dependency>` yourself!
# Backend
## Environment Variables
Your backend setup for the hypertext system we are building is mostly complete! Thus, this lab/assignment will mostly be devoted to frontend development inside the `client` folder. In terms of scope for your own projects, particularly the final project, it is likely that you will want to alter the backend `types` to change what the metadata of your node objects look like.
For example, if you wanted your application to handle users, you would want to create an additional `MongoDB` collection (`user-model`) to store the users.
:::success
**Task 3:** Time to get the backend running! You should be an expert on this by now! Use the same `.env` file that you did for your own MongoDB connection in Unit 2 and add it into your `server` folder.
It should look something like this:
```bash=
DB_URI = <YOUR OWN URI>
PORT=8000
```
You may need to change the port number. If you do, make sure the port number in the server `.env` file matches the one in the client `endpoint.ts`.
:::
# Frontend
<!-- ## Environment Variables
Add the following to your frontend's `.env` file (`client/src/.env`).
```bash=
TSC_COMPILE_ON_ERROR=true
ESLINT_NO_DEV_ERRORS=true
``` -->
## Installing a package for editable text
Now that you have a better idea of what `dependencies` are and how they work, you are going to install your own package. Remember all of the tedious code that we were writing during the last lab? This package handles `innerHTML` manipulations for us! This package is extemely useful for `editable text` and we will be using it for `Unit 3`.
### What is `ProseMirror`?
ProseMirror is a toolkit for building rich text editors - it is not an out-the-box solution which some packages may provide (e.g. https://github.com/bkniffler/draft-wysiwyg). This means ProseMirror has a steep learning curve - there are many concepts and terms to learn, and it can be difficult to structure your codebase in a logical manner.
### What is `tiptap`?
[Tiptap](https://tiptap.dev) is a wrapper library for ProseMirror, it is an abstraction layer that makes ProseMirror easier to work with, and provides React and ProseMirror integration.
Tiptap provides extensions that abstract over various ProseMirror concepts such as schemas, commands and plugins, making it much simpler to group related logic together.
In the assignment itself you will be using `tiptap` to make your text editable. In this lab, text nodes will not support linking and anchoring; setting those up will be explained and included in the assignment.
:::success
**Task 4:** Make sure to `cd` into your `client` folder in your terminal, then run the following command to install `tiptap` (documentation [here](https://tiptap.dev/installation/react))
```
npm install @tiptap/react @tiptap/starter-kit @tiptap/extension-link @tiptap/core
```
:::
This will add `tiptap` to your `package.json` dependencies and also install other relevant packages. In particular, when you are installing `tiptap`, you are also installing the relevant `ProseMirror` packages.
Wondering where all of the code goes? It goes into your `node_modules` folder! If you can find the `@tiptap` directory, that means that your installation was successful!
### Using `tiptap` in your codebase
Once you have installed it, it's ready to use - getting all of that useful code is really as easy as that!
When you have packages installed, you can access them in any of your files. Since we use `ES6` (read more [here](https://www.w3schools.com/js/js_es6.asp)), the syntax for importing packages is:
`import Donut from 'PVDonuts'`
In earlier version of JavaScript, `require` was often used to import packages, however since ES6, and throughout this codebase we will only be using `import`. So to import `tiptap` into our codebase (in particular `TextContent.tsx`, because that is where we want to use it) - we add the following lines:
```
import { Link } from '@tiptap/extension-link'
import { Editor, EditorContent, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
```
:::success
**Task 5:** Go to `client / src / components / NodeView / NodeContent / TextContent / TextContent.tsx` and add the aforementioned `tiptap` imports to the top of the file
:::
**Note:** At this point, you may still see some red warning messages in your code. That is fine.
#### Creating a `tiptap` editor
:::success
**Task 6:** Creating the `tiptap` editor!
:::
Do this by creating an instance of the editor by replacing `const editor = null` with
```typescript=
const editor = useEditor({
extensions: [
StarterKit,
Link.configure({
openOnClick: true,
autolink: false,
linkOnPaste: false,
}),
],
content: currentNode.content,
});
```
Once you have added the editor, you will want to add the `tiptap` components that we are actually going to render. You should replace the final return statement (not the one in the `!editor` conditional), with the following:
```typescript=
return (
<div>
<TextMenu editor={editor} />
<EditorContent editor={editor} onPointerUp={onPointerUp} />
</div>
);
```
We've created a basic, editable text component using `tiptap`, without any buttons to apply styling.
**Confirm that you're able to add and delete text from a text node as expected.**
Note that we haven't done anything to save our changes to the database, so all of your changes should go away when you leave the node. We'll now update the database so your changes persist in Assignment 3!
:::success
**Task 7:** Follow the instructions below to add a button that can make text **bold**
:::
The way that `tiptap` works is that it has a set of commands that you can use to alter the text in the editor. Rather than you having to go in and change the inner CSS, `tiptap` handles everything for you!
There are some really cool `tiptap` [extensions](https://tiptap.dev/api/marks) - ranging from simple marks like *Italic* and **Bold** text, to getting setup with `code snippets`. For this lab, we'll walk you through setting up the **bold** extension.
The **bold** extension is already included in StarterKit, so we don't need to add it separately to our `tiptap editor` extension list. By default, we can make the text bold using the `ctrl / cmmd + B` keyboard shortcut, but we also want to add a button to visually show users that they can make text bold.
To add a button to make text bold, add the following into the return statement of the `TextMenu.tsx` file:
```typescript=
<button
onClick={() => editor.chain().focus().toggleBold().run()}
disabled={!editor.can().chain().focus().toggleBold().run()}
className={
"textEditorButton" +
(editor.isActive("bold") ? " activeTextEditorButton" : "")
}
>
Bold
</button>
```
You should end up with editable text which looks like the following image:

:::info
Tip: When we implement things it's important to make sure everything is functioning as expected before improving the user interface. In Assignment 3 you'll improve the user interface!
:::
:::success
**Task 8**: Add the ability to make the text italicized! This should be a matter of doing the exact same thing we did for bold, except with `toggleItalic`, instead of `toggleBold`. You should reference the `tiptap` [documentation](https://tiptap.dev/api/marks/italic) as needed.
:::
Like with **bold**, the *italic* extension is already included in StarterKit, so we don't need to add it separately to our `tiptap editor` extension list. If we wanted to add a different extension that isn't in StarterKit, we would need to list it in the list of extensions in `useEditor`.
## Editing the node's title
1. Go to `client / src / components / NodeView / NodeHeader / NodeHeader.tsx` where you will find the TODOs for this task!
2. We have two state variables to keep track of the `title` and whether it is in an `editing` state or not, they are as follows:
```typescript=
// State variable for current node title
const [title, setTitle] = useState(currentNode.title)
// State variable for whether the title is being edited
const [editingTitle, setEditingTitle] = useState<boolean>(false)
```
:::success
**Task 9:** We will now fill in `handleUpdateTitle`, which is a function that updates the text in the database
This method takes in the updated text and adds it to the database using the `NodeGateway.updateNode` method. Get used to using that method, because we'll be using it a lot!
:::
It is important that we also let the user know if the update failed! This is an important thing to do in any web application because otherwise the user is misled to believe that the update successfully was written to the database when in reality it has not!
Put the following code inside the `handleUpdateTitle` function located in `NodeHeader.tsx`. Right now, this just sets up the logic for handling title changes, which we will be able to use once task 10 is complete.
```typescript=
setTitle(title)
const nodeProperty: INodeProperty = makeINodeProperty('title', title)
const titleUpdateResp = await FrontendNodeGateway.updateNode(currentNode.nodeId, [
nodeProperty,
])
if (!titleUpdateResp.success) {
setAlertIsOpen(true)
setAlertTitle('Title update failed')
setAlertMessage(titleUpdateResp.message)
}
setRefresh(!refresh)
```
One thing to consider, and a notable design decision, is how frequently the `EditableText` makes a call to the database. Currently whenever any letter in `title` changes it updates the database by making a call to update for every `onChange`. This could also be done by only making a call to the database `onBlur`. Have a look at `EditableText` to see how exactly it works!
:::info
The **onBlur** event occurs when an object loses focus.
The **onChange** event occurs when the value of an element has been changed.
:::
:::success
**Task 10:** We will now add three different ways to make the text editable! Note that it is not always ideal to have all three of these implementations as there may be conflicting or inaccessible behavior. However, for the purpose of this assignment, this gives us the oppurtunity to introduce different ways to update the title.
:::
Essentially what we are doing here is looking at different ways where `editingTitle` is true so that the title is in `editable` mode.
1. `DoubleClick`
The first thing that we should do is add the `onDoubleClick` tag to the `nodeHeader-title` text `<div>`, so that when the user `right-clicks`, it sets `editingTitle` to true.
```typescript=
onDoubleClick={() => setEditingTitle(true)}
```
2. `ContextMenu`
When it comes to context menus, we do not always want to use our own context menu because the default context menu (see attached image) has some important components when it comes to accessibility. For example, someone may need to be able to `Translate` a selected portion of text. Using a custom context menu would prevent them from doing so.
<div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:300px" src="https://i.imgur.com/vouIV1g.png" /></div>
The first thing that we should do is add the `onContextMenu` tag to the `nodeHeader-title` text `<div>`, so that when the user `right-clicks` it calls the method that we pass in. We would do that as follows:
```typescript=
onContextMenu={handleTitleRightClick}
```
In the `handleTitleRightClick` method, what we want to do is make a call to the `ContextMenuItems` list of JSX.Elements (which is an exported list from `ContextMenu.tsx`). Essentially what we are doing here is saying that we should add an item to the list. You could add other context menu items if you like this way of interacting with content for your web app!
Note: There is a known bug in our usage of the context menu, where the context menu seemingly does not close after clicking away. This is okay/expected behavior. We will let you know if we make a fix for this.
Insert the following code in the `handleTitleRightClick` function in `NodeHeader.tsx`
```typescript=
ContextMenuItems.splice(0, ContextMenuItems.length);
const menuItem: JSX.Element = (
<div
key={"titleRename"}
className="contextMenuItem"
onClick={(e) => {
ContextMenuItems.splice(0, ContextMenuItems.length);
setEditingTitle(true);
}}
>
<div className="itemTitle">Rename</div>
<div className="itemShortcut">ctrl + shift + R</div>
</div>
);
ContextMenuItems.push(menuItem);
```
:::info
Tip: The following code snippet lets us determine what operating system the person is using. This could be used to help us determine what we should give as the `itemShortcut`. On `win` it would be `ctrl` but on `mac` it would be `cmd`. This can be useful for other reasons as well!
```typescript=
let os: string = ''
if (navigator.userAgent.indexOf('Win') != -1) os = 'win'
if (navigator.userAgent.indexOf('Mac') != -1) os = 'mac'
if (navigator.userAgent.indexOf('X11') != -1) os = 'x11'
if (navigator.userAgent.indexOf('Linux') != -1) os = 'linux'
```
:::
3. `Keyboard shortcuts`
Keyboard shortcuts are pretty snazzy, but they also have shortcomings - for example, what we do in the code snippet below is say that if `Ctrl + Shift + R` is pressed we should enter the editing title workflow - but the problem with that is we now lose the ability to reload the page (which `Ctrl + Shift + R` also handles)!
The first step is to ensure that the browser is listening for `keydown` events in the `useEffect` on load. This uses `addEventListener`, which you can set it up to listen for `pointerdown`, `pointerup`, and many more events!
:::info
The EventListener interface represents an object that can handle an event dispatched by an object in the HTML DOM. You can read more about it, and other EventListeners [**here**](https://developer.mozilla.org/en-US/docs/Web/API/EventListener).
:::
```typescript=
document.addEventListener('keydown', nodeKeyHandlers)
```
Once you have added the line above, you should add the following switch statement into your `nodeKeyHandlers` function. It is important to call `e.preventDefault()` to prevent the possibility of the default shortcut (`reload` in this case) also being called in addition to our custom shortcut.
```typescript=
// key handlers with no modifiers
switch (e.key) {
case "Enter":
if (editingTitle == true) {
e.preventDefault();
setEditingTitle(false);
}
break;
case "Escape":
if (editingTitle == true) {
e.preventDefault();
setEditingTitle(false);
}
break;
}
// ctrl + shift key events
if (e.shiftKey && e.ctrlKey) {
switch (e.key) {
case "R":
e.preventDefault();
setEditingTitle(true);
break;
}
}
```
# Checkoff
- Show a TA that you have a `tiptap` text editor with buttons to make the text bold and italic.
- Show a TA that you are able to rename a node AND that the change persists
- Have a dazzling day 😎
<div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:200px" src="https://media2.giphy.com/media/8aM0z4EOqAgPZIYprz/giphy.gif" /></div>