How to fix my simple custom v-on directive that does not execute a method immediately? - vue.js

I am here to ask some help about why is my custom directive not working properly. I am trying to create my own v-on (named v-myOn) directive that would just change the background color of the text when it is clicked. The problem is that the method is executed instantly when vue js application is started (meaning the element background has the color style already) and not when a certain event happened which is when the element is clicked. Thanks in advance!
Update: Problem as what Phil stated is I execute the function directly in the template. But in this case I am trying to pass an argument to the method changeColor. So how could i prevent it from executing while being able to pass arguments?
Here is my code in App.vue:
<template>
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
<h1 v-myOn:click="changeColor('blue')" :style="{background: color}" ref="heading">Directives Exercise</h1>
<!-- Exercise -->
<!-- Build a Custom Directive which works like v-on (Listen for Events) -->
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return{
color: ''
}
},
directives: {
myOn : {
bind(el, binding, vnode) {
el.addEventListener(binding.arg, binding.value)
}
}
},
methods: {
changeColor(color)
{
this.color = color;
}
}
}
</script>
<style>
</style>

You have to wrap in an arrow function otherwise it will get executed immediately as you have discovered and #Phil has pointed out.
<h1 v-myOn:click="() => changeColor('blue')" :style="{background: color}" ref="heading">Directives Exercise</h1>

Related

Vuejs transition javascript hook not triggering (nuxtjs)

I'm trying to add some animations to my vuejs(v2)+nuxt site using GSAP. To do that I need to trigger some animations on scroll. Vue provides some handy javascript hooks for it but they seem to not be working in my case.
<template>
<div class="container-xxl">
<div class="row">
<div class="col-12">
<div class="text-center">
<transition #before-enter="beforeEnter" #enter="enter" #leave="leave">
<h1>Test Component</h1>
</transition>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'TestComponent',
methods: {
beforeEnter (el) {
console.log('before enter')
},
enter (el, done) {
console.log('enter')
},
leave (el, done) {
console.log('leave')
}
}
}
</script>
When using JavaScript-only transitions, the done callbacks are required for the enter and leave hooks. Otherwise, the hooks will be called synchronously and the transition will finish immediately.

unexpected vue warn on a declared prop

