before-leave transition JavaScript hook not working Vue js - javascript

My variable, transitionName, is not changing in the beforeLeaveHook. The value of transitionName remains to be 'right' when it should change to 'left'. Please help.
<template lang="pug">
transition(:name="transitionName" v-on:before-leave="beforeLeaveHook")
.componentMain
h1 {{transitionName}}
</template>
<script>
export default {
name: 'Component',
data () {
return {
transitionName: 'right',
}
},
methods: {
beforeLeaveHook: function(event){
this.transitionName = 'left';
}
}
}
</script>

The before-leave event won't fire until you remove the .componentMain element.
Here's an example:
new Vue({
el: '#app',
data () {
return {
transitionName: 'right',
show: true,
}
},
methods: {
beforeLeaveHook() {
this.transitionName = 'left';
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<button #click="show = !show">Toggle</button>
<transition :name="transitionName" v-on:before-leave="beforeLeaveHook">
<div class="componentMain" v-if="show">
<h1>
{{transitionName}}
</h1>
</div>
</transition>
</div>

Thanks for the help. I needed route specific transitions for my app. I solved the problem using beforeRouteUpdate(to, from, next) in the parent component to check the from and to paths before calling next(), in order to set the correct transition, using Vuex, and then using a computed property in my child component to listen for the transitionName change.

Related

Active events in vue instance encapsulated inside shadowDOM

I'm searching to trigger event from a vue instance which is encapsulated inside a shadowDOM.
For the moment, my code create a new Vue instance inside a shadowDOM and print the template without the style (because nuxt load the style inside the base vue instance).
I can call methods of each instance
But, when i'm trying to add an event / listener on my component, it doesn't work.
DOM.vue
<template>
<section ref='shadow'></section>
</template>
<script>
import bloc from '#/components/bloc.vue'
import Vue from 'vue'
export default {
data() {
return {
vm: {},
shadowRoot: {},
isShadowReady: false
}
},
watch: {
isShadowReady: function() {
let self = this
this.vm = new Vue({
el: self.shadowRoot,
components: { bloc },
template: "<bloc #click='listenClick'/>",
methods: {
listenClick() { console.log("I clicked !") },
callChild() { console.log("I'm the child !") }
},
created() {
this.callChild()
self.callParent()
}
})
}
},
mounted() {
const shadowDOM = this.$refs.shadow.attachShadow({ mode: 'open' })
this.shadowRoot = document.createElement('main')
shadowDOM.appendChild(this.shadowRoot)
this.isShadowReady = true
},
methods: {
callParent() {
console.log("I'am the parent")
},
}
}
</script>
bloc.vue
<template>
<div>
<p v-for='word in words' style='text-align: center; background: green;'>
{{ word }}
</p>
</div>
</template>
<script>
export default {
data() {
return {
words: [1,2,3,4,5,6]
}
}
}
</script>
Any idea ?
Thank you
Hi so after i watched at your code i think it's better to use the $emit to pass event from the child to the parent and i think it's more optimized then a #click.native
template: "<bloc #someevent='listenClick'/>",
<template>
<div #click="listenClickChild">
<p v-for='(word, idx) in words' :key="idx" style='text-align: center; background: green;'>
{{ word }}
</p>
</div>
</template>
<script>
export default {
data() {
return {
words: [1,2,3,4,5,6]
}
},
methods: {
listenClickChild() {
this.$emit('someevent', 'someValue')
}
}
}
</script>
bloc #click.native='listenClick'/>

Dynamically changing props

On my app, I have multiple "upload" buttons and I want to display a spinner/loader for that specific button when a user clicks on it. After the upload is complete, I want to remove that spinner/loader.
I have the buttons nested within a component so on the file for the button, I'm receiving a prop from the parent and then storing that locally so the loader doesn't show up for all upload buttons. But when the value changes in the parent, the child is not getting the correct value of the prop.
App.vue:
<template>
<upload-button
:uploadComplete="uploadCompleteBoolean"
#startUpload="upload">
</upload-button>
</template>
<script>
data(){
return {
uploadCompleteBoolean: true
}
},
methods: {
upload(){
this.uploadCompleteBoolean = false
// do stuff to upload, then when finished,
this.uploadCompleteBoolean = true
}
</script>
Button.vue:
<template>
<button
#click="onClick">
<button>
</template>
<script>
props: {
uploadComplete: {
type: Boolean
}
data(){
return {
uploadingComplete: this.uploadComplete
}
},
methods: {
onClick(){
this.uploadingComplete = false
this.$emit('startUpload')
}
</script>
Fixed event name and prop name then it should work.
As Vue Guide: Custom EventName says, Vue recommend always use kebab-case for event names.
so you should use this.$emit('start-upload'), then in the template, uses <upload-button #start-upload="upload"> </upload-button>
As Vue Guide: Props says,
HTML attribute names are case-insensitive, so browsers will interpret
any uppercase characters as lowercase. That means when you’re using
in-DOM templates, camelCased prop names need to use their kebab-cased
(hyphen-delimited) equivalents
so change :uploadComplete="uploadCompleteBoolean" to :upload-complete="uploadCompleteBoolean"
Edit: Just noticed you mentioned data property=uploadingComplete.
It is easy fix, add one watch for props=uploadComplete.
Below is one simple demo:
Vue.config.productionTip = false
Vue.component('upload-button', {
template: `<div> <button #click="onClick">Upload for Data: {{uploadingComplete}} Props: {{uploadComplete}}</button>
</div>`,
props: {
uploadComplete: {
type: Boolean
}
},
data() {
return {
uploadingComplete: this.uploadComplete
}
},
watch: { // watch prop=uploadComplete, if change, sync to data property=uploadingComplete
uploadComplete: function (newVal) {
this.uploadingComplete = newVal
}
},
methods: {
onClick() {
this.uploadingComplete = false
this.$emit('start-upload')
}
}
})
new Vue({
el: '#app',
data() {
return {
uploadCompleteBoolean: true
}
},
methods: {
upload() {
this.uploadCompleteBoolean = false
// do stuff to upload, then when finished,
this.uploadCompleteBoolean = true
},
changeStatus() {
this.uploadCompleteBoolean = !this.uploadCompleteBoolean
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<button #click="changeStatus()">Toggle Status {{uploadCompleteBoolean}}</button>
<p>Status: {{uploadCompleteBoolean}}</p>
<upload-button :upload-complete="uploadCompleteBoolean" #start-upload="upload">
</upload-button>
</div>
The UploadButton component shouldn't have uploadingComplete as local state (data); this just complicates the component since you're trying to mix the uploadComplete prop and uploadingComplete data.
The visibility of the spinner should be driven by the parent component through the prop, the button itself should not be responsible for controlling the visibility of the spinner through local state in response to clicks of the button.
Just do something like this:
Vue.component('upload-button', {
template: '#upload-button',
props: ['uploading'],
});
new Vue({
el: '#app',
data: {
uploading1: false,
uploading2: false,
},
methods: {
upload1() {
this.uploading1 = true;
setTimeout(() => this.uploading1 = false, Math.random() * 1000);
},
upload2() {
this.uploading2 = true;
setTimeout(() => this.uploading2 = false, Math.random() * 1000);
},
},
});
<script src="https://rawgit.com/vuejs/vue/dev/dist/vue.js"></script>
<div id="app">
<upload-button :uploading="uploading1" #click="upload1">Upload 1</upload-button>
<upload-button :uploading="uploading2" #click="upload2">Upload 2</upload-button>
</div>
<template id="upload-button">
<button #click="$emit('click')">
<template v-if="uploading">Uploading...</template>
<slot v-else></slot>
</button>
</template>
Your question seems little bit ambiguë, You can use watch in that props object inside the child component like this:
watch:{
uploadComplete:{
handler(val){
//val gives you the updated value
}, deep:true
},
}
by adding deep to true it will watch for nested properties in that object, if one of properties changed you ll receive the new prop from val variable
for more information : https://v2.vuejs.org/v2/api/#vm-watch
if not what you wanted, i made a real quick example,
check it out hope this helps : https://jsfiddle.net/K_Younes/64d8mbs1/

VueJS 2.0 Communicating from the Parent to the Child

I'm sure there is a simple way to do this, but I can't figure it out.
In my html file I have the following button:
<button #click="showModal">Show Modal</button>
Clicking that button runs the following method:
new Vue({
el: '#root',
methods: {
showModal() { Event.$emit('showModal'); },
}
});
What I want to happen when I click on that button is to toggle a class in a modal component. Here is the relevant code for the component:
Vue.component('modal', {
template: `
<div :class="[ modalClass, {'is-active' : isActive }]">
ETC...
</div>
`
data() {
return {
isActive: false,
modalClass: 'modal'
}
}
I am new to VueJS and am trying to figure out how to communicate in Vue. I can't seem to figure out the next step. What do I have to do so that isActive is set to true when the button is clicked?
Thanks for any help you can offer.
All the best,
Moshe
In your main.js (or where ever you instantiate your Vue app)
You can use a plain vue instance as an eventbus
Vue.prototype.bus = new Vue();
this way you can use it as so :
this.bus.$on('event', (params) => {})
this.bus.$emit('event', params)
Check out one of my vue project on github as an example, i the eventbus a lot.
https://github.com/wilomgfx/vue-weather
Also check out this free amazing series on VueJS 2, its really great :
https://laracasts.com/series/learn-vue-2-step-by-step
Full blown example using the op's question:
https://codepen.io/wilomgfx/pen/OpEVpz?editors=1010
To communicate from parent to child you can use components props.
If you have a more deeper communication (parent communicate with little-little...-child) you can use the busEvent mention by #WilomGfx.
Code for communication from parent to child :
Vue.component('modal', {
template: '<div :class="[ modalClass, {\'is-active\' : isActive }]">' +
'Hello Word !' +
'</div>',
data() {
return {
modalClass: 'modal'
}
},
props: {
isActive: { type: Boolean, default: false }
}
});
new Vue({
el: '#root',
data() {
return {
isActive: false,
}
},
methods: {
showModal() {this.isActive = true },
}
});
.is-active {
color: red;
}
<script src="https://vuejs.org/js/vue.min.js"></script>
<div id="root">
<modal :is-active="isActive"></modal>
<button #click="showModal">Show Modal (going red when prop isActive is true)</button>
</div>

Mouseover or hover vue.js

I would like to show a div when hovering over an element in vue.js. But I can't seem to get it working.
It looks like there is no event for hover or mouseover in vue.js. Is this really true?
Would it be possible to combine jquery hover and vue methods?
i feel above logics for hover is incorrect. it just inverse when mouse hovers. i have used below code. it seems to work perfectly alright.
<div #mouseover="upHere = true" #mouseleave="upHere = false" >
<h2> Something Something </h2>
<some-component v-show="upHere"></some-component>
</div>
on vue instance
data: {
upHere: false
}
Here is a working example of what I think you are asking for.
http://jsfiddle.net/1cekfnqw/3017/
<div id="demo">
<div v-show="active">Show</div>
<div #mouseover="mouseOver">Hover over me!</div>
</div>
var demo = new Vue({
el: '#demo',
data: {
active: false
},
methods: {
mouseOver: function(){
this.active = !this.active;
}
}
});
There's no need for a method here.
HTML
<div v-if="active">
<h2>Hello World!</h2>
</div>
<div v-on:mouseover="active = !active">
<h1>Hover me!</h1>
</div>
JS
new Vue({
el: 'body',
data: {
active: false
}
})
To show child or sibling elements it's possible with CSS only. If you use :hover before combinators (+, ~, >, space). Then the style applies not to hovered element.
HTML
<body>
<div class="trigger">
Hover here.
</div>
<div class="hidden">
This message shows up.
</div>
</body>
CSS
.hidden { display: none; }
.trigger:hover + .hidden { display: inline; }
With mouseover and mouseleave events you can define a toggle function that implements this logic and react on the value in the rendering.
Check this example:
var vm = new Vue({
el: '#app',
data: {btn: 'primary'}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<div id='app'>
<button
#mouseover="btn='warning'"
#mouseleave="btn='primary'"
:class='"btn btn-block btn-"+btn'>
{{ btn }}
</button>
</div>
I think what you want to achieve is with the combination of
#mouseover, #mouseout, #mouseenter and #mouseleave
So the two best combinations are
"#mouseover and #mouseout"
or
"#mouseenter and #mouseleave"
And I think, It's better to use the 2nd pair so that you can achieve the hover effect and call functionalities on that.
<div #mouseenter="activeHover = true" #mouseleave="activeHover = false" >
<p v-if="activeHover"> This will be showed on hover </p>
<p v-if ="!activeHover"> This will be showed in simple cases </p>
</div>
on vue instance
data : {
activeHover : false
}
Note: 1st pair will effect/travel on the child elements as well but 2nd pair will only effect where you want to use it not the child elements. Else you will experience some glitch/fluctuation by using 1st pair. So, better to use 2nd pair to avoid any fluctuations.
I hope, it will help others as well :)
Though I would give an update using the new composition api.
Component
<template>
<div #mouseenter="hovering = true" #mouseleave="hovering = false">
{{ hovering }}
</div>
</template>
<script>
import { ref } from '#vue/composition-api'
export default {
setup() {
const hovering = ref(false)
return { hovering }
}
})
</script>
Reusable Composition Function
Creating a useHover function will allow you to reuse in any components.
export function useHover(target: Ref<HTMLElement | null>) {
const hovering = ref(false)
const enterHandler = () => (hovering.value = true)
const leaveHandler = () => (hovering.value = false)
onMounted(() => {
if (!target.value) return
target.value.addEventListener('mouseenter', enterHandler)
target.value.addEventListener('mouseleave', leaveHandler)
})
onUnmounted(() => {
if (!target.value) return
target.value.removeEventListener('mouseenter', enterHandler)
target.value.removeEventListener('mouseleave', leaveHandler)
})
return hovering
}
Here's a quick example calling the function inside a Vue component.
<template>
<div ref="hoverRef">
{{ hovering }}
</div>
</template>
<script lang="ts">
import { ref } from '#vue/composition-api'
import { useHover } from './useHover'
export default {
setup() {
const hoverRef = ref(null)
const hovering = useHover(hoverRef)
return { hovering, hoverRef }
}
})
</script>
You can also use a library such as #vuehooks/core which comes with many useful functions including useHover.
Reference: Vuejs composition API
It's possible to toggle a class on hover strictly within a component's template, however, it's not a practical solution for obvious reasons. For prototyping on the other hand, I find it useful to not have to define data properties or event handlers within the script.
Here's an example of how you can experiment with icon colors using Vuetify.
new Vue({
el: '#app'
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-toolbar color="black" dark>
<v-toolbar-items>
<v-btn icon>
<v-icon #mouseenter="e => e.target.classList.toggle('pink--text')" #mouseleave="e => e.target.classList.toggle('pink--text')">delete</v-icon>
</v-btn>
<v-btn icon>
<v-icon #mouseenter="e => e.target.classList.toggle('blue--text')" #mouseleave="e => e.target.classList.toggle('blue--text')">launch</v-icon>
</v-btn>
<v-btn icon>
<v-icon #mouseenter="e => e.target.classList.toggle('green--text')" #mouseleave="e => e.target.classList.toggle('green--text')">check</v-icon>
</v-btn>
</v-toolbar-items>
</v-toolbar>
</v-app>
</div>
With mouseover only the element stays visible when mouse leaves the hovered element, so I added this:
#mouseover="active = !active" #mouseout="active = !active"
<script>
export default {
data(){
return {
active: false
}
}
</script>
I came up with the same problem, and I work it out !
<img :src='book.images.small' v-on:mouseenter="hoverImg">
There is a correct working JSFiddle: http://jsfiddle.net/1cekfnqw/176/
<p v-on:mouseover="mouseOver" v-bind:class="{on: active, 'off': !active}">Hover over me!</p>
Please take a look at the vue-mouseover package if you are not satisfied with how does this code look:
<div
#mouseover="isMouseover = true"
#mouseleave="isMouseover = false"
/>
vue-mouseover provides a v-mouseover directive that automaticaly updates the specified data context property when the cursor enters or leaves an HTML element the directive is attached to.
By default in the next example isMouseover property will be true when the cursor is over an HTML element and false otherwise:
<div v-mouseover="isMouseover" />
Also by default isMouseover will be initially assigned when v-mouseover is attached to the div element, so it will not remain unassigned before the first mouseenter/mouseleave event.
You can specify custom values via v-mouseover-value directive:
<div
v-mouseover="isMouseover"
v-mouseover-value="customMouseenterValue"/>
or
<div
v-mouseover="isMouseover"
v-mouseover-value="{
mouseenter: customMouseenterValue,
mouseleave: customMouseleaveValue
}"
/>
Custom default values can be passed to the package via options object during setup.
Here is a very simple example for MouseOver and MouseOut:
<div id="app">
<div :style = "styleobj" #mouseover = "changebgcolor" #mouseout = "originalcolor">
</div>
</div>
new Vue({
el:"#app",
data:{
styleobj : {
width:"100px",
height:"100px",
backgroundColor:"red"
}
},
methods:{
changebgcolor : function() {
this.styleobj.backgroundColor = "green";
},
originalcolor : function() {
this.styleobj.backgroundColor = "red";
}
}
});
This worked for me for nuxt
<template>
<span
v-if="item"
class="primary-navigation-list-dropdown"
#mouseover="isTouchscreenDevice ? null : openDropdownMenu()"
#mouseleave="isTouchscreenDevice ? null : closeDropdownMenu()"
>
<nuxt-link
to="#"
#click.prevent.native="openDropdownMenu"
v-click-outside="closeDropdownMenu"
:title="item.title"
:class="[
item.cssClasses,
{ show: isDropdownMenuVisible }
]"
:id="`navbarDropdownMenuLink-${item.id}`"
:aria-expanded="[isDropdownMenuVisible ? true : false]"
class="
primary-navigation-list-dropdown__toggle
nav-link
dropdown-toggle"
aria-current="page"
role="button"
data-toggle="dropdown"
>
{{ item.label }}
</nuxt-link>
<ul
:class="{ show: isDropdownMenuVisible }"
:aria-labelledby="`navbarDropdownMenuLink-${item.id}`"
class="
primary-navigation-list-dropdown__menu
dropdown-menu-list
dropdown-menu"
>
<li
v-for="item in item.children" :key="item.id"
class="dropdown-menu-list__item"
>
<NavLink
:attributes="item"
class="dropdown-menu-list__link dropdown-item"
/>
</li>
</ul>
</span>
</template>
<script>
import NavLink from '#/components/Navigation/NavLink';
export default {
name: "DropdownMenu",
props: {
item: {
type: Object,
required: true,
},
},
data() {
return {
isDropdownMenuVisible: false,
isTouchscreenDevice: false
};
},
mounted() {
this.detectTouchscreenDevice();
},
methods: {
openDropdownMenu() {
if (this.isTouchscreenDevice) {
this.isDropdownMenuVisible = !this.isDropdownMenuVisible;
} else {
this.isDropdownMenuVisible = true;
}
},
closeDropdownMenu() {
if (this.isTouchscreenDevice) {
this.isDropdownMenuVisible = false;
} else {
this.isDropdownMenuVisible = false;
}
},
detectTouchscreenDevice() {
if (window.PointerEvent && ('maxTouchPoints' in navigator)) {
if (navigator.maxTouchPoints > 0) {
this.isTouchscreenDevice = true;
}
} else {
if (window.matchMedia && window.matchMedia("(any-pointer:coarse)").matches) {
this.isTouchscreenDevice = true;
} else if (window.TouchEvent || ('ontouchstart' in window)) {
this.isTouchscreenDevice = true;
}
}
return this.isTouchscreenDevice;
}
},
components: {
NavLink
}
};
</script>
<style scoped lang="scss">
.primary-navigation-list-dropdown {
&__toggle {
color: $white;
&:hover {
color: $blue;
}
}
&__menu {
margin-top: 0;
}
&__dropdown {
}
}
.dropdown-menu-list {
&__item {
}
&__link {
&.active,
&.nuxt-link-exact-active {
border-bottom: 1px solid $blue;
}
}
}
</style>
You can also use VueUse composables.
This is for mouse hover
<script setup>
import { useElementHover } from '#vueuse/core'
const myHoverableElement = ref()
const isHovered = useElementHover(myHoverableElement)
</script>
<template>
<button ref="myHoverableElement">
{{ isHovered }}
</button>
</template>
This one for mouse hover
import { useMouse } from '#vueuse/core'
const { x, y, sourceType } = useMouse()
or even in a specific element.
TLDR: quite a few handy composables for your Vue2/Vue3 apps!

How to keep data in component when using v-with in Vue js

So here is my problem:
I want to make a component that takes it's values from v-with="values" and add them to my component model after some modification, then display those modified properties.
But from what I understand, when I set values with "v-with", component data are erased so the binding between my component data (not v-with one) and my directives are lost.
I'm really new to this framework, I don't see any solution, so I guess it was time to ask my first question here !
Here is the HTML:
<script type="text/x-template" id="my-template">
<p v-on="click:reloadParentMsg">Msg parent : {{ParentMsg}}</p>
<p v-on="click:reloadChildMsg">Msg child : {{ChildMsg}}</p>
</script>
<div id="myVue">
<my-component v-with="ParentData" ></my-component>
</div>
And here is the Javascript:
Vue.component('my-component', {
template: '#my-template',
data: function () {
return {
ChildMsg: "wololo"
}
},
methods:{
reloadParentMsg : function(){
this.ParentMsg="Parent";
console.log(this.ParentMsg);
},
reloadChildMsg : function(){
this.ChildMsg="Child";
console.log(this.ChildMsg);
}
}
})
var myVue = new Vue({
el: '#myVue',
data: {
ParentData:{ParentMsg: "gloubiboulga"}
}
})
And the js fiddle http://jsfiddle.net/KwakawK/hfj1tv4n/3/
I'm not totally clear on what you're trying to do, but I believe it can be solved by using the second form of v-with, which is v-with="childProp: parentProp". Rather than the parent property overriding all of the child data, this will replace only the property on the left of the colon.
So I think your code can be fixed by changing the v-with to this:
<my-component v-with="ParentMsg: ParentData.ParentMsg" ></my-component>
Here's the updated code as a snippet:
// register the grid component
Vue.component('my-component', {
template: '#my-template',
data: function () {
return {
ChildMsg: "wololo"
}
},
methods:{
reloadParentMsg : function(){
this.ParentMsg="Parent";
console.log(this.ParentMsg);
},
reloadChildMsg : function(){
this.ChildMsg="Child";
console.log(this.ChildMsg);
}
}
})
// bootstrap the demo
var myVue = new Vue({
el: '#myVue',
data: {
ParentData:{ParentMsg: "gloubiboulga"}
}
})
<script src="http://cdnjs.cloudflare.com/ajax/libs/vue/0.11.4/vue.min.js"></script>
<script type="text/x-template" id="my-template">
<p v-on="click:reloadParentMsg">Msg parent : {{ParentMsg}}</p>
<p v-on="click:reloadChildMsg">Msg child : {{ChildMsg}}</p>
</script>
<div id="myVue">
<my-component v-with="ParentMsg: ParentData.ParentMsg" ></my-component>
</div>
See the Vue guide for more information.

Categories

Resources