Parent onClick is not triggered when the child element is clicked inside button - javascript

I have a dropdown-button component which has a click lister. The button has some text and icon. The click event is triggered if we click the button carefully on the outline and not on the text or icon. Here is my component:
<template lang="html">
<div>
<button class="button dropbtn" #click="toggleDropdown">
<span>{{ name }}</span>
<span class="icon"><i class="fa fa-caret-down"></i></span>
</button>
<div v-show="visible" class="dropdown-content is-primary-important">
<slot>
</slot>
</div>
</div>
</template>
<script>
export default {
props: ['name'],
data: function() {
return {
visible: false
}
},
mounted: function() {
this.hideDropdownOnClick();
},
methods: {
toggleDropdown: function() {
// trigged on click of the button
// make the dropdown visible
console.log("toggling dropdown");
this.visible = !this.visible;
},
hideDropdownOnClick: function() {
window.onclick = (event) => {
console.log(event.target);
if (!event.target.matches('.dropbtn')) {
console.log("here", this.visible);
this.visible = false;
}
}
}
}
}
</script>
<style lang="css">
/* Dropdown Content (Hidden by Default) */
.dropdown-content {
position: absolute;
background-color: #fff;
min-width: 140px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
}
/* Links inside the dropdown */
.dropdown-content a {
padding: 12px 16px;
text-decoration: none;
display: block;
}
</style>
I feel that I am missing something very basic here, can someone help me with this? Thanks.
Edit
It seems like a bug with the browser as the answer to this question says:
Button element not firing click event when clicking on button text and releasing off it (but still inside the button)?

Adding this CSS rule fixes the issue for me:
span {
pointer-events: none;
}

When You click in span its toggled twice:) once by toggleDropdown method,
second by windows onclick handler.
here is working example: jsfiddle
<template id="tmp">
<div>
<button class="button dropbtn" #click="toggleDropdown">
<span>{{ name }}</span>
<span class="icon"><i class="fa fa-caret-down"></i></span>
</button>
<div v-show="visible" class="dropdown-content is-primary-important">
<slot>
</slot>
</div>
</div>
</template>
<div id="x">
<tmp name="my name">
<span>toggle me!</span>
</tmp>
</div>
Vue.component('tmp', {
template: '#tmp',
props: ['name'],
data: function() {
return {
visible: false
}
},
mounted: function() {
this.hideDropdownOnClick();
},
methods: {
toggleDropdown: function() {
// trigged on click of the button
// make the dropdown visible
console.log("toggling dropdown");
this.visible = !this.visible;
}
}
});
new Vue({
el: '#x',
data: function(){
return {
name: 'myName'
}
}
});
edit:
if you don't want to use clickaway here is small directive to detect clicks outside of element:
var VueClickOutSide = {
bind: function(el, binding, vNode) {
var bubble = binding.modifiers.bubble;
var handler = function(e) {
if (bubble || (!el.contains(e.target) && el !== e.target)) {
binding.value(e);
}
}
el.__vueClickOutside__ = handler;
document.addEventListener('click', handler);
},
unbind: function (el, binding, vNode) {
document.removeEventListener('click', el.__vueClickOutside__);
el.__vueClickOutside__ = null;
}
};
You just have to register that directive:
Vue.directive('click-outside', VueClickOutSide)
And use it in template:
v-click-outside:delay="hideDropdownOnClick"

