How to get Vue to catch event? - vue.js

Edited to correct unreported syntax error (see comments). It now works as desired.
I'm having trouble getting my event handler to fire in the following Vue code.
As you see, there are two components, posts and post, and a root Vue instance. The button in the post template should fire the remove event, which is captured by the v-on:remove handler in posts which calls posts.deleteItem with the index of the post. Can someone give me a hint what I'm doing wrong?
<!DOCTYPE html>
<html lang="en">
<head>
<title>Posts</title>
<!--link href="../css/bootstrap.css" rel="stylesheet" /-->
<script src="../vue.js"></script>
</head>
<body>
<div id="app">
<posts></posts>
</div>
<script>
window.onload = function() {
// A post
Vue.component('post-item', {
props: ['post'],
data: function() {
return {
editing: false,
_cachedItem: ''
}
},
methods: {
deleteItem(postId) {
debugger
this.$emit('remove', event.target.value);
},
},
template: `
<div v-on:remove="deleteItem">
<li v-show="!editing">
<p v-html="post.text"></p>
<button v-on:click="$emit('remove')">Delete</button>
</li>
</div>
`
})
Vue.component('posts', {
data: function() {
return {
posts: [
{id: 0, text: "Day at beach"},
{id: 1, text: "Carving the canyons"},
{id: 2, text: "Kickin' it"}
],
};
},
methods: {
deleteItem(index) {
debugger
this.posts.splice(index, 1);
}
},
template: `
<div>
<ol>
<post-item
v-for="(post, index) in posts"
v-bind:post="post"
v-bind:key="post.id"
v-on:remove="deleteItem(index)" />
</ol>
</div>
`
});
// Root Vue instance
new Vue({
el: '#app'
});
}
</script>
</body>
</html>

Looks like you're getting a little confused with the event creation and handling.
Events are emitted up to parent components. You don't typically add an event listener within the same component.
All you really need in your post-item component is to emit the remove event with the appropriate data (ie, the post object)
<div>
<li v-show="!editing">
<p v-html="post.text"></p>
<button #click="$emit('remove', post)">Delete</button>
</li>
</div>
Then in your parent component (posts), listen for this event on the post-item component and assign the event handler
<post-item v-for="post in posts" :key="post.id" :post="post" #remove="deleteItem" />
and handle the event with post payload
methods: {
deleteItem (post) {
this.posts.splice(this.posts.indexOf(post), 1)
}
}
The post object emitted by the post-item component should be the very same object passed in to its prop which is why you can directly use this.posts.indexOf(post). There's no need to go searching for matching id properties.

Related

How to access a Vue component's data from a script

Here are the simplified html and javascript files of the page. It has a button and component which is a text displays the data of the component. I want the component's data to be changed when I click the button. But how to access the component's data from a script?
index.html
<body>
<div id="app">
<my-component></my-component>
<button id="btn"> change data </button>
</div>
<script src="https://unpkg.com/vue#next"></script>
<script src="./main.js"></script>
</body>
main.js
let app = Vue.createApp({});
app.component('my-component', {
data: function() {
return {
component_data : "foo"
}
},
template: '<p> data = {{ component_data }} </p>'
}
);
app.mount("#app");
document.querySelector("btn").onclick = function() {
// HOW TO CHANGE component_data TO "bar"
}
One possibility is to incorporate the button into the HTML within the component's template. If that's feasible for your app then you can add a function to the component and bind the function to the button's click event.
E.g. (Note this is untested so may have typos)
app.component('my-component', {
data: function() {
return {
component_data : "foo"
}
},
methods: {
changeData() {
this.component_data = "The data changed";
}
},
template: `<p> data = {{ component_data }} </p>
<button #click="changeData">Change data</button>`
}
);
If the button can't be incorporated into my-component then I'd recommend using the Vuex datastore. Vuex is a reactive datastore that can be accessed across the entire application.
You can use component props change data between components.
index.html
<body>
<div id="app">
<my-component :component-data="text"></my-component>
<button #click="handleBtnClick"> change data </button>
</div>
<script src="https://unpkg.com/vue#next"></script>
<script src="./main.js"></script>
</body>
main.js file
let app = Vue.createApp({
data() {
return { text: 'foo' }
},
methods: {
handleBtnClick() {
this.text = 'bar';
}
}
});
app.component('my-component', {
props: {
componentData: {
type: String,
default: 'foo'
}
}
template: '<p> data = {{ componentData }} </p>'
}
);
app.mount("#app");
I think you new in Vuejs. You have to first read Vue documentation
To get the reference of a component outside of it, you can use the template refs
Here is the refactor of the code provided in the above question to access the components data from the script.
<div id="app">
<my-component ref="my_component"></my-component>
<button #click="onBtnClick()"> change data </button>
</div>
let app = Vue.createApp({
methods: {
onBtnClick() {
this.$refs.my_component.component_data = "bar";
}
}
});

