v0.6 - Events and Style Bindings

Download v0.5

Events

When the user interacts with our component (Clicks a button, Types into an input etc) sometimes we want to notify other parts of our app about the action. This can also be useful when creating dumb/pure components.

There are a few ways to accomplish event emitting and listening. First, we'll cover the most simple approach which is just to emit an event from the child and listen for it on the parent via a v-on handler this is nice because it looks the same as our other event handlers (remember @click?)

Lets imagine we are creating a huge application and we want the buttons to be consistent in behavior and appearance. It would make sense for us to create a component that we can re-use every time we want a button. This way we can be sure that our application has a consistent feel.

Create a new file and call it BaseButton.js. We will write the component here and import it in our index.html after Vue but before our app.js file.


 


<script type="text/javascript" src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<script type="text/javascript" src="BaseButton.js"></script>
<script type="text/javascript" src="app.js"></script>

We are creating a prop on this component called content. This will be the text inside of our button and we will make it a required prop. It is always recommended that you put as much validation on your inputs as you can- especially in the case of a Base component you want to make sure other developers are using the component as intended.

We will add a simple template string to this that just uses the button class and wraps the content. Nothing fancy so far.

BaseButton.js

const BaseButton = Vue.component('base-button', {
    props: {
        content: {
            type: String,
            required: true
        }
    },
    template: `
        <div class="button">
            {{content}}
        </div>
    `
})

Now that we are done the component. We are going to register it and use it in our ConcertView component. So we add the components key and register the BaseButton there. We are also going to add it to the template supplying the content See Location.



 
 
 








 



const ConcertView = Vue.component('concert-view', {
    props: {...},
    components: {
        BaseButton
    },
    computed: {...},
    template: `
        <div class="card">
            {{concert.name}}
            <div style="font-size:1.25rem">
                {{concert.place.name}} - {{time}}
            </div>

            <base-button :content="'See location'"/>
        </div>
    `

Now, naturally we want to know when the button is clicked. Since the component is simple and we have , we could simply put and @click= on the base-button but that would be a bit lame because we would have to register another one inside the component to capture it in there as well. We can get around this with event emitting and listening. We start by writing an @click handler and the method that will be called from it.

BaseButton.js


 
 
 
 
 

 




    props: {...},
    methods: {
        click() {
            this.$emit('clicked');
        }
    },
    template: `
        <div class="button" @click="click">
            {{content}}
        </div>
    `

We can then listen for this event where we are using the component in ConcertView using the same syntax as we would when listening for a native event.

Lets start by creating a method that opens the concerts location in google maps. We can register this to the clicked event that we defined in the BaseButton component. When we click that button in our app we will now see a new tab open with the location of the venue. We will again use template strings to grab the name of the venue and then use it as the q query parameter to google maps. We will pass that url to window.open with the target parameter as _blank so that it opens in a new tab.

We can then register the method to the event by adding @clicked="openMap" to our component. The @clicked part is what is listening to the clicked event fired in our button component. The event name can be anything you want as long as it doesn't conflict with any native events.




 
 
 
 
 
 







 




    props: {...},
    components: {...},
    computed: {...},
    methods: {
        openMap() {
            const url = `https://www.google.com/maps/?q=${this.concert.place.name}`;
            window.open(url, '_blank');
        }
    },
    template: `
        <div class="card">
            {{concert.name}}
            <div style="font-size:1.25rem">
                {{concert.place.name}} - {{time}}
            </div>

            <base-button :content="'See location'" @clicked="openMap"/>
        </div>
    `
})

Now if we click the button we will see a new tab showing google maps on the concert venue. We can also reuse this button as much as we like.

Style bindings

Sometimes we need to change the class or styles of an element based on what is happening in the Javascript. This could be changing the background to red if we are about to delete something or activating an animation for a loading state. Whatever the use case is Vue makes this really easy for us with its class and style bindings.

Lets give our button a little style, something as simple as changing it to a darker color on click to start.

To begin we need to store some state. Different than in our App.js the data attribute here needs to be a function.
When the component is created, it runs this function and uses the result as its initial state. We are going to add a simple loading boolean.

BaseButton.js



 
 
 
 
 



const BaseButton = Vue.component('base-button', {
    props: {...},
    data() {
        return {
            loading: false
        }
    },
    methods: {...},
})

We can then start on our style binding. To do a really quick mock up- lets set the background of all of the buttons to red.

Applying inline styles with Vue can be done simply with a v-bind:style="" (or :style=""). You take the css you want to apply and create an Object with the style name as the key and the style as the value.









 






const BaseButton = Vue.component('base-button', {
    props: {...},
    data() {...},
    methods: {...},
    template: `
        <div
          class="button"
          @click="click"
          :style="{'background-color': 'red'}"
        >
            {{content}}
        </div>
    `
})

The value for the inline style can take a component variable, a computed variable or we can replace the entire object with a variable from the component.

Lets make the button only turn red for a second after we click.

First we'll take the click function and stuff everything that it does into a setTimeout. Which is a native function that takes a function and runs it after a number of milliseconds. Lets immediately set loading to true and then in the timeout set it back to false.

BaseButton.js

methods: {
    click() {
        this.loading = true;
        setTimeout(() => {
            this.$emit('clicked');
            this.loading = false;
        }, 1000)
    }
},

Lets add a computed object and have it return a style object similar to what we mocked up a second ago. This time we will have it check the loading variable and return red if it is true and null if it is not. With Vue's dynamic styling undefined or null values will have no effect on the style.

BaseButton.js

computed: {
    loading_style() {
        return {
            'background-color': this.loading ? 'red' : null,
        }
    }
},

Finally lets trade out our mock style for the computed variable we just wrote.

BaseButton.js

template: `
    <div class="button" @click="click" :style="loading_style">
        {{content}}
    </div>
`

When we click the button we will see a second wait before the click is emitted and our new tab is spun up. This timeout mimics a real world request to a server or waiting for something to complete before showing. It is good in cases where the user is waiting on requests to give them immediate feedback that the click registered and something is loading.

If we want to do something more complicated, like a nice animated loading background for the button we may want to use class bindings. The implementation here is really simple. We simply give a :class an Array with the classes we want to apply in it, or an object with the class names as keys and the values as true or false if we want them to apply or not.

We first want to update our loading_style computed variable to loading_class. We will return an object with the key loading-background a class I wrote with an animated loading background. And put the value of the key as this.loading. This tells vue to apply the class when the button is loading.

In the template lets swap out loading_style with loading_class

BaseButton.js




 
 
 
 
 
 








const BaseButton = Vue.component('base-button', {
    props: {...},
    data() {...},
    computed: {
        loading_class() {
            return {
                'loading-background': this.loading
            }
        }
    },
    methods: {...},
    template: `
        <div class="button" @click="click" :class="loading_class">
            {{content}}
        </div>
    `

Thats it! We will now see a nice animated loading gradient when we click the See Location button and Google Maps open a second later.

screenshot