---
title: Lab 3 | Editable Nodes
tags: lab
---
<span style="font-size: 50px;">**Lab 3: Editable Nodes**</span>
:::info
**Released: October 21st, 4:00pm ET**
**Due: October 29th 11:59pm ET**
:::
<!-- 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, or within the same node, and you can follow links, but there is a lot more that you should 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 foundations 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!
:::success
**Objective:** Introduce you to NPM packages, `Prosemirror`, `Remirror`, and how to make your content editable!
:::
## **Checklist**
- [ ] Assignment #2 [Feedback Form](https://forms.gle/4VRmaiicKYrbWm676)
- [ ] ==Icebreaker== Play the `Wikipedia game`. Go to [this link](https://www.thewikigame.com/?code=497151) and await TA instructions! Feel free to do this even if you are not in the lab (don't worry - this is not required for checkoff!)
- [ ] Brainstorm ideas for your final project
- [ ] Clone the repository [here](https://github.com/CS1951v-F21/editable-nodes-lab)
- [ ] Get setup in your code base with `remirror`. TAs will be there to support you if you have any questions!
- [ ] Different ways of editing a node's title
- Double click
- Context menu
- Keyboard shortcuts
# Reflecting on Assignment #2
Please complete the following [feedback form](https://docs.google.com/forms/d/e/1FAIpQLSfiUgTdt5AAkducq2okRr-gXret3aSoZxG7r-YpPCTDz9Zc-Q/viewform) for Assignment #2. Great job on a tough assignment ~ let us know what you liked and did not like!
# 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>
You will be working in groups of 3 to create your final project, which is your own `hypermedia system`! As you are already aware, the scope of a hypermedia system is extremely broad. It can range from a system focussed on `temporal media` to a system that focusses on `visualisations`, or even just a system like `Wikipedia` that is primarily textual.
:::info
**Note:** for the final project, you are welcome (and encouraged) to use the code that you have worked on throughout the semester however if you are ambitious you are welcome to completely redesign it! You will be paired with a TA who will be available to help you navigate the tough design questions and who will be there to support you with technical questions you may have at any time!
:::
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 0:** Feel free to do this with a partner. 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!
:::
# Cloning the GitHub Repo
You can clone the repository [here](https://github.com/CS1951v-F21/editable-nodes-lab), eveything else in terms of setup should be the same as Unit 2 once you have it stored locally!
Always remember to run `yarn install` in both the frontend and backend portions of the code as well as add your `.env` files from your `Assignment 2` repository. Let us know if you have any questions!
# Using npm modules from the npm registry
<div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:700px" src="https://snyk.io/wp-content/uploads/Malicious-code-found-in-npm-package-event-stream-downloaded-8-million-times-in-the-past-2.5-months-.jpg" /></div>
As an 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 your 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!
## Yarn vs NPM
In projects so far we have used `Yarn` instead of `npm`, although you could do everything with `npm` as well.
**Wondering what the difference is between Yarn and npm?**
They are both package managers to manage a project's dependencies. Facebook developed `Yarn` to fix performance and security concerns with `npm`. Rather than thinking of `Yarn` as a replacement for `npm` think of it something that acts as a superset of `npm` and provides a faster and more secure development environment.
## Dependencies
Project dependencies are the packages that are being used in the code of the project. We have seperated our frontend and backend into two seperate folders, each with their own project dependencies. There are multiple types of project dependencies outlined below.
:::success
**Task 1:** Have a look at the existing `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. At example of a dependency that would go into this list is `ESLint`
### Optional Dependencies
Our project has no optional dependencies, but as the name suggests an optional dependency is optional. If they fail to install, Yarn 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 dependcies 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 in your plugin/package.
You won't have to worry about including peer dependencies for this web application but you may come across `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 is mostly complete! As a result, this unit will mostl be devoted to `frontend` and will be in the `client` folder with development. 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 to add a `user-model` you would also want to store the users as an additional `MongoDB` collection.
:::info
**Note:** Our backend design and implementation is one way to have done this - we acknowledge that there are other ways. Feel free to ask us any questions about how we went about designing / creating an overview for the backend!
:::
:::success
**Task 2:** You should know how to do 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.
:::
```bash=
DB_URI = <YOUR OWN URI>
PORT=5000
TSC_COMPILE_ON_ERROR=true
ESLINT_NO_DEV_ERRORS=true
```
# Frontend
## Environment Variables
Please 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 throughout `Unit 3`.
### What is `remirror`?
<div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:300px" src="https://raw.githubusercontent.com/remirror/remirror/next/support/assets/logo-animated-light.svg?sanitize=true" /></div>
Remirror is a wrapper library for ProseMirror, it is an abstraction layer that makes ProseMirror easier to work with, and provides React and ProseMirror integration.
:::info
**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 you codebase in a logical manner.
:::
Remirror 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 `remirror` to make your text editable, there will be more in-depth explanations in the **Assignment 3** handout as to exactly how you should set it up with links and anchors. In this lab, text nodes will not support linking and anchoring. Setting those up will be explained and included in the assignment handout. In this lab, you are simply expected to have `remirror` installed and to get it to show up.
:::success
**Task 3:** Install `remirror` (documentation [here](https://remirror.io/docs/getting-started/installation))
```
yarn add remirror @remirror/react @remirror/pm
```
:::
To install the `remirror` package you will run the following in your `client` directory. This will add `remirror` to your `package.json` dependencies and also install other relevant packages. In particular when you are installing `remirror`, you are also installing the relevant `ProseMirror` packages.
:::info
The above `Yarn` installation is the same as installing the `remirror`, `@remirror/react`, and `@remirror/pm` packages seperately.
```
yarn add remirror
yarn add @remirror/react
yarn add @remirror/pm
```
If you were using `npm` then you would run:
```
npm install remirror @remirror/react @remirror/pm
```
:::
Wondering where all of the code goes? It goes into your `node_modules` folder! If you can find the `@remirror` directory that means that your installation was successful!
### Using `remirror` in your codebase
Once you have installed it, it is ready to use - getting all of that useful code is really as easy as that!
:::success
**Task 4:** Go to `client / src / components / NodeView / NodeContent / TextContent / TextContent.tsx` and import `remirror`
:::
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 `remirror` into our codebase (in particular `TextContent.tsx`, because that is where we want to use it) - we do:
```
import { Remirror, EditorComponent, useRemirror } from '@remirror/react'
import { RemirrorEventListenerProps } from '@remirror/core'
```
That will import the necessary resources for us to get started!
:::info
`shift + alt + O` is a super useful shortcut in `VSCode` which organises the imports! Try it out in a file where you have a bunch of unused imports!
:::
#### Creating a `remirror` manager
:::success
**Task 5:** Create a `remirror` manager!
:::
Do this by creating an instance of the manager.
```typescript=
const { manager, state } = useRemirror({
content: currentNode.content,
selection: 'start',
stringHandler: 'html',
})
```
Once you have added the manager you will want to add the `remirror` components that we are actually going to render. You should replace the `{currentNode.content}` with the following:
```html=
<Remirror manager={manager} initialContent={state}>
<TextMenu />
<EditorComponent />
</Remirror>
```
Now we should have created a basic, editable text component using `remirror`, without the ability to mark it up. **Now test your code and check that everything is working as expected.** Ignore the `<TextMenu />` component for now - we'll get to that in **Task 6**.
:::success
**Task 6:** Adding a `remirror` extension so that you can make text **bold**
:::
The way that `remirror` works is that it has a set of extensions that you can use to alter and edit the text. Rather than you having to go in and change the inner CSS, the `remirror` extension handles everything for you!
There are some really cool `remirror` extensions - ranging from simple extensions like *Italic* and **Bold** text, to getting setup with `code snippets`. For this lab, we'll walk you through setting up a **bold** extension. [Here](https://remirror.io/docs/extensions/index) is an overview of all of the extensions `remirror` has to offer!
Add the following import to your `TextContent.tsx` file:
```
import { BoldExtension } from 'remirror/extensions'
```
Update your `useRemirror` manager to include the extension as follows:
```typescript=
const { manager, state } = useRemirror({
content: currentNode.content,
selection: 'start',
stringHandler: 'html',
extensions: () => [new BoldExtension()],
})
```
Now that we have that set up, all we need is a way of making the text bold (in addition to the `ctrl / cmmd + B` shortcut).You should import the relevant `remirror` commands into `TextMenu.tsx` as follows!
```import { useActive, useCommands } from '@remirror/react'```
To create a (very very basic) menu, add the following into the `TextMenu.tsx` file, which is currently empty:
```typescript=
const { toggleBold, focus } = useCommands()
const active = useActive()
return (
<div className="textButtons">
<Button
onClick={() => {
toggleBold()
focus()
}}
text={'B'}
style={{ fontWeight: active.bold() ? 'bold' : undefined }}
/>
</div>
)
```
You should end up with editable text which looks like the following image:

:::info
We know it is not the most beautiful of UI layouts - but when we first implement things its important to make sure everything is functioning as expected first!
:::
:::success
**Task 7**: Add the ability to make the text italic! This should be a matter of doing the exact same thing we did for bold, except with the `ItalicExtension`, instead of the **boldExtension**.
:::
:::warning
None of the changes you are currently making are reflected in the database, **don't worry** - you'll get to that in the assignment - first let us explore how we would do this (and the different UI interactions to do so) when editing a node's title!
:::
## Editing the node's title
We have added two new components that we hope you can make use of, they are the `EditableText` and `ContextMenu` components.
:::success
**Task 8**: Make your INode `title` an editable element and ensure that it gets updated in the database!
:::
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:** Write a method that updates the text in the database
:::
3. 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 mislead to believe that it has been uploaded in the database when in reality it has not!
```typescript=
setTitle(title)
const nodeProperty: INodeProperty = makeINodeProperty('title', title)
const titleUpdateResp = await NodeGateway.updateNode(currentNode.nodeId, [
nodeProperty,
])
if (!titleUpdateResp.success) {
setAlertIsOpen(true)
setAlertTitle('Title update failed')
setAlertMessage(titleUpdateResp.message)
}
setRefreshLinkList(!refreshLinkList)
```
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 databse `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:** Add the different ways to make the text editable! Note that it is not always ideal to have all three of these as there may be conflicting, or inaccessible, however this also gives us an oppurtunity to introduce you to exactly how to set these three things up.
:::
Essentially what we are doing here is looking at different ways we can make it such that `editingTitle` is true so that the title is in `editable` mode.
4. `DoubleClick`
The first thing that we should do is add the `onDoubleClick` tag to the `nodeView-title` text div, so that when the user `right-clicks` it sets `editingTitle` to true. In Unit 2, we introduced `e.detail` as part of the `onClick` method, this is another way of doing the same thing!
```typescript=
onDoubleClick={(e) => setEditingTitle(true)}
```
5. `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 `nodeView-title` text, 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!
```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
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 `cmmd`. 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'
```
:::
6. `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 go in to edit the title - but the problem with that is we lose the ability to reload (which `Ctrl + Shift + R` also handles).
First in the useEffect on load, you should ensure that your browser is listening for `keydown` events. This uses `addEventListener`, you can also set it up to listen for `pointerdown`, `pointerup` etc. 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 that you should add the following switch statement into your `nodeKeyHandlers`. 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
- Have any questions about the final project? Ask a TA.
- Show a TA that you have successfully installed the `remirror` dependency.
- Show a TA that you are able to rename a node AND that when you rename it, it updates the node in the database.
- 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>