Detecting a Non-Namespaced Event in jQuery - javascript

I'm trying to detect with jQuery's on only events that are not namespaced.
I tried checking the namespace within the callback of on as follows, but it's always undefined:
$(".selector").on(
"click",
function (e) {
console.log(e.namespace); // Undefined, not "mynamespace"
}
);
$(".selector").trigger("click.mynamespace");
I've also read that you can append ".$" to the event name to rule out any namespaces, but that only seems to apply within trigger, not within on. The following runs even if the event is namespaced:
$(".selector").on(
"click.$",
function (e) {
// runs even when triggered with a namespace
}
);
I'm not sure where to go from here since when I log the event object, the namespace is nowhere to be found.
Thanks in advance!

Try to access it through handleObj.
HTML:
<div class="selector">
BOX
</div>
<button onclick="clickWithNamespace()">with ns</button>
<button onclick="clickWithoutNamespace()">without ns</button>
JS:
$(".selector").on(
"click.mynamespace",
manageClick
);
$(".selector").on(
"click",
manageClick
);
function manageClick(event) {
console.log(event.handleObj.namespace);
$(".selector").text(event.handleObj.namespace);
}
function clickWithNamespace() {
$(".selector").trigger("click.mynamespace");
}
function clickWithoutNamespace() {
$(".selector").trigger("click");
}
https://jsfiddle.net/j52rmxrs/15/

Related

Stimulus controller: event listened multiple times; how do I remove event listeners and retain the Context?

I've got the following controller on my HTML page:
...
<div data-controller="parent">
<div data-target="parent.myDiv">
<div data-controller="child">
<span data-target="child.mySpan"></span>
</div>
</div>
</div>
...
This child controller is mapped to the following child_controller.js class:
export default class {
static targets = ["mySpan"];
connect() {
document.addEventListener("myEvent", (event) => this.handleMyEvent(event));
}
handleMyEvent(event) {
console.log(event);
this.mySpanTarget; // Manipulate the span. No problem.
}
}
As you can see, there is an event listener on the connect() of the Stimulus controller, and when it detects that the event was fired up, it logs the event and manipulates the span target.
The problem arises when I replace the contents of the target myDiv from my parent_controller.js:
...
let childControllerHTML = "<div data-controller="child">...</div>"
myDivTarget.innerHTML= childControllerHTML;
...
Now that the myEvent gets fired up, the event listener picks it not once, but twice (because the same event got logged twice). With every subsequent replacement of the child HTML, the event gets logged one more time than it did before.
I know that one can make use of document.removeEventListener to prevent the old controller from still listening to the events:
export default class {
static targets = ["mySpan"];
connect() {
this.myEventListener = document.addEventListener("myEvent", (event) => this.handleMyEvent(event));
}
disconnect() {
document.removeEventListener("myEvent", this.myEventListener);
}
handleMyEvent(event) {
console.log(event);
this.mySpanTarget; // FAILS. Can't find span.
}
}
But doing it like this makes the handleMyEvent method lose the context as it no longer finds the mySpanTarget under this.
How can I remove the listener from the child controller to which I already got no access as it is no longer in the DOM, while retaining the context?
I found the answer on StimulusJS's Discourse page.
One has to make use of the bind method when initializing the controller:
export default class {
static targets = ["mySpan"];
initialize() {
this.boundHandleMyEvent = this.handleMyEvent.bind(this);
}
connect() {
document.addEventListener("myEvent", this.boundHandleMyEvent);
}
disconnect() {
document.removeEventListener("myEvent", this.boundHandleMyEvent);
}
handleMyEvent(event) {
console.log(event);
this.mySpanTarget; // Manipulate the span. No problem.
}
...
}
Now, the event is only listened once, and the context is not lost inside the handleMyEvent method.

Run Child's Onclick But Not Parent's Where Argument is Passed