Custom events in Vue.js 2

I'm trying to figure out how to get a child component to communicate with the parent component, without having a hard binding between them.
From what I've read, custom events should be the thing. But I can't get the parent component to receive and act on the event.
In my sample below I expect clicking on the "Do stuff" button in <child> to trigger doStuff() in <parent>. I see the log message that indicates the button was clicked, but I see no log message indicating that the emitted message was ever received by the parent.
Sample HTML:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="app">
<parent>
<child></child>
</parent>
</div>
</body>
</html>
Sample Javascript:
Vue.component('parent', {
props: [],
template: `
<div v-on:stuff="doStuff">
<h1>Hello World (from parent)!</h1>
<slot></slot>
</div>
`,
methods: {
doStuff: function() {
console.log('Do stuff');
}
}
});
Vue.component('child', {
props: [],
template: `
<div>
Hello World (from child)!<br>
<button v-on:click="performClick">Do stuff</button>
</div>
`,
methods: {
performClick: function() {
console.log('Do something');
this.$emit('stuff');
}
}
});
var app = new Vue({
el: '#app',
})
You access to emitted is wrong . stuff is emitted from child component so you need to access that emit in child component tag so you should use child component tag in parent component template . Like below
Vue.component('parent', {
props: [],
template: `
<div>
<h1>Hello World (from parent)!</h1>
<slot :test="doStuff"></slot>
</div>
`,
methods: {
doStuff: function() {
console.log('Do stuff');
}
}
});
Vue.component('child', {
props: [],
template: `
<div>
Hello World (from child)!<br>
<button v-on:click="performClick">Do stuff</button>
</div>
`,
methods: {
performClick: function() {
console.log('Do something');
this.$emit('stuff');
}
}
});
var app = new Vue({
el: '#app'
})
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="app">
<parent>
<template slot-scope="scope"><child v-on:stuff="scope.test"></child></template>
</parent>
</div>
</body>
</html>
Updated
You need to use slot and set slot-scope in parent component by setting like :{scopeName} and then you can access from template by slot-scope . When child component is emitted , you just need to call that {scopeName}

Vue component not showing no errors

