I am currently writing a program in JS using jQuery, which is basically a checkers game.
I am using jQuery's .on() and .off() functions to create events for each of the pieces. What happens is that the program will loop through each of the pieces and will set a function to be called when the piece is clicked. This function will then show the player the available moves that the piece can make.
This is setup using a for-loop and this code:
$("#" + String(playerPositions[i])).on('click', function() {movePiece(validMoves, this)});
This passes the valid moves of that piece as well as the id of that piece to the movePiece function which then deals with highlighting the moves.
The problem lies in my "clean up" function, where I want to remove the onClick handler from all the pieces once a move is made. I use this code:
var elements = $('.' + classToClean);
//clean off the onclick
elements.off("click"); <-- this doesn't work
//clean off the classes
elements.removeClass(classToClean);
The strange thing is that a) the .removeClass function works perfectly, and b) the onClick attribute only is removed from the piece that I have just moved.
I have tried using attaching an empty function to the piece, but this did not work. I also cannot use $('.validPieces').on('click', function () ... ) because I need to pass variables unique to the piece with each piece's onclick.
Thanks in advance for any help, and I apologise about the wall of text but I wanted to make sure everything was clear.
Using .off('click') should remove all event handlers of that type. If that doesn't work it is likely the element(s) you are removing from don't match the ones they were attached to.
If that removes more than you want, you will need to include a reference to your handler in the .off() call. To preserve the different validMoves variable for each call you will need to use a closure:
function move(validMoves) {
return function() {
movePiece(validMoves, this);
}
}
// within your for loop
keepMoveFn[i] = move(validMoves);
$("#" + String(playerPositions[i])).on('click', keepMoveFn[i] );
// elsewhere in your code:
//clean off the onclicks
keepMoveFn.forEach(function(fn) {
el.off("click", fn );
}
Note that you will need to either keep a reference to the move function or have access to it when you call the .off() function. In the snippet above I assume you are keeping an array of functions that you can then later iterate to remove the click events.
Related
I have a code block that works perfectly in jQuery but it's the last bit of a project I am converting to plain vanilla Javascript with the specific aim to remove any and all dependencies on jQuery.
Here is the existing code block at issue:
$("input[data-pattern='comma']").on({
keyup: function() {inputComma($(this));}
});
How do I achieve this same functionality using ONLY plain pure JS?
Note: inputComma is a custom handler that conforms the value for EACH input element instance to a comma-delimited regex pattern as the user is typing.
If it helps, I tried...
document.querySelectorAll("input[data-pattern='comma']").forEach(function(elem) {
elem.addEventListener("keyup", function() {inputComma(elem);});
});
and also...
document.querySelectorAll("input[data-pattern='comma']").forEach(function(elem) {
elem.addEventListener("keyup", () => {inputComma(elem);});
});
I know jQuery's $(this) is different from "this" and that arrow functions also materially affect which "this" is being referenced. I tried to get around that issue by referencing the iterating object but I think that may be part of my problem. Not opposed to using the "this" pointer if there is a way to make it work in pure JS.
Thanks in advance for your help!
I think this can be better done using event delegation. It would look something like:
document.addEventListener(`keyup`, handle);
function handle(evt) {
if (evt.target.dataset?.pattern === `comma`) {
return inputComma(evt.target);
}
}
function inputComma(elem) {...}
And therefore adding a bit of computing load?
For those of you unfamiliar with the .one() jquery function it basically triggers an event just once. Such as if you wanted to add a div on the first time a page is scrolled.
To bring background to the matter, I came across this question:
How to alert when scroll page only first time using javascript?
I have been in projects where I had to add hundreds or thousands of events, so for me it’s always very important to optimize computing power, plus, I am a curious person so I just need to know.
One of the answers where the guy uses vanilla javascript is basically an endless loop where you switch a boolean on the first instance and basically have to continually enter the function to see if it has been already triggered.
var xxx;
$(window).scroll(function () {
if(!xxx)
{
xxx = true;
var div = $("#myDiv");
alert(div.height());
}
});
My idea is that jquery being already heavy on the page it probably just performs this same action under the hood, but I would like to be completely certain as for my future implementations.
No. jQuery's .one works similarly to, for example:
calling addEventListener, and then, in the callback, calling removeEventListener
calling addEventListener with { once: true } in the options object
in jQuery, like calling .on, and then, in the callback, calling .off
Once the listener runs once, it's de-attached; no further logic takes place when the event occurs in the future, because the listener is no longer connected at all.
So .one is very light on computing resources, even if you add lots and lots of .ones.
You can see the source code of one here:
if (one === 1) {
origFn = fn;
fn = function (event) {
// Can use an empty set, since event contains the info
jQuery().off(event); // <-------------------------------------------------
return origFn.apply(this, arguments);
};
// Use same guid so caller can remove using origFn
fn.guid = origFn.guid || (origFn.guid = jQuery.guid++);
}
return elem.each(function () {
jQuery.event.add(this, types, fn, data, selector);
});
where jQuery() returns a jQuery collection containing elements matching the current selector. When one is called, the callback is wrapped in another that calls .off as soon as the function is executed.
The sample code in the answer you linked to is quite inefficient, and should not be used, especially for scroll events, which fire very frequently.
So I am trying to remove my addEventListener function using the removeEventListener function. I've read alot about it needing to include an handler function, which I have done.
One of the issues is that I am running into is that I would like to remove the eventlistener when I change an input using google's searchbox. Don't mind google but really all what is happening is it is identifying when the input value has changed and providing new results. So a bit of code
var input = document.getElementById('search-input');
var searchBox = new google.maps.places.SearchBox(input);
google.maps.event.addListener(searchBox, 'places_changed', locationChange);
function previousButtonFunction(){
//Does something here and does not return anything. Lets just say it places markers all over the map
}
function locationChange() {
var previousButton = document.getElementById('previous');
previousButton.removeEventListener('click', previousButtonFunction());
previousButton.addEventListener('click', function () { previousButtonFunction()})
};
So this code looks like it doesn't make any sense to you probably, but what I am trying to get is that on the first input it would run the add event listener, and not run the removeEventListener function. Once the value of input has changed, I would like to remove the current listener and re-identify the previousButton with a new addeventlistener.
At the first go, I realize that the function of previousButtonFunction() is run, which I thought that it would only run if there was an identified listener. So the first question is the removeEventListener function supposed to run if the eventlistener wasn't added? Second how can I remove the addEventListener without running the function? Would I need to pass in a tracking identifier such as true => run it/false => don't run?
Thanks any help is greatly appreciated
Don't use ()after the function name. You want to pass only a reference to the function. Putting() after the name calls the function and passed the return value. This is a very common mistake.
Change this:
previousButton.removeEventListener('click', previousButtonFunction());
to this:
previousButton.removeEventListener('click', previousButtonFunction);
The same goes for .addEventListener(). Don't put () after the function name unless the function returns another function that you want to be the listener.
FYI, it's a little unclear why you're attempting to remove and then add back the same exactly same listener function. Unless there are multiple listeners on that object and you're trying to change the order of listeners, this is essentially a noop.
I'm trying to move away from jQuery for my everyday site functionality, and I'm having a little bit of trouble with the onclick event. I'd like to put together a function like jQuery's .click(), but simply using document.getElementsByTagName and adding a func onclick won't work.
The question then is how would one add a single function to fire onclick to all elements in the list object returned by querying document.getElementsByTagName('h4')
EDIT: Just in case someone finds this and would like some code, here's what I did:
var headings = document.getElementsByTagName('h4')
for (var g in headings) {
headings[g].onclick = function() {
//code
}
}
You need to loop through the list and pass the event to each item.
I think there is no simpler way to do this, expect you need a library like jQuery or you write your own eventManager...
The Objective
I want to dynamically assign event handlers to some divs on pages throughout a site.
My Method
Im using jQuery to bind anonymous functions as handlers for selected div events.
The Problem
The code iterates an array of div names and associated urls. The div name is used to set the binding target i.e. attach this event handler to this div event.
While the event handlers are successfully bound to each of the div events, the actions triggered by those event handlers only ever target the last item in the array.
So the idea is that if the user mouses over a given div, it should run a slide-out animation for that div. But instead, mousing over div1 (rangeTabAll) triggers a slide-out animation for div4 (rangeTabThm). The same is true for divs 2, 3, etc. The order is unimportant. Change the array elements around and events will always target the last element in the array, div4.
My Code - (Uses jQuery)
var curTab, curDiv;
var inlineRangeNavUrls=[['rangeTabAll','range_all.html'],['rangeTabRem','range_remedial.html'],
['rangeTabGym','range_gym.html'],['rangeTabThm','range_thermal.html']];
for (var i=0;i<inlineRangeNavUrls.length;i++)
{
curTab=(inlineRangeNavUrls[i][0]).toString();
curDiv='#' + curTab;
if ($(curDiv).length)
{
$(curDiv).bind("mouseover", function(){showHideRangeSlidingTabs(curTab, true);} );
$(curDiv).bind("mouseout", function(){showHideRangeSlidingTabs(curTab, false);} );
}
}
My Theory
I'm either not seeing a blindingly obvious syntax error or its a pass by reference problem.
Initially i had the following statement to set the value of curTab:
curTab=inlineRangeNavUrls[i][0];
So when the problem occured i figured that as i changed (via for loop iteration) the reference to curTab, i was in fact changing the reference for all previous anonymous function event handlers to the new curTab value as well.... which is why event handlers always targeted the last div.
So what i really needed to do was pass the curTab value to the anonymous function event handlers not the curTab object reference.
So i thought:
curTab=(inlineRangeNavUrls[i][0]).toString();
would fix the problem, but it doesn't. Same deal. So clearly im missing some key, and probably very basic, knowledge regarding the problem. Thanks.
You need to create a new variable on each pass through the loop, so that it'll get captured in the closures you're creating for the event handlers.
However, merely moving the variable declaration into the loop won't accomplish this, because JavaScript doesn't introduce a new scope for arbitrary blocks.
One easy way to force the introduction of a new scope is to use another anonymous function:
for (var i=0;i<inlineRangeNavUrls.length;i++)
{
curDiv='#' + inlineRangeNavUrls[i][1];
if ($(curDiv).length)
{
(function(curTab)
{
$(curDiv).bind("mouseover", function(){showHideRangeSlidingTabs(curTab, true);} );
$(curDiv).bind("mouseout", function(){showHideRangeSlidingTabs(curTab, false);} );
})(inlineRangeNavUrls[i][0]); // pass as argument to anonymous function - this will introduce a new scope
}
}
As Jason suggests, you can actually clean this up quite a bit using jQuery's built-in hover() function:
for (var i=0;i<inlineRangeNavUrls.length;i++)
{
(function(curTab) // introduce a new scope
{
$('#' + inlineRangeNavUrls[i][1])
.hover(
function(){showHideRangeSlidingTabs(curTab, true);},
function(){showHideRangeSlidingTabs(curTab, false);}
);
// establish per-loop variable by passsing as argument to anonymous function
})(inlineRangeNavUrls[i][0]);
}
what's going on here is that your anonmymous functions are forming a closure, and taking their outer scope with them. That means that when you reference curTab inside your anomymous function, when the event handler runs that function, it's going to look up the current value of curTab in your outer scope. That will be whatever you last assigned to curTab. (not what was assigned at the time you binded the function)
what you need to do is change this:
$(curDiv).bind("mouseover", function(){showHideRangeSlidingTabs(curTab, true);} );
to this:
$(curDiv).bind("mouseover",
(function (mylocalvariable) {
return function(){
showHideRangeSlidingTabs(mylocalvariable, true);
}
})(curTab)
);
this will copy the value of curTab into the scope of the outer function, which the inner function will take with it. This copying happens at the same time that you're binding the inner function to the event handler, so "mylocalvariable" reflects the value of curTab at that time. Then next time around the loop, a new outer function, with a new scope will be created, and the next value of curTab copied into it.
shog9's answer accomplishes basically the same thing, but his code is a little more austere.
it's kinda complicated, but it makes sense if you think about it. Closures are weird.
edit: oops, forgot to return the inner function. Fixed.
I think you're making this more complicated than it needs to be. If all you're doing is assigning a sliding effect on mouseover/out then try the hover effect with jquery.
$("#mytab").hover(function(){
$(this).next("div").slideDown("fast");},
function(){
$(this).next("div").slideUp("fast");
});
If you posted your full HTML I could tell you exactly how to do it :)
You can put your variable's value into a non existing tag, and later you can read them from there. This snippet is part of a loop body:
s = introduction.introductions[page * 6 + i][0]; //The variables content
$('#intro_img_'+i).attr('tag' , s); //Store them in a tag named tag
$('#intro_img_'+i).click( function() {introduction.selectTemplate(this, $(this).attr('tag'));} ); //retrieve the stored data