-
Notifications
You must be signed in to change notification settings - Fork 0
Home
-
Download VSCode
-
Download the template code
-
Open the code folder using VSCode
-
Install the Live Server plugin
- For Atom, install the atom-live-server package
- Or, if Python installed, run
python -m SimpleHTTPServer 8000on the code folder.
-
Click "Go Live" from the VSCode status bar:

-
You will see the following page in your browser:

The template code has the following structure:
/img
/css
- main.css
- nav.css
- post.css
/js
- main.js
- utils.js
index.html
In this lab, you will only look at index.html and main.js.
In the HTML file, main.js file is included as a module (type="module") as below:
<script src="js/main.js" type="module"></script>It means you can use the new import & export syntax in ES6 in the main.js file. This file serves as an entry point to other javascript files.
In addition, main.js will be loaded after all HTML elements are loaded. As a result, you no longer have to use window.onload in order to wait until the document is fully loaded.
The index.html file also includes CSS files. For modularity, CSS files are separated into three different files. nav.css contains a stylesheet for the navigation bar at the top, post.css contains a stylesheet relevant to the post in the middle of the page, and main.css file contains all other styles.
<link rel="stylesheet" href="css/main.css">
<link rel="stylesheet" href="css/nav.css">
<link rel="stylesheet" href="css/post.css">The only part relevant to this activity is the following code, which is currently empty but will be dynamically filled by using Javascript. We will get to this part soon.
<main class="main">
<div class="posts">
<!-- posts will be dynamically added using javascript -->
</div>
</main>This javascript file contains all the code you need for this activity. The current code generates a static web page; that is, no interactions are supported. You need to fill in missing codes to make the web page (slightly) interactive such as liking a post or making comments. To be specific, I left comments where you need to work. Please look for TODO # in the file.
Let's briefly go over the code to get you started.
See the top line in the file. You can easily import functions in another file utils.js thanks to ES6. Without it, you need to include each script in the HTML file. If your app has a lot of scripts, you know what happens. For more info about the import syntax, see here.
// import some utility functions
import { uniqueId, timespan } from './utils.js';Next, you see the data that is used to create posts.
// this is the underlying data of the web page
let posts = [
{
id: "post_jd4111h4", // post id
userId: "nkim", // user id who created this post
userImg: "/img/nkim.png", // thumbnail image of the user
img: "/img/post1.jpg", // post image
likes: 0, // # of likes
datetime: "2018-02-02T00:40:33.000Z", // time posted
comments: [ // user comments for this post
{
userId: "nkim", // user id who commented
text: "Let's Go Travel!" // text of the comment
}
]
},
{
id: "post_jd5ikpep",
userId: "nkim",
userImg: "/img/nkim.png",
img: "/img/post2.jpg",
likes: 0,
datetime: "2018-02-01T00:40:33.000Z",
comments: []
}
];It is an array containing a list of posts, while each post in the array is an object which has a dictionary structure with keys and properties. You also see an array of comments in each post. Technically, Array is also a subset of Object.
To familiarize yourself with JSON (JavaScript Object Notation), let's try adding a new post to the data.
Hints:
- You can use any image (or the ones in the
/imgfolder such asposts.jpg). - To generate a unique id, please use the
uniqueIdimported fromutils.js. - For example,
uniqueId('post_')will generate a unique id using a current timestamp with the prefix 'post_'. - To fill in the
datatimeproperty, you can usenew Date().toISOString()which formats the current date toYYYY-MM-DDTHH:mm:ss.sssZ. - You can add a new post by directly editing the data above or using a syntax like
posts[id] = {...}.
Similarly, add a new comment for the new post.
Print the data using console.log. You can see the printed data using the Web Console. To open the Web Console using Chrome, 1) right-click on a web page, 2) click 'inspect' in the context menu, and 3) click 'Console' tab at the top tool bar as below:

