Custom events in Vue.js 2 - vuejs2

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}

Related

Vue.js component is not showing visually on the page

I have the following Vue component called TeamCard:
<template>
<div class="m-8 max-w-sm rounded overflow-hidden shadow-lg">
<!-- removed fro brevety -->
div class="text-gray-900 font-bold text-xl mb-2">{{ name }}</div>
<p class="text-gray-700 text-base"> {{ description }} </p>
<!-- removed fro brevety -->
</div>
</div>
</template>
<script>
export default {
name: "TeamCard",
props: {
name: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
}
};
</script>
<style scoped>
#import "https://unpkg.com/tailwindcss#^2/dist/tailwind.min.css";
</style>
I call the component in an HTML page (Spring boot template) the following way:
<teamCard v-bind:name="'Hawks'" v-bind:description="'Best team'" ></teamCard>
<script src="https://unpkg.com/vue"></script>
<script th:src="#{/TeamCard/TeamCard.umd.min.js}"></script>
<script>
(function() {
new Vue({
components: {
teamCard: TeamCard
}
})
})();
</script>
When I go to the page in the browser that has the second snippet, nothing is shown. There are no errors in the console. What am I doing wrong? How can I make the component be shown?
I've never seen Vue used this way but it looks like there is no mounting element for the app. Try this:
<div id="app">
<team-card v-bind:name="'Hawks'" v-bind:description="'Best team'"></team-card>
</div>
and
(function() {
new Vue({
el: '#app',
components: {
teamCard: TeamCard
}
})
})();

Property or method "item" is not defined on the instance but referenced during render

The application throws me an error in the console.
The property or method "logo" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option or for class-based components, by initializing the property
This is what HTML and script look like.
<template id="items-template">
<span class="items">
<img v-if="logo.name" src="/images/name.png"/>
</span>
</template>
#section scripts {
<script type="text/javascript">
Vue.component('items', {
props: ['logo'],
template: '#items-template'
});
</script>
}
Here's an example of how it should work:
<div id="app">
<items :logo="logo"></items>
</div>
<script type="text/x-template" id="items-template">
<div>
<span class="items">
<img v-if="logo.name" src="https://thumbs-prod.si-cdn.com/d4e3zqOM5KUq8m0m-AFVxuqa5ZM=/800x600/filters:no_upscale():focal(554x699:555x700)/https://public-media.si-cdn.com/filer/a4/04/a404c799-7118-459a-8de4-89e4a44b124f/img_1317.jpg"/>
</span>
</div>
</script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var itemComponent = {
template: "#items-template",
props: {
logo: Object
}
}
new Vue({
el: "#app",
data: {
logo: {
name: 'someName'
}
},
components: {
'items': itemComponent
}
})
</script>
In this example, div#app is parent, and it passes down logo to items-component logo prop.

VuesJS components template

I'm a VueJS beginner and i'm struggling to understand some component logic.
If i have my component (simplified for clarity) :
Vue.component('nav-bar', {
template: '<nav [some code] ></nav>'
}
This component represent the whole navigation bar of my page.
In my HTML file, how can i insert code inside the component?
Something like:
<nav-bar>
<button></button>
...
</nav-bar>
Could you please tell me if it is the right way to do it?
There are at least three options I can think of:
Using ref, or
Slot props with scoped slots, or
provide/inject.
1. Example with ref
Vue.component('NavBar', {
template: `
<nav>
<slot></slot>
</nav>
`,
methods: {
run() {
console.log('Parent\'s method invoked.');
}
}
});
new Vue().$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<nav-bar ref="navbar">
<button #click="$refs.navbar.run()">Run with refs</button>
</nav-bar>
</div>
2. With Scoped <slot>
Vue.component('NavBar', {
template: `
<nav>
<slot v-bind="$options.methods"></slot>
</nav>
`,
methods: {
run() {
console.log('Parent\'s method invoked.');
}
}
});
new Vue().$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<nav-bar>
<template #default="methods">
<button #click="methods.run">Run with slot props</button>
</template>
</nav-bar>
</div>
3. With provide and inject
Vue.component('NavBar', {
template: `
<nav>
<slot></slot>
</nav>
`,
provide() {
const props = {
...this.$options.methods,
// The rest of props you'd like passed down to the child components.
};
return props;
},
methods: {
run() {
console.log('Parent\'s method invoked.');
}
}
});
// In order to "receive" or `inject` the parent props,
// the child(ren) needs to be a component itself.
Vue.component('Child', {
template: `
<button #click="run">
<slot></slot>
</button>
`,
// Inject anything `provided` by the direct parent
// This could also be `data` or `props`, etc.
inject: ['run']
});
new Vue().$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<nav-bar>
<template>
<child>Run with injected method</child>
</template>
</nav-bar>
</div>

How to get Vue to catch event?

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.

How to use same template on different components in vue js?

Currently I am using as
<template> ... </template>
<script src="test1.js"> </script>
Where all my business logics are written in test1.js. Now I need to use test2.js with same template. I need to reuse the template with different component.
My current code goes like this...
common.vue
<template>
<div>
{{ page }}
</div>
<template>
<script src="../scripts/test1.js"></script>
Where in test1.js
export default {
name: 'home',
components: {
test: test
},
data() {
return {
page: "test1",
}
}
}
But also i need to use common.vue in my test2.js. Which i am not able to import both the .js seperately.
In angular we can write the fallowing late bindig which binds .html and .js(in ui.router)
.state('home', {
url:'/',
templateUrl: 'templates/home.html',
controller: 'HomeController'
})
Is there something similar to this?
You can share the template through the id attribute.
Vue.component('my-comp1', {
template: '#generic',
data () {
return {
text: 'I am component 1'
}
}
})
Vue.component('my-comp2', {
template: '#generic',
data () {
return {
text: 'I am component 2'
}
}
})
new Vue({
el: '#app'
})
<div id="app">
<my-comp1></my-comp1>
<my-comp2></my-comp2>
</div>
<template id="generic">
<div>
<p>{{ text }}</p>
</div>
</template>
<script src="https://unpkg.com/vue"></script>