Vuejs事件交互

Published on 2016 - 06 - 16

we are going to create and expand previous examples, learn new things concerning ‘methods’, ‘event handling’ and ‘computed properties’. We will develop a few examples using different approaches. It’s time to see how we can implement Vue’s interactivity to get a small app, like a Calculator, running nice and easy.

Event Handling

HTML events are things that happen to HTML elements. When Vue.js is used in HTML pages, it can react to these events.

In HTML, events can represent everything from basic user interactions to things happening in the rendering model.

These are some examples of HTML events:

  • A web page has finished loading
  • An input field was changed
  • A button was clicked
  • A form was submitted

The point of event handling is that you can do something whenever an event takes place. In Vue.js, to listen to DOM events you can use the v-on directive.

The v-on directive attaches an event listener to an element. The type of the event is denoted by the argument, for example v-on:keyup listens to the keyup event.

Handling Events Inline

Enough with the talking, let’s move on and see event handling in action. Below, there is an ‘Upvote’ button which increases the number of upvotes every time it gets clicked.

<html>
<head>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.cs\ s" rel="stylesheet">
    <title>Upvote</title>
</head>
<body>
    <div class="container">
        <button v-on:click="upvotes++">
            Upvote! {{upvotes}} 
        </button>
    </div> 
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.js"></script> <script type="text/javascript">
new Vue({
    el: '.container',
    data: {
        upvotes: 0 
    }
}) 
</script> 
</html>

As you can see above, we have a basic setup and this time we use the class container in our view model. There is an upvotes variable within our data. In this case, we bind an event listener for click, with the statement that is right next to it. Inside the quotes we’re simply increasing the count of upvotes by one, each time the button is pressed, using the increment operator (upvotes++).

Shown above is a very simple inline JavaScript statement.

Handling Events using Methods

Now we are going to do the exact same thing as before, using a method instead. A method in Vue.js is a block of code designed to perform a particular task. To execute a method you have to define it and then invoke it.

<html>
<head>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.cs\ s" rel="stylesheet">
    <title>Upvote</title>
</head>
<body>
    <div class="container">
        <button v-on:click="upvote">
            Upvote! {{upvotes}} 
        </button>
    </div> 
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.js"></script> <script type="text/javascript">
new Vue({
    el: '.container',
    data: {
        upvotes: 0 
    },
    // define methods under the **`methods`** object
    methods: {
        upvote: function(){
            // **`this`** inside methods points to the Vue instance
            this.upvotes++; 
        }
    } 
})
</script> 
</html>

We are binding a click event listener to a method named ‘upvote’. It works just as before, but cleaner and easier to understand when reading your code.

Shorthand for v-on

When you find yourself using v-on all the time in a project, you will find out that your HTML will quickly becomes dirty. Thankfully, there is a shorthand for v-on, the @ symbol. The @ replaces the entire v-on: and when using it, the code looks a lot cleaner, but everyone has their own practices and this is totally optional.

Using the shorthand the button of our previous example will be:

Listening to ‘click’ using v-on:

<button v-on:click="upvote">
     Upvote! {{upvotes}}
</button>

Listening to ‘click’ using @ shorthand

<button @click="upvote"> Upvote! {{upvotes}}
</button>

Event Modifiers

Now we will move on and create a Calculator app. To do so, we gonna use a form with two inputs and one dropdown to select the desired operation.

Even though the following code seems fine, our calculator does not work as expected.

<html> 
<head>
    <title>Calculator</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.\ css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <h1>Type 2 numbers and choose operation.</h1> 
        <form class="form-inline">
            <!-- Notice here the special attribute 'number' is passed in order to parse inputs as numbers.--> 
            <input v-model="a" number class="form-control"> 
            <select v-model="operator" class="form-control">
                <option selected>+</option> 
                <option>-</option> 
                <option>*</option> 
                <option>/</option>
            </select>
            <!-- Notice here the special attribute 'number' is passed in order to parse inputs as numbers.--> 
            <input v-model="b" number class="form-control"> 
            <button type="submit" @click="calculate" class="btn btn-primary">
                Calculate 
            </button>
        </form>
        <h2>Result: {{a}} {{operator}} {{b}} = {{c}}</h2> 
        <pre>
            {{$data | json}} 
        </pre>
    </div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.js"></script> <script type="text/javascript">