In order to dynamically render posts and comments as HTMLs, here are two functions that generate HTML texts for posts and comments using a template string (ES6).
function renderComment(comment) {
return `<div class="comment">
<a class="id" href="/">${comment.userId}</a><span>${comment.text}</span>
</div>`
}
function renderPost(post) {
return `<div class="post">
<header class="header">
<a class="photo" href="/">
<img src="${post.userImg}">
</a>
<a class="id" href="/">${post.userId}</a>
</header>
<div class="content">
<img class="image" src="${post.img}"/>
</div>
<div class="footer">
<div class="action">
<a class="icon ${post.likes > 0 ? 'heart' : 'like'}" href="/">Like</a>
<a class="icon comment" href="/">Comment</a>
<a class="icon save" href="/">Save</a>
</div>
<div class="likes">
${post.likes} likes
</div>
<div class="comments">
${post.comments.map(c => renderComment(c)).join('')}
</div>
<time class="time">
${timespan(post.datetime).toUpperCase()} AGO
</time>
<div class="add-comment">
<input type="text" placeholder="Add a comment…"></input>
</div>
</div>
</div>`
}Take a careful look the code, especially the renderPost function, and think about how a post and its comments will be rendered on the web page.
-
Explain in your words how comments will be rendered based on this code
comments.map(c=>renderComment(c)).join(''). -
Also, briefly explain what happens when
post.likesis greater than zero- Hint: multiple classes
Lastly, what is generated from the renderPost is a pure string, not a DOM element. How can we handle user interactions? We can add event listeners only to the DOM instance, not the string. Thus, we will add event listeners later on not in this function.
When user actions happen on a specific post, we should be able to know which post it is. Fortunately, data-attribute in HTML allows us to embed custom data into HTML and retrieve it later using Javascript.
- Let's add a data-attribute called
data-post-idin the root div (i.e., class="post") and set the value as the id of each post.
Here is our root render function. This will retrieve the posts element in the HTML file using querySelector, and set its innerHTML as the string generated by renderPost and renderComment. The innerHTML completely replace the contents of the element using the given string. Unlike querySelectorAll, querySelector returns only the first element matching the selector.
function render(posts) {
// get the posts element
let postsElm = document.querySelector('.posts');
// redner posts inside postsElm
postsElm.innerHTML = posts.map(p => renderPost(p)).join('');
let imageElms = document.querySelectorAll('.post .content .image');
imageElms.forEach(el => el.addEventListener('dblclick', function () {
let postId = this.parentNode.parentNode.getAttribute('data-post-id');
increaseLike(posts, postId);
}));
let commentElms = document.querySelectorAll('.post .add-comment input');
commentElms.forEach(el => el.addEventListener('change', function () {
let postId = null;
addComment(posts, postId, 'kgajos', this.value);
this.value = ''; //empty
}))
}As previously mentioned, we did not yet set event listeners. Once innerHTML is set, DOM elements for posts and comments are instantiated. The rest of the render function finds necessary elements and add event listeners to them.
- TODO 7 : Explain what is selected by the following code
let imageElms = document.querySelectorAll('.post .content .image');Here we are adding two interactions. First, adding a listener for double-click on the post image and another listener for a change on the comment section. It is looping over all images of the posts and add the listener to each image. Same for the comment input.
When the double-click happens, the callback function traverses the DOM tree to retrieve the data-post-id. That is because we need to know which post to update. To traverse up the DOM tree, you can use node.parentNode, to traverse down the DOM tree, you can use node.childNodes.
- TODO 8 : Print 'this' keyword to the Web Console and explain what's contained in 'this' in the callback. You can test this by double-clicking the image on the web browser
Once the post's id is found, the increaseLike is called, which is defined as below:
function increaseLike(posts, postId) {
let post = posts.find(p => p.id == postId);// return the first element matching the condition
if (!post) return;
post.likes = post.likes + 1;
localStorage.setItem('posts', JSON.stringify(posts));
render(posts); // re-render with the updated posts
}Look at the last two lines.
Whenever updating the data, we are saving it to localStorage. It allows us to save the data "locally" within the user's browser. It has no expiration date unlike sessions and also dead simple to use. You save data using setItem which accepts two parameters: key and value. The value has to be a string which is why we are using JSON.stringify to serialize the posts.
The last line calls the render function again to re-render the web page with the updated data.
Let's do the same for the comment input. In the event callback,
-
TODO 9 : Print 'this.value' to the Web Console in the
changecallback. You can test this by writing a comment and pressing 'enter' in the comment section. -
TODO 9 : And, similar to the image dblclick callback, retrieve the corresponding post's id and pass it to
addCommentfunction. -
TODO 10 :
addCommentis currently empty. Fill in the function similar toincreaseLike.
After all done, you can now like a post or comment on it as below.