I've been banging my head against this problem for hours. I can't see a problem and from what I can tell I am following the documentation here: https://v2.vuejs.org/v2/guide/components-custom-events.html
code below
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="wrap">
<test-listen>
<test-emit></test-emit>
</test-listen>
</div>
<script>
Vue.component('test-listen', {
data: function(){
return {};
},
methods: {
customHandler : function(e){
console.log(e);
console.log("works");
}
},
template: `
<div class='test_listen' v-on:custom="customHandler">
<slot></slot>
</div>
`
});
Vue.component('test-emit',{
data: function(){
return {};
},
methods: {
clickHandler : function(){
this.$emit('custom');
}
},
template : `
<div class='test_emit' v-on:click="clickHandler">
test
</div>
`
});
new Vue({
el:"#wrap"
});
</script>
<style>
.test_listen{
display:block;
padding:20px;
border:1px solid #000;
}
.test_emit{
display:block;
width:100px;
height:100px;
background-color:#f0f;
color:#fff;
font-weight:700;
font-size:20px;
}
</style>
But the listeners are definitely bound to the element, because if I dispatch a vanillaJS CustomEvent it triggers the console log just fine. What am I missing?
I see only one mistake here. You should add v-on to the child component.
When you $emit('custom') from inside it will call "customHandler" on the parent component.
Working jsfiddle
<test-listen>
<test-emit v-on:custom="customHandler"></test-emit>
</test-listen>
There are 2 things wrong here.
Vue events can be bound to components only I guess (talking vue events here)
Slots are not good with events. (Source - Evan You, Vue author)
If you really want to pass data here and there without restriction better to use Global Event Bus Approach
Working example of your code with some minor correction.
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="wrap">
<test-listen>
</test-listen>
</div>
<script>
Vue.component('test-listen', {
data: function(){
return {};
},
methods: {
customHandler : function(e){
console.log(e);
console.log("works");
}
},
template: `
<div class='test_listen' >
<test-emit v-on:custom="customHandler"></test-emit>
</div>
`
});
Vue.component('test-emit',{
data: function(){
return {};
},
methods: {
clickHandler : function(e){
// e => event : didnt pass here as console will stuck so
this.$emit('custom', 'somedata');
}
},
template : `
<div class='test_emit' v-on:click="clickHandler">
test
</div>
`
});
new Vue({
el:"#wrap"
});
</script>
<style>
.test_listen{
display:block;
padding:20px;
border:1px solid #000;
}
.test_emit{
display:block;
width:100px;
height:100px;
background-color:#f0f;
color:#fff;
font-weight:700;
font-size:20px;
}
</style>
With this.$emit() is a way to say to the parent component that hey i created an event and now you can listen on this event
You are doing well but you don't listen in parent component the event emitted by child. I made it to work.Click here to see it in action
So in order to make your code to work,In test-listen component,use as child the test-emit component.
Then inside the div #wrap use the test-listen component
Your problem was that you did't listen to the event in parent component.
Related
In the following code, codePen demo here
child component emits a custom event changedMsg to parent which changes msg data property on the parent component. Not sure, why changedMsg does not work. It does not modify msg property of parent.
Note: In a single file setup this works, but not in the setup below which is using template tag. Not sure, why?
VueJS
var child = {
template: '#child',
props: ['parentMsg'],
methods: {
changeParentMsg() {
console.log(this.parentMsg)
this.parentMsg = 'Message was changed by CHILD'
this.$emit('changedMsg', this.parentMsg)
}
}
}
new Vue({
el: '#parent',
data() {
return {
msg: 'Hello World'
}
},
components: {
child
},
methods: {
changeMsg() {
this.msg = 'Changed Own Msg'
}
},
})
HTML
<div>
<h4>Parent</h4>
<p>{{ msg }}</p>
<button #click="changeMsg">Change Own Message</button>
<br>
<div class="child">
<h4>Child</h4>
<child :parentMsg="msg" #changedMsg= "msg = $event"></child>
</div>
</div>
</div>
<template id="child">
<div>
<button #click="changeParentMsg">Change Parnets msg</button>
</div>
</template>
CSS
#parent {
background-color: lightblue;
border: 2px solid;
width: 300px;
}
.child {
background-color: lightgray;
border: 2px solid;
margin-top: 20px
}
Thanks
Note: In a single file setup this works, but not in the setup below which is using template tag. Not sure, why?
This is explained in the docs:
Event Names
Unlike components and props, event names will never be used as variable or property names in JavaScript, so there’s no reason to use camelCase or PascalCase. Additionally, v-on event listeners inside DOM templates will be automatically transformed to lowercase (due to HTML’s case-insensitivity), so v-on:myEvent would become v-on:myevent – making myEvent impossible to listen to.
For these reasons, we recommend you always use kebab-case for event names.
So, it's not a good practice to make the logic on the listem event directive, i made this way and worked:
<child :parentMsg="msg" #changed-msg="msgChanged"></child>
</div>
</div>
on the child tag i changed the #changedMsg to kebab-case so now it's #changed-msg and made it call a function instead of do the logic on the child tag, function called msgChanged, so you need to create it on your methods section:
methods: {
changeMsg() {
this.msg = 'Changed Own Msg'
},
msgChanged(param) {
console.log(param)
}
}
hope that helps
How can using Vuex store action and mutation to implement this sidebar? And need by adding a close button in element aside.
<div id="example-1">
<button #click="show = !show">{{btnText}}</button>
<transition name="slide">
<aside v-show="show">hello</aside>
</transition>
</div>
<style>
.slide-enter-active {
transition: all .3s cubic-bezier(0.215, 0.61, 0.355, 1);
}
.slide-leave-active {
transition: all .1s ease;
}
.slide-enter, .slide-leave-active {
transform: translateX(100%);
}
</style>
<script>
new Vue({
el: '#example-1',
data: {
show: false
},
computed: {
btnText: function() {
if(this.show) {
return '✕'
}
return '☰'
}
}
})
<script>
https://codepen.io/pershay/pen/Pxgqwd
In order to use Vuex for this functionality you will want 3 things
mutation
getter
state
the state of the side bar will be controlled by a getter which can be changed by a mutation
Below is a basic working example of toggling a sidebar with vuex.
Note it is not using getter, if you don't understand how those work I can go into more detail
Jsfiddle
Below is using a toggle inside aside component with transition effect.
Jsfiddle
Here is Vue documentation for transitions Vue Transitions
I have a modal in one of my pages and I want to add a class “active” on body when I open the modal, so I can make the body overflow hidden (no scroll).
Is there a way to toogle a class on the body tag when I click from one component? I can't figure it out...
I use routes
<template>
<div id="app">
<Header />
<router-view/>
<Footer />
</div>
</template>
Thx in advance
The correct way of doing this in Vue is to communicate between components, in this case it might not be a simple parent/child communication, so you might want to create an Event Bus.
By using this approach the modal's code is has minimum effects on the rest of your application, it only dispatches events that you can subscribe to from any other component.
Note: In this case you won't add the class on your body tag (because you can't mount Vue on body), but you may just add it to your root div to have a similar result.
const eventBus = new Vue();
Vue.component('modal', {
props: ['isOpen'],
template: `
<div class="modal" v-if="isOpen">This is a modal</div>
`,
});
Vue.component('wrapper', {
template: `
<div>
<modal :isOpen="isModalOpen"></modal>
<button #click="toggleModal">toggle modal</button>
</div>
`,
data() {
return {
isModalOpen: false,
}
},
methods: {
toggleModal() {
this.isModalOpen = !this.isModalOpen;
eventBus.$emit('toggleModal', this.isModalOpen);
}
}
});
new Vue({
el: "#app",
data: {
active: false,
},
created() {
eventBus.$on('toggleModal', (isModalOpen) => {
this.active = isModalOpen;
});
},
})
.active {
background: grey;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app" :class="{active}">
<wrapper></wrapper>
</div>
This should help
document.body.className += 'active'
Component
<template lang="html">
<div class="chat-log">
<chat-message v-for="message in messages" :message="message"></chat-message>
</div>
</template>
<script>
export default {
props: ["messages"]
}
</script>
<style lang="css">
.chat-log .chat-message:nth-child(even) {
background-color: #ccc;
}
.chat-log {
overflow-y: auto;
max-height: 400px;
}
</style>
When I change the above script code to below. I get errors..
<script>
export default {
props: ["messages"]
},
created() {
$(".chat-log").scrollTop($(".chat-log").prop('scrollHeight'));
}
</script>
Error Details
Unexpected token, expected ;
Issue comes only when adding the created method, Am I missing anything?
The created lifecyle method goes within the body of the Vue component itself, not outside. I mean:
export default {
props: ["messages"],
created() {
$(".chat-log").scrollTop($(".chat-log").prop('scrollHeight'));
}
}
Vue.js Lifecycle
Your created(){} method should be encapsulated within your export default {} block.
In other words, change your code this:
export default {
props: ["messages"],
created() {
$(".chat-log").scrollTop($(".chat-log").prop('scrollHeight'));
}
},
I am using vuejs style bindings to render changes dynamically as the styles are computed.
This works great for everything within the scope of my Vue instance but how can I compute styles for body or html tags?
This used to be possible when you could bind the vue instance to but vue no longer lets you do it.
I want to dynamically update the background color of using my computed variables in vue.
edit: added code snippet to demonstrate
var app = new Vue({
el: '#app',
data: {
color: '#666666'
},
computed: {
backgroundColor: function() {
return {
'background-color': this.color
}
}
},
methods: {
toggleBackground: function() {
if(this.color=='#666666'){
this.color = '#BDBDBD'
} else {
this.color = '#666666'
}
}
}
})
<script src="https://vuejs.org/js/vue.min.js"></script>
<html>
<body>
<div id="app" :style="backgroundColor">
<div>
lots of content...
</div>
<button #click="toggleBackground"> Click to toggle </button>
</div>
</body>
</html>
If you really need to style body itself, you'll need to do it with plain JavaScript in a watcher. A simple example is below.
You should (not something I've tried, but I'm hypothesizing) be able to defeat overscrolling effects by making body and your outer container non-scrolling. Put a scrollable container inside that. When it overscrolls, it will show your outer container, right?
The reasons for not binding to body are here (for React, but applies to Vue).
What’s the problem with ? Everybody updates it! Some people have
non-[Vue] code that attaches modals to it. Google Font Loader will
happily put elements into body for a fraction of second, and
your app will break horribly and inexplicably if it tries to update
something on the top level during that time. Do you really know what
all your third party scripts are doing? What about ads or that social
network SDK?
new Vue({
el: '#app',
data: {
isRed: false
},
watch: {
isRed() {
document.querySelector('body').style.backgroundColor = this.isRed ? 'red' : null;
}
}
});
#app {
background-color: white;
margin: 3rem;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<input type="checkbox" v-model="isRed">
</div>
I think I found better solution than using jQuery/querySelector
You can add tag style right in your Vue template.
And add v-if on this, smth like that:
<style v-if="true">
body {
background: green;
}
</style>
Thus you can use computed/methods in this v-if and DOM always will update when you need.
Hope this will help someone ;)
UPD:
Using tag "style" in templates is not best idea, but you can create v-style component, then everything will be fine:
Use style tags inside vuejs template and update from data model
My snippet:
Vue.component('v-style', {
render: function (createElement) {
return createElement('style', this.$slots.default)
}
});
new Vue({
el: '#app',
data: {
isRed: false,
color: 'yellow',
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="checkbox" v-model="isRed">
<v-style v-if="isRed">
body {
background: red; /*one more benefit - you can write "background: {{color}};" (computed)*/
}
</v-style>
</div>