Given some simple HTML such this, where one element has an onclick function and it's child also has an onclick function:
<div style='background-color:blue;width:500px;height:500px;'
onclick='thing1();'>
<div style='background-color:red;width:100px;height:100px;'
onclick='thing2(57);'></div>
</div>
What would be the correct approach so that when a user clicks the child element, only the child's onclick is executed and not the parent's, but when the parent is clicked, it's onclick is still executed? I see that event.stopPropagation() would be the correct way to go, but since I'm passing an argument to the function thing2(), I can't seem to pass the event as well. For example:
function thing2(a,ev) {
// Do something with a
ev.stopPropagation();
}
Doesn't work, failing with the error TypeError: ev is undefined.
JQuery is fine.
The event is the first param.
function thing2(ev) {
var a = ev.target
ev.stopPropagation()
}
Secondly, it's best not to use onclick=. Instead, give your div classes or ids and do something like this:
<div class="thing-1" data-thingy="57">
<div class="thing-2" data-thingy="65"></div>
</div>
<script>
$('.thing-1').click(function (ev) {
ev.stopPropagation()
parseInt($(ev.target).data('thingy'), 10) // 57
})
$('.thing-2').click(function (ev) {
ev.stopPropagation()
parseInt($(ev.target).data('thingy'), 10) // 65
})
</script>
When you call a function on click, no event will be passed as argument and it somehow you can do that, that is not a Jquery object and that will not have stopPropagation property. SO you need to define jQuery click event handler for both divs, let's give them ids div1 and div2.
HTML
<div id="div1" style='background-color:blue;width:500px;height:500px;'>
<div id="div2" style='background-color:red;width:100px;height:100px;'></div>
</div>
In Javascript,
function thing2(ev) {
// Do something with a
console.log(ev);
alert('hi2');
ev.stopPropagation();
}
function thing1(ev) {
// Do something with a
alert('hi1');
}
$('#div1').click(thing1);
$('#div2').click(thing2);

Changing an element's class on another element's click event

I have the following code, but it's not working. I'm trying to add or remove a class based on a click event.
Javascript:
function done(e){
if (e.hasClass("Gset")) {
e.removeClass("Gset")
}
else {
e.addClass("Gset")
}
}
HTML:
<h4 id="test">
<input type="checkbox" onClick="done(test)">
Ready?
</h4>
Here is a Jfiddle link showing it
Any help would be greatly appreciated!
Your code isn't working because e in your function is a DOM element, not a jQuery object. DOM elements don't have jQuery functions on them.
I should note that the only reason that it's a DOM element is that by giving the element an id, you've caused the browser to create an automatic global for it, which is why onClick="done(test)" works at all (the test there is a variable reference, and will pick up the automatic global).
The minimal fix is to make it a jQuery object:
function done(e){
e = $(e); // <===
if (e.hasClass("Gset")){e.removeClass("Gset") }
else {e.addClass("Gset") }
}
But a more thorough fix is to use toggleClass as well:
function done(e) {
$(e).toggleClass("Gset");
}
And even more thorough update would be to hook up the handler using jQuery rather than using the long-outdated onxyz attributes, not least because relying on automatic globals is error-prone (for instance, id="name" would fail):
$("#test input").on("click", function() {
$("#test").toggleClass("Gset");
});
.Gset {
background: yellow;
}
<h4 id="test">
<input type="checkbox">
Ready?
</h4>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Send the parameter as:
done(this)
And then in the function:
function done(e) {
e = $(e).parent();
if (e.hasClass("Gset")) {
e.removeClass("Gset")
} else {
e.addClass("Gset")
}
}
There's a simpler way:
function done(e) {
$(e).parent().toggleClass("Gset");
}
Reasons:
e, as passed as this will be a DOMElement, but hasClass, addClass and removeClass works only on jQuery objects.
It will be better if you avoid inline-event onClick.
HTML :
<h4 id="test">
<input type="checkbox">Ready?
</h4>
JS :
$('#test input').click(function(){
$('#test').toggleClass("Gset");
})
Hope this helps.

Binding a function that is already bound to another element

