How to do to allow a link to be deactivated 1500ms before I can click on it again? I want to be able a click every 1500ms and not nonstop. I tried something like this and without success. I don't want to use via CSS pointer-events, i want combine somehow preventdefault into my code.
<router-link :to="item.path" #click="blockedLinkOnClick(e)">{{ item.name }}</router-link>
And my method:
blockedLinkOnClick(e) {
setTimeout(() => e.preventDefault(), 1500);
}
I would do it this way
<router-link :to="item.path" :event="!isBlocked ? 'click' : ''">{{ item.name }}</router-link>
isBlocked = false;
clickable() {
if (this.isBlocked)
{
setTimeout(() => this.isBlocked = false, 1500);
}
else
{
this.isBlocked = true;
}
}
In the template, we added a class of blocked to the router-link when isLinkActive is false. You can style this class however you like to visually indicate that the link is not currently active.
So, like this:
<template>
<router-link :to="item.path" #click="blockedLinkOnClick(item.path)" :class="{ 'blocked': !isLinkActive }">{{ item.name }}</router-link>
</template>
<script>
export default {
data() {
return {
isLinkActive: true
}
},
methods: {
blockedLinkOnClick(path) {
if (this.isLinkActive) {
this.isLinkActive = false;
this.$router.push(path);
setTimeout(() => {
this.isLinkActive = true;
}, 1500);
}
}
}
}
</script>
Related
I am emitting two different custom events from child, and for some reason the second event doesn't work.
I have checked on the Vue console and both events are being fired, however, nothing happens for the second event. When I comment out the first one, the second works correctly, as if the first one was blocking the second one.
<child-component
#upload="handleUpload"
#uploading="mediaUploading"
/>
methods: {
handleUpload(response) { //doesn't work
this.mediaLoading = false;
console.log(this.mediaLoading);
},
mediaUploading() { //works
this.mediaLoading = true;
},
}
In child component
this.$emit('uploading');
try {
data = await this.$api.get('/photo');
} catch (e) {
console.log(e)
}
this.$emit('upload', data);
i have pasted the code on my machine and both are working fine
parent
<template>
<child-component #upload="handleUpload" #uploading="mediaUploading" />
</template>
<script>
import childComponent from "./page4child.vue";
export default {
components: {
childComponent,
},
data() {
return {
mediaLoading: true,
};
},
methods: {
handleUpload(response) {
//works
this.mediaLoading = false;
console.log(this.mediaLoading);
},
mediaUploading() {
//works
this.mediaLoading = true;
console.log(this.mediaLoading);
},
},
};
</script>
Child
<template>
<div>
<button #click="trigger('item')">run</button>
</div>
</template>
<script>
export default {
methods: {
async trigger(uploadedItem) {
this.$emit("uploading");
await new Promise((res) => {
console.log("duck");
res();
});
this.$emit("upload", uploadedItem);
},
},
};
</script>
So I'm currently working on a Vue/Nuxt application and I'm trying to make it possible to switch from mobile to desktop and have certain elements appear using v-if. I've got the v-if currently in this component:
<template>
<div>
<Search />
<div v-if="device" class="header__mobile-menu-toggle">
<a #click="$emit('openMobileMenu', $event)" href="#" title="Open menu"><i class="fas fa-bars"></i></a>
</div>
<div v-if="device" class="header__mobile-menu-close">
<a #click="$emit('closeMobileMenu', $event)" href="#" title="Sluit menu"><i class="fa fa-times"></i></a>
</div>
</div>
</template>
<script>
import Search from './components/Search.vue';
export default {
props: {
data: Object
},
components: {
Search
},
computed: {
device() {
return this.$store.getters['collection/layout/getDevice'];
}
}
}
</script>
As you can see I have a computed property called "device", which is set to false or true in the default.vue layout:
mounted() {
window.addEventListener('resize', this._debounce(determineScreenSize.bind(this), 100))
function determineScreenSize() {
if(window.innerWidth < 1025) {
this.$store.commit('collection/layout/setDevice', true);
}
if(window.innerWidth < 768) {
this.$store.commit('collection/layout/setMobile', true);
}
if(window.innerWidth > 767) {
this.$store.commit('collection/layout/setMobile', false);
}
if(window.innerWidth > 1024) {
this.$store.commit('collection/layout/setDevice', false);
}
}
if(this !== undefined) {
determineScreenSize.apply(this);
}
},
And of course I have this value in the Vuex store (module) with it's appropriate mutation and getter.
export const state = () => ({
data: {},
isDevice: false,
isMobile: false
})
export default {
getters: {
getDevice: state => {
console.log(state.isDevice);
return state.isDevice
}
getMobile: state => {
return state.isMobile
}
}
, mutations : {
setDevice( state, data ) {
state.isDevice = data
}
setMobile( state, data ) {
state.isMobile = data
}
}
}
On initial pageload everything is going as planned. If the user is on a device, it displays it and if not, it doesn't. However, as you can see on resize it changes the state.isDevice and state.isMobile properties. But the template itself doesn't get repainted by Vue/Nuxt. Is there a reason for that? Am I doing something in a completely wrong way?
Thanks in advance!
EDIT: the function is actually called, here's a screenshot with the VueDevtools after resizing the window a few times:
I'm facing a problem using nextTick() in Vue.
Here is the template code :
In the template :
<template>
<div>
<div v-if="editing">
<span ref="theInput"
contenteditable="true">{{ someValue }}</span>
</div>
<div v-else>
<span>{{ someValue }}</span>
</div>
...
</template>
And the script code :
data() {
editing: false
}
...
methods: {
toggle() {
this.editing = ! this.editing;
if (this.editing) {
this.$refs.theInput.focus();
}
}
}
Of course this.$refs.theInput is not rendered yet when I ask to focus in it.
So far, the only way I've figured out how to make it work is to add a timeout, which is a very bad solution :
methods: {
toggle() {
this.editing = ! this.editing;
if (this.editing) {
setTimeout(() => this.$refs.theInput.focus(), 100)
}
}
}
Trying to make it more clean I tried this, but the result is the same :
methods: {
toggle() {
this.editing = ! this.editing;
if (this.editing) {
this.$nextTick(() => {
this.$refs.theInput.focus();
});
}
}
}
Shouldn't nextTick wait for the dom to render before trying to focus on theInput ?
Thanks for your help.
Found it, $nextTick wasn't at the good place. Move it like this works :
methods: {
toggle() {
this.editing = ! this.editing;
this.$nextTick(() => {
if (this.editing) {
this.$refs.theInput.focus();
};
});
}
}
I am looking for a way to close a component when there it a click outisde of the element.
I tried an addEventListener.
This closes the component but after being closed it will not open again.
window.addEventListener('click', function(e){
if (document.getElementById('shopcartpreview').contains(e.target)){
console.log("Clicked in Box");
} else{
console.log("Clicked outside Box");
$('#shopcartpreview').hide();
}
})
Is there a way to accomplish this?
<template>
<div id="shopcartpreview" v-if="carthover">
<div class="cartitem" v-for="item in cartitems">
<div class="cartitempic"><img class="productImg" width="80px" height="80px" v-bind:src="'assets/products/' + item.image"></div>
<div class="cartitemdetails">
<div class="cartitemname">{{item.name}}</div>
<div class="cartitemqty">1 X </div>
<div class="cartitemprice">€{{item.unit_price}}</div>
</div>
<div class="cartitemdelete">
<img src="assets/images/icon-bin.png" width="15px" height="15px">
</div>
</div>
<div class="carttotal">
<div class="carttotaltext">TOTAL:</div>
<div class="carttotalprice">€2,860.00</div>
</div>
<div class="cartcheckouttbn">PROCEED TO CHECKOUT</div>
<div class="viewcart">VIEW CART</div>
</div>
</template>
<script>
module.exports = {
data: function () {
return{
cartitems: 0,
carthover: false,
}
},
created(){
EventBus.$on('addToCart', (payload) =>{
this.cartitems = payload
}),
EventBus.$on('mouseover', (carthover) =>{
this.carthover = carthover
})
}
}
</script>
I created a div element at the end of the component like that:
<div v-if="isPopup" class="outside" v-on:click="away()"></div>
Where .outside class is specified in CSS as follows:
.outside {
width: 100vw;
height: 100vh;
position: fixed;
top: 0px;
left: 0px;
}
And away() is a method in Vue instance as:
away() {
this.isPopup = false;
}
Easy, works well.
2020.11.10 Update
I find the previous solution I answered has so many errors, so I have to update it.
There are multiple solutions to close a component by clicking outside of it.
Firstly, there are some libraries which handle this problem, for example
simplesmiler/vue-clickaway, the nuxt also use this script if you have read the source code.
Secondly, if you want to implement it manually, here is code:
onClickOutside ( event: Event ) {
const path = event.path || (event.composedPath ? event.composedPath() : undefined)
// check if the MouseClick occurs inside the component
if (path && !path.includes(this.em) && !this.em.contains(event.target as HTMLElement)) {
this.closeThisComponent() // whatever method which close your component
}
}
Then, you must bind this eventHandler onClickOutside to the document.documentElement after open your component and remove this eventHandler from document.documentElement after you close your component.
Please notice the timing and refers to the event loop of JavaScript, you must understand the difference between MicroTask and MacroTask.
For example, open the component
openThisComponent () {
this.showThisCompoennt = true // whatever codes which open your component
// You can also use Vue.$nextTick or setTimeout
requestAnimationFrame(() => {
document.documentElement.addEventListener('click', this.onClickOutside, false)
})
}
closeThisComponent () {
this.showComponent = false // whatever codes which close your component
document.documentElement.removeEventListener('click', this.onClickOutside, false)
}
Demo Fiddle : https://jsfiddle.net/bq8m4fhe/
Create a clickoutside directive ... Detect click outside element
module.exports = {
data: function() {
return {
cartitems: 0,
carthover: false
};
},
directives: {
clickoutside: {
bind: function(el, binding, vnode) {
el.clickOutsideEvent = function(event) {
// here I check that click was outside the el and his childrens
if (!(el == event.target || el.contains(event.target))) {
// and if it did, call method provided in attribute value
vnode.context[binding.expression](event);
}
};
document.body.addEventListener("click", el.clickOutsideEvent);
document.body.addEventListener("touchstart", el.clickOutsideEvent);
},
unbind: function(el) {
document.body.removeEventListener("click", el.clickOutsideEvent);
document.body.removeEventListener("touchstart", el.clickOutsideEvent);
},
stopProp(event) {
event.stopPropagation();
}
}
},
created() {
EventBus.$on("addToCart", payload => {
this.cartitems = payload;
}),
EventBus.$on("mouseover", carthover => {
this.carthover = carthover;
});
}
};
Use that directive like this.
<div id="shopcartpreview" v-if="carthover" v-clickoutside="SHOPPING_CART_HIDE_FUNCTION">
I think this might be a typo somewhere, but can't find the issue. I have this Vuejs template, that renders just fine if I remove the v-if verification. However when I use it, it does not render anything at all. Already placed a debugger both in return true, and return false, and the logic test returns true only once, as expected. Can anybody spot what am I doing wrong?
template: `
<div class="workbench container">
<ul class="collapsible popout" data-collapsible="expandable">
<collapsible-cards
v-for="tipo, index in tiposCollapsibles"
v-if="mostraApenasPerfilEspecificado(perfil, tipo)"
v-bind:key=index
v-bind:dados="tipo"
>
</collapsible-cards>
</ul>
</div>`,
mounted: function() {
for (key in this.tiposCollapsibles) {
if (this.tiposCollapsibles[key].perfisQuePodemVer.indexOf(this.perfil) >= 0) {
this.queryTeleconsultorias(key);
}
}
},
methods: {
mostraApenasPerfilEspecificado(perfil, tipo) {
tipo['perfisQuePodemVer'].forEach(function(value) {
if (perfil === value) {
return true;
}
});
return false;
},
...
Update: For anyone who is having the same problem, I ended up using a computed property, rather than a method itself. The v-if/-v-show behaviour to show/hide elements was moved to the computed property. In the end I was not sure if this was an issue with Vuejs. Here is the working code:
template: `
<div class="workbench container">
<ul class="collapsible popout" data-collapsible="expandable">
<collapsible-cards
v-if="showTipoCollapsibles[index]"
v-for="tipo, index in tiposCollapsibles"
v-bind:key="index"
v-bind:object="tipo"
>
</collapsible-cards>
</ul>
</div>`,
mounted: function() {
this.executeQuery(this.perfil);
},
computed: {
showTipoCollapsibles: function() {
let perfisVisiveis = {};
for (tipo in this.tiposCollapsibles) {
perfisVisiveis[tipo] = this.tiposCollapsibles[tipo].enabledForProfiles.includes(this.perfil);
}
return perfisVisiveis;
},
},
methods: {
executeQuery: function(value) {
if (value === 'monitor') {
var query = gql`query {
entrada(user: "${this.user}") {
id
chamadaOriginal {
datahoraAbertura
solicitante {
usuario {
nome
}
}
}
...
Change from v-if to v-show
v-show="mostraApenasPerfilEspecificado(perfil, tipo)"
You can also use template to use v-if outside child component as
template: `
<div class="workbench container">
<ul class="collapsible popout" data-collapsible="expandable">
<template v-for="(tipo, index) in tiposCollapsibles">
<collapsible-cards
v-if="mostraApenasPerfilEspecificado(perfil, tipo)"
v-bind:key="index"
v-bind:dados="tipo">
</collapsible-cards>
</template>
</ul>
</div>`,
If not work, share live demo
There appears to be a similar bug in Bootstrap-Vue, where v-if only works on non-Bootstrap elements.
With otherwise identical code, this element will not appear when this.error = true:
<b-alert v-if="error" variant="danger">Failed!</b-alert>
But this will:
<div v-if="error">Failed!</div>
Actually, you have to use computed rather than method.
computed: {
showTipoCollapsibles: function() {},
executeQuery: function(value) {},
},
methods: {}