Building an app in Vue JS (webpack, axios, bootstrap 4, reddit, and infinite scrolling in vanilla javascript)

Now, before we go any further… For god’s sake follow me on Instagram, Facebook and subscribe on my YouTube channel (Always plug)

https://www.facebook.com/hysarenato

https://www.instagram.com/renato.hysa/

https://www.youtube.com/user/codexecutable

Diving in

First of all, since we are building an app in Vue JS, make sure you have node, npm and the vue-cli installed. If you don’t know how to do that… then I GOT YAH BRUH!

Here, watch my videos on that https://www.youtube.com/playlist?list=PL3ZhWMazGi9IommUd5zQmjyNeF7s1sP7Y :D

Now let’s create an empty Vue project using webpack. On your terminal enter this command.

vue init webpack vuemedium

From there, just press enter. It will setup things like eslint, vue-router, unit tests, etc… We won’t need those, but just press enter, who cares!

If you know how to use them, then you can extend the app using that full setup!

Make sure you follow my instructions exactly as I type them, otherwise eslint will complain!

If you think your girlfriend complaining is a nightmare, then you haven’t tried eslint!

Because of this, you probably don’t want to press enter for eslint!

Then simply follow the commands printed in the terminal, in that specific order!

cd vuemedium
npm install
npm run dev

The last command will run the app and will automatically open a tab in your browser!

Why is that? Well… everything started when I was 5… Just kidding! :P

The answer is inside package.json. Under scripts, you have dev, thus when you run npm run dev, it will basically execute this command node build/dev-server.js.

Aaaaaanyway! This is not the purpose of this post, so let’s continue!

First lines of code

Since this is just a medium post, I will do everything inside App.vue.

First, let’s grab bootstrap 4 from a cdn (still in alpha btw).

Inside index.html include bootstrap’s 4 css link.

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">

Now inside App.vue delete everything so we are left with an (almost) empty file. Like this…

<template>
<div id="app">

</div>
</template>

<script>
export default {
}
</script>

Now let’s create a container and card-columns. Card columns are new in bootstrap and they are awesome. Well… they are just cards to be honest, but whatever!

So your template now should look like this.

<template>
<div id="app">
<div class="container">
<div class="card-columns">
<div class="card p-3">
<blockquote class="card-blockquote">
<footer>
<small>
<a target="_blank">
Read more on
</a>
</small>
</footer>
</blockquote>
</div>
</div>
</div>
<div class="text-center">
Loading...
</div>
</div>
</template>

We basically have some html needed for the cards and we also added a loading text, so don’t get scared, you can learn more about bootstrap 4 cards here https://v4-alpha.getbootstrap.com/components/card/#card-columns.

If you take a look at your browser now, you should be able to see this.

Nothing fun about that… So let’s continue.

Hello vue!

Let’s now use Vue JS, because I got a bit bored with that plain HTML bullshit!

First let’s install axios.

npm install axios

You can also use the save flag, to save it as a dependency in your package.json file, but on medium, whenever I have 2 dashes, it does this => — . Which it is not cool! :@

Now let’s work with the script. Let’s import axios and create a getPosts method which accepts a page and call it in the created lifecycle hook.

Did I go fast there? Well, if you know Vue JS, that’s a piece of cake, if you don’t, then you definitely have some questions here, but everything is explained in my Vue tutorials, click below for more. ALWAYS PLUG!

Vue 2.0 for Beginners

Vue 2.0 and Laravel 5.3

Our script now, looks like this.

<script>
import axios from 'axios'
export default
{
created () {
this.getPosts()
},

methods: {
getPosts (page) {

}
}
}
</script>

Let’s work with our data now. What do we need?

  1. We need to store the posts.
  2. We need to see if we are making an http request to the reddit api.
  3. And we need to determine the next page.

Piece of cake. Here you are!

<script>
import axios from 'axios'
export default
{
created () {
this.getPosts()
},
      data () {
return {
posts: [],
postsLoading: false,
nextPage: null
}
},
      methods: {
getPosts (page) {

}
}
}
</script>

Posts is initially an empty array, postsLoading is set to false… well, because we are not loading any post when the app starts, and next page is null, because it wants to be null.

Now, let’s do some work with the getPosts method.

When we call that method, we want to set postsLoading to true, we want to store reddi’s api url (not technically an api url, but whatever), we need to check if we have a page passed to the getPosts method (reddit pagination is a bit weird) and finally make the http call using axios.

Let’s work on that. This is the code for the getPosts method.

getPosts (page) {
this.postsLoading = true

var
url = 'https://www.reddit.com/r/all/top.json?limit=30&count=30'

if
(page != null) {
url = 'https://www.reddit.com/r/all/top.json?limit=30&count=30&after=' + page
}

axios.get(url)
.then(response => {
})
.catch(error => {
})
}

If we eventually pass a page to the getPosts method, we concatenate it to the url with the after url query string. Why? Because this is how reddit’s pagination works! Ask them!

