214 lines
14 KiB
Markdown
214 lines
14 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
|
||
Ø 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.
|
||
Fig. 2
|
||
Ø 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; besides, 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
|
||
Ø 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
|
||
Ø 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.
|
||
Ø 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.
|
||
Ø 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.
|
||
2. Following steps in AJAX_JSON_MongoDB_setup_and_examples.pdf, run MongoDB server,
|
||
and create a database “assignment2” in the database server.
|
||
3. 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)
|
||
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.
|
||
2. 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.
|
||
3. 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.
|
||
4. 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. Return the lastsavedtime and _id of the note document in the
|
||
nodeList collection to the client in JSON if database operations are successful, and the error
|
||
if failure.
|
||
5. 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. Return the lastsavedtime to the client if success and the error
|
||
if failure.
|
||
6. 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 the error if failure.
|
||
7. 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
|