Skip to main content

Assignment 2 - WeatherFeed: Javascript & DOM

Due Friday, February 19th 11:59pm

Setup

Accept this GitHub Classroom assignment and clone this repo that contains the stencil code for Assignment 2

You should implement all of your HTML in index.html , CSS in css/index.css , and all of your JavaScript in index.js .

In addition, you are allowed to reuse your code from Assignment 1 Part 2 for the user interface of this assignment , though you are not required to. You are allowed to use external libraries such as Bootstrap and others as long as they don’t trivialize the assignment, and you should provide a brief description in your README of how you used them. Lastly, your user interface can but does not need to look like that of Twitter’s, and we encourage you to be creative!

Introduction

Remy the rat is wondering whether or not he needs to wear a rainjacket today, but wants to know what everyone else is wearing on Twitter before he makes his decision. He needs you to design a gadget that will let him view a feed of all weather-related tweets.

We have provided a web server that will fetch 10 random tweets about the weather every time you ask for them. You will build a website that takes those tweets and presents them to the user in some kind of a streaming list.

You will be responsible for periodically querying the server for new Tweets, and using the DOM to append them to a new part of the page. You will also be responsible for filtering out duplicates, as some Tweets may overlap with the last time you requested them. Finally, there should be a search bar to easily look for specific content in the tweets you receive.

Overview

The Twitterfeed Server

You will be interacting with a server we’ve provided that will give you weather-related Tweets. The server is active at the following address: http://twitterfeedserverrails-env.eba-xmqy8ybh.us-east-1.elasticbeanstalk.com

Make a GET request to the /feed/random?q=weather endpoint of the server (example response) to obtain a new block of 10 random Tweets.

The Chrome extension JSON Formatter may be optionally used to more easily view the response, which will be returned as a JSON array.

Take note that some of these Tweets may be duplicates of Tweets that you’ve already seen, and it is your responsibility to filter those out. Every Tweet has a unique id field (id) that you can use for this purpose. Note that for the purposes of this assignment retweets count as their own tweets since they have a different id! Also note, for this assignment, we care about the tweet content (text) - it is possible to get a tweet object with different usernames/profiles but the same text content due to the mechanisms of our Twitter proxy server. If two tweets do have the same text content (are duplicates), those tweets are guaranteed to have the same id.

Response

The Search API's example response describes what a success response from the server looks like.

The available properties for each tweet (represented by an object in statuses array) are those returned by the Twitter API for Tweets.

In each tweet you’ll want to display at least the the author’s name and profile image , the date in a readable format , and the text content for the tweet .

Note that some tweets you receive will be truncated by an ellipsis (...). This is okay as it is done by the API.

For displaying the date in a readable format, you may use the Moment.js library if you wish (the script is already imported for you in the stencil file).

Guide

Since the idea of the TwitterFeed is that it’s a live stream of Tweets, having the entire page refresh every second with new content would be a bit annoying. Thus, the solution is to obtain the data in the background and update the page without refreshing it through JavaScript. There are multiple ways to do this, but we recommend using Fetch .

The standard template for a fetch request looks something like this:

                        
// specify a url, in this case our web server
const url = "http://twitterfeedserverrails-env.eba-xmqy8ybh.us-east-1.elasticbeanstalk.com/feed/random?q=weather"

fetch(url)
  .then(res => res.json())
  .then(data => {
    // do something with data
  })
  .catch(err => {
    // error catching
    console.log(err)
  })
                        
                    

Combine the above template with something like setInterval(...) (see the Mozilla Developer Network’s documentation ) to periodically load new Tweets. Note that the timeout value passed to setInterval(...) is in milliseconds, and we ask that you don’t use a value smaller than 5000 (5 seconds) to avoid overloading our server .

Removing duplicates

For duplicate checking, you should use data.statuses[index].id (id of the tweet) rather than data.statuses[index].user.id (id of the user who made the tweet). It may be a good idea to keep a list or Set of ids that you have seen so far, as well as a master list that stores all the tweets that you might filter and sort on with the duplicates already removed.

Pausing and Unpausing the Feed

The user must be able to pause and unpause the feed (hint: if you have a checkbox <input> element in your HTML file, you can use its boolean property checked after selecting it in JavaScript to determine whether to continue refreshing the feed). You should NOT be making any GET requests to the endpoint while the feed is paused.

Search

The feed must implement a search bar that immediately updates the page with only the filtered tweets whenever the input value is changed. Here’s an example of what this might look like in HTML.
<input id="searchBar" type="search" placeholder="Search in tweets">

And in JavaScript, you could add the following eventListener:

                            
let searchString = "" // here we use a global variable

const handleSearch = event => {
    searchString = event.target.value.trim().toLowerCase()
    ... // you may want to update the displayed HTML here too
}
document.getElementById("searchBar").addEventListener("input", handleSearch)
                            
                        