I am trying to do a very simple vue example and it won't display. I've done similar things before, but this won't work.
It is an extremely simple task list. It is an input with a submit button that adds an item to a list. For some reason the component does not render at all. I am very lost am supposed to give a presentation on vue. I was hoping to use this as an example.
I'm really not sure what else to say about this, but stack overflow won't let me submit this without typing more information about the issue.
<div id="app">
<task-list></task-list>
</div>
Vue.component('task-list-item', {
props: ["task"],
template: '#task-list-item-template'
})
Vue.component('task-list', {
data: function () {
return {
taskList: [],
newTask: ''
}
},
methods: {
addTask: function () {
var self = this;
if (self.newTask !== ""
&& self.newTask !== null
&& typeof self.newTask !== "undefined") {
this.taskList.push(self.newTask);
this.newTask = "";
}
}
},
template: '#task-list-template'
})
new Vue({
el: '#app',
data: function () {
return {
}
}
})
<script id="task-list-template" type="text/x-template">
<input v-model="newTask" />
<button v-on:click="addTask()">Add Task</button>
<ul>
<task-list-item v-for="taskItem in taskList"
v-bind:task="taskItem">
</task-list-item>
</ul>
</script>
<script id="task-list-item-template" type="text/x-template">
<li>{{task}}</li>
</script>
I am getting no error messages of any kind.
I think the problem is there should be only 1 child under <script id="task-list-template" type="text/x-template"></script>. In task-list-template, you have multiple children. Try to wrap them in 1 div
<script id="task-list-template" type="text/x-template">
<div>
<input v-model="newTask" />
<button v-on:click="addTask()">Add Task</button>
<ul>
<task-list-item v-for="taskItem in taskList"
v-bind:task="taskItem">
</task-list-item>
</ul>
</div>
</script>
Demo on codepen
According to A Single Root Element
Every component must have a single root element
To fix you can do some thing like:
<script id="task-list-template" type="text/x-template">
<div>
<input v-model="newTask" />
<button v-on:click="addTask()">Add Task</button>
<ul>
<task-list-item v-for="taskItem in taskList" v-bind:task="taskItem">
</task-list-item>
</ul>
</div>
</script>

Understanding components nesting in VueJS

learning VueJS and going through some of the tutorials online including the guide on vuejs.org and I'm having one hell of a time understanding how components can be nested and have them communicating through props.
Simple example seem to be ok but the following code (slightly tweaked but pretty much out of the VueJS guide) is giving me trouble.
I cannot seem to be able to have 'blog-item' nested within 'blog-items.
I would appreciate it if someone could explain the big picture of how components could be nested along with the use the v-for directive.
I have gone through many tutorials and everything seems to work one the component is nested inside the top-level 'app' component that's supplying the data but I cannot seem to be able to translate that into the scenario below.
As a newbie, I could be missing a key concept or totally off the rails understanding Vue :)
hope you can help.
thanks
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Components Basics - from vuejs.org</title>
<!-- development version, includes helpful console warnings -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<!-- This works. I get it. -->
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<hr>
<!-- This works too and I get it. -->
<div id="blog-post-demo-simple">
<blog-post-simple title="My journey with Vue"></blog-post-simple>
<blog-post-simple title="Blogging with Vue"></blog-post-simple>
<blog-post-simple title="Why Vue is so fun"></blog-post-simple>
</div>
<hr>
<!-- This is where I'm totally confused -->
<!-- How do I structure this to make sure blog-items is binding the 'post' -->
<!-- correctly? What is not clear to me is where the directives should be placed -->
<!-- Vue keeps complainig with the following: -->
<!-- Property or method "posts" is not defined on the instance but referenced during render -->
<blog-items>
<blog-item
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post">
</blog-item>
</blog-items>
<hr>
</div>
<script>
// Define a new component called button-counter. Cool. No problem here.
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
// This is also clear.
Vue.component('blog-post-simple', {
template:
'<h3>{{title}}</h3>',
props: {
title: {
type: String,
required: true
}
}
})
Vue.component('blog-items', {
data() { return {
posts: [
{ id: 1, title: '1. My journey with Vue' },
{ id: 2, title: '2. Blogging with Vue' },
{ id: 3, title: '3. Why Vue is so fun' }
]
}
}
})
Vue.component('blog-item', {
template:
'<h2>{{post.title}}</h2>',
props: ['post']
})
var app = new Vue({
el: '#app'
})
</script>
</body>
</html>
Remember when you access a property in a template you're fetching that property from the component that uses that template. In this case it's your root #app component. Since that component doesn't have a property or method with the name posts then Vue is going to complain. What you need to do is move that part within the blog-items component's template since that component is holding your posts.
So what you need to do is this ..
<!-- This is where I'm totally confused -->
<!-- How do I structure this to make sure blog-items is binding the 'post' -->
<!-- correctly? What is not clear to me is where the directives should be placed -->
<!-- Vue keeps complainig with the following: -->
<!-- Property or method "posts" is not defined on the instance but referenced during render -->
<blog-items></blog-items>
Vue.component('blog-items', {
template: `
<div>
<blog-item v-for="post in posts" v-bind:key="post.id" v-bind:post="post" />
</div>
`,
data() {
return {
posts: [
{ id: 1, title: '1. My journey with Vue' },
{ id: 2, title: '2. Blogging with Vue' },
{ id: 3, title: '3. Why Vue is so fun' }
]
}
}
})
Otherwise you'll have to resort to using Scoped Slots, which allow you to expose properties/methods from the child component's scope to the parent ..
<blog-items>
<template slot-scope="{ posts }">
<blog-item v-for="post in posts" v-bind:key="post.id" v-bind:post="post">
</blog-item>
</template>
</blog-items>
Vue.component('blog-items', {
template:`
<div>
<slot :posts="posts"></slot>
</div>`,
data() {
return {
posts: [
{ id: 1, title: '1. My journey with Vue' },
{ id: 2, title: '2. Blogging with Vue' },
{ id: 3, title: '3. Why Vue is so fun' }
]
}
}
})
I found this especially helpful in understanding how scoped slots work.

