Book Image

Vue.js 2 and Bootstrap 4 Web Development

Book Image

Vue.js 2 and Bootstrap 4 Web Development

Overview of this book

In this book, we will build a full stack web application right from scratch up to its deployment. We will start by building a small introduction application and then proceed to the creation of a fully functional, dynamic responsive web application called ProFitOro. In this application, we will build a Pomodoro timer combined with office workouts. Besides the Pomodoro timer and ProFitOro workouts will enable authentication and collaborative content management. We will explore topics such as Vue reactive data binding, reusable components, routing, and Vuex store along with its state, actions, mutations, and getters. We will create Vue applications using both webpack and Nuxt.js templates while exploring cool hot Nuxt.js features such as code splitting and server-side rendering. We will use Jest to test this application, and we will even revive some trigonometry from our secondary school! While developing the app, you will go through the new grid system of Bootstrap 4 along with Vue.js’ directives. We will connect Vuex store to the Firebase real-time database, data storage, and authentication APIs and use this data later inside the application’s reactive components. Finally, we will quickly deploy our application using the Firebase hosting mechanism.
Table of Contents (19 chapters)
Vue.js 2 and Bootstrap 4 Web Development
Credits
About the Author
Acknowledgments
About the Reviewer
www.PacktPub.com
Customer Feedback
Preface
Index

Making things functional with Vue.js


So, what do we want to achieve with our form? We want the new message to be created. This message has to be composed of title, text, and the timestamp. We also want to add this message to our messages reference array.

Let's call this new message newMessage and add it to the data attributes of App.vue:

//App.vue
<script>
  <...>
  export default {
    data () {
      return {
        newMessage: {
          title: '',
          text: '',
          timestamp: null
        }
      }
    },
  <...>
  }
</script>

Now, let's bind the title and the text of this newMessage object to input and textarea of our form. Let's also bind a method called addMessage to the submit handler of our form so that the whole form's markup looks like this:

<template>
<...>
  <form @submit="addMessage">
    <div class="form-group">
      <input class="form-control"v-model="newMessage.title"maxlength="40"autofocus  placeholder="Please introduce yourself :)" />
    </div>
    <div class="form-group">
      <textarea class="form-control"v-model="newMessage.text" placeholder="Leave your message!"  rows="3"></textarea>
    </div>
    <button class="btnbtn-primary" type="submit">Send</button>
  </form>
  <...>
</template>

Well, we have bound the "addMessage" method to the submit callback of the form, but we haven't defined this method yet! So, let's define it. Add the methods object to our App.vue export section and define the addMessage method inside it. This method will receive the event attribute from our form and will just grab the newMessage object and push it into the messagesRef array. Doesn't it sound easy?

//App.vue
<script>
  export default {
  <...>
    methods: {
      addMessage (e) {
        e.preventDefault()
        this.newMessage.timestamp = Date.now()
        messagesRef.push(this.newMessage)
      }
    }
  }
</script>

Now, open the page, fill in the form, and hit the Send button. You'll see your message immediately appearing on the list of messages:

The message we introduce in the form is immediately propagated to the messages list

There is still something we need to fix. We don't want the values we fill the form with to remain there after our message is added to the messages list. So, we need to clear it inside the addMessage method. Probably, some basic check, at least for the title, would also be nice. So, rewrite the method as follows:

//App.vue
addMessage (e) {
  e.preventDefault()
  if (this.newMessage.title === '') {
    return
  }
  this.newMessage.timestamp = Date.now()
  messagesRef.push(this.newMessage)
  this.newMessage.text = ''
  this.newMessage.title = ''
  this.newMessage.timestamp = null
}

Now, if you start adding more messages, things look a bit weird. The way we're displaying the messages is probably not the best way for our case. Do you remember we wrapped up our message cards into div with the card-group class? Let's try to replace it with the card-columns class and check whether it looks better. In fact, it does. Let's keep it like that.

Adding utility functions to make things look nicer

We already have a fully functional single-page application, but it still lacks some awesomeness. For example, it's not really beautiful that the time appears as a timestamp. Let's write the utility function that will transform our timestamp into something beautiful.

We will use the Moment.js library (https://momentjs.com/). Install it in the application folder:

npm install moment --save

Create a folder and call it utils. Add a file called utils.js to this folder. Import moment and write the following function:

//utils.js
import moment from 'moment'

function dateToString (date) {
  if (date) {
    return moment(date).format('MMMM Do YYYY, h:mm:ss a')
  }
  return''
}

Export it in the end of the file:

//utils.js
<...>
export { dateToString }

Let's import this function to App.vue and use it to format our timestamp. Open the App.vue file and add the import statement at the beginning of the script section:

//App.vue
<script>
  import Firebase from 'firebase'
  import { dateToString } from './utils/utils'
  <...>
</script>

In order to be able to use this function within the Vue template, we have to export it in the methods section. Just add a new entry to the methods object:

//App.vue
<script>
  export default {
    <...>
    methods: {
      dateToString: dateToString,
      <...>
    }
</script>

Since we use ES6, we can just write the following lines of code:

methods: {
  dateToString
}

Now, we can use this method inside the template section. Just wrap the message.timestamp binding object in the dataToString method:

<p class="card-text"><small class="text-muted">Added on {{ dateToString(message.timestamp) }}</small></p>

Check out the page! Now, you can see beautiful dates instead of Unix timestamps.

Exercise

I have a small exercise for you. You saw how easy it was to add a utility function to transform the timestamp into the nicely formatted date. Now, create another utility function and call it reverse. This function should be used to display the array of messages in the reversed order, so the most recent messages should appear first. Check the code for this chapter in case you're in doubt.

Extracting message cards to their own component

You probably noticed that the first message of the demo application is always there. It's not moved by other, fresh message items. So, it seems that it's kind of a special message, and it's treated in a special way. In fact, it is. If you want to make a card sticky, just add it before the card element that iterates through other messages. You can also add some class to this card to show that it's really special. In my case, I added Bootstrap's card-outline-success class that outlines the element in a nice green color:

//App.vue
<div class="card-columns">
  <div class="card card-outline-success">
    <div class="card-block">
      <h5 class="card-title">Hello!</h5>
      <p class="card-text">This is our fixed card!</p>
      <p class="card-text"><small class="text-muted">Added on {{ dateToString(Date.now()) }}</small></p>
    </div>
  </div>
  <div class="card" v-for="message in messages">
    <div class="card-block">
      <h5 class="card-title">{{ message.title }}</h5>
      <p class="card-text">{{ message.text }}</p>
      <p class="card-text"><small class="text-muted">Added on {{ dateToString(message.timestamp) }}</small></p>
    </div>
  </div>
</div>

Now, you have a nice sticky card with a color that differs from other cards' color. But… don't you see any problem? We have the very same code repeated twice in our template. I'm pretty sure that you are aware of the rule of thumb of any developer: DRY—don't repeat yourself!

Let's extract the card to an individual component. It's really easy. Add a component called Card.vue to the components folder. The code for this component is really simple:

//Card.vue
<template>
  <div class="card">
    <div class="card-block">
      <h5 class="card-title">{{ title }}</h5>
      <p class="card-text">{{ text }}</p>
      <p class="card-text"><small class="text-muted">{{ footer }}</small></p>
    </div>
  </div>
</template>

<script>
  export default {
    props: ['title', 'text', 'footer']
  }
</script>

Now, let's invoke this component from App.vue with different values for title, text, and footer. First of all, it should be imported and exported in the Vue components object:

//App.vue
<script>
<...>
  import Card from './components/Card'
  <...>
  export default {
  <...>
    components: {
      Card
    }
  }
</script>

Now, we can use the <card> element within our template. We need to bind title, text, and footer. Footer is actually the text that says Added on …. So, the markup for the first card will look like this:

//App.vue
<template>
  <div class="card-columns">
    <card class="card-outline-success":title="'Hello!'":text="'This is our fixed card!'":footer="'Added on ' + dateToString(Date.now())"></card>
  </div>
</div>
</template>

The list of other messages will follow the same logic. For each message from the messages array, we will bind the corresponding message's entries (title, text, and timestamp). So, the markup for the list of message cards will look like this:

<div class="card-columns">
<...>
  <card v-for="message in messages":title="message.title":text="message.text":footer="'Added on ' + dateToString(message.timestamp)"></card>
</div>
</div>

As you can see, we have replaced fourteen lines of code with only two lines! Of course, our component also contains some lines of code, but now, we can reuse it again and again.

Exercise

The way we've extracted the card code into its individual component is, without any doubt, great, but the way we are binding attributes for the first message is a bit ugly. What if at some point we need to change the message's text? First of all, it's not easy to find the text inside the markup. Also, it is pretty difficult to manage the text inside the markup attributes, because we have to be really careful not to mess up with double/single quotes. And, admit it, it's just ugly. Your task for this exercise is to extract title, text, and date for the first message into something nicer (for example, export it in the data object) and bind it the same way we bind other messages. If you have doubts regarding this exercise, check out this chapter's code.

Note

Don't be confused by the v-bind directive in the provided code. We've been using it already, just its shortened version—the name of a bound property written after the semicolon. So, for example, v-bind:messages is the same as :messages.