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

Published Sep 02, 2017Last updated Feb 23, 2018
Building an app in Vue JS (webpack, axios, bootstrap 4, reddit, and infinite scrolling in vanilla javascript)

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 😄

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 it is ok for now!

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! 😛

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 post, I will do everything inside App.vue.
First, let’s grab bootstrap 4 from a cdn.

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!
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.

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

1*3WAkBMC2ddFHVUaQmcvBHg.png

Nothing fun about that… So let’s continue.

Hello vue!

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!

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?

  • We need to store the posts.
  • We need to see if we are making an http request to the reddit api.
  • 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.

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.

1*dpvu2ncL3rYMJCe9GPpY4Q.png

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

1*dhiNBNAPyKaV463mycvc_A.png

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>
Discover and read more posts from Renato Hysa
get started