new Vue({
    el: '.container', 
    data: {
        a: 1,
        b: 2,
        c: null, operator: " ",
    }, 
    methods:{
        calculate: function(){ 
            switch (this.operator) {
                case "+":
                    this.c = this.a + this.b
                    break;
                case "-":
                    this.c = this.a - this.b
                    break;
                case "*":
                    this.c = this.a * this.b
                    break;
                case "/":
                    this.c = this.a / this.b
                    break;
            }
        }
    },
}); 
</script> 
</html>

If you try and run this code yourself, you will find out that when the “calculate” button is clicked, instead of calculating, it reloads the page.

This makes sense because when you click “calculate”, in the background, you are submitting the form and thus the page reloads.

To prevent the submission of the form, we have to cancel the default action of the onsubmit event. It is a very common need to call event.preventDefault() inside our event handling method. In our case the event handling method is called calculate.

So, our method will become:

calculate: function(){ 
    event.preventDefault(); 
    switch (this.operator) {
        case "+":
            this.c = this.a + this.b 
            break;
        case "-":
            this.c = this.a - this.b 
            break;
        case "*":
            this.c = this.a * this.b 
            break;
        case "/":
            this.c = this.a / this.b 
            break;
    }
}

Although we can do this easily inside methods, it would be better if the methods can be purely ignorant about data logic rather than having to deal with DOM event details.

Vue.js provides two event modifiers for v-on to prevent the event default behavior:

  1. .prevent
  2. .stop

So, using one of them, our submit button will change from:

<button type="submit" @click="calculate">Calculate</button> 

to:

<button type="submit" @click.prevent="calculate">Calculate</button> 
<!-- or -->
<button type="submit" @click.stop="calculate">Calculate</button>

And we can now safely remove event.preventDefault() from our calculate method.

Key Modifiers

If you hit enter when you are focused in one of the inputs, you will notice that the page reloads again instead of calculating. This happens because we have prevented the behavior of the submit button but not of the inputs.

To fix this, we have to use ‘Key Modifiers’.

<input v-model="a" @keyup.enter="calculate">
<input v-model="b" @keyup.enter="calculate">

When you have a form with a lot of inputs/buttons/etc and you need to prevent their default submit behavior you can modify the submit event of the form. Example:

Finally, the calculator is up and running.

Computed Properties

Vue.js inline expressions are very convenient, but for more complicated logic, you should use computed properties. Practically, computed properties are variables which their value depends on other factors.

Computed properties work like functions that you can use as properties. But there is a significant difference, every time a dependency of a computed property changes, the value of the computed property re-evaluates.

In Vue.js, you define computed properties within the computed object inside your Vue instance.

<html>
<head>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.cs\s" rel="stylesheet">
    <title>Hello Vue</title>
</head>
<body>
<div class="container">
    a={{ a }}, b={{ b }}
    <pre>
        {{$data | json}} 
    </pre>
</div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.js"></script> <script type="text/javascript">
new Vue({
    el: '.container',
    data: {
        a: 1, 
    },
    computed: {
        // a computed getter 
        b: function () {
            // **`this`** points to the Vue instance
            return this.a + 1 
        }
    } 
});
</script> 
</html>

This is a basic example demonstrating the use of computed properties. We’ve set two variables, the first, a, is set to 1 and the second, b, will be set by the returned result of the function inside the computed object. In this example the value of b will be set to 2.

<html>
<head>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.cs\ s" rel="stylesheet">
    <title>Hello Vue</title>
</head>
<body>
<div class="container">
    a={{ a }}, b={{ b }} 
    <input v-model="a"> 
    <pre>
        {{$data | json}} 
    </pre>
</div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.js"></script>
<script type="text/javascript">
new Vue({
    el: '.container',
    data: {
        a: 1, 
    },
    computed: {
        // a computed getter 
        b: function () {
            // **`this`** points to the vm instance
            return this.a + 1 
        }
    } 
});
</script> 
</html>

Above there is the same example as before with one difference, there is an input binded to the a variable. The desired outcome is to change the value of the binded attribute and update instantly the result of b. But what you will notice here, is that it does not work as we would expect.

If you run this code and enter an input for variable a the number 5, you expect that b will be set to 6. Sure, but it doesn’t, b is set to 51.