I'm learning vuex. I'm facing a strange issue after I've migrated some methods to vuex actions.
I get this error in a component that has worked fine until I've migrated some things to vuex and I've implemented inside the component ...mapGetters and ...mapActions
the error is [Vue warn]: Property or method "isVisible" is not defined on the instance but referenced during render
but in my data I've declared the prop
data() {
return {
id: state.userInfo.id,
endCursor: state.userInfo.end_cursor,
nextPageLoaded: false,
isVisible: false,
isVideo: null,
url: null
}
}
<div class="modal fade show" tabindex="-1" role="dialog" v-if="isVisible">
<div class="modal-dialog">
<div class="modal-content h-100 rounded-0">
<div class="modal-header">
<button type="button" class="close mb-3 float-right" #click.prevent="closeZoomModal()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<!-- display image -->
<img class="img-fluid w-100 h-100 img-zoom" :src="url" v-if="!isVideo">
<!-- display video -->
<div class="embed-responsive embed-responsive-4by3 h-100" v-else>
<iframe class="embed-responsive-item h-100" :src="url" title=""></iframe>
</div>
</div>
</div>
</div>
</div>
This happen after the user click on the home component to search for ome data and the result component where the error is fires, is loaded.
How I can fix it? can the error be caused from ...mapGetters or ...mapActions ?
state is not available in data. According to docs, you can pass the instance as first param of the data function
data: vm => ({
isVisible: vm.$store.state.isVisible
})
... but I personally haven't used this (it doesn't work with Typescript and the component is still in an early lifecycle stage and a lot of things are missing from it). Besides, this is merely an assignment (it only runs once - it's not a getter - so if the state changes after data has been set, data won't react to it. You'd have to modify the data prop itself).
So what you need to do is move all store related component properties from data into computed by using either ...mapState() (if they're vuex state props), ...mapGetters() (if they're vuex getters) or use explicit computed syntax:
computed: {
isVisible() {
return this.$store.state.isVisible; // if store state prop
// return this.$store.getters['isVisible'] // if store getter
}
}
If you also want to be able to assign to it (as you would to a data property), you have to replace the above computed syntax (only getter) with a getter + setter syntax:
computed: {
isVisible: {
get() {
return this.$store.state.isVisible;
},
set(value) {
this.$store.dispatch('setVisibility', value);
// you can also commit mutations `this.$store.commit()` from here
}
}
}
If you're still having trouble, please create a minimal reproducible example on codesandbox.io and I'll help sort it out.

Not able to add #keyup listener to layout component

So I have my outer default.vue layout in nuxt architecture. I'my trying to add #keyup.esc="test" to outer element of default.vue:
<template>
<div #keyup.esc="test">
<navigation></navigation>
<nuxt/>
<transition name="fade">
<overlay-modals v-if="showModalLogin || showModalRegister"></overlay-modals>
</transition>
<transition name="zoom">
<div class="modal__outer" v-if="showModalRegister || showModalLogin">
<modal-login v-if="showModalLogin"></modal-login>
<modal-register v-if="showModalRegister"></modal-register>
</div>
</transition>
</div>
</template>
methods: {
test() {
alert('come on...');
},
test method is never fired which makes me confused.
The keyup event will only be detected by the div when the div has focus, so you have to set tabindex to make it focusable, and you have to give it focus.
new Vue({
el: '#app',
methods: {
test() {
console.log("Come on");
}
},
mounted() {
this.$el.focus();
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app" #keyup.esc="test" tabindex="0">
Here is my div
</div>
if u look into documentation u will see that #keyup used in inputs. In your case - u are using this to div, and there is no focus on it so keyup will not possible. However u need to add some stuff to make it working. Please read this

Nesting a slot in a slot for vue

Update: Here's a simplified version of what I'm trying to achieve here (from the threaded conversation below):
Accept Component A - Accept Component B - Accept a condition - if
condition is true : wrap Component B with Component A [and render]- else only
render component B.
I'm interested in creating a component that renders a wrapper conditionally. I figured a theoretical approach like this would probably be best**:**
<template>
<div>
<slot v-if="wrapIf" name="wrapper">
<slot name="content"></slot>
</slot>
<slot v-else name="content"></slot>
</div>
</template>
<script>
export default {
props: {
wrapIf: Boolean,
}
}
</script>
Then when we implement, it would look something like this:
...
<wrapper-if :wrap-if="!!link">
<a :href="link" slot="wrapper"><slot></slot></a>
<template slot="content">
content
</template>
</wrapper-if>
The idea being that, in this case, if there is a link, then let's wrap the content with the wrapper slot (which can be any component/element). If there isn't, then let's just render the content without the wrapped link. Pretty simple logic, but it seems that I'm misunderstanding some basic vue functionality because this particular example does not work.
What is wrong with my code or is there some kind of native api that already achieves this or perhaps a dependency that does this sort of thing already?
The output should look like this:
wrapIf === true
<a href="some.link">
content
</a>
wrapIf === false
content
Just focus on the content itself, and let the component worry about whether or not to wrap the default or named content slot.
If you need the wrapper to be dynamic, a dynamic component should solve that. I've updated my solution accordingly. So if you need the wrapper to be a label element, just set the tag property to it, and so on and so forth.
const WrapperIf = Vue.extend({
template: `
<div>
<component :is="tag" v-if="wrapIf" class="wrapper">
<slot name="content"></slot>
</component>
<slot v-else name="content"></slot>
</div>
`,
props: ['wrapIf', 'tag']
});
new Vue({
el: '#app',
data() {
return {
link: 'https://stackoverflow.com/company',
tagList: ['p', 'label'],
tag: 'p',
wrap: true
}
},
components: {
WrapperIf
}
})
.wrapper {
display: block;
padding: 10px;
}
p.wrapper {
background-color: lightgray;
}
label.wrapper {
background-color: lavender;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<wrapper-if :wrap-if="wrap" :tag="tag">
<a :href="link" slot="content">
content
</a>
</wrapper-if>
<div>
Change wrapper type:
<select v-model="tag">
<option v-for="tag in tagList">{{tag}}</option>
</select>
</div>
<button #click="wrap = !wrap">Toggle wrapper</button>
</div>

How to customize style of VueJS 2.0 using stylus?

I am using v-text-field without vuetify.min.css just use stylus.
Here is my code.
<template>
<v-text-field type="text" name="password"></v-text-field>
</template>
<style lang="stylus" scoped="scoped">
.input-group_details {
XXX
}
</style>
I am trying to hide some divs in v-text-field.
But I got nothing changed.
That is not possible using scoped styles (That's the point of scoping)
What you could do is either passing down a prop which indicates that the divs are hidden or handle it globally.
passing down a prop:
const textField = {
template: `
<div>
<div>Always shown</div>
<div v-if="shown">
Conditionally shown
</div>
</div>
`,
props: { shown: Boolean }
};
Vue.component('v-text-field', textField);
new Vue({}).$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<b>shown = true:</b>
<v-text-field :shown="true"></v-text-field>
<br>
<b>shown = false:</b>
<v-text-field :shown="false"></v-text-field>
</div>
As per https://vue-loader.vuejs.org/en/features/scoped-css.html#notes
you need to use >>> operator for CSS
So this should work:
<style scoped>
>>> .input-group_details {
//your css
}
</style>
You can use lang="stylus" and it will work, but your IDE might throw some syntax errors.
I'm not sure what's correct stylus syntax for that.
Note that it was implemented in v12.2.0