This time, we are going to be building a Random Quote Machine. We must code a page that replicates the functionalities present here. The user stories that we must fulfil are the following:
- As a user, I can click a button to show me a new random quote.
- Bonus: As a user, I can press a button to tweet out a quote.
I’m going to take you through the process of setting up the JS code necessary for this app to work. If you want to give React a chance, go ahead and visit this other post, where we’ll build this same app using Facebook’s React framework.
I’m going to go ahead and provide the HTML and SCSS files for the markup, since they are very simple this time around.
Random Quote Machine markup
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Random Quote Machine</title> <link rel="stylesheet" href="css/main.css"/> </head> <body><button class="random-quote">Random quote!</button> </div> http://js/app.js </body> </html>@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700); * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; } .main-container { width: 80%; margin: 20px auto 0 auto; max-width: 960px; } .quote-container { padding: 20px; border: 1px solid #555; min-height: 150px; p.quote-text { margin-bottom: 20px; } p.said-by { font-weight: 700; text-align: right; } } button.random-quote { position: relative; top: -1px; width: 100%; height: 30px; background-color: transparent; border: 1px solid #555; cursor: pointer; font-weight: 700; transition: 0.2s ease-out all; &:hover { color: #E4460C; border-color: #E4460C; } }Go ahead and borrow from those or create your own. We are going to add the Tweet button later on, so I haven’t added anything related to it yet. We’ll come back to this later.
At the moment, we just need an element to display the random quote, another element to display the author and a button that will get us a new random quote.
Now, onto the application logic, first of all, I’m going to set up an array with a few quotes that I found around the internet. Each quote will have the following format:
{ author: "Mr. Author", quote: "This is the quote text!" }Here’s the quote array once I’ve populated it, I’m going to create a separate JS file and add it to the index.html as a reference before app.js. We’ll call it quotes.js:
var quotes = [ {author: "Charles Bernstein", quote: "The combination of low culture and high technology is one of the most fascinating social features of the video game phenomenon. Computers were invented as super drones to do tasks no human in her or his right mind (much less left brain) would have the patience, or the perseverance, to manage. Now our robot drones, the ones designed to take all the boring jobs, become the instrument for libidinal extravaganzas devoid of any socially productive component. Video games are computers neutered of purpose, liberated from functionality. The idea is intoxicating; like playing with the help on their night off."}, {author: "John D. Carmack", quote: "Story in a game is like a story in a porn movie. It's expected to be there, but it's not that important."}, {author: "Barack Obama", quote: "It's tough to "buy American" when a video game sold by a U.S. company has been developed by Japanese software engineers and packaged in Mexico."}, {author: "Heather Chaplin & Aaron Ruby", quote: "Show me your children's games, and I will show you the next hundred years."}, {author: "Sid Meier", quote: "In a way, trying to impress people with design or personality or whatever works to promote movies doesn't work with games because it takes the focus off the player who is supposed to be the star. The more the player is the star, the better a game you have."}, {author: 'Half Life 2', quote: "The right man in the wrong place can make all the difference in the world."}, {author: "Bioshock", quote: "Is a man not entitled to the sweat of his brow?"}, {author: "Duke Nukem", quote: "It's time to kick ass and chew bubble gum, and I'm all out of gum."}, {author: "The Legend of Zelda", quote: "It’s dangerous to go alone; take this!"} ];Here’s the updated index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Random Quote Machine</title> <link rel="stylesheet" href="css/main.css"/> </head> <body><button class="random-quote">Random quote!</button> </div> http://js/quotes.js <!-- Quotes are defined here! --> http://js/app.js </body> </html>Now that any variable that we declare outside of a function will be global (a property of the window object), so we can access them from any other script file as long as we declared said variable before we attempt to use it.
Application logic
I’m going to go ahead and start working on the app.js file now. First, I’ll set a few references to the elements that we’ll be using along the way. I’ll write the whole thing inside a IIFE (Immediatly Invoked Function Expression). It’s a similar concept to the jQuery $(document).ready() function, and will prevent our variables from being global:
(function() { // Immediately Invoked Function Expression // We set the quote and author containers along with the get random quote button. var elRandomQuoteButton = document.querySelector('button.random-quote') elQuoteContainer = document.querySelector('.quote-text'), elAuthor = document.querySelector('.said-by'); })();Let’s also add an click event listener to the button element. We’ll set the working logic in a little bit:
(function() { var elRandomQuoteButton = document.querySelector('button.random-quote') elQuoteContainer = document.querySelector('.quote-text'), elAuthor = document.querySelector('.said-by'); elRandomQuoteButton.addEventListener('click', function() { // We'll get a new random quote here! }); })();We now need a function that will get us a random quote, and update both the quote container and author elements. Instead of directly updating the elements by grabbing them from the function scope, I’ll create the function to that it takes in three arguments. The quotes array, quote container element and author element:
(function() { var elRandomQuoteButton = document.querySelector('button.random-quote') elQuoteContainer = document.querySelector('.quote-text'), elAuthor = document.querySelector('.said-by'); elRandomQuoteButton.addEventListener('click', function() { // We'll get a new random quote here! }); function newRandomQuote(quoteArray, quoteElement, authorElement) { var newQuote = quoteArray[randomGen(0, quoteArray.length - 1)]; quoteElement.innerText = newQuote.quote; authorElement.innerText = newQuote.author; function randomGen(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } } })();This function simply generates a random number based on the length of the quotes array and then get’s us a quote using that random number as the index. Then, it sets the inner text for the quote container and author to the new values.
Let’s go ahead and execute this method every time the random quote button is clicked (also, make sure that you call this function once outside the click event, so that we get a random quote the first time that the page loads):
(function() { var elRandomQuoteButton = document.querySelector('button.random-quote') elQuoteContainer = document.querySelector('.quote-text'), elAuthor = document.querySelector('.said-by'); // We call the function once outside the function. newRandomQuote(quotes, elQuoteContainer, elAuthor); elRandomQuoteButton.addEventListener('click', function() { // Every time the button is clicked the function will update the page. newRandomQuote(quotes, elQuoteContainer, elAuthor); }); function newRandomQuote(quoteArray, quoteElement, authorElement) { var newQuote = quoteArray[randomGen(0, quoteArray.length - 1)]; quoteElement.innerText = newQuote.quote; authorElement.innerText = newQuote.author; function randomGen(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } } })();At this point, our app should be working. But we still need to add the insidious Tweet Button. This little guy is a problem child. The way in which these tweet buttons work is the following:
- We add the Twitter widgets.js file to our page.
- We generate a tweet button anchor tag using the Twitter API site. (It’s a simple <a> element with a few attributes).
- We place the button in our page.
- When the page get’s initialized, widgets.js generates a tweet button out of every anchor tag (it converts every tweet button anchor tag into an iframe).
What’s the problem here, you say? We’ll get to it in a minute. Let’s first add the widgets.js code to our page. You can use the code here or get it from Twitter for Websites using their button generator.
This is what the index.html looks like with the script added (I have not added the button code though, that’s on purpose by the way):
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Random Quote Machine</title> <link rel="stylesheet" href="css/main.css"/> </head> <body><button class="random-quote">Random quote!</button> </div> window.twttr = (function(d, s, id) { var js, fjs = d.getElementsByTagName(s)[0], t = window.twttr || {}; if (d.getElementById(id)) return t; js = d.createElement(s); js.id = id; js.src = "https://platform.twitter.com/widgets.js"; fjs.parentNode.insertBefore(js, fjs); t._e = []; t.ready = function(f) { t._e.push(f); }; return t; }(document, "script", "twitter-wjs")); http://js/quotes.js http://js/app.js </body> </html>Now, the twitter button works as follows: We place the tweet button anchor tag in our HTML, it looks like so:
<a href="https://twitter.com/share" class="twitter-share-button" data-count="horizontal" data-text="CUSTOM_TWEET_TEXT">Tweet</a>Widgets.js will then turn this thing into an iframe. The data-text attribute is what contains the text that will be shown in the Tweet window when the button is clicked. We need a way to update it, but widgets.js only runs once at load time. We need to manually remove the previous button (an already generated iframe), add a new button (using the <a> element above) and force the twitter library to refresh the buttons in the page. We’ll do it using a JS function that will get called once at load time, and every time that we generate a new random quote.
This function will only take an argument: text. This argument is the text that we want displayed in the Tweet window when the tweet button is clicked. The function looks like so:
function generateTweetButton(text) { var tweetButton = document.createElement('a'); // We create a new element (<a>) tweetButton.setAttribute('href', 'https://twitter.com/share'); // We set the href attribute to the default tweetButton.setAttribute('class', 'twitter-share-button'); // We set the class to the default tweetButton.setAttribute('data-count', 'horizontal'); // We want to display the amount of times this has been twitted tweetButton.setAttribute('data-text', text); // We set the text to whatever we passed to the function return tweetButton; // We return the element }This function will only generate the tweet button element and set it’s attributes. It does not put it in the page, but it returns an element that we’ll use very soon.
Since we want this button to be generated every time that a new quote is generated, I’m going to alter the newRandomQuote function to also create and append the new tweet button to the DOM:
function newRandomQuote(quoteArray, quoteElement, authorElement) { var newQuote = quoteArray[randomGen(0, quoteArray.length - 1)]; quoteElement.innerText = newQuote.quote; authorElement.innerText = newQuote.author; var tweetButton = generateTweetButton(newQuote.quote); // We create a new tweet button with the new quote text. document.querySelector('.main-container').appendChild(tweetButton); // We append the tweet button as the last element in the main-container div. function randomGen(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } }We just have two little problems left. The first time that the page get’s loaded, the tweet button is fine. But as we click the Random Quote button, we need to do two things:
- Force widgets.js to reload so that it generates the button.
- Remove the previous tweet button, since we are going to add a new one, and we don’t want to be showing a million tweet buttons to the user! (pic related).
Simply enough, we just need to add two lines of code inside the click event handler, this is what the finished version of app.js looks like:
(function() { var elRandomQuoteButton = document.querySelector('button.random-quote') elQuoteContainer = document.querySelector('.quote-text'), elAuthor = document.querySelector('.said-by'); newRandomQuote(quotes, elQuoteContainer, elAuthor); elRandomQuoteButton.addEventListener('click', function() { document.querySelector('iframe').remove(); // We remove the previous button iframe newRandomQuote(quotes, elQuoteContainer, elAuthor); twttr.widgets.load(); // We force the reload using the twttr object that we have created in the index.html file }); function newRandomQuote(quoteArray, quoteElement, authorElement) { var newQuote = quoteArray[randomGen(0, quoteArray.length - 1)]; quoteElement.innerText = newQuote.quote; authorElement.innerText = newQuote.author; var tweetButton = generateTweetButton(newQuote.quote); document.querySelector('.main-container').appendChild(tweetButton); function randomGen(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } } function generateTweetButton(text) { var tweetButton = document.createElement('a'); tweetButton.setAttribute('href', 'https://twitter.com/share'); tweetButton.setAttribute('class', 'twitter-share-button'); tweetButton.setAttribute('data-count', 'horizontal'); tweetButton.setAttribute('data-text', text); return tweetButton; } })();All set, ready to go, here’s the live demo, along with the AngularJS and React versions. Start randomizing to your hearts content. Remember, that you can also check out the tutorial on the React version of this project here.
Feel free to email me, ping me on twitter or post a comment below with any questions, feedback or whatever you feel like typing really!