Why is this happening? Well, as you might have already thought of, b takes the given value from the input (“a”) as a string, and appends the number 1 at the end of it.

One solution to solve this problem is to use the parseFloat() function that parses a string and returns a floating point number.

 new  Vue({
    el: '.container',
    data: {
        computed: {
            b: function () {
                return parseFloat(this.a) + 1
            }
        }
    });

Another option that comes to mind, is to use the <input type="number"> that is used for input fields that should contain a numeric value.

But there is a more neat way. With Vue.js, whenever you want your user inputs to be automatically persisted as numbers, you can add the special attribute number to these inputs.

<body>
<div class="container">
    a={{ a }}, b={{ b }} 
    <input v-model="a" number> 
    <pre>
        {{$data | json}} 
    </pre>
</div> 
</body>

The number attribute is going to give us the desired result without any further effort.

To demonstrate a wider picture of computed properties, we are going to make use of them and build the calculator we have showed before again, but this time using computed properties instead of methods.

Lets start with a simple example, where a computed property c contains the sum of a plus b.

<html> 
<head>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.mi\ n.css" rel="stylesheet">
    <title>Hello Vue</title> 
</head>
<body>
    <div class="container">
        <h1>Enter 2 numbers to calculate their sum.</h1> 
        <form class="form-inline">
            <input v-model="a" number class="form-control"> 
            +
            <input v-model="b" number class="form-control">
        </form>
        <h2>Result: {{a}} + {{b}} = {{c}}</h2> 
        <pre> {{$data | json}} </pre>
    </div> 
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.js"></script>
<script type="text/javascript">
new Vue({
    el: '.container',
    data: { 
        a: 1,
        b: 2 
    },
    computed: {
        c: function () {
            return this.a + this.b 
        }
    }
}); 
</script> 
</html>

The initial code is ready, and at this point the user can type in 2 numbers and get the sum of these two. A calculator that can do the four basic operations is the goal, so let’s continue building!

Since the HTML code will be the same with the calculator we build in the previous section, I am gonna show you here only the Javascript codeblock.

new Vue({
    el: '.container',
    data: { 
        a: 1,
        b: 2,
        operator: " ",
     },
    computed: {
        c: function () {
            switch (this.operator) { 
                case "+":
                    return this.a + this.b 
                    break;
                case "-":
                    return this.a - this.b 
                    break;
                case "*":
                    return this.a * this.b 
                    break;
                case "/":
                    return this.a / this.b 
                    break;
            }
        }
    }, 
});

The calculator is ready to be put to use. We just had to move whatever was inside calculate method to the computed property c and we are done! Whenever you change the value of a or b the result updates in real time! We don’t need no buttons, no events, nor anything. How awesome is that??

Using Computed Properties to Filter an Array

A computed property can also be used to filter an array. Using a computed property to perform array filtering gives you in-depth control and more flexibility, since it’s full JavaScript, and allows you to access the filtered result elsewhere. For example you can get the length of a filtered array elsewhere in your code.

To see how it’s done, we will filter the famous stories as we did in the Custom Filter example. This time we will create a computed property that returns the filtered Array.

new Vue({
    el: '.container', 
    data: {
        stories: [
            {
                plot: "I crashed my car today!",
                writer: "Alex",
                upvotes: 28
            },
            {
                plot: "Yesterday, someone stole my bag!",
                writer: "John",
                upvotes: 8
            },
            {
                plot: "Someone ate my chocolate...",
                writer: "John",
                upvotes: 51                
            },
            {
                plot: "I ate someone's chocolate!",
                writer: "Alex",
                upvotes: 74                
            },
        ]
    },
    computed: {
        famous: function() {
            return this.stories.filter(function(item) {
                    return item.upvotes > 25;
                });
        }
    }
})

In our HTML code, instead of stories array, we will render the famous computed property.

<body>
    <div class="container">
        <h1>Let's hear some famous stories! ({{famous.length}})</h1>
        <ul class="list-group">
            <li v-for="story in famous" class="list-group-item"
                    {{ story.writer }} said "{{ story.plot }}"
                    and upvoted {{ story.upvotes }} times. 
                </li>
        </ul> 
    </div>
</body>

That’s it. We have filtered our array using a computed property. Did you notice how easily we managed to display the number of famous stories next to our heading message using {{famous.length}}?

Reference