You can use vue-clickaway to hide dropdown when click outside :
$ npm install vue-clickaway --save
import { mixin as clickaway } from 'vue-clickaway';
export default {
mixins: [ clickaway ],
... the rest of your code ...
You put v-clickaway="visible = false" in your root div of your dropdown-button component.

On click you show and immediately hide list.
You click text or icon (they are "span"),
this.visible = !this.visible;
then code goes to window.onclick where
if (!event.target.matches('.dropbtn'))
and your spans dont have this class. So you set
this.visible = false;
Change check to
if (!event.target.closest('.dropbtn'))

Related

div array won't close when external close button div is clicked (Javascript)

I'm actually occurring a situation when making an accordion. I already add a close button (x text) inside my div to close the accordion, but it won't close after I clicked on that. Btw, my reference design is from https://dribbble.com/shots/6584063-Daily-UI-Accordion-Cards-Experiment. It's only the example of the behavior. Like in my reference, I don't want to have an active class on the first time. Then when clicked the other tab, the current active class is inactive, and have an external close button.
document.addEventListener("DOMContentLoaded", (e) => {
const accordion = document.querySelectorAll('.a');
const button = document.querySelectorAll('b');
/* add Class from the div itself being clicked */
accordion.forEach((accs, idx) => {
accs.addEventListener('click', () => {
addActive(accs, idx);
});
});
function addActive(el, index) {
el.classList.add('active');
accordion.forEach((accs, idx) => {
if (idx !== index) {
accs.classList.remove("active");
}
});
}
/* remove class from button */
button.forEach(xer => {
xer.addEventListener('click', () => {
removeActive();
});
});
function removeActive() {
accordion.forEach(accs => {
accs.classList.remove('active');
})
}
})
.toggle {
display: none
}
.a .b {
display: none;
}
.a.active {
color: blue;
}
.a.active .b {
color: rgba(0, 0, 0, 1);
display: block;
cursor: pointer;
}
<div id="1" class="a"> Source
<div id="button-1" class="b"> x </div>
</div>
<div id="2" class="a"> Share
<div id="button-2" class="b"> x </div>
</div>
<div id="3" class="a"> Report
<div id="button-3" class="b"> x </div>
</div>
Please help me to fix that. Thank you so much.
There were two problems in your snippet:
const button = document.querySelectorAll('b'); will select all <b/> elements, not elements with class .b; updated below to const button = document.querySelectorAll('.b');
Your close button is inside your accordion. So, while your close function was working and removing the .active class, then the click handler for the accordion was triggering and immediately reopening it. Adding a e.stopPropagation(); inside the handler stopped the event from bubbling up to the parent and resolved the problem.
See below for a working example:
document.addEventListener("DOMContentLoaded", (e) => {
const accordion = document.querySelectorAll('.a');
const button = document.querySelectorAll('.b');
/* add Class from the div itself being clicked */
accordion.forEach((accs, idx) => {
accs.addEventListener('click', () => {
addActive(accs, idx);
});
});
function addActive(el, index) {
el.classList.add('active');
accordion.forEach((accs, idx) => {
if (idx !== index) {
accs.classList.remove("active");
}
});
}
/* remove class from button */
button.forEach(xer => {
xer.addEventListener('click', (e) => {
e.stopPropagation();
removeActive();
});
});
function removeActive() {
accordion.forEach(accs => {
accs.classList.remove('active');
})
}
})
.toggle {
display: none
}
.a .b {
display: none;
}
.a.active {
color: blue;
}
.a.active .b {
color: rgba(0, 0, 0, 1);
display: block;
cursor: pointer;
}
<div id="1" class="a"> Source
<div id="button-1" class="b"> x </div>
</div>
<div id="2" class="a"> Share
<div id="button-2" class="b"> x </div>
</div>
<div id="3" class="a"> Report
<div id="button-3" class="b"> x </div>
</div>

Displaying and hiding box onclick in vue js

I want to hide and display when I click on the icon cart. The problem is in hiding that box again,
icon before click : https://imgur.com/RxmcwsX
after click: https://imgur.com/cCt4mk0
Here is css image : https://imgur.com/d6ZPUbY
vue js : https://imgur.com/2kWZdly
mycss code :
<li class="nav-item" id="cart">
<i class="fa fa-shopping-cart fa-lg" #click="showCart"></i>
<div id="list-cart">
<div class="shadow-lg" style="position:absolute;background-color: #FFF;width:300px;height:300px;right:210px;top:60px;border-radius: 5px;" v-bind:style="{ visibility: computedVisibility }"></div>
</div>
vue code
var cart = new Vue({
el: '#cart',
data: {
visibility: 'hidden'
},
computed: {
computedVisibility: function() {
return this.visibility;
}
},
methods: {
showCart: function(event) {
this.visibility = 'visible';
}
}
});
Use v-if instead of directly manipulating the styles:
<li class="nav-item" id="cart">
<i class="fa fa-shopping-cart fa-lg" #click="visible = !visible"></i>
<div id="list-cart">
<div class="shadow-lg" v-if="visible"></div>
</div>
var cart = new Vue({
el: '#cart',
data: () => ({
visible: false
})
});
You could try binding it to a class instead. Then you can have a ternary expression that determines your class.
<li class="nav-item" id="cart">
<i class="fa fa-shopping-cart fa-lg" #click="showCart"></i>
<div id="list-cart">
<div
style="position:absolute;
background-color: #FFF;
width:300px;
height:300px;
right:210px;
top:60px;
border-radius: 5px;"
v-bind:class="[visible ? 'show' : 'hide', 'shadow-lg']">
</div>
</div>
Then you can have a data element, visible, that is set initially to false. You should also make data a function
data: () => ({
visible: false
})
then your show cart function can just be:
showCart() {
this.visible = !this.visible
}
which you can also call to close the cart.
And then set your styles:
<style scoped>
.show {
visibility: visible
}
.hide {
visibility: hidden
}
</style>
That said there are plenty of packages that offer 'modals' where this would largely be handled for you. I'd recommend vuetify but if you're the old fashioned type you could even use bootstrap.
If the given script in your question works, you may just change the showCart function as below.
var cart = new Vue({
el: '#cart',
data: {
visibility: 'hidden'
},
computed: {
computedVisibility: function() {
return this.visibility;
}
},
methods: {
showCart: function(event) {
if(this.visibility ==='visible'){
this.visibility = 'hidden';
}else if(this.visibility==='hidden'){
this.visibility = 'visible'
}
}
}
});

Toggling a class to an element when clicked using 'this'

I have an element it has no ID, eg.
<div class='classA' v-on:click='addClassB(this)'></div>
When you click on it I want a class to be added, or removed if it exists already, it calls this function:
methods: {
addClassB: function () {
this.classList.toggle("classB");
}
}
This obviously doesn't work, I want to be able to toggle 'classB' on just that item, because I could have something like this:
<div class='classA' v-on:click='addClassB(this)'></div>
<div class='classA' v-on:click='addClassB(this)'></div>
<div class='classA' v-on:click='addClassB(this)'></div>
So when one is clicked, I don't want all of them to have classB added, just the one clicked.
I am also using Vue.
Just access the event target from the method itself:
methods: {
addClassB: function (event) {
event.target.classList.toggle("classB");
}
}
So there's no longer any need to pass any arguments into the handler:
<div class='classA' v-on:click='addClassB'></div>
See proof-of-concept example below:
new Vue({
el: '#app',
methods: {
addClassB(event) {
event.target.classList.toggle('classB');
}
}
});
#app {
display: flex;
}
#app div {
width: 50px;
height: 50px;
margin: 10px;
}
.classA {
background: red;
}
.classB {
background: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="classA" v-on:click="addClassB"></div>
<div class="classA" v-on:click="addClassB"></div>
<div class="classA" v-on:click="addClassB"></div>
</div>
Instead of using this use event.target. From Vue doc - https://v2.vuejs.org/v2/guide/events.html#Method-Event-Handlers:
greet: function (event) {
// `this` inside methods points to the Vue instance
alert('Hello ' + this.name + '!')
// `event` is the native DOM event
if (event) {
alert(event.target.tagName)
}
}
In your code:
methods: {
addClassB: function (event) {
event.target.classList.toggle("classB");
}
}
and:
<div class='classA' v-on:click='addClassB'></div>

Vue 2 event listener on component root

I'm trying to capture an event on the component root node, but the following does not work. I don't want to just listen on a node in the component. I want to be able to click on any element and then hit backspace to remove it. The code below is a basic example of how I setup my code.
<template>
<div v-on:keydown.delete="delete()">
<img id="image" src="..." v-on:click="set_active()">
</div>
</template>
<script>
export default {
return {
data() {
active: ''
},
methods: {
delete(){
delete this.$refs[this.active][0];
},
set_active() {
this.active = event.target.getAttribute('id');
}
}
}
}
</script>
After doing some tests, here is what I discovered:
Having a method called delete won't work. I don't know why, the question remains unanswered here. Rename it to remove, for example.
When trying to catch keyboard events on a div, you may need to add a tabindex attribute for it to work. (See here)
Interactive demo
Vue.component('my-component', {
template: '#my-component',
data() {
return {
images: [
"https://media.giphy.com/media/3ohs7KtxtOEsDwO3GU/giphy.gif",
"https://media.giphy.com/media/3ohhwoWSCtJzznXbuo/giphy.gif",
"https://media.giphy.com/media/8L0xFP1XEEgwfzByQk/giphy.gif"
],
active: null
};
},
methods: {
set_active(i) {
this.active = i;
},
remove() {
if (this.active !== null) {
this.images = this.images.filter((_, i) => i !== this.active);
this.active = null;
}
}
}
});
var vm = new Vue({
el: '#app'
});
div {
outline: none; /* Avoid the outline caused by tabindex */
border: 1px solid #eee;
}
img {
height: 80px;
border: 4px solid #eee;
margin: .5em;
}
img:hover {
border: 4px solid #ffcda9;
}
img.active {
border: 4px solid #ff7c1f;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.min.js"></script>
<div id="app">
<my-component></my-component>
</div>
<template id="my-component">
<div #keydown.delete="remove" tabindex="0">
<img
v-for="(img, i) in images"
:key="i"
:src="img"
:class="{ active: active === i }"
#click="set_active(i)"
/>
</div>
</template>

How to open open a side panel upon a button press using vue.js?

I'm developing my first app with Vue and need some advice with trying to open a panel upon pressing a button in the header.
I'm starting with a basic HTML template, trying to add interactivity after pressing a button that's contained in the header.
When pressing the button, I'm getting the following message in the console:
vue.js:597 [Vue warn]: Invalid handler for event "click": got
undefined
(found in )
The code I'm using is as follows:
HTML:
<!-- Doctype HTML5 -->
<!DOCTYPE html>
<html lang="en" />
<head>
<link rel="stylesheet" href="css/normalize.css">
{{ $style := resources.Get "scss/main.scss" | toCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $style.Permalink }}">
<script src="js/vue.js"></script>
<script type="text/javascript" src="js/vue-proj-info.js"></script>
</head>
<body>
<header>
<div class="h-branding">
<div id="h-nLogo">
<img src="img/med-logo-rev.png"height="70" />
</div>
<div id="h-title">
<h1>Executive Blueprint</h1>
<p class="subheader"><span class="tr-tier">Tier 1</span> - <span class="tr-service">Executive</span></p>
</div>
</div>
<div id="h-toggles">
<div class="buttonGroup">
<button type="" name="tier1">Tier 1</button>
<button type="" name="tier2">Tier 2</button>
<button type="" name="tier3">Tier 3</button>
</div>
<div class="buttonGroup">
<button type="" name="executive">Executive</button>
<button type="" name="concierge">Concierge</button>
</div>
</div>
<proj-slideout ref="proj-slideout" id="proj-slideout" :class="{ isOpen: isOpen }">></proj-slideout>
<div id="h-infoButton">
<div class="buttonGroup">
<button type="button" name="projInfo" class="proj-slideout-opener"
#click="open">Project Information</button>
</div>
</div>
</header>
JS:
Vue.component('proj-slideout', {
template: '#proj-slideout',
props: ['show'],
data: () => ({
isOpen: false,
projContent: 'overview' /* overview, jtbd, tiers, files */
}),
methods: {
close() {
this.isOpen = false;
},
open() {
this.isOpen = true;
console.log('Open Panel');
}
}
});
document.addEventListener("DOMContentLoaded", function(event) {
var app = new Vue({
el: 'header'
})
});
SCSS:
#proj-slideout {
position: fixed;
top: 0;
right: 0;
width: 90vw;
height: 100vh;
padding: 30px;
display: flex;
flex-direction: row;
background-color: white;
transform: translateX(-100%);
transition: transform 0.6s ease(out-cubic);
display: none;
&.isOpen {
transform: translateX(0);
}
Any advice would be helpful!
In your code, #click="open" is refer to Vue parent component scope, so you should define isOpen and open in parent Vue component
document.addEventListener("DOMContentLoaded", function(event) {
var app = new Vue({
el: 'header',
data: () => ({
isOpen: false,
}),
methods: {
close() {
this.isOpen = false;
},
open() {
this.isOpen = true;
console.log('Open Panel');
}
}
})
});
All you need is to use v-show
On the template In your component tag add v-if or v-show like so :
<projContent v-show="isOpen" ... />
On the trigger button use directly the click event to set isOpen to false or true like this :
<button #click="isOpen = !isOpen" ... >Project Information</button>//or even better #click="isOpen ^= 1"
Now when you click the button it will set isOpen to its opposite value, if it's false it will become true and your projContent component will show up, if it's true, it will be set to false, hiding automatically your component, you don't even need to set a method to get the job done, it's directly set in the event.
Don't forget to remove display:none from your scss
If you need to add a transition animation here is the link "Vue transitions"

Categories