Vue.js + Call the click event for whole page document - javascript

With JQuery, click event of the any item in the page can be captured as below.
$(document).click(function(event){
// event.target is the clicked element object
});
How to do the same with Vue.js?

The answer provided by M U is correct and works.
Yet if you don't like messing with your template (e.g. not put a lot of event handlers in it) or your Vue app is only a small part of a bigger application, it's also perfectly fine and acceptable to register event handlers manually.
To add global event handlers in your script the Vue way you should register them in the mounted and remove them in the beforeDestroy hooks.
Short example:
var app = new Vue({
el: '#app',
mounted: function () {
// Attach event listener to the root vue element
this.$el.addEventListener('click', this.onClick)
// Or if you want to affect everything
// document.addEventListener('click', this.onClick)
},
beforeDestroy: function () {
this.$el.removeEventListener('click', this.onClick)
// document.removeEventListener('click', this.onClick)
},
methods: {
onClick: function (ev) {
console.log(ev.offsetX, ev.offsetY)
}
}
})

All of the answers provided works, but none of them mimic the real behavior of $(document).click(). They catch just clicks on the root application element, but not on the whole document. Of course you can set your root element to height: 100% or something. But in case you want to be sure, it's better to modify Bengt solution and attach event listener directly to document.
new Vue({
...
methods: {
onClick() {},
}
mounted() {
document.addEventListener('click', this.onClick);
},
beforeDestroy() {
document.removeEventListener('click', this.onClick);
},
...
});
Remember you need to use #click.stop in children elements if you for some reason need to stop event propagation to the main handler.

Create div as top node, right after <body>
Make it main container and mount VueJS on it.
<div id='yourMainDiv' #click='yourClickHandler'>
In your VueJS <script> part use it:
methods: {
yourClickHandler(event) {
// event.target is the clicked element object
}
}

Also, if you need to track click event outside of specific element, you
can use vue-clickaway component. Example from the demo:
<div id="demo">
<p v-on-clickaway="away" #click="click" :style="{ color: color }">{{ text }}</p>
</div>
new Vue({
el: '#demo',
mixins: [VueClickaway.mixin],
data: {
text: 'Click somewhere.',
color: 'black',
},
methods: {
click: function() {
this.text = 'You clicked on me!';
this.color = 'green';
},
away: function() {
this.text = 'You clicked away...';
this.color = 'red';
},
},
});

Related

Remove window event listener on unbind Vue directive lifecycle

