In this tutorial, we will learn how to create a random quote generator with VueJS using Quotes and Design API

Filed under

Getting Started

To properly follow this material, you must have installed the latest NPM and Node.JS:

Setup

Let's create our project directory and named it vuejs-quotes-generator. Copy the project structure below:

vuejs-quotes-generator/
	src/
		components/
		app.js
	index.html
	package.json

First, let's add some dependecies on our package.json file:

{
	"name": "vue-quotes-generator",
	"description": "Random Quotes Generator",
	"version": "1.0.0",
	"scripts": {
		"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
		"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
	},
	"dependencies": {
		"vue": "^2.5.11"
	},
	"devDependencies": {
		"babel-core": "^6.26.0",
		"babel-loader": "^7.1.2",
		"babel-preset-env": "^1.7.0",
		"babel-preset-stage-3": "^6.24.1",
		"cross-env": "^5.0.5",
		"css-loader": "^0.28.7",
		"vue-loader": "^13.0.5",
		"vue-template-compiler": "^2.4.4",
		"webpack": "^3.6.0",
		"webpack-dev-server": "^2.9.1"
	}
}

Since we will be using webpack to bundle our application, let's create a config file named webpack.config.js inside our project directory. Then place the following:

var path = require('path')
var webpack = require("webpack")

module.exports= {
	entry : "./src/app.js",
	output : {
		path: path.resolve(__dirname, './dist'),
    	publicPath: 'dist/',
		filename : "build.js"
	},
	module : {
		rules : [
			{
				test : /\.vue$/,
				loader : "vue-loader"
			}
		]
	}
}

As you can see above, we define the entry point of our application, in this case the app.js file, then the output filename and directory on where should the build of our application will go. Lastly, in our rules section, this should tell webpack on how to handle .vue files.

Modify our HTML file as shown below:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<title>Vue Example</title>
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
	<link href="https://fonts.googleapis.com/css?family=VT323" rel="stylesheet">
</head>
<body>
	<div id="app"></div>
</body>
// Here we link the output file specified in our config file
<script src="dist/build.js"></script>
</html>

Create a file named App.vue. This should act as the container of our application. The following code should be placed in your App.vue file:

<template>
	<div class="content-container">
		{{ greeting }}
	</div>
</template>

<script>
export default {
	data() {
		return {
			greeting : "Hi there!"
		}
	}
}
</script>

<style>
html {
	background-color: #3e3f3a;
	color: #ffffff;
	font-family: 'VT323';
}
body {
	margin: 0;
	padding: 0;
}
*, :after, :before {
	box-sizing: border-box;
}
.content-container {
	display: table;
	width: 70%;
	margin: 100px auto;
}
</style>

Open our app.js file. Our app.js file now should import the App.vue file, create a new Vue instance, render our App.vue file and bind it to the #app element as shown below:

import Vue from 'vue'
import App from "./App.vue"

new Vue({
	el : "#app",
	render: function (createElement) {
		return createElement(App);
	},
	components : { App }
})

Let's test now our application if the setup works. First, let's run this command to install the dependecies we included in our package.json:

npm install

After all dependecies have been installed, execute this command:

npm run dev

