Prevent on click on parent when clicking button inside div - vue.js

Is it possible to prevent the function on the <div> element from running when clicking the button inside the div?
When clicking the button element, the function: toggleSystemDetails should not be triggered? Is this possible in Vue?
<div v-on:click="toggleSystemDetails($event, system.id)" v-for="(system, index) in organization.systems" :key="system.id">
Outer Div
<button v-on:click="toggleTileOptionsMode($event, system.id, system.name, system.layout)">
Inner Button
</button>
</div>

Have a look at Event Modifiers in the Vue.js v3 docs (v2 docs here), v-on:click.stop will stop that click from propagating or "bubbling" up to the parent element.

Here is how to master this problem.
Say you have a parent element and some child elements.
1.(1st case) You want the parent click to do not affect the child clicks. Just put at the parent element the .self modifier:
<div class="parent" #click.self="parent"> <!-- .self modifier -->
<span class="child" #click="child1">Child1</span>
<span class="child" #click="child2">Child2</span>
<span class="child" #click="child3">Child3</span>
</div>
See it in action here
note: if you remove the .self when you click a child, the parent event will fire too.
2.(2nd case) You have the below code:
<div #click="parent">
Click to activate
<i class="fa fa-trash" title="delete this" #click="delete_clicked"></i>
</div>
The problem is:
When you click the icon element the parent click will fire. (you don't want this)
You can NOT use the 1st solution because you want to fire the parent event even if the text "Click to activate" gets clicked.
The solution to this is to put the .stop modifier to the icon element so the parent event will not fire.
See it in action here

as mentioned on the link provided by Justin
you can .self in the click event
<!-- only trigger handler if event.target is the element itself -->
<!-- i.e. not from a child element -->
<div v-on:click.self="doThat">...</div>

I just want to put my two cents here, since I do find myself looking for this problem again and again (one day I will remember).
I have found in some instances, the child element needs #click.stop.prevent and that's it, to stop it from bubbling to the parent.
I assume v-on:click.stop.prevent would have the same effect (non-shorthand).

additionally, if a child element detains you from clicking the parent element, you can fix this by adding CSS to the child element pointer-events: none.
this solution is valid if there is no event in the child element.

You can use #click.stop on child component, for examaple
.modal(#click="myfunc")
default-content(#click.stop)

Related

Add event on slot

I'm trying to implement generic modal component with Vue 3.
And I want to close modal window on click outside the modal's content.
So I added 'close' event on modalWrapper and 'contentClick' to prevent closing (bubbling) when content is clicked:
contentClick(event:Event){
event.stopPropagation();
}
Modal:
<template>
<teleport to="body">
<div class="modal-window" v-on:click="close" v-show="isOpened">
<slot class="modal-window__content" v-on:click="contentClick($event)"></slot>
</div>
</teleport>
</template>
The problem is that contentClick(event:Event) is not fired for some reason. I can wrap slot into another div and put contentClick event on it, but not sure that it's a good solution

Vue scoped slot child not rendering slot content

I think I've stumbled over something really strange. I have a Menu component which has two ways of managing visibility of a dropdown div:
Internally using local state;
Using v-model to keep track on the parent component.
I also have a child component called MenuItem. This MenuItem (one or more) is to be passed into the Menu default slot. The MenuItem has a slot of its own, for an icon.
The Menu also has a slot for the "activator", an element which toggles the menu visibility. This can be a regular slot, or a scoped slot.
If the visibility of the menu is handled through the scoped slot:
<Menu>
<template v-slot:activator="{ on }">
<button v-on="on" type="button">Click me</button>
</template>
</Menu>
Then the icon for MenuItem will render the first time the menu is visible, but subsequent changes to visibility won't render the icon. The icon slot is optional, so I am checking for it's presences using this.$slots['icon-left'], and after the first render this is undefined.
However, if I manage the visibility on the parent component using v-model, this behaviour is not present.
<Menu v-model="isMenuVisible">
<template v-slot:activator>
<button #click="isMenuVisible = !isMenuVisible" type="button">Click me</button>
</template>
</Menu>
I am using v-if for toggling the visibility of the dropdown menu. This behaviour is not present if I use v-show. This behaviour is also not present if I do not check for the existence of this.$slots['icon-left'].
I have a full demo of the issue.
Basically I am very confused as to why this is happening, and I don't even know what to Google.

<router-link> Vue Router #click event

I tried using a click event on a <router-link>. It works, but it is reloading the page everytime the link is clicked. I would like to avoid it but I can't figure out how.
I am aware that <router-link> does not accept a simple #click event. I saw on some forums that #click.I native would work, but as we know, that is deprecated.
So I would like to know if there is any solution other than wrapping the router link in a div and putting the listener on that div.
The reason why I want to do this is that I want to bind a class dinamicaly when the link is clicked. I have created a dropdown menu which is triggered onClick. But then when I follow a link inside that dropdown menu, the menu remains open. Therefore, I would like to have an additional #click event to dinamically bind a class (display: none) to the dropdown menu. The thing is that the items inside the dropdown are iterated which send parameters to a Vuex Mutation and therefore i can’t use regular tags and wrapping the router-links with a span or div is also not giving me the desired effect.
Thank you !
Regards,
T.
I have managed to solve the problem using a div wrapper and changing my css (that was preventing the code to work properly)
<div class="dropdown">
<a class="dropbtn" #click="dropClick"><i class="ri ico ri-draft-line"></i> Docs <i class="ri ico ri-arrow-drop-down-line ri-1x"></i></a>
<div class="dropdown-content" :class="{ 'dropdown-content-display': clicked }">
<div class="wrapper" v-for="route in $store.state.menuItems" :key="route.name" #click="dropClick">
<router-link :to="{ name: 'docs', params: { title: route.name } }"> <i :class="'ico ri ' + route.icon"></i> {{ route.name }}
</router-link>
</div>
</div>
</div>
If a understand your question, there is a "active-class" property on vue-router(router-link). You can set your classes dynamically by based on an active route.

How to disable modal-ok slot of b-modal in vue bootstrap?

I have used modal-ok slot available in b-modal slots to render the OK button of b-modal. I want to conditionally disable the OK button. I have tried 2 methods with no luck. Any suggestion is welcome on how to disable the OK button rendered using the slot.
Disabled prop
<div
slot="modal-ok"
:disabled="true"
#click.stop="uploadFile(item.id)"
>
Upload
</div>
ok-disabled prop of b-modal
<div
slot="modal-ok"
:ok-disabled="true"
#click.stop="uploadFile(item.id)"
>
Upload
</div>
Use the ok-disabled prop on <b-modal>, to conditionally enable/disable the ok button.
<b-modal :ok-disabled="true">
<!-- Content -->
</b-modal>
For more information check out this section of the documentation.
Just add hide-footer if you want to get rid of the buttons
<b-modal hide-footer>
<!-- Content -->
</b-modal>
model-ok scope can't modify button itself, it just change button content
You need to use modal-footer scope instead and declare buttons manually there.

Pass a button with click handler via slot to recursive child component

I've got a page template with the following code part:
<nested-draggable v-bind:list="list" v-bind:selected="selected" v-bind:group="dragGroup">
<slot>
<v-icon v-on:click="$root.$emit('click', el)" small v-if="allowcreate" style="float: right">mdi-plus</v-icon>
</slot>
</nested-draggable>
the sub component ("nested-draggable.vue") for the recursion looks like this:
<template>
<ul class="tree">
<draggable
class="dragArea"
tag="li"
v-for="el in list"
v-bind:elementdata="el"
v-bind:key="el._id"
v-bind:list="list_empty"
v-bind:selected="selected"
v-bind:group="group"
v-on:add="add"
>
<span v-bind:class="{'selected' : el._id === selected._id}" v-on:click="elemClicked(el)">{{ el.title }}</span>
<slot></slot>
<!-- render children of the current iterated element -->
<nested-draggable
v-bind:list="el.children" v-bind:selected="selected" v-bind:group="group">
<!--<slot></slot>-->
</nested-draggable>
</draggable>
</ul>
</template>
so I'd like to have the click event from the button within the passed slot emited with the current iteration's var "el" when the "plus" button is clicked, but within the slot the "el" var that is used within the iteration at the nested-draggable component can not be accessed. Vue tells that there is no "el" reference when trying to emit. (Throwing this error: https://pastebin.com/8bNwMcDr)
So how can I access the recursive data within the passed slot? How do I have to define my slot when passing it?
The only solution I found is putting the button/event-link directly into the nested-draggable component (not as slot) but I think to be clean and write a nice separated component, this would not belong into the nested draggable component, but in its parent.
You don't need to pass your event from the template because you can get in your method anyways. This should help you out.