How to attach events in vue directives? - vue.js

I need to attach functions to element using directives.
I want to do it with Vue method $on, but it's not working.
When I do it with addEventListener, event.target.value gives me unchanged value after first input, second works correctly.
How to fix it?
Example: http://jsfiddle.net/rjeu8Lc1/1/
directives: {
rinput: {
bind: function(el, bind, vnode) {
el.addEventListener('input', function(event) {
vnode.context.eventListenerCalled = true;
// wrong value on the first input in event.target.value
vnode.context.value = event.target.value; //changing data.value
});
vnode.context.$on('input', function(event) {
// never executed =(
vnode.context.vueEventListenerCalled = true;
});
}
}
}

I agree with Bert that you should not be trying to adjust the Vue object through directives. On the other hand, you should be able to set up an event handler. In your event handler, event.target.value does not have the updated value. This appears to be related to the fact that you also attached an input handler via Vue. When I removed #input="setDirty", that got fixed. So I think the first lesson is: mixing event listeners can cause conflicts.
That aside, you can actually wind up with a facsimile of v-model:
new Vue({
el: '#app',
data() {
return {
value: 'initial',
eventListenerCalled: false
}
},
directives: {
rinput: {
bind: function(el, bind, vnode) {
el.value = bind.value;
el.addEventListener('input', function(event) {
vnode.context.eventListenerCalled = true;
vnode.context.value = event.target.value; //changing data.value
});
}
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<input v-rinput="value">
<p>{{ value }}</p>
<div v-if="eventListenerCalled"> Event Listener Called </div>
</div>
Here's a minimal snippet to illustrate the bug. If the Vue-style event handler modifies data, event.target.value is wrong in the JS-style event handler (which is called after the Vue-style handler).
new Vue({
el: '#app',
data: {
value: 'initial',
isDirty: false
},
directives: {
rinput: {
bind: function(el, bind, vnode) {
el.addEventListener('input', function(event) {
console.log("Listener called", event.target.value);
});
}
}
},
methods: {
setDirty(event) {
console.log("Dirty called", event.target.value);
this.isDirty = !this.isDirty; // Without this, the listener works fine
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<input #input="setDirty" v-rinput="value" :value="value">
<p>{{ value }}</p>
<div v-if="isDirty"> Dirty function called </div>
</div>

Related

How to call the method of element and pass arguments from directive Vue.js?

I need to call method of element from directive and pass some parameters to this method.
<div id="app">
<div >
<button
#mousetrapped="trapped"
v-keyboardtrap
>
keyboardtrap
</button>
<div/>
</div>
<script>
Vue.directive('keyboardtrap', {
bind: function (el, binding, vnode) {
el.addEventListener('keydown', onKeyDown);
function onKeyDown(e) {
vnode.context.$emit('mousetrapped', e)
}
},
})
new Vue({
el: '#app',
methods: {
trapped (e) {
alert('trapped' + e)
}
}
});
</script>
This example on codepen: https://codepen.io/4e4e4i/pen/OJLewNr
For my solution. It's needToPassDatas is you needed to pass an argument data
<div id="app">
<div>
<button #mousetrapped="trapped" v-keyboardtrap="needToPassDatas">
keyboardtrap
</button>
<div/>
</div>
Vue.directive('keyboardtrap', {
bind: function (el, binding, vnode) {
el.addEventListener('keydown', onKeyDown);
function onKeyDown(e) {
var handlers = (vnode.data && vnode.data.on) ||
(vnode.componentOptions && vnode.componentOptions.listeners);
if (handlers && handlers['mousetrapped']) {
handlers['mousetrapped'].fns(binding.value);
}
}
},
})
new Vue({
el: '#app',
methods: {
trapped (e) {
alert('trapped' + e)
}
}
})
Ref by: vuejs.org Dynamic Directive Arguments, stackoverflow.com Trigger emit in directive method

How do I trigger a recalculation in a Vue app?

I'm working on a project with Vue and VueX. In my component, I have a calculated method that looks like this:
...mapState([
'watches',
]),
isWatched() {
console.log('check watch');
if (!this.watches) return false;
console.log('iw', this.watches[this.event.id]);
return this.watches[this.event.id] === true;
},
And in my store, I have the following:
addWatch(state, event) {
console.log('add', state.watches);
state.watches = {
...state.watches,
[event]: true,
};
console.log('add2', state.watches);
},
However, this doesn't trigger a recalculation. What's going on?
Try changing return this.watches[this.event.id] === true;
to
return this.$store.commit("addWatch", this.event.id);
The code you have shown is correct, so the problem must be elsewhere.
I assume by 'calculated method' you mean computed property.
Computed properties do not watch their dependencies deeply, but you are updating the store immutably, so that is not the problem.
Here is a bit of sample code to give you the full picture.
Add event numbers until you hit '2', and the isWatched property becomes true.
Vue.use(Vuex);
const mapState = Vuex.mapState;
const store = new Vuex.Store({
state: {
watches: {}
},
mutations: {
addWatch(state, event) {
state.watches = { ...state.watches, [event]: true };
}
}
});
new Vue({
el: "#app",
store,
data: {
numberInput: 0,
event: { id: 2 }
},
methods: {
addNumber(numberInput) {
this.$store.commit("addWatch", Number(numberInput));
}
},
computed: {
...mapState(["watches"]),
isWatched() {
if (!this.watches) return false;
return this.watches[this.event.id] === true;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.0/vuex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>Watches: {{ watches }}</div>
<div>isWatched: {{ isWatched }}</div>
<br>
<input v-model="numberInput" type="number" />
<button #click="addNumber(numberInput)">
Add new event
</button>
</div>

Vue JS: Setting computed property not invoking v-if

When a method sets a computed property, v-ifs are not getting invoked. I thought a computed property logically worked just like a 'regular' property.
// theState can't be moved into Vue object, just using for this example
var theState = false;
var app = new Vue({
el: '#demo',
data: {
},
methods: {
show: function() {
this.foo = true;
},
hide: function() {
this.foo = false;
}
},
computed: {
foo: {
get: function() {
return theState;
},
set: function(x) {
theState = x;
}
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="demo">
<input type=button value="Show" #click="show()">
<input type=button value="Hide" #click="hide()">
<div v-if="foo">Hello</div>
</div>
Am I doing something wrong?
Vue doesn't observe changes in variables outside the component; you need to import that value into the component itself in order for the reactivity to work.
var theState = false; // <-- external variable Vue doesn't know about
var app = new Vue({
el: '#demo',
data: {
myState: theState // <-- now Vue knows to watch myState for changes
},
methods: {
show: function() {
this.foo = true;
theState = true; // <-- this won't affect the component, but will keep your external variable in synch
},
hide: function() {
this.foo = false;
theState = false; // <-- this won't affect the component, but will keep your external variable in synch
}
},
computed: {
foo: {
get: function() {
return this.myState;
},
set: function(x) {
this.myState = x;
}
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="demo">
<input type=button value="Show" #click="show()">
<input type=button value="Hide" #click="hide()">
<div v-if="foo">Hello</div>
</div>
(Edited to remove incorrect info; I forgot computed property setters existed for a while there)
You need to move theState into data. Otherwise it wont be reactive, so vue wont know when its changed, so v-if or any other reactivity wont work.
var app = new Vue({
el: '#demo',
data: {
foo2: false,
theState: false
// 1
},
methods: {
show: function() {
this.foo = true;
},
hide: function() {
this.foo = false;
}
},
computed: {
foo: { // 2
get: function() {
return this.theState
},
set: function(x) {
this.theState = x;
}
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="demo">
<input type=button value="Show" #click="show()">
<input type=button value="Hide" #click="hide()">
<div v-if="foo">Hello</div>
</div>

How to reinitialize vue.js's getter and setter bindings?

Below here i provide a sample code. So what happens here is, i have some objects that i will load from API. The object later will be extended by the UI, in case that some of property that will be used for binding in the UI missing #app2. Under normal condition, if all the properties are provided like in #app1, the Vue will do the binding recursively to the content of the data object. But currently, in #app2, the property is missing and in the UI logic, i add the missing property.
The problem now is, when i added the property that way, the app2.contentObject.toggleStatus is not vue's object with getter and setter. how can i manually reinitialize the state of getter and setter so that the changes will be reflected in UI?
var app1 = new Vue({
el: "#app1",
data: {
contentObject: {
toggleStatus: false
}
},
computed: {
content: function(){
var contentObject = this.contentObject;
return contentObject;
}
},
methods: {
toggle : function(){
this.contentObject.toggleStatus = !this.contentObject.toggleStatus;
}
}
})
var app2 = new Vue({
el: "#app2",
data: {
contentObject: {
}
},
computed: {
content: function(){
var contentObject = this.contentObject;
contentObject.toggleStatus = false;
return contentObject;
}
},
methods: {
toggle : function(){
this.contentObject.toggleStatus = !this.contentObject.toggleStatus;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app1">
current toggle status: {{content.toggleStatus}}<br/>
<button #click="toggle">Toggle (working)</button>
</div>
<div id="app2">
current toggle status: {{content.toggleStatus}}<br/>
<button #click="toggle">Toggle (not working)</button>
</div>
1. In app2 vue instance's case you are trying to add a new property toggleStatus and expecting it to be reactive. Vue cannot detect this changes. So you got to initialize the properties upfront as you did in app1 instance or use this.$set() method. See Reactivity in depth.
2. You are using a computed property. Computed properties should just return a value and should not modify anything. So to add a property toggleStatus to contentObject make use of created lifecycle hook.
So here are the changes:
var app2 = new Vue({
el: "#app2",
data: {
contentObject: {}
},
created() {
this.$set(this.contentObject, "toggleStatus", false);
},
methods: {
toggle: function() {
this.contentObject.toggleStatus = !this.contentObject.toggleStatus;
}
}
});
Here is the working fiddle
It doesn't work in second case first because in your computed property you always assign false to it.
contentObject.toggleStatus = false;
And secondly you are looking for Vue.set/Object.assign
var app1 = new Vue({
el: "#app1",
data: {
contentObject: {
toggleStatus: false
}
},
computed: {
content: function(){
var contentObject = this.contentObject;
return contentObject;
}
},
methods: {
toggle : function(){
this.contentObject.toggleStatus = !this.contentObject.toggleStatus;
}
}
})
var app2 = new Vue({
el: "#app2",
data: {
contentObject: {
}
},
computed: {
content: function(){
var contentObject = this.contentObject;
return contentObject;
}
},
methods: {
toggle : function(){
this.$set(this.contentObject, 'toggleStatus', !(this.contentObject.toggleStatus || false));
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app1">
current toggle status: {{content.toggleStatus}}<br/>
<button #click="toggle">Toggle (working)</button>
</div>
<div id="app2">
current toggle status: {{content.toggleStatus}}<br/>
<button #click="toggle">Toggle (not working)</button>
</div>

Can't emit from one child to another

I have component, inside it I am doing emiting:
methods: {
sendClick(e)
{
bus.$emit('codechange', this.mycode);
console.log(this.selectedLanguage);
this.$parent.sendCode();
this.$parent.changeView();
}
}
In the parent component I am hadling data:
var app = new Vue({
el: '#app',
data: {
currentView: 'past-form',
mycode: ''
},
methods:
{
changeView()
{
console.log(this.mycode);
},
sendCode()
{
console.log("Code is sended to server");
this.currentView = 'view-form';
bus.$emit('codechange', this.mycode);
}
},
created()
{
bus.$on('codechange', function(mycode){
console.log("test"); // this works
this.mycode = mycode; // this works
}.bind(this));
}
})
Handling in parent work fine. But on clicking on sendCode() I want to send data to third component. The third component code:
Vue.component('view-form', {
template: `
<div class="ViewCodeContainer">
<div class="ViewCode">my code here</div>
<code> {{mycode}} </code>
<div class="ViewCodeMenu">my menu here</div>
</div>`,
data() {
return {
mycode: ''
}
},
created()
{
bus.$on('codechange', function(mycode){
console.log("hererere");
console.log(mycode);
this.mycode = mycode;
}.bind(this));
console.log("test");
}
})
But handling of code does not working. Block console.log("hererere"); is not executed. What I am doing wrong?
#Wostex is correct in this case. Essentially, your view-form component doesn't exist when the event is emitted. It doesn't exist until you change the view, which you are doing in the event handler. So there is no way for it to receive the event because your handler doesn't exist.
If your dynamic component is a child of the parent, just pass the code as a property.
<component :is="currentView" :mycode="mycode"></component>
And update your view-form component.
Vue.component('view-form', {
props:["mycode"],
template: `
<div class="ViewCodeContainer">
<div class="ViewCode">my code here</div>
<code> {{code}} </code>
<div class="ViewCodeMenu">my menu here</div>
</div>`,
data() {
return {
code: this.mycode
}
}
})
Here is a working example.