Cannot add/remove/toggle element class inside Vue directive? - javascript

This works: https://jsfiddle.net/hxyv40ra
However, when I place this code inside of a vue directive the button event is triggered and the console shows the class is removed but nothing visually changes?
Here's an example: https://jsfiddle.net/hLga2jxq
Directive code is also below (to appease stackoverflow's rules).
styles
.hide {
display: none;
}
html
<div id="app">
<button v-hide-for="'uniqueID'">toggle to show?</button>
<div class="hide" hide-name="uniqueID">
Hello! :D
</div>
</div>
js
Vue.directive('hide-for', {
bind(button, b, vnode, oldVnode) {
console.log(b);
var elsToToggle = document.querySelectorAll(`[hide-name="${b.value}"]`);
console.log(button, b.value, `[hide-name="${b.value}"]`, elsToToggle);
button.addEventListener('click', (b) => {
console.log(button, " clicked");
elsToToggle.forEach((el) => {
console.log(el);
el.classList.toggle('hide');
})
}, false)
}
});
var app = new Vue({
name: "test",
el: '#app',
data: {}
})

So I tried this from another angle and also made the 'hide-name' attribute a directive as well, then on click I emitted the 'uniqueID' which 'hide-name' directive picked up.
I'm still not sure why Vue is not visually updating the browser but I'm guessing it must have something to do with the 'virtual-dom'.
demo: https://jsfiddle.net/hLga2jxq/3/
Vue.directive('hide-for', {
bind(el, b, vnode) {
el.addEventListener('click', (event) => vnode.context.$emit(b.value, event) );
}
});
Vue.directive('hide-name', {
bind(el, b, vnode, oldVnode) {
vnode.context.$on(b.value, function(){
let hasHideClassAttr = el.getAttribute('hide-class');
if(hasHideClassAttr) hasHideClassAttr.split(' ').forEach((c) => el.classList.toggle(c) );
else el.classList.toggle('hide');
});
}
});

Related

Same function Buttons but used them separately Vue & Vuetify

Codepen Demo
new Vue({
el: '#app',
data: () => ({
Frequired: true,
Lrequired: false
}),
methods: {
handleChanges () {
this.Frequired = !this.Frequired;
this.Lrequired = !this.Lrequired;
}
}
})
How do I use the same function botton /checkout but use them separately?
In the example above, I would love to do a check for Required First Name Field and do a check on Last Name Field, now the function links to both checkbox, would love to know a clean way to click them separately.
you may remove handleChanges and use something like this
#click="Frequired = !Frequired"
and
#click="Lrequired= !Lrequired"
demo: https://codepen.io/jacobgoh101/pen/ZoabNB?editors=1000
** as #Jacob Goh said, or if you need more logic then
You can pass a param instead of doing
<v-checkbox label="Required" #click="handleChanges"></v-checkbox>
you can do
<v-checkbox label="Required" #click="handleChanges('f')"></v-checkbox>
and check for it in your method
methods: {
handleChanges (param) {
if(param==='f'){
this.Frequired = !this.Frequired;
// do more stuff
}else {
this.Lrequired = !this.Lrequired;
// do more stuff
}
}
}

Vue watcher executed before the new data is bound?

I am using this code:
var vueApp = new Vue({
el: '#app',
data: {
modalKanji: {}
},
methods: {
showModalKanji(character) {
sendAjax('GET', '/api/Dictionary/GetKanji?character=' + character, function (res) { vueApp.modalKanji = JSON.parse(res); });
}
},
watch: {
'modalKanji': function (newData) {
setTimeout(function () {
uglipop({
class: 'modalKanji', //styling class for Modal
source: 'div',
content: 'divModalKanji'
});
}, 1000);
}
}
});
and I have an element that when clicked on, displays a popup with the kanji data inside:
<span #click="showModalKanji(kebChar)" style="cursor:pointer;>
{{kebChar}}
</span>
<div id="divModalKanji" style='display:none;'>
<div v-if="typeof(modalKanji.Result) !== 'undefined'">
{{ modalKanji.Result.literal }}
</div>
</div>
It works, but only when used with a setTimeout delay to "let the time for Vue to update its model"...if I remove the setTimeout so the code is called instantaneousely in the watch function, the popup data is always "1 iteration behind", it's showing the info of the previous kanji I clicked...
Is there a way for a watcher function to be called AFTER Vue has completed is binding with the new data?
I think you need nextTick, see Async-Update-Queue
watch: {
'modalKanji': function (newData) {
this.$nextTick(function () {
uglipop({
class: 'modalKanji', //styling class for Modal
source: 'div',
content: 'divModalKanji'
});
});
}
}

VueJs 2 emit custom event firing, but not being "heard"

Probably not possible, but I have an object that extends Vue/ VueComponent (tried both) that $emits a custom event that would normally be caught on its parent.
Please see this pen: https://codepen.io/anon/pen/MvmeQp?editors=0011 and watch the console.
class nonVueComponent extends Vue {
constructor(age,...args){
super(args)
console.log('new Blank Obj')
setTimeout(() => {
console.log('customEvent event does fire, but nothing hears it. Probably because it isnt in the DOM?', age)
this.$emit('customEvent', `custom event from nonVueComponent...${age}`)
},500)
}
}
Vue.component('test', {
template: `<div>
{{content}}
<child :childAge="age" #customEvent="customEvent"></child>
<child-secondary #secondaryEvent="customEvent"></child-secondary>
</div>`,
props: {},
data () {
return {
content: 'hello from component!',
age : 20
}
},
methods : {
customEvent(data){
console.log('PARENT: custom event triggered!', data)
this.content = data
},
secondaryEvent(data){
console.log('PARENT: !!secondary custom event triggered', data)
this.content = data
}
}
})
Vue.component('child',{
template: `<div>+- child {{childAge}}</div>`,
props: ['childAge'],
data () {
outsideOfVue: new nonVueComponent(this.childAge)
}
})
Vue.component('child-secondary',{
template: `<div>+- secondary event</div>`,
mounted(){
setTimeout( ()=>{
this.$emit('secondaryEvent', 'from secondary event....')
},125 )
}
})
let vm = new Vue({ el: '#app'})
Aside from using an eventBus, is there any other way to get the event up and out from the <child> ? Maybe make the nonVueComponent a mixin?
Thanks.
code:https://codepen.io/anon/pen/EvmmKa?editors=0011
The object who emits the event should be the instace of child-secondary.
Try to convey the instance to the nonVueComponent's constructor.
class nonVueComponent extends Vue {
constructor(age,comp,...args){
super(args)
console.log('new Blank Obj')
setTimeout(() => {
console.log('customEvent event does fire, but nothing hears it. Probably because it isnt in the DOM?', age)
comp.$emit('customEvent', `custom event from nonVueComponent...${age}`)
},500)
}
}

Vue 2 - different behavior on $emit when fired from click or input event

In the below (fiddle here) the $emit fired by the click event below works as expected. The $emit fired by the input event is ( / seems to be) wired up the same way, but the parent doesn't receive the $emit.
<div id="app">
{{ message }}
<child-comp
:prop="property"
#emitted="receiveEmit"
#emittedFromInput="receiveEmitFromInput"
></child-comp>
{{ otherMessage }}
</div>
Vue.component('child-comp', {
template: '<div><button #click="sendEmit">emit</button><input type="text" #input="onInput"><p v-if="inputEventFired">The input event fired</p></div>',
data: function() {
return {
inputEventFired: false
};
},
methods: {
onInput: function(e) {
this.inputEventFired = true;
this.$emit('emittedFromInput', 'never see this');
},
sendEmit: function() {
this.$emit('emitted', 'can change from click event that emits');
}
}
});
new Vue({
el: '#app',
data: function() {
return {
message: 'allo',
otherMessage: 'cannot change this from input event that emits'
};
},
methods: {
receiveEmit: function(val) {
this.message = val;
},
receiveEmitFromInput: function(val) {
alert('i do not happen')
this.message = val;
}
}
});
Just to make the above more readable, the template for the child component is
<div>
<button #click="sendEmit">emit</button>
<input type="text" #input="onInput">
<p v-if="inputEventFired">The input event fired</p>
</div>
Someone outside of SO helped me on this, and I'll post their info here. The issue here is neither the events nor the emitter, but rather (my ignorance of) the case-insensitivity of html. It seems that #someCamelCasedEvent is actually passed along to the javascript as #somecamelcasedevent. working fiddle
this.$emit('emitted-from-input', 'never see this');
<child-comp
#emitted="receiveEmit"
#emitted-from-input="receiveEmitFromInput">
</child-comp>

vue.js: how to handle click and dblclick events on same element

I have a vue component with separate events for click/dblclick. Single click (de)selects row, dblclick opens edit form.
<ul class="data_row"
v-for="(row,index) in gridData"
#dblclick="showEditForm(row,$event)"
#click="rowSelect(row,$event)"
>
Doing it like this, i get 3 events fired on double click. Two click events and lastly one dblclick. Since the click event fires first , is there a way (short of deferring click event for a fixed amount of ms) for stopping propagation of click event on double click ?
Fiddle here
As suggested in comments, You can simulate the dblclick event by setting up a timer for a certain period of time(say x).
If we do not get another click during that time span, go for the single_click_function().
If we do get one, call double_click_function().
Timer will be cleared once the second click is received.
It will also be cleared once x milliseconds are lapsed.
See below code and working fiddle.
new Vue({
el: '#app',
data: {
result: [],
delay: 700,
clicks: 0,
timer: null
},
mounted: function() {
console.log('mounted');
},
methods: {
oneClick(event) {
this.clicks++;
if (this.clicks === 1) {
this.timer = setTimeout( () => {
this.result.push(event.type);
this.clicks = 0
}, this.delay);
} else {
clearTimeout(this.timer);
this.result.push('dblclick');
this.clicks = 0;
}
}
}
});
<div id="example-1">
<button v-on:dblclick="counter += 1, funcao()">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
},
methods: {
funcao: function(){
alert("Sou uma funcao");
}
}
})
check out this working fiddle https://codepen.io/robertourias/pen/LxVNZX
i have a simpler solution i think (i'm using vue-class but same principle apply):
private timeoutId = null;
onClick() {
if(!this.timeoutId)
{
this.timeoutId = setTimeout(() => {
// simple click
}, 50);//tolerance in ms
}else{
clearTimeout(this.timeoutId);
// double click
}
}
it does not need to count the number of clicks.
The time must be short between click and click.
In order to get the click and double click, only one counter is required to carry the number of clicks(for example 0.2s) and it is enough to trap the user's intention when he clicks slowly or when he performs several that would be the case of the double click or default case.
I leave here with code how I implement these features.
new Vue({
el: '#app',
data: {numClicks:0, msg:''},
methods: {
// detect click event
detectClick: function() {
this.numClicks++;
if (this.numClicks === 1) { // the first click in .2s
var self = this;
setTimeout(function() {
switch(self.numClicks) { // check the event type
case 1:
self.msg = 'One click';
break;
default:
self.msg = 'Double click';
}
self.numClicks = 0; // reset the first click
}, 200); // wait 0.2s
} // if
} // detectClick function
}
});
span { color: red }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.0/vue.js"></script>
<div id='app'>
<button #click='detectClick'>
Test Click Event, num clicks
<span>{{ numClicks }}</span>
</button>
<h2>Last Event: <span>{{ msg }}</span></h2>
</div>
I use this approach for the same problem. I use a promise that is resolved either by the timeout of 200ms being triggered, or by a second click being detected. It works quite well in my recent web apps.
<div id="app">
<div
#click="clicked().then((text) => {clickType = text})">
{{clickType}}
</div>
</div>
<script>
new Vue({
el: "#app",
data: {
click: undefined,
clickType: 'Click or Doubleclick ME'
},
methods: {
clicked () {
return new Promise ((resolve, reject) => {
if (this.click) {
clearTimeout(this.click)
resolve('Detected DoubleClick')
}
this.click = setTimeout(() => {
this.click = undefined
resolve('Detected SingleClick')
}, 200)
})
}
}
})
</script>
Working fiddle:
https://jsfiddle.net/MapletoneMartin/9m62Lrwf/
vue Component
// html
<div class="grid-content">
<el-button
   #click.native="singleClick"
   #dblclick.native="doubleClick"
   class="inline-cell">
click&dbclickOnSameElement</el-button>
</div>
// script
<script>
let time = null; // define time be null
export default {
name: 'testComponent',
data() {
return {
test:''
};
},
methods: {
singleClick() {
// first clear time
clearTimeout(time);
time = setTimeout(() => {
console.log('single click ing')
}, 300);
},
  
doubleClick() {
clearTimeout(time);
console.log('double click ing');
}
}
}
</script>
selectedFolder = ''; // string of currently selected item
folderSelected = false; // preview selected item
selectFolder(folder) {
if (this.selectedFolder == folder) {
// double click
this.folderSelected = false;
this.$store.dispatch('get_data_for_this_folder', folder);
} else {
// single click
this.selectedFolder = folder;
this.folderSelected = true;
}
},
#click.stop handles a single click and #dblclick.stop handles double click
<v-btn :ripple="false"
class="ma-0"
#click.stop="$emit('editCompleteGrvEvent', props.item)"
#dblclick.stop="$emit('sendCompleteGrvEvent',props.item)">
<v-icon>send</v-icon>
</v-btn>
Unless you need to do expensive operations on single select, you can rework rowSelect into a toggle. Setting a simple array is going to be a lot faster, reliable, and more straightforward compared to setting up and canceling timers. It won't matter much if the click event fires twice, but you can easily handle that in the edit function.
<template>
<ul>
<li :key="index" v-for="(item, index) in items">
<a
:class="{ 'active-class': selected.indexOf(item) !== -1 }"
#click="toggleSelect(item)"
#dblclick="editItem(item)"
>
{{ item.title }}
</a>
<!-- Or use a checkbox with v-model
<label #dblclick="editItem(item)">
<input type="checkbox" :value="item.id" v-model.lazy="selected" />
{{ item.title }}
</label>
-->
</li>
</ul>
</template>
<script>
export default {
data: function () {
return {
items: [
{
id: 1,
title: "Item 1",
},
{
id: 2,
title: "Item 2",
},
{
id: 3,
title: "Item 3",
},
],
selected: [],
};
},
methods: {
editItem(item) {
/*
* Optionally put the item in selected
* A few examples, pick one that works for you:
*/
// this.toggleSelect(item); // If the item was selected before dblclick, it will still be selected. If it was unselected, it will still be unselected.
// this.selected = []; // Unselect everything.
// Make sure this item is selected:
// let index = this.selected.indexOf(item.id);
// if (index === -1) {
// this.selected.push(item.id);
// }
// Make sure this item is unselected:
// let index = this.selected.indexOf(item.id);
// if (index !== -1) {
// this.selected.splice(index, 1);
// }
this.doTheThingThatOpensTheEditorHere(item);
},
toggleSelect(item) {
let index = this.selected.indexOf(item.id);
index === -1
? this.selected.push(item.id)
: this.selected.splice(index, 1);
},
// For fun, get an array of items that are selected:
getSelected() {
return this.items.filter((item) => this.selected.indexOf(item.id) !== -1);
},
},
};
</script>

Categories

Resources