With axios, we are making a get request to that url and we have our usual then and catch. Inside catch let’s just log the error.

Inside then, we want to concatenate whatever posts we get from the api call to our posts array.

Then we want to assign the next page to nextPage and postsLoading goes back to false. Let’s see that in action!

getPosts (page) {
this.postsLoading = true

var
url = 'https://www.reddit.com/r/all/top.json?limit=30&count=30'

if
(page != null) {
url = 'https://www.reddit.com/r/all/top.json?limit=30&count=30&after=' + page
}

axios.get(url)
.then(response => {
this.posts = this.posts.concat(response.data.data.children)

this.nextPage = response.data.data.after

this.postsLoading = false
})
.catch(error => {
console.log(error)
})
}

Easy peasy lemon squeezy!

To be honest, you still get that nonsense page on your screen, however if you check your network activity, we successfully make a get request to reddit.

And if you have Vue JS dev tools installed on Google Chrome, you also get this result.

Let’s go back to our template.

Back to the template

First of all, we want to loop through each post inside the posts array and display each post as a card.

We also want to add a link that points to each origin for the “Read more on” part.

And finally, we want to display the loading text only when we make a get request.

Thus, our template now changes to this.

<template>
<div id="app">
<div class="container">
<div class="card-columns">

<div class="card p-3"
v-for="post
in posts">

<blockquote class="card-blockquote">
<p>
{{ post.data.title }}
</p>
<footer>
<small>
<a target="_blank"
:href="post
.data.url">
Read more on {{ post.data.domain }}
</a>
</small>
</footer>
</blockquote>

</div>

</div>

<div class="text-center"
v-show="postsLoading"
>
Loading...
</div>
</div>
</div>
</template>

Let’s break down the changes.

Here

<div class="card p-3"
v-for="post
in posts">

We simply loop through each post.

Here

<p>
{{ post.data.title }}
</p>

We display the title of the post.

Here

<a target="_blank"
:href="post
.data.url">
Read more on {{ post.data.domain }}
</a>

We have the link to the post. And finally…

Here

<div class="text-center"
v-show="postsLoading"
>
Loading...
</div>

We want to display the loading text only when we make a get request.

CONGRATS! We finally have something on our screen!

Not bad!

Infinite scrolling

Finally, let’s close this post by implementing infinite scrolling in vanilla javascript!

Because, if you can do it in vanilla javascript, then JUST DO IT!

In the created lifecycle hook, we will add a scroll listener. Thus, created changes to this.

created () {
this.getPosts()

window.addEventListener('scroll', this.handleScroll)
}

As you see, we are calling a handleScroll method, so let’s create that.

handleScroll () {
if (document.body.scrollHeight - window.innerHeight - document.body.scrollTop <= 5) {
if (this.nextPage != null) {
this.getPosts(this.nextPage)
}
}
}

The most complex part here is this line

if (document.body.scrollHeight - window.innerHeight - document.body.scrollTop <= 5)

This line simply says, “If you scroll down to the bottom of your screen, then do something.

What we do is to check if nextPage is not null, if it is not null then we call the getPosts method and we pass as an argument the next page.

Now… whenever you scroll at the bottom of your screen, the loading text appears, we wait for a second and we load the next posts.

The end

That’s all for today! We used vue js, webpack, axios, bootstrap 4, the reddit api (if you can call it an api), and we implemented infinite scrolling in vanilla javascript.

In case I missed something above, here you have the whole working source code.

<template>
<div id="app">
<div class="container">
<div class="card-columns">

<div class="card p-3"
v-for="
post in posts">

<blockquote class="card-blockquote">
<p>
{{ post.data.title }}
</p>
<footer>
<small>
<a target="_blank"
:href="
post.data.url">
Read more on {{ post.data.domain }}
</a>
</small>
</footer>
</blockquote>

</div>

</div>

<div class="text-center"
v-show="postsLoading"
>
Loading...
</div>
</div>
</div>
</template>

<script>
import axios from 'axios'

export default
{
created () {
this.getPosts()

window.addEventListener('scroll', this.handleScroll)
},

data () {
return {
posts: [],
postsLoading: false,
nextPage: null
}
},

methods: {
getPosts (page) {
this.postsLoading = true

var
url = 'https://www.reddit.com/r/all/top.json?limit=30&count=30'

if
(page != null) {
url = 'https://www.reddit.com/r/all/top.json?limit=30&count=30&after=' + page
}

axios.get(url)
.then(response => {
this.posts = this.posts.concat(response.data.data.children)

this.nextPage = response.data.data.after

this.postsLoading = false
})
.catch(error => {
console.log(error)
})
},

handleScroll () {
if (document.body.scrollHeight - window.innerHeight - document.body.scrollTop <= 5) {
if (this.nextPage != null) {
this.getPosts(this.nextPage)
}
}
}
}
}
</script>