Vue propagate event to child element - javascript

I have some html that renders these little boxes:
<div class="token-checkboxes">
<span class="checkbox-span" v-for="token_obj in token_to_vue_obj">
<input v-on:change="plot()" type="checkbox" id="checkbox" v-model="token_obj.show">
<label for="checkbox">{{ token_obj.token }}</label>
</span>
</div>
I want the effect from clicking on the outer pill element (the grey background area) to be the same as the effect from clicking on the checkbox itself. Is there a simple way to "forward" an event on a parent element to a child or something like that?

You can add an event listener on the outer pill element to change the model value. This isn't really forwarding an event but it should have the same effect (clicking the pill toggles the checkbox).
<span class="checkbox-span"
v-for="token_obj in token_to_vue_obj"
v-on:click="token_obj.show = !token_obj.show; plot()">
Edit (see comments): Remove plot() from the <input> element's change handler to prevent the plot() function being called twice if you click the checkbox.
Check out the snippet below.
var app = new Vue({
el: '.token-checkboxes',
methods: {
plot() {
console.log('Plot called!');
}
},
data: {
token_to_vue_obj: [
{ token: '_#misc', show: true },
{ token: '_#study', show: true },
{ token: '_#code', show: true },
{ token: '_#debug', show: true },
{ token: '_data', show: false }
]
}
})
.checkbox-span {
background-color: lightgrey;
padding: 0.5em;
border-radius: 0.5em;
border: 2px solid black;
margin: 0.5em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div class="token-checkboxes">
<span class="checkbox-span"
v-for="token_obj in token_to_vue_obj"
v-on:click="token_obj.show = !token_obj.show;plot()"
v-bind:key="token_obj.token">
<input type="checkbox" class="checkbox" v-model="token_obj.show">
<label for="checkbox">{{ token_obj.token }}</label>
</span>
</div>
Edit: see comment below. The non-unique id="checkbox" causes problems.

Related

Attaching keydown event listener on div in contenteditable parent div not working?

Here's the parent div:
<div
id="comment"
placeholder="Your comment"
class="form-control ellipsesDropdown"
contenteditable="true"
#input="CommentChanged($event)"
> <!-- comments are divided into spans and divs with spans containg normal text and divs containing tags -->
<span>​</span>
</div>
Now when a user clicks a Tag, I create the Tag as follows:
const newTag = document.createElement('div');
newTag.setAttribute("tabindex", "-1");
newTag.style.cssText = "background-color: rgba(29,155,209,0.1); color: #1264a3; display: inline-block; font-weight: bold;";
const tagContent = document.createTextNode(`#${p}`); // imagine p is an input argument
newTag.append(tagContent);
// attach on key down event listener
newTag.onkeydown = function(event) {
console.log(event)
};
// add tag to the comment div
document.getElementById("comment")!.appendChild(newTag);
However, I get nothing when I press keys in the tag div, click events do work though. I took a look at How can I use a 'keydown' event listener on a div? and have added the tabindex attribute.
I've also tried attaching the event listener as:
newTag.addEventListener('keydown', function(event) {
console.log(event);
});
But this still doesn't work.
Any idea about what's going on?
EDIT: As requested, here's a codesandbox link: https://codesandbox.io/s/blue-bird-tdidr
EDIT 2: I've added more code from my project that basically implements to a certain extent what I'm trying to accomplish. Think about Twitter/Instagram/Slack, when you #someone as you're typing the post then perhaps some options appear for who to # and when you click someone then that "tag" is added to your post. In the example, go ahead and write something, and include #, you'll see a list of options open, click either bread or toast and see it become a tag. What I'm trying to do it add the on keydown EventListener on the Tag so go ahead and see that it doesn't work!
I see you want to create a comment function i guess? But the way you do its not the Vue.js way. Here is a simple comment box example:
let el = new Vue({
el: "#app",
template: "",
data(){
return {
comment: "",
comments: []
}
},
methods: {
addComment(){
let txt = this.convertTags(this.comment);
console.log(txt);
var d = new Date();
var n = d.toLocaleTimeString();
this.comments.push({
commentText: txt,
time: n
});
this.comment = "";
},
clickedTag(tag){
console.log(tag);
},
convertTags(str){
let strArr = str.split(" ");
let mappedArr = strArr.map(el => {
if(el.includes("#")){
el = `<span onclick='el.clickedTag("${el.replace("#", "")}")' class='tag'>${el}</span>`;
}
return " " +el;
});
this.template = mappedArr.join("")
return this.template;
}
}
});
#app {
width: 80%;
margin: 0 auto;
}
.comment {
background: #6c5ce7;
width: 100%;
margin: 5px 0;
color: white;
padding: 8px 5px;
}
input {
margin: 0 auto;
width: 100%;
display: block;
padding: 5px;
}
.tag {
color: black;
background: white;
padding: 2px;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="comment in comments" class="comments">
<div class="comment">
<p v-html="comment.commentText"></p>
<small>{{ comment.time }}</small>
</div>
</div>
<input type="text" v-model="comment" #keyup.enter="addComment">
</div>
Apparently I don't need to add an event listener to the divs I'm adding. I can just set their contenteditable attribute to false as user SleepWalker menstions in his answer here: How to delete an HTML element inside a div with attribute contentEditable?
So my answer would then become:
...
newTag.setAttribute("contenteditable", "false")
document.getElementById("comment")!.appendChild(newTag);
This allows me to delete the tag in one go!

Vue.js how to replace the text of the button clicked

I my app UI I have a table with a set of permissions listed. In each row there is a toggle-button that sets the default state of each permission to either "deny" or "grant" in the DB.
If the user clicks the button, async action is triggered in the background. It all works perfectly fine, but what I want to add is when user click the button its inner html changes to a spinner or some sort of "wait..." text and the button get disable while the action runs. This is to prevent user from clicking multiple time is the action take a bit longer to complete, giving impression like nothing is happening.
Now, I know how to do it in jQuery or even plain JS, but I have no idea how to access the button properties in VUE.js
My button look like this:
<button #click="defaultPermissionState(perm.id,'grant',$event)">Deny</button>
I'm only recently started into vue.js, so still learning it ;)
UPDATE: I've actually managed to find a way to do it by exploring the $event and being able to change the text and button properties by doing this:
event.path[0].innerHTML = 'wait...';
event.path[0].disabled = true;
but this does not look like a very elegant solution, so if anyone knows of something better I would still like to hear it
You can use v-if with :disabled. Check this quick example:
new Vue({
el: "#app",
data: {
isLoadingArray: []
},
methods: {
clicked(index) {
this.$set(this.isLoadingArray, index, true)
setTimeout(() => {
this.$set(this.isLoadingArray, index, false)
}, 2000)
}
}
})
.lds-dual-ring {
display: inline-block;
width: 64px;
height: 64px;
}
.lds-dual-ring:after {
content: " ";
display: block;
width: 46px;
height: 46px;
margin: 1px;
border-radius: 50%;
border: 5px solid #fff;
border-color: #fff transparent #fff transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
#keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button type="button" #click="clicked(0)" :disabled="isLoadingArray[0]">
<div v-if="isLoadingArray[0]" class="lds-dual-ring"></div>
<span v-else>click me</span>
</button>
<button type="button" #click="clicked(1)" :disabled="isLoadingArray[1]">
<div v-if="isLoadingArray[1]" class="lds-dual-ring"></div>
<span v-else>click me</span>
</button>
<button type="button" #click="clicked(2)" :disabled="isLoadingArray[2]">
<div v-if="isLoadingArray[2]" class="lds-dual-ring"></div>
<span v-else>click me</span>
</button>
</div>
You can do it like this
data: function() {
return {
waiting: false,
...otherstuffs
}
},
methods: {
callAsync() {
this.waiting = true;
callASYNC()
.then((result) => {
this.waiting = false;
})
}
}
In your HTML
<button :disabled="waiting"> {{ waiting ? 'Waiting ...' : 'Deny' }} </button>
So basically, just set a flag before you hit the request, and set it back when the call finishes. Use this flag to set the button value to whatever you want
This should help
<template>
<button disabled={{disableBtn}}
#click="defaultPermissionState(perm.id,'grant',$event)">{{btnText}}
</button>
</template>
export default {
data() {
return {
btnText: 'Deny',
disableBtn: false
}
},
method: {
defaultPermissionState(id, type, e) {
this.disableBtn = true;
this.btnText = 'Clicking.....';
}
}
}
Hide the button and show the spinner using a data or computed property. Update the 'busy' property from your async function.
<button v-if='!busy' #click="defaultPermissionState(perm.id,'grant',$event)">Deny</button>
<spinner v-else />
you can use $event to change the inner html for buttons
$event.path[0].innerHTML = "Write the inner html"

VueJS parent mouseover event masking child mouseover event

I am using VueJS and trying to fire a mouseover event on two elements, one a child element of the other.
I am unable to get the child mouseover event to fire. It appears the parent element is "covering" the child div and only the parent mouseover event is registered.
var vm = new Vue({
el: '#app',
data: {
hoverTarget: 'none'
},
methods: {
parentHover: function() {
this.hoverTarget = 'parent'
},
childHover: function() {
this.hoverTarget = 'child'
}
}
});
#parent {
width: 100px;
height: 100px;
background: #000000;
}
#child {
width: 50px;
height: 50px;
background: #FFFFFF;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<div id='app'>
<div id='parent' #mouseover="parentHover">
<div id='child' #mouseover="childHover">
</div>
</div>
{{ hoverTarget }}
</div>
Additionally, you could abbreviate this, using an event modifier, to #mouseover.stop="childHover".
<div id='app'>
<div id='parent' #mouseover="parentHover">
<div id='child' #mouseover="childHover">
</div>
</div>
{{ hoverTarget }}
</div>
This is happening because of the event bubbling principal
When an event happens on an element, it first runs the handlers on it,
then on its parent, then all the way up on other ancestors.
that means childHover handler will get executed and immediately after it
the parentHover will be executed making the child execution invisible.
to solve your problem you can use event.stopPropagation() method of the event to make sure no bubbling happens from child to parent.
var vm = new Vue({
el: '#app',
data: {
hoverTarget: 'none'
},
methods: {
parentHover: function() {
this.hoverTarget = 'parent'
},
childHover: function(event) {
event.stopPropagation()
this.hoverTarget = 'child'
}
}
});

Trigger an event on an element from another element in vue.js

I have a div and a file input box. When we click on the div, the click of file input is to be triggered. How to do this in vue.js?
You will have to access the DOM to trigger the click event for the input.
But Vue can make it pretty convenient with the ref/$refs feature
With it, you can "mark" an element in the template and access it conveniently from within your component's code without relying on selectors.
new Vue({
el: '#app',
methods: {
clickHandler() {
this.$refs.fileInput.click()
}
}
})
.button {
padding: 10px;
border-radius: 5px;
border: 1px solid #CCC;
display: inline-block;
}
<script src="https://unpkg.com/vue#2.3/dist/vue.js"></script>
<div id="app">
<div class="button" #click="clickHandler">Click me!</div>
<input type="file" ref="fileInput">
</div>
Add a ref to you input and a click listener to your wrapper div
<div #click="triggerFileInput" id="wrapper">
<input type="file" ref="myFile">
</div>
methods:{
triggerFileInput(){
this.$refs.myFile.click();
}
}

Bootstrap popover does not save ckeckboxes

I need to count :checked checkboxes on popover content on bootstrap 3.
The problem is when I change checkboxes and close popover it doesn't saved. I tried to reinstall/destroy popovers and change dynamically content option, but no results.
I also tried to create empty array and count checked checkboxes by hands, push every new check to array, but no result again and it is very hard way.
js:
$(function () {
$('.item').popover({
placement: 'bottom',
html: true,
content: function () {
return $(this).find('.filters').html();
}
});
$('#count').click(function() {
var filter = $('.item input[type=checkbox]:checked').map(function () {
return this.value;
}).get();
$('#res').text(filter);
});
});
html:
<div class="item">
click for popover
<div class="filters">
<ul class="list-unstyled">
<li>
<input type="checkbox" value="1" checked="checked" id="filter1">
<label for="filter1">Filter 1</label>
</li>
<li>
<input type="checkbox" value="2" checked="checked" id="filter2">
<label for="filter2">Filter 2</label>
</li>
<li>
<input type="checkbox" value="3" id="filter3">
<label for="filter2">Filter 3</label>
</li>
</ul>
</div>
</div>
<br>
count
<div id="res"></div>
css:
.filters {
display: none;
}
.popover-content {
width: 100px;
}
update: http://jsfiddle.net/sirjay/0vetvfpz/
When you create the popover, you duplicate the content of your .filters div, meaning that you have it twice. One that's hidden because it's in the .filters div that's hidden because of
.filters {
display: none;
}
and one that's visible in your popover.
When you're counting, you're actually counting the checked boxes that are invisible and not those in the popover. The popover gets created outside of the .item div and thus does not match the .item input[type=checkbox]:checked selector. Changing it to .popover input[type=checkbox]:checked would maybe do what you want.
Update
I've done a bit of research and found out that this usecase was not thougth about by the creators. So doing it is really tricky. But I've managed to find a solution for you:
$(function () {
$('.item').popover({
placement: 'bottom',
html: true,
content: function () {
return $(this).find('.filters').html();
}
});
//Magic
$(".item").on("shown.bs.popover",function(){
$(".popover-content input").on("change",function(){
if(this.checked){
this.setAttribute("checked","checked");
}else{
this.removeAttribute("checked");
}
$(".filters").html($(".popover-content").html());
});
});
$('#count').click(function() {
var filter = $('.item input[type=checkbox]:checked').map(function () {
return this.value;
}).get();
$('#res').text(filter);
});
});

Categories

Resources