It will run the app in development mode and it will open the page (http://localhost:8080) in the browser. Now you shoud see a greeting in our page that say's "Hi there!".

Creating components

Now we will be creating different components that we will be using for our application. First, let's create a component called "Row". Inside our components directory, create a file named Row.vue and place the following:

<template>
<div class="row"><slot></slot></div>
</template>

<script>
export default {
	name : "Row"
}
</script>

<style scoped>
.row {
	margin-left: -15px;
    margin-right: -15px;
	margin-bottom: 15px;
}
</style>

We want to use this component in our App.vue file, and later on, if necessary, on other components. Let's register this component globally. To do this, create a file named index.js inside our components directory. In this file, let's import our Row.vue file and register it as a component as shown below:

import Vue from "vue"

import Row from "./Row.vue"

Vue.component("Row", Row)

export {
	Row
}

Now let's import this index.js file in our app.js. Add this line before creating the Vue instance:

import { index } from './components/index'

Open our App.vue file and let's use our Row component as shown below:

<template>
	<div class="content-container">
		<Row>
		</Row>
	</div>
</template>

Now this doesn't do much yet. Let's proceed by creating a component called "Container". By it's name, it will act as the container for our quote generated. This component is much similar to our Row component, the only difference is the CSS rules. Create a file named Container.vue inside our components directory and place the following:

<template>
	<div class="container">
		<slot></slot>
	</div>
</template>

<script>
export default {
    name: "Container"
}
</script>

<style scoped>
.container {
	position: relative;
	display: block;
	word-wrap: break-word;
	height: 410px;
	text-align: center;
}
@media (max-width: 768px) {
	.container {
		height: auto;
	}
}
</style>

Then the same with our Row component, register this Container component globally. Do the necessary changes to our index.js file. Then use our Container component in our App.vue file in such way as shown below:

<template>
	<div class="content-container">
		<Row>
			<Container>
			</Container>
		</Row>
	</div>
</template>

Now we will be creating our TextButton component. Create a file named "TextButton.vue" inside our components directory and place the following code:

<template>
<button type="submit" class="btn" v-on:click="$emit('get-quote')">
	{{ label }}
</button>
</template>

<script>
export default {
	name : "TextButton",
	props : [ "label" ]
}
</script>

<style scoped>
.btn {
    display: block;
	margin: 0 auto;
    font-weight: 400;
    text-align: center;
    white-space: nowrap;
    transition: all .15s ease-in-out;
    border-radius: 0;
	cursor: pointer;
	user-select: none;
    border: 1px solid transparent;
    padding: .375rem .75rem;
    font-size: 18px;
    line-height: 1.5;
	background-color: #f3f3f3;
    border-color: #f3f3f3;
	color: #000000;
	text-transform: uppercase;
	font-family: 'VT323';
	width: 30%;
	border-radius: 0.25rem;
}
</style>

Also, make the necessary changes for this component to be registered globally. Did you notice how we have assigned our click event on our button?

v-on:click="$emit('get-quote')"

The reason for this is we will be using custom events. Our TextButton will not listen to an event locally, which means the event that it will be listening will based on the event passed to it on how it was used. On our case, let's use this TextButton component on our App.vue file as shown below:

<template>
	<div class="content-container">
		<Row>
			<Container>
			</Container>
		</Row>
		<Row>
			<TextButton label="Get Quote" v-on:get-quote="generateQuote" />
		</Row>
		<Credit />
	</div>
</template>

Since the event name specified in our TextButton is get-quote, listening to an event when using the TextButton component should match the exact name. Now let's create the method generateQuote on our App.vue file:

<script>
export default {
	data() {
		return {
			greeting : "Hi there!"
		}
	},
	methods : {
		generateQuote() {
		}
	}
}
</script>

Now it's time to make some API calls to Quotes and Design API. Create a method in our App.vue called getRandomQuote and on this method, make an API call to Quotes and Design API as shown below:

async getRandomQuote() {
	var timestamp = new Date()
	var apiUrl = "http://quotesondesign.com/wp-json/posts?filter[orderby]=rand&filter[posts_per_page]=1&" + timestamp
	let response = await fetch(apiUrl)
	return await response.json()
}

In our data section in App.vue, replace the greeting key into quote then the default value should be just an empty string. Let's use this getRandomQuote method in our generateQuote method.

async generateQuote() {
	var randomQuote = await this.getRandomQuote()
	if(randomQuote) {
		this.quote = randomQuote.shift()
	}
}

NOTE: Don't forget to add the async keyword to our generateQuote method since we need this when using the await keyword.

Now if you refresh our page, if you try clicking the Get Quote button, it still does not display anything, this is because even we are making an API call, the response is actually not yet use on our template section. To do this, modify the template section in our App.vue so it will look like this as shown below:

<template>
	<div class="content-container">
		<Row>
			<Container v-show="quote">
				<blockquote v-html="quote.content"></blockquote>
				<h2 v-show="quote">- {{ quote.title }}</h2>
			</Container>
		</Row>
		<Row>
			<TextButton label="Get Quote" v-on:get-quote="generateQuote" />
		</Row>
	</div>
</template>

Noticed how we used the response content ? Using the directive v-html. A sample response from Quotes and Design API is shown below:

{
	"ID" : 1521,
	"title": "Dragos Roua",
	"content":"<p>Perfection is boring. Getting better is where all the fun is.<\/p>\n",
	"link":"https:\/\/quotesondesign.com\/dragos-roua\/"
}

The content key actually contains HTML tags. So instead of actually writing a piece of code that will removed those HTML tags before displaying the content, we can just use the directive v-html instead. Also, our Container component will only show if our data quote is not null. This is because of the directive used, which is v-show="quote".

Let's add some CSS styles to make our blockquote and h2 tag a little nicer. Place the following CSS rules in our style section of our App.vue file:

h2 {
	font-size: 20px;   
    font-weight: 100;
    text-transform: uppercase;
}
blockquote {
	font-size: 40px;
    font-weight: 100;
    text-transform: uppercase;
    text-shadow: 1px 1px 4px black;
	border-bottom: 1px solid rgba(255, 255, 255, .125);
}

If you refresh the page, then click the Get Quote button, our generated quote should be displayed by now. But we also want that when the page is loaded, we will already be displaying a generated quote instead of waiting for the user to click the button. To do this, let's call the generateQuote method when our App.vue is mounted as shown below:

<script>
export default {
	data() {
		return {
			quote : ""
		}
	},
	methods : {
		async getRandomQuote() {
			var timestamp = new Date()
			var apiUrl = "http://quotesondesign.com/wp-json/posts?filter[orderby]=rand&filter[posts_per_page]=1&" + timestamp
			let response = await fetch(apiUrl)
			return await response.json()
		},
		async generateQuote() {
			var randomQuote = await this.getRandomQuote()
			if(randomQuote) {
				this.quote = randomQuote.shift()
			}
		}
	},
	mounted() {
		this.generateQuote()
	}
}
</script>

If you refresh the page, you should see now a generated quote from Quotes and Design API loaded in our page. Congratulations! You have succesfully created a Random Quotes Generator using VueJS and Quotes and Design API! You can check this demo for a full working sample of this program.


Twitter Facebook LinkedIn Youtube Slideshare Github