VueJS - Ajax communication between templates

I'm very new to VueJS and i'm having a difficult to share a result from Two template, that come from AJAX Request.
This is the home page:
<div>
<search-bar></search-bar>
<tracking-results></tracking-results>
</div>
This is the search-bar component, where i have a text input field and after press the button, it will do an Ajax Request:
<template>
<div class="row">
<div class="col-lg-8 col-lg-offset-3">
<div class="col-lg-5">
<div class="input-group">
<input type="text" class="form-control" placeholder="Numero Spedizione" v-model="trackingNumber">
<span class="input-group-btn">
<button class="btn btn-default"
type="button"
#click.prevent="search">Ricerca</button>
</span>
</div><!-- /input-group -->
</div><!-- /.col-lg-3 -->
</div>
</div><!-- /.row -->
</template>
<script>
export default {
data() {
return {
trackingNumber: '',
}
},
methods: {
search() {
Vue.http.options.emulateJSON = true;
this.$http.post('/endpoint').then(function (response) {
var parsedResponse = JSON.parse(response.data) || undefined;
/* HERE I WANT TO SEND THE RESPONSE TO ANOTHER COMPONENT */
}, function (err) {
console.log('ERROR', err);
});
}
}
}
</script>
I did tried with $broadcast, but my components arent child, are sibling.
I did see a way can be Vuex, but my application will not be written entirely with Vue. I will use this framework just to "simplify some Javascript process".
The only alternative i did find is to "merge" the search-bar and tracking-result in a single component. In this way the data will be "shared", and i can communicate with the state.
[Update: sync is removed in Vue 2, so you would need to follow the standard props-down, events-up design pattern]
You can have the parent viewmodel pass a prop to each of the components, using sync for the search bar. The search bar would populate the value in the ajax call, it would sync up to the parent and down to the tracking-results.
Some example code:
Vue.component('child1', {
props: ['ajaxData'],
methods: {
loadData: function () {
this.ajaxData = 'Some data is loaded';
}
},
template: '<div>Child1: {{ajaxData}} <button v-on:click="loadData">Load data</button></div>'
});
Vue.component('child2', {
props: ['ajaxData'],
template: '<div>Child2: {{ajaxData}}</div>'
});
new Vue({
el: 'body',
data: {
hi: 'Hello Vue.js!'
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<child1 :ajax-data.sync='hi'></child1>
<child2 :ajax-data='hi'></child2>
Ideally, you can send data to the parent, then the parent send data to the component via props. The parent handles the communication between the siblings.
Another way of doing it is using state management or vuex. But that depends on the complexity of your project. If it's a simple thing, I suggest to let the parent handle the communication.