I have a bunch of elements that get three different classes: neutral, markedV and markedX. When a user clicks one of these elements, the classes toggle once: neutral -> markedV -> markedX -> neutral. Every click will switch the class and execute a function.
$(document).ready(function(){
$(".neutral").click(function markV(event) {
alert("Good!");
$(this).addClass("markedV").removeClass("neutral");
$(this).unbind("click");
$(this).click(markX(event));
});
$(".markedV").click(function markX(event) {
alert("Bad!");
$(this).addClass("markedX").removeClass("markedV");
$(this).unbind("click");
$(this).click(neutral(event));
});
$(".markedX").click(function neutral(event) {
alert("Ok!");
$(this).addClass("neutral").removeClass("markedX");
$(this).unbind("click");
$(this).click(markV(event));
});
});
But obviously this doesn't work. I think I have three obstacles:
How to properly bind the changing element to the already defined function, sometimes before it's actually defined?
How to make sure to pass the event to the newly bound function [I guess it's NOT accomplished by sending 'event' to the function like in markX(event)]
The whole thing looks repetitive, the only thing that's changing is the alert action (Though each function will act differently, not necessarily alert). Is there a more elegant solution to this?
There's no need to constantly bind and unbind the event handler.
You should have one handler for all these options:
$(document).ready(function() {
var classes = ['neutral', 'markedV', 'markedX'],
methods = {
neutral: function (e) { alert('Good!') },
markedV: function (e) { alert('Bad!') },
markedX: function (e) { alert('Ok!') },
};
$( '.' + classes.join(',.') ).click(function (e) {
var $this = $(this);
$.each(classes, function (i, v) {
if ( $this.hasClass(v) ) {
methods[v].call(this, e);
$this.removeClass(v).addClass( classes[i + 1] || classes[0] );
return false;
}
});
});
});
Here's the fiddle: http://jsfiddle.net/m3CyX/
For such cases you need to attach the event to a higher parent and Delegate the event .
Remember that events are attached to the Elements and not to the classes.
Try this approach
$(document).ready(function () {
$(document).on('click', function (e) {
var $target = e.target;
if ($target.hasClass('markedV')) {
alert("Good!");
$target.addClass("markedV").removeClass("neutral");
} else if ($target.hasClass('markedV')) {
alert("Bad!");
$target.addClass("markedX").removeClass("markedV");
} else if ($target.hasClass('markedX')) {
alert("Ok!");
$target.addClass("neutral").removeClass("markedX");
}
});
});
OR as #Bergi Suggested
$(document).ready(function () {
$(document).on('click', 'markedV',function (e) {
alert("Good!");
$(this).addClass("markedV").removeClass("neutral");
});
$(document).on('click', 'markedX',function (e) {
alert("Bad!");
$(this).addClass("markedX").removeClass("markedV");
});
$(document).on('click', 'neutral',function (e) {
alert("Ok!");
$(this).addClass("neutral").removeClass("markedX");
});
});
Here document can be replaced with any static parent container..
How to properly bind the changing element to the already defined function, sometimes before it's actually defined?
You don't bind elements to functions, you bind handler functions to events on elements. You can't use a function before it is defined (yet you might use a function above the location in the code where it was declared - called "hoisting").
How to make sure to pass the event to the newly bound function [I guess it's NOT accomplished by sending 'event' to the function like in markX(event)]
That is what happens implicitly when the handler is called. You only need to pass the function - do not call it! Yet your problem is that you cannot access the named function expressions from outside.
The whole thing looks repetitive, the only thing that's changing is the alert action (Though each function will act differently, not necessarily alert). Is there a more elegant solution to this?
Yes. Use only one handler, and decide dynamically what to do in the current state. Do not steadily bind and unbind handlers. Or use event delegation.

Event listener loop

How do I add a listener for hovering over div tags like this:
btn1
btn2
btn3
btn4
I want to add a listener that loops through them like I show below and then applies a function if it has mouseover.
function listen() {
for (i=1;i<=10;i++) {
wat = document.getElementById('btn'+i);
wat.addEventListener('mouseover',functionwat,false );
}
}
I have this and its not working, and yes it is calling the function listen(), because I added an alert thing in there to make sure its working correctly, and functionwat works right too. Any idea what I'm doing wrong?
What browser are you using? Registering event handlers is different browser to browser. PPK has some good discussion of browser events here.
In short, this is the cross-browser code for adding a handler.
function addEventSimple(obj,evt,fn) {
if (obj.addEventListener)
obj.addEventListener(evt,fn,false);
else if (obj.attachEvent)
obj.attachEvent('on'+evt,fn);
}
Now you can attach the event with
function listen() {
for (i=1;i<=10;i++) {
wat = document.getElementById('btn'+i);
addEventSimple(wat, 'mouseenter', functionwat);
}
}
Instead of looping for each item and attaching events, look into implementing event delegation. As it relates your situation, let assume you use jQuery and your buttons' markup is as followed:
<div id="btnList">
<button id="btn1">btn1</button>
<button id="btn2">btn2</button>
<button id="btn3">btn3</button>
<button id="btn4">btn4</button>
</div>
JavaScript:
$(document).ready(function()
{
$("#btnList button").bind(
"mouseenter mouseleave",
function(e) {
//do something based on target/id
alert(this.id);
});
});
It seems that you might be somewhat messy with your variables. For instance, you do not use var to declare i, so it might end up in the global namespace. Following this, are you sure functionwat is really a function at the time listen() executes?
You could check that by;
function listen() {
if(typeof functionwat !== "function") {
alert("functionwat is not a function, but a " + typeof functionwat);
}
for (var i = 1; i <= 10; ++i) {
wat = document.getElementById("btn"+i);
wat.addEventListener("mouseover", functionwat, false);
}
}
David,
You're not having any luck because, I am almost positive you are using a browser which is not IE. Your events will not fire in a non-IE browser because the event "mouseenter" is only exposed in IE. To make it work, you need to change "mouseenter" to use "mouseover".
function listen() {
for (i=1;i<=10;i++) {
wat = document.getElementById('btn'+i);
addEventSimple(wat, 'mouseenter', functionwat);
}
}
to
function listen() {
for (i=1;i<=10;i++) {
wat = document.getElementById('btn'+i);
if(wat) { addEventSimple(wat, 'mouseover', functionwat); }
}
}

Categories