282 lines
15 KiB
Markdown
282 lines
15 KiB
Markdown
In this assignment, we are going to develop a simple single-page iNotes application using the MERN stack (MongoDB, Express.JS, ReactJS, and Node.js). The main workflow of the iNotes
|
||
application is as follows.
|
||
|
||
|
||
Upon loading, the sketch of the page is shown in Fig. 1:
|
||
|
||
Fig. 1
|
||

|
||
|
||
Fig. 2
|
||

|
||
|
||
Fig. 3
|
||

|
||
|
||
Fig. 4
|
||

|
||
|
||
Fig. 5
|
||

|
||
|
||
Fig. 6
|
||

|
||
|
||
|
||
-----
|
||
|
||
## TEST1
|
||
|
||
### After a user (e.g., Andy) has logged in, the sketch of the page is in Fig. 2.
|
||
- ✔️The user’s icon,
|
||
- ✔️user name and a
|
||
- ✔️logout button are displayed on the top.
|
||
- ✔️A list of this user’s notes are shown on the left panel (node `title` only) together with a
|
||
- ✔️search bar, and the
|
||
- ✔️right panel is empty except an icon indicating the creation of a new note
|
||
- (i.e., the `New note icon`).
|
||
- ✔️The node titles in the left panel should be listed in reverse chronological order of the last saved time of the notes.
|
||
- ✔️The total number of notes should be given in the ( ) on the top of the list.
|
||
|
||
-----
|
||
|
||
✔️❌📖📄
|
||
|
||
## TEST2
|
||
|
||
### After one clicks on the `New note icon` (on any page view where it is shown),
|
||
- ✔️A new node creation panel shows in the right panel (Fig. 3).
|
||
- ✔️There is a node `title` input field and a note `content` input field, into which the user can enter texts.
|
||
- ✔️There is a `Cancel` button, clicking which a confirmation box `Are you sure to quit editing the note?` will be popped up:
|
||
- ✔️if the user confirms quitting, the page view goes back to the one shown in Fig. 2;
|
||
- ✔️otherwise, the current page view remains.
|
||
- ✔️There is a `Save` button, clicking which the newly created note is shown on the right panel,
|
||
- ✔️with the `Last saved` time and a `Delete` button displayed on top of the note, as shown in Fig. 4;
|
||
- ✔️The note `title` should be listed in the left panel, as the first in the list (as it is the latest),
|
||
- ✔️The note `title` should be highlighted in a different color than the rest of the note titles in the list (since this note’s `content` is shown in the right panel),
|
||
- ✔️and the total number of notes in ( ) on top of the list should be incremented.
|
||
|
||
Fig. 3 Fig. 4
|
||
|
||
-----
|
||
|
||
## TEST3
|
||
|
||
### At any time, when one clicks on one note `title` in the left panel, the note’s `content` should be displayed in the right panel
|
||
- ✔️as shown in Fig. 5 (which is in fact the same page view as Fig. 4),
|
||
- and the node `title` in the left panel should be highlighted.
|
||
- On the page view (i.e., Fig. 4 or Fig. 5’s page view), if one clicks into the note `title` input field or note `content` input field,
|
||
- ✔️the page view changes to the one in Fig. 6, with a `Cancel` button and a `Save` button (indicating a note editing mode).
|
||
- ✔️When `Cancel` is clicked, a confirmation box `Are you sure to quit editing the note?` will be popped up:
|
||
- ✔️if the user confirms quitting, the page goes back to the previous note view (Fig. 4 or Fig. 5);
|
||
- otherwise, the current page view remains.
|
||
- ✔️When `Save` is clicked, a page view as in Fig. 4 or Fig. 5 is shown, except that the `Last saved` time on the top of the right panel should be the updated latest note saved time.
|
||
|
||
Fig. 5 Fig. 6
|
||
|
||
-----
|
||
|
||
### TEST4
|
||
|
||
### One can input a search string into the search bar at the top of the left panel and press `Enter` on the keyboard.
|
||
- ✔️Then only the notes whose `title` or `content` contains the search string will be listed in the left panel,
|
||
- ✔️ordered in reserve chronological order of their last saved time, and the number in ( ) shows the number of matched notes.
|
||
- On a page view as in Fig. 3, 4, 5 or 6,
|
||
- ✔️the search does not influence the display in the right panel,
|
||
- and if the note whose details are displayed in the right panel matches the search,
|
||
- its `title` in the searched list in the left panel should be highlighted.
|
||
- For a search on a page view as in Fig. 6,
|
||
- ✔️last saved `title` and `content` of the note that is being edited are used for matching the search;
|
||
- ✔️when the note is saved, if its `title` and `content` do not match the search, it will not be displayed in the searched list in the left panel.
|
||
|
||
-----
|
||
|
||
### TEST5
|
||
|
||
### On a page view as in Fig. 4 or Fig. 5, after one clicks the `Delete` button,
|
||
- ✔️a confirmation box pops up showing `Confirm to delete this note?`
|
||
- ✔️If the user confirms the deletion, the note information will be removed from both the left and right panels.
|
||
- ✔️In the left panel, the total note number will be decremented;
|
||
- ✔️the right panel will show no note information as in Fig. 2.
|
||
- ✔️If the user cancels the deletion, the page view remains unchanged.
|
||
|
||
-----
|
||
|
||
### TEST6
|
||
|
||
### When one clicks the `log out` button on a page view as in Fig. 2, 4 or 5,
|
||
- ✔️the page view directly goes back to Fig. 1.
|
||
- When one clicks the `log out` button on a page view as in Fig. 3 or 6,
|
||
- ✔️an alert box `Are you sure to quit editing the note and log out?` will be popped up:
|
||
- ✔️if the user confirms quitting, the page view goes back to Fig.1;
|
||
- ✔️ otherwise, the current page view remains.
|
||
|
||
We are going to achieve this web application by implementing code in a backend Express app
|
||
and a frontend React app.
|
||
• Express app:
|
||
app.js
|
||
./routes/notes.js
|
||
• React app:
|
||
./src/App.js
|
||
./src/index.js
|
||
./src/App.css
|
||
|
||
# Task 1. Backend Web Service
|
||
We implement the backend web service logic using Express.js. The web service is accessed at `http://localhost:3001/xx`.
|
||
|
||
### Preparations
|
||
|
||
1. Following steps in setup_nodejs_runtime_and_examples_1.pdf, create an Express project named NoteService.
|
||
1. Following steps in AJAX_JSON_MongoDB_setup_and_examples.pdf, run MongoDB server, and create a database `assignment2` in the database server.
|
||
1. Insert a few user records to a userList collection in the database in the format as follows.
|
||
|
||
We will assume that user names are all different in this application. `db.userList.insert({'name': 'Andy', 'password': '123456', 'icon': 'icons/andy.jpg'})`. Create a folder `icons` under the public folder in your Express project directory (NoteService). Copy a few icon images to the icons folder. For implementation simplicity,
|
||
|
||
we do not store icon images in MongoDB.
|
||
|
||
Instead, we store them in the harddisk under the NoteService/public/icons/ folder, and store the path of an icon in the userList collection only, using which we can identify the icon image in the icons folder.
|
||
|
||
Insert a number of records to a `noteList` collection in the database in the format as follows, each corresponding to one note in the app. Here userId should be the value of `_id` of the record in the userList collection, corresponding to the user who added the note.
|
||
|
||
```
|
||
db.noteList.insert(
|
||
{ 'userId': 'xxx',
|
||
'lastsavedtime': '20:12:10 Tue Nov 15 2022',
|
||
'title': 'assigment2',
|
||
'content': 'an iNotes app based on react'
|
||
})
|
||
```
|
||
|
||
Implement backend web service logic (NoteService/app.js, NoteService/routes/notes.js)
|
||
|
||
|
||
# Marking scheme
|
||
### app.js (10 marks)
|
||
|
||
In app.js, load the router module implemented in ./routes/notes.js. Then add the middleware to specify that all requests for http://localhost:3001/ should be handled by this router.
|
||
|
||
Add necessary code for loading the MongoDB database you have created, creating an instance of the database, and passing the database instance for usage of all middlewares.
|
||
|
||
Also load any other modules and add other middlewares which you may need to implement
|
||
this application.
|
||
|
||
We will let the server run on the port 3001 and launch the Express app using command
|
||
`node app.js`.
|
||
|
||
### ./routes/notes.js (22 marks)
|
||
In notes.js, implement the router module with middlewares to handle the following
|
||
endpoints:
|
||
|
||
1. HTTP `POST` requests for http://localhost:3001/signin.
|
||
- The middleware should parse the body of the HTTP `POST` request and extract the username and password carried in request body.
|
||
- Then it checks whether the username and password match any record in the userList collection in the database.
|
||
- If no, send `Login failure` in the body of the response message.
|
||
- If yes, create a session variable `userId` and store this user’s `_id` in the session variable.
|
||
- Retrieve name and icon of the current user (according to the value of the `userId` session variable), `_id`, `lastsavedtime` and `title` of all notes of the current user from the respective collections in the MongoDB database.
|
||
- Send all retrieved information as a JSON string to the client if database operations are successful, and the error if failure.
|
||
- You should decide the format of the JSON string and parse it accordingly in the front-end code to be implemented in Task 2.
|
||
|
||
1. HTTP `GET` requests for http://localhost:3001/logout.
|
||
- The middleware should clear the `userId` session variable and send an empty string back to the user.
|
||
|
||
1. HTTP `GET` requests for http://localhost:3001/getnote?noteid=xx.
|
||
- Retrieve `_id`, `lastsavedtime`, `title` and `content` of the note from the `noteList` collection based on the value of `nodeid` carried in the URL. Send retrieved information as a JSON string in the body of the response message if database operations are successful, and the error if failure.
|
||
- You should decide the format of the JSON string to be included in the response body.
|
||
|
||
1. HTTP `POST` requests for http://localhost:3001/addnote.
|
||
- The middleware should parse the body of the HTTP `POST` request and extract the note `title` and `content` carried in the request body.
|
||
- Then it saves
|
||
- the new note into the noteList collection together with the `_id` of the current user (based on the value of `userId` session variable)
|
||
- and the current time on the server as the `lastsavedtime`.
|
||
- if database operations are successful.
|
||
- Return the `lastsavedtime` and `_id` of the note document in the nodeList collection to the client in JSON
|
||
- and the error if failure
|
||
|
||
1. HTTP `PUT` requests for http://localhost:3001/savenote/:noteid
|
||
|
||
- The middleware should update the `lastsavedtime`, `title` and `content` of the note in the noteList collection based on
|
||
- the `nodeid` carried in the URL,
|
||
- the current time on the server and
|
||
- the data contained in the body of the request message.
|
||
- if success Return the `lastsavedtime` to the client .
|
||
- and the error if failure
|
||
|
||
1. HTTP `GET` requests for http://localhost:3001/searchnotes?searchstr=xx.
|
||
|
||
- The middleware should find in the noteList collection all notes of the current user (based on the value of `userId` session variable) whose `title` or `content` contains the searchstr carried in the URL.
|
||
- Send `_id`, `lastsavedtime` and `title` of those notes in JSON to the client if database operations are successful, and
|
||
- error if failure.
|
||
|
||
1. HTTP `DELETE` requests for http://localhost:3001/deletenote/:noteid
|
||
|
||
- The middleware, should delete the note from the `noteList` collection according to the `noteid` carried in the URL.
|
||
- Return an empty string to the client if success and
|
||
- the error if failure
|
||
|
||
## Task 2
|
||
### Front-end React App
|
||
|
||
Implement the front-end as a React application.
|
||
The application is accessed at http://localhost:3000/.
|
||
|
||
### Preparations
|
||
Following steps in React_I_examples.pdf, create a React app named noteapp and install the jQuery module in the React app.
|
||
|
||
Implement the React app (`noteapp/src/index.js`, `noteapp/src/App.js`, `noteapp/src/App.css`)
|
||
|
||
### index.js (3 marks)
|
||
|
||
Modify the generated `index.js` in the `./src` folder of your react app directory,
|
||
|
||
which should render the component you create in `App.js` in the `root` division in the default `index.html`, and remove any unnecessary code.
|
||
|
||
### App.js (50 marks)
|
||
|
||
`App.js` should import the jQuery module and link to the style sheet `App.css`.
|
||
|
||
Design and implement the component structure in `App.js`, such that the front-end page
|
||
views and functionalities as illustrated in Figures 1-6 can be achieved.
|
||
|
||
### Hints:
|
||
- You can use conditional rendering to decide if the page view in Fig. 1 or Fig. 2 should be rendered. Suppose the root component you are creating in App.js is iNoteApp. In iNoteApp, you may use a state variable to indicate if the user has logged in or not,
|
||
and then render the component presenting the respective page view accordingly.
|
||
|
||
Initialize the state variable to indicate that no user has logged in, and update it when
|
||
a user has successfully logged in and logged out, respectively.
|
||
|
||
- In the component implementing the page view as in Fig. 1, add an event handler for the onClick event on the `Sign in` button. When handling the event, send an HTTP `POST` request for http://localhost:3001/signin (refer to this website for AJAX cross-origin with cookies: http://promincproductions.com/blog/cross-domain-ajax-request-cookies-cors/).
|
||
|
||
- According to the response received from the backend service, remain on the page view and display the `Login failure` message at the top of the page, or render the page view as in Fig. 2. You can limit the number of characters in each note `title` in the left panel to a small fixed number n, i.e., only the first n characters of the note `title` is shown and the rest shown as `…`.
|
||
|
||
- When handling the `onClick` event on the `log out` button in a page view as in Figures
|
||
2-6, send an HTTP `GET` request for http://localhost:3001/logout and handle the
|
||
response accordingly.
|
||
|
||
- When handling the onClick event on a node `title` in the left panel of a page view as in Figures 2-6, send an HTTP `GET` request for http://localhost:3001/getnote?nodeid=xx, where xx should be the `_id` of the note that you store with the note `title` in the list. Then render a page view as in Fig. 5.
|
||
|
||
- When handling the onClick event on the `Save` button in a page view as in Fig. 3, send an HTTP `POST` request for http://localhost:3001/addnote carrying the new node’s `title` and `content` in the body of the request message, and handle the response accordingly.
|
||
|
||
- When handling the onClick event on the `Save` button in a page view as in Fig. 6, send an HTTP `PUT` request for http://localhost:3001/updatenote/xx where xx should be the `_id` of the note being updated, and handle the response accordingly.
|
||
|
||
- When handling the onClick event on the `Delete` button in a page view as in Fig. 4 or Fig. 5, send an HTTP `DELETE` request for http://localhost:3001/deletenote/xxx (where xxx is `_id` of the note to be deleted). If success response is received, update the page view accordingly.
|
||
|
||
- When handling the onKeyUp event (event.key == "Enter") on the search input box in a page view as in Figures 2-6, send an HTTP `GET` request for http://localhost:3001/searchnotes?searchstr=xxx (where xx is the input search string). When success response is received, update the page view accordingly.
|
||
|
||
### App.css (10 marks)
|
||
|
||
Style your page views nicely using CSS rules in App.css.
|
||
|
||
### Other marking criteria: (5 marks)
|
||
- Good programming style (avoid redundant code, easy to understand and maintain).
|
||
- You are encouraged to provide a readme.txt file to let us know more about your programs.
|
||
|
||
### Submission:
|
||
You should zip the following files (in the indicated directory structure) into a yourstudentID-a2.zip file
|
||
|
||
```
|
||
NoteService/app.js
|
||
NoteService /routes/notes.js
|
||
noteapp/src/App.js
|
||
noteapp/src/index.js
|
||
noteapp/src/App.css
|
||
``` |