Think about how this behavior will interact with your polling. Your search bar and polling should both work together seamlessly, i.e. when polling is on and while there is something entered in the search bar, only new tweets that match the search criteria should appear at the time. Because you will still be adding to your master list of tweets as long as polling is turned on, all those tweets should then show up when the search criteria is cleared. Think carefully about the pipeline you’ll use to achieve this, and feel free to take a look at the tips provided in the next section if you get stuck.

Sorting

You should sort the tweets by chronological order, from most recent at the top to oldest at the bottom.

Displaying the Tweets

Finally, you’ll want some way to put all the logic together and display the tweets on the page. There are many ways to do this, but here is one possible pipeline:
SPOILER ALERT! ( )

Say you have a <div> with id 'tweet-container' in your HTML file and all your tweet objects with duplicates removed are stored in the tweets variable. Then, in JavaScript you could do something like this:

                                
const tweetContainer = document.getElementById('tweet-container');

/**
 * Removes all existing tweets from tweetList and then append all tweets back in
 *
 * @param {Array<Object>} tweets - A list of tweets
 * @returns None, the tweets will be renewed
 */
function refreshTweets(tweets) {
    // feel free to use a more complicated heuristics like in-place-patch, for simplicity, we will clear all tweets and append all tweets back
    // {@link https://stackoverflow.com/questions/3955229/remove-all-child-elements-of-a-dom-node-in-javascript}
    while (tweetContainer.firstChild) {
        tweetContainer.removeChild(tweetContainer.firstChild);
    }

    // create an unordered list to hold the tweets
    // {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement}
    const tweetList = document.createElement("ul");
    // append the tweetList to the tweetContainer
    // {@link https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild}
    tweetContainer.appendChild(tweetList);

    // all tweet objects (no duplicates) stored in tweets variable

    // filter on search text
    // {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter}
    const filteredResult = tweets.filter(...);
    // sort by date
    // {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort}
    const sortedResult = filteredResult.sort(...);

    // execute the arrow function for each tweet
    // {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach}
    sortedResult.forEach(tweetObject => {
        // create a container for individual tweet
        const tweet = document.createElement("li");

        // e.g. create a div holding tweet content
        const tweetContent = document.createElement("div");
        // create a text node "safely" with HTML characters escaped
        // {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode}
        const tweetText = document.createTextNode(tweetObject.text);
        // append the text node to the div
        tweetContent.appendChild(tweetText);

        // you may want to put more stuff here like time, username...
        tweet.appendChild(tweetContent);

        // finally append your tweet into the tweet list
        tweetList.appendChild(tweet);
    });
}
                            

You would want to do this every time you finish adding new data from the server and every time the input search value changes, so it would make sense to abstract out this functionality into its own function.

There are several important things going on here:

  • filter , sort , and forEach are very useful here, and you should check out their relevant documentation on MDN if you plan on using them.
  • we wrote the argument to the map function with arrow notation (recall the prelab), which is not required but generally looks cleaner.
  • We used the JSdoc to write comments and function headers.

Accessibility

Properly Styled Skip Links

In Assignment 1, you implemented a basic skip link. Now, you will be styling your skip links in a better way: either by using the a:focus CSS pseudoclass , or by using Bootstrap’s way of creating skip links .
This allows your skip link to be invisible most of the time (and therefore unobtrusive to your design) – until, that is, it receives keyboard focus. For an example of this in action, visit the course website, WebAIM , or Starbucks’s website and hit tab as soon as the site loads. When you hit tab again, the link disappears.

There are multiple ways to do this, of course. As long as your skip link works (i.e. it appears when you hit Tab and redirects focus to your main content when you hit Enter ), you will get credit for this requirement.

Keyboard Navigation

In this assignment, and all subsequent assignments, you will also be required to make your website so that it can be efficiently navigated using only a keyboard. This is extremely important as it allows people with motor disabilities, visually impared people, and people with specially customized keyboards that make up for a missing or deformed body parts to freely use your website with the convenience that anyone else can!

This means that the website must have a logical tab order that allows users to navigate to all required sections of the page with relative ease. A comprehensive guide on how to best achieve this as well as some help with the skip links can be found here .

Requirements

In summary, a complete TwitterFeed will be a website that:

General Notes

As with the previous assignment, you should run your HTML and CSS syntax through validators, and you should also be using an accessibility checker such as WAVE. For errors you dod not choose to fix, make sure to explain your rationale in README.

It is now also recommended that you run your JavaScript code through a linter such as ESLint (we provided a default configuration for ESLint in the Git repo, but feel free to refer to the beginning of lab2 for more details on custom setup).

We also highly encourage you document your code with the use of JSDoc ( simple JSDoc function header annotations would suffice) especially for any code you write whose purpose may not be immediately obvious to the reader.

While we won’t check that you used ESLint or JSDoc directly in this assignment, we will also not hesitate to deduct points if your code is hard to read which could have easily been fixed by the use of such tools.

Troubleshooting

As with Assignment 1, it is recommended that you take advantage of all the online resources we have provided so far. If you get stuck, we would be happy to troubleshoot with you at TA hours!

Hand in

To hand in your code for Assignment 2, upload the directory containing your solution to Gradescope.