Getting Started with Vue.js

Vue Events: Building an Accordion

We'll continue using Vue to listen for HTML events by building out an accordion. By combining Vue's ability to add and remove classes with some CSS styles, we can quickly create a working accordion.

To get us started, we'll be using a Bulma message as our accordion. Whenever a user clicks the message header, then we'll apply an is-closed class to the message with Vue.

The HTML

Here's the HTML for our message:

<div id="app">
<article class="message">
    <div class="message-header">
        Hello World        
    </div>
    <div class="message-body">
        <div class="message-content">
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
        </div>
    </div>
</article>
</div>

So far, we're just using plain HTML with Bulma's message classes.

Adding Vue Directives

Next up, let's start adding our Vue into this template:

<div id="app">
<article class="message" :class="accordionClasses">
    <div class="message-header" @click="toggleAccordion">
        Hello World        
    </div>
    <div class="message-body">
        <div class="message-content">
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
        </div>
    </div>
</article>
</div>

Notice the two places we have used Vue directives with :class and with @click.

  • :class: We are binding some classes (mainly the is-closed class) to the message
  • @click: We will listen for the click event and then call the toggleAccordion method we create.

The CSS

In order to get our accordion to open and close, we've had to create some classes and change the Bulma classes a bit. The main thing our CSS needs to do is to open and close the accordion using an is-closed class.

The is-closed class will set the height of the message to 0 and by default, the message will have a height of 10em.

This is a good practice to let JavaScript add the classes and let CSS style the elements.

.message {
    max-width: 500px;
    margin-left: auto;
    margin-right: auto;
}

.message-header {
    cursor: pointer;
}

.message-body   {
    padding: 0;
    max-height: 10em;
    overflow: hidden;
    transition: 0.3s ease all;
}

.is-closed .message-body {
    max-height: 0;
}

.message-content {
    padding: 20px;
}

The Vue JavaScript

Now we need to create our JavaScript data:

  • isOpen will determine if we should open or close the accordion
  • accordionClasses will house our classes that we want to apply based on the isOpen variable

The Data

const app = new Vue({
    el: '#app',
    data: {
        isOpen: true
    },
    methods: {
        toggleAccordion: function() {
            this.isOpen = !this.isOpen;
        }
    },
    computed: {
        accordionClasses: function() {
            return {
                'is-closed': !this.isOpen,
                'is-primary': this.isOpen,
                'is-dark': !this.isOpen
            };
        }
    }
});

A big trend you'll see when moving to Vue is that you don't directly change the HTML. You change data variables and make Vue reflect those changes reactively.

Here we have an isOpen variable that we'll use to determine if we should add the is-closed class or remove it. The is-closed class will be responsible for setting the height of the message.

The toggleAccordion Method

Now that we have our data variable, we need to create our method for toggling the isOpen between true and false.

const app = new Vue({
    el: '#app',
    data: {
        isOpen: true
    },
    methods: {
        toggleAccordion: function() {
            this.isOpen = !this.isOpen;
        }
    }
});

The code to toggle will be this.isOpen = !this.isOpen; this is a clean way to switch a boolean from true to false and vice versa.

With the data and methods created, we now have to build out the accordionClasses object. We'll use Vue's computed properties.

Vue's Computed Properties

While the Vue data object is good for creating simple variables like strings, numbers, objects, arrays, there are times when we need to use those data variables to create other relevant information.

In this case, we need to use the isOpen variable to determine what classes to apply to our message.

In general, if you need to reference data variables when creating a new variable, you should use computed methods.

In our example above, we wanted to create the accordionClasses object, but we needed to use the isOpen variable to do it. This is why we went with computed methods.

The Full Code

For reference, here's the full code for this example:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Learning Vue Like a Boss!</title>

  <!-- css -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.5.3/css/bulma.css">
  <style>
    .message {
      max-width: 500px;
      margin-left: auto;
      margin-right: auto;
    }
    .message-header {
      cursor: pointer;
    }
    .message-body   {
      padding: 0;
      max-height: 10em;
      overflow: hidden;
      transition: 0.3s ease all;
    }
    .is-closed .message-body {
      max-height: 0;
    }
    .message-content {
      padding: 20px;
    }
  </style>

  <!-- js -->
  <script src="https://unpkg.com/vue"></script>
</head>
<body>
  <!-- our template -->
  <section id="app" class="section container">

  <article class="message" :class="accordionClasses">
    <div class="message-header" @click="toggleAccordion">
      Hello World        
    </div>
    <div class="message-body">
      <div class="message-content">
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. <strong>Pellentesque risus mi</strong>
      </div>
    </div>
  </article>

  </section>

  <!-- our javascript -->
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        isOpen: true
      },
      methods: {
        toggleAccordion: function() {
          this.isOpen = !this.isOpen;
        }
      },
      computed: {
        accordionClasses: function() {
          return {
            'is-closed': !this.isOpen,
            'is-primary': this.isOpen,
            'is-dark': !this.isOpen
          };
        }
      }
    });
  </script>
</body>
</html>