I have just come accross with an issue related to event listening in Vue directives.
I have a component which holds following code inside:
function setHeaderWrapperHeight() { ... }
function scrollEventHandler() { ... }
export default {
...
directives: {
fox: {
inserted(el, binding, vnode) {
setHeaderWrapperHeight(el);
el.classList.add('header__unfixed');
window.addEventListener(
'scroll',
scrollEventListener.bind(null, el, binding.arg)
);
window.addEventListener(
'resize',
setHeaderWrapperHeight.bind(null, el)
);
},
unbind(el, binding) {
console.log('Unbound');
window.removeEventListener('scroll', scrollEventListener);
window.removeEventListener('resize', setHeaderWrapperHeight);
}
}
}
...
}
And this component is re-rendered everytime I change router path, I achieved this behaviour by assigning current route path to :key prop so whenever path changes it gets re-rendered. But the propblem is though event listeners are not being removed/destroyed causing terrible performance issues. So how do I remove event listeners?
Calling bind on a function creates a new function. The listeners aren't being removed because the function you're passing to removeEventListener is not the same function you passed to addEventListener.
Communicating between hooks in directives is not particularly easy. The official documentation recommends using the element's dataset, though that seems clumsy in this case:
https://v2.vuejs.org/v2/guide/custom-directive.html#Directive-Hook-Arguments
You could just store the listeners on the element directly as properties so that they're available in the unbind hook.
The code below takes a slightly different approach. It uses an array to hold all of the elements that are currently bound to the directive. The listener on window is only ever registered once, no matter how many times the directive is used. If the directive isn't currently being used then that listener is removed:
let foxElements = []
function onClick () {
console.log('click triggered')
for (const entry of foxElements) {
clickHandler(entry.el, entry.arg)
}
}
function clickHandler (el, arg) {
console.log('clicked', el, arg)
}
new Vue({
el: '#app',
data () {
return {
items: [0]
}
},
directives: {
fox: {
inserted (el, binding) {
console.log('inserted')
if (foxElements.length === 0) {
console.log('adding window listener')
window.addEventListener('click', onClick)
}
foxElements.push({
el,
arg: binding.arg
})
},
unbind (el, binding) {
console.log('unbind')
foxElements = foxElements.filter(element => element.el !== el)
if (foxElements.length === 0) {
console.log('removing window listener')
window.removeEventListener('click', onClick)
}
}
}
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<button #click="items.push(Math.floor(Math.random() * 1000))">Add</button>
<hr>
<button
v-for="(item, index) in items"
v-fox:example
#click="items.splice(index, 1)"
>Remove {{ item }}</button>
</div>
However, all of this assumes that a directive is even the right way to go. If you can just do this at the component level then it may get a lot simpler because you have the component instance available to store things. Just remember that calling bind creates a new function, so you'll need to keep a reference to that function somewhere so you can pass it to removeEventListener.
Just for the records, and to help whoever passes through here as there is already an accepted answer, what one could do in this case (on Vue 3 at least, not tested on Vue 2) is to use binding.dir (which is a reference to the directive's own object) to host the function for adding the event listener on the directive object and take it back later when there's a need to remove this listener.
One simple example (not related to the original question) for binding one focus event:
export default {
...
directives: {
fox: {
handleFocus: () => { /* a placeholder to rewrite later */ },
mounted(el, binding) {
binding.dir.handleFocus = () => { /* do whatever */ }
el.addEventListener('focus', binding.dir.handleFocus);
},
beforeUnmount(el, binding) {
el.removeEventListener('focus', binding.dir.handleFocus);
}
}
}
...
}
A practical example of what I'm doing with this, in my case, is to have a focus/blur notifier for any input or textarea tag. I made a Gist here of this, it is on a project built on Vue 3 with TypeScript.

Adding onselectionchange to vue

i'm trying to implement a text selection listener to display a toolbar for some custom options
<script>
export default {
name: "home",
created() {
document.onselectionchange = function() {
this.showMenu();
};
},
data() {
return {
...
};
},
methods: {
showMenu() {
console.log("show menu");
}
}
</script>
<style scoped>
</style>
but it still display that can't call showMenu of undefined, so i tried in this way:
created() {
vm = this;
document.onselectionchange = function() {
vm.showMenu();
};
},
so, nothing changed =(
i need to use this selectionchange because its the only listener that i can add that will handle desktop and mobile together, other method i should implement a touchup, touchdown and its not working for devices
Functions declared the classic way do have their own this. You can fix that by either explicitly binding this using Function.prototype.bind() or by using an ES6 arrow function (which does not have an own this, preserving the outer one).
The second problem is that if you have more than one of those components you've shown, each will re-assign (and thus, overwrite) the listener if you attach it using the assignment document.onselectionchange =. This would result in only the last select element working as you expect because it's the last one assigned.
To fix that, I suggest you use addEventListener() instead:
document.addEventListener('selectionchange', function() {
this.showMenu();
}.bind(this));
or
document.addEventListener('selectionchange', () => {
this.showMenu();
});
A third solution stores a reference to this and uses that in a closure:
const self = this;
document.addEventListener('selectionchange', function() {
self.showMenu();
});

Use vue.js method in EventListener click in other method

I have a vue.js script that generates an element 'lens' in a method.
Now, I would like to add an EventListener that calls another method when the lens element is clicked.
The issue:
I have tried two different ways to add the listener.
1: lens.addEventListener("click", this.openLightbox(src));
Works but is executed on pageload, not on click
2: lens.addEventListener("click", function() { this.openLightbox(src) }, false);
Is executed on click and not on payload, but throws error: Uncaught TypeError: this.openLightbox is not a function
The question:
How can I call the lightbox method in my zoom method? I does work if I copy the code from the lightbox mehtod into the zoom method itself as a function, however since the lightbox method is called by other elements as well that would lead to duplicate code.
Here is the full code:
initVue(target: string) : void {
this.vue = new Vue({
el: "#" + target,
store,
delimiters: vueDelimiters,
data: {
},
methods: {
openLightbox(src) {
console.log(src);
},
imageZoom(src) {
lens = document.createElement("DIV");
// works but is executed on pageload, not on click
lens.addEventListener("click", this.openLightbox(src));
// Is executed on click and not on payload, but throws error: Uncaught TypeError: this.openLightbox is not a function
lens.addEventListener("click", function() { this.openLightbox(src) }, false);
}
}
});
}
You have to attach this to the anonymous function like this :
lens.addEventListener("click", function() { this.openLightbox(src) }.bind(this), false);
Or define an alias before the statement, like this :
var self = this;
lens.addEventListener("click", function() { self.openLightbox(src) }, false);
Otherwise, this will not reference the parent context that you need.

How to unwrap the extra HTML element generaed by View while maintaining events?

The extra wrapping element generated by Backbone/Marionette views causes problems (with jQuery Mobile), so I need to unwrap it. After unwrapping, the layout is good, but events are no longer fired (see demo).
How to unwrap the extra element while maintaining events?
Demo
It uses the Marionette.BossView plugin but the idea is the same without.
Marionette.ItemView extends from Backbone.View which has property named tagName. When you mentioning it in view declaration your wrapping element became the element mentioned in tagName.
For you demo example better to use tagName instead of changing the way Marionette renders the view.
Change you view to the following and it will work!
var SubView1 = Backbone.Marionette.ItemView.extend({
className: 'SubView1',
template: function () {
return '(SubView1) Click Me!'; // moved button tag and mentioned it in tagName
},
tagName: 'button',
triggers: {
'click': 'click:button' // no need to 'button' selector, it's already a root element
}
});
var SubView2 = Backbone.Marionette.ItemView.extend({
className: 'SubView2',
template: function () {
return '(SubView2) Click Me!'; // moved button tag and mentioned it in tagName
},
tagName: 'button',
triggers: {
'click': 'click:button' // no need to 'button' selector, it's already a root element
}
});
var TopView = Backbone.Marionette.BossView.extend({
className: 'TopView',
subViews: {
buttonView1: SubView1,
buttonView2: SubView2
},
subViewEvents: {
'buttonView1 click:button': 'onSubViewClickButton',
'buttonView2 click:button': 'onSubViewClickButton' // there was a typo here
},
onSubViewClickButton: function () {
$('body').append('<div>You clicked it, and TopView responded!</div>');
}
});
var topView = new TopView();
topView.render().$el.appendTo($('body'));
Hope this helps!

Trouble getting Backbone.js view event to fire

It appears as though the following code is getting inside initialize but my event doesn't appear to be firing.
What am I missing here?
var index = (function ($, window, document) {
var methods = {};
methods = {
init: function () {
},
getView: Backbone.View.extend({
el: $('.settings'),
events: {
'click .settings': 'addUl'
},
initialize: function () {
console.log('init');
},
render: function () {
},
addUl: function () {
console.log('addUI');
this.el.append("<ul> <li>hello world </li> </ul>");
}
})
};
return methods; } (jQuery, window, document));
var stuff = new index.getView();
Link to the jsbin
Remove the space in 'click .settings'
Actually remove .settings entirely.
'click .settings' is registering a click handler for a descendant of this.el that matches '.settings'.
In your example you want to register an event on this.el directly so you don't need the descendant selector.
The problem is that it is your view element ($el) that has the settings class and not a child.
click .settings tells backbone to bind a "click" event on the $el for any children that have .settings. However, because, it is $el which has the class settings the binding never match.
This is why when you remove .settings it works, because you say "any 'click' on $el"
The reason the documentation says click .blah is because it assumes that the html element(s) with the class='blah' are children of the $el element.
Hope this help.

Categories