Removing A Function from an Array - javascript

I have an array of functions that I'm trying to use like a delegate event in c#.
I push a function to the array, and when the array loops over the functions it will get called.
Problem is I'm having trouble removing the function once I'm done with it, which kind of defeats the purpose.
Here is my code. I'm totally up for a different method of doing this, I'm fairly new to JS/JQ so this is what I came up with.
var MouseMoveFunctions = [];
$(document).ready(function(){
//Create the event to call our functions.
$(document).mousemove(function(e){
CallMouseMoveFunctions(e);
});
});
function CallMouseMoveFunctions(e){
//Only continue if there is atleast 1 function in the array.
if(MouseMoveFunctions.length == 0) return;
//Call each function in the array.
for(var i = 0; i < MouseMoveFunctions.length;i++){
MouseMoveFunctions[i](e);
}
}
$(document).ready(function(){
//Add the TrackMouse function to the array.
MouseMoveFunctions.push(function(event){TrackMouse(event)});
});
var mX = 0;
var mY = 0;
function TrackMouse(e){
mX = e.pageX;
mY = e.pageY;
var index = MouseMoveFunctions.indexOf(function(event){TrackMouse(event)});
alert(index); //Always coming up -1, so it isn't getting removed
//Try and remove the function if it exists, just for now so I know its working
if(index != -1){
MouseMoveFunctions.splice(index);
}
}

//Always coming up -1, so it isn't getting removed
You always get -1 because you're passing a unique function object to .indexOf(), so it can't already be in the array. The anonymous function that you pushed into the array has no other reference, so you can't remove it by reference.
Because you're pushing and removing a function that simply passes along the event argument, you can instead push the function itself.
MouseMoveFunctions.push(TrackMouse);
Then you'll be able to successfully find it if you look for the same function by identity.
var index = MouseMoveFunctions.indexOf(TrackMouse);
Note that if you put the same function into the array more than once, you'll need to remove it separately for each time.
Also, as noted by Scott, you need to provide the number of items to remove.
A better solution would be to use a Set instead of an Array. Also, you can get rid of those .ready() handlers.
var MouseMoveFunctions = new Set();
//Create the event to call our functions.
$(document).mousemove(CallMouseMoveFunctions);
function CallMouseMoveFunctions(e){
//Only continue if there is atleast 1 function in the array.
if(MouseMoveFunctions.size == 0) return;
//Call each function in the set.
for(const fn of MouseMoveFunctions) {
fn(e);
}
}
//Add the TrackMouse function to the array.
MouseMoveFunctions.add(TrackMouse);
var mX = 0;
var mY = 0;
function TrackMouse(e){
mX = e.pageX;
mY = e.pageY;
MouseMoveFunctions.delete(TrackMouse);
}

You need to pass a 1 as the second argument to splice to tell it to remove 1 element at the index position you provided as the first argument.
.splice(index, 1)

Related

Loop and Functions

I'm having troubles gathering information about clicked eventListeners.
I have this loop which builds an array:
myButtonList = document.getElementsByTagName('a');
myAnchorList = [];
for (i=0; i < myButtonList.length;i++) {
if (myButtonList[i].getAttribute('class') == 'flagged') {
myAnchorList.push(myButtonList[i]);
}
}
For each <a> put into myAnchorList array, I also create another array storing other informations from the same tag (classe and other atrributes).
Here's where I'm struggling. I'm trying to set up an eventListener to send me back those information when those <a> are being clicked. But somehow, the fact that I create a function (for the eventListener) within a loop breaks everything.
for (i=0; i < myAnchorList.length; i++) {
myAnchorList[i].addEventListener("click", function(i){
console.log(alpha+' - '+beta[i]+" - "+charlie[i]);
});
}
My values will either be undefined or some other values which will be the same for each buttons I clicked. alpha is working well as it doesn't depend on any iteration of the loop, but not the others.
Can anybody see what I'm doing wrong here?
for (var i = 0; i < myAnchorList.length; i++) {
(function (i) { //Passes i to your function
myAnchorList[i].addEventListener("click", function () {
console.log(alpha+' - '+beta[i]+" - "+charlie[i]);
});
})(i);
}
The variable "i" in closure that you created in the loop will always retrieve the last value(myAnchorList.length - 1). You shouldn't create closure in a loop, and you can use a "factory" method to create closure instead.

Using querySelectorAll to get ALL elements with that class name, not only the first

I've ditched jquery about 9(ish) months ago and needed a selector engine (without all the hassle and don't mind ie<7 support) so i made a simplified version of document.querySelectorAll by creating this function:
// "qsa" stands for: "querySelectorAll"
window.qsa = function (el) {
var result = document.querySelectorAll(el)[0];
return result;
};
This works perfectly fine for 95% of the time but I've had this problem for a while now and i have researched mdn, w3c, SO and not to forget Google :) but have not yet found the answer as to why I only get the first element with the requested class.
And I know that only the first element being returned is caused by the "[0]" at the end, but the function won't work if I remove it so I've tried to make a for loop with an index variable that increases in value depending on the length of elements with that class like this:
window.qsa = function (el) {
var result, el = document.querySelectorAll(el);
for(var i = 0; i < el.length; ++i) {
result = el[i];
}
return result;
};
Again that did not work so I tried a while loop like this:
window.qsa = function (el) {
var result, i = 0, el = document.querySelectorAll(el);
while(i < el.length) {
i++;
}
result = el[i];
return result;
};
By now I'm starting to wonder if anything works? and I'm getting very frustrated with document.querySelectorAll...
But my stubborn inner-self keeps going and I keep on failing (tiering cycle) so I know that now is REALLY the time to ask these questions :
Why is it only returning the first element with that class and not all of them?
Why does my for loop fail?
Why does my while loop fail?
And thank you because any / all help is much appreciated.
Why is it only returning the first element with that class and not all of them?
Because you explicitly get the first element off the results and return that.
Why does my for loop fail?
Because you overwrite result with a new value each time you go around the end of loop. Then you return the last thing you get.
Why does my while loop fail?
The same reason.
If you want all the elements, then you just get the result of running the function:
return document.querySelectorAll(el)
That will give you a NodeList object containing all the elements.
Now that does what you say you want, I'm going to speculate about what your real problem is (i.e. why you think it doesn't work).
You haven't shown us what you do with the result of running that function, but my guess is that you are trying to treat it like an element.
It isn't an element. It is a NodeList, which is like an array.
If you wanted to, for instance, change the background colour of an element you could do this:
element.style.backgroundColor = "red";
If you want to change the background colour of every element in a NodeList, then you have to change the background colour of each one in turn: with a loop.
for (var i = 0; i < node_list.length; i++) {
var element = node_list[i];
element.style.backgroundColor = "red";
}
You are returning a single element. You can return the array. If you want to be able to act on all elements at once, jQuery style, you can pass a callback into your function;
window.qsa = function(query, callback) {
var els = document.querySelectorAll(query);
if (typeof callback == 'function') {
for (var i = 0; i < els.length; ++i) {
callback.call(els[i], els[i], i);
}
}
return els;
};
qsa('button.change-all', function(btn) {
// You can reference the element using the first parameter
btn.addEventListener('click', function(){
qsa('p', function(p, index){
// Or you can reference the element using `this`
this.innerHTML = 'Changed ' + index;
});
});
});
qsa('button.change-second', function(btn) {
btn.addEventListener('click', function(){
var second = qsa('p')[1];
second.innerHTML = 'Changed just the second one';
});
});
<p>One</p>
<p>Two</p>
<p>Three</p>
<button class='change-all'>Change Paragraphs</button>
<button class='change-second'>Change Second Paragraph</button>
Then you can call either use the callback
qsa('P', function(){
this.innerHTML = 'test';
});
Or you can use the array that is returned
var pList = qsa('p');
var p1 = pList[0];
This loop
for(var i = 0; i < el.length; ++i) {
result = el[i];
}
overwrites your result variable every time. That's why you always get only one element.
You can use the result outside though, and iterate through it. Kinda like
var result = window.qsa(el)
for(var i = 0; i < result.length; ++i) {
var workOn = result[i];
// Do something with workOn
}

why loop inside recursive function not complete in javascript

I need to :
check every node in tree start from the root, loop on its node ,check each node if there any child related to that node and call the same function again (recursive function).
My code is:
var tree = new Array();
tree[0] = ["Node1", "Node2", "Node3", "Node4"]
tree[1] = ["Node1", "Node2"]
tree[2] = ["Node1", "Node2", "Node4"]
function schTree(treeArr){
//LOOP ON CHILD NODE TO CHECK IF THE NODE IS PARANT FOR SOME OTHER NODE OR NOT and do some actions
for(i=0;i<treeArr.length; i++){
if(treeArr[i].length){
schTree(treeArr[i]);
};
}
}
//call search tree function
schTree(tree);
The problem is :
The loop did not complete if I recall the function.
I think that each calling for the function recursively It construct new function above the current function (work on the same place in memory not creating new function)
How to :
Make normal recursive function ?????
Thanks in advance
Maged Rawash
You only received the first node because you declared your for loop without the keyword var
for(i=0;i<treeArr.length; i++)
By the time the first nodes array was traversed, i was equal to 3
Here are my comments on the rest of your code
var tree = [];
tree[0] = ["Node1", "Node2", "Node3", "Node4"]
tree[1] = ["Node5", "Node6"]
tree[2] = ["Node7", "Node8", "Node9"]
//recursive function that checks if a node
function schTree(treeArr){
//FIX A
//find out if element is an array
if(Array.isArray(treeArr)){
//FIX B
//You did not declare i with var, meaning each recursive call
//carried the prior value of i with it.
//so after iterating over the first node, i = 3
//which is greater than the length of node 1 and 2
for(var i=0;i<treeArr.length; i++){
//FIX C
//removed inner length check, not needed
//schTree checks for array before getting to this
//point
schTree(treeArr[i]);
}
}
else {
//While you'd probably want to do a clause for
//objects, we'll assume only arrays here.
//FIX D
//Do something with the non array element
console.log(treeArr);
}
}
schTree(tree);
The way you're checking it isn't going to work because letters have a .length of 1, so you're going to get pass in tree, tree[0], tree[0][0], and then infinitely pass in tree[0][0][0] until the stack gets too big and you get a RangeError. Instead, do this:
function schTree(treeArr){
for (var i = 0; i < treeArr.length; i++){
if (treeArr[i].constructor === String && treeArr[i].length > 1) schTree(treeArr[i]);
}
}

change array passed to function

I pass 2 arrays to a function and want to move a specific entry from one array to another. The moveDatum function itself uses underscorejs' methods reject and filter. My Problem is, the original arrays are not changed, as if I was passing the arrays as value and not as reference. The specific entry is correctly moved, but as I said, the effect is only local. What do I have to change, to have the original arrays change as well?
Call the function:
this.moveDatum(sourceArr, targetArr, id)
Function itself:
function moveDatum(srcDS, trgDS, id) {
var ds = _(srcDS).filter(function(el) {
return el.uid === uid;
});
srcDS = _(srcDS).reject(function(el) {
return el.uid === uid;
});
trgDS.push(ds[0]);
return this;
}
Thanks for the help
As mentioned in the comments, you're assigning srcDS to reference a new array returned by .reject(), and thus losing the reference to the array originally passed in from outside the function.
You need to perform your array operations directly on the original array, perhaps something like this:
function moveDatum(srcDS, trgDS, id) {
var ds;
for (var i = srcDS.length - 1; i >= 0; i--) {
if (srcDS[i].uid === id) {
ds = srcDS[i];
srcDS.splice(i,1);
}
}
trgDS.push(ds);
return this;
}
I've set up the loop to go backwards so that you don't have to worry about the loop index i getting out of sync when .splice() removes items from the array. The backwards loop also means ds ends up referencing the first element in srcDS that matches, which is what I assume you intend since your original code had trgDS.push(ds[0]).
If you happen to know that the array will only ever contain exactly one match then of course it doesn't matter if you go forwards or backwards, and you can add a break inside the if since there's no point continuing the loop once you have a match.
(Also I think you had a typo, you were testing === uid instead of === id.)
Copy over every match before deleting it using methods which modify Arrays, e.g. splice.
function moveDatum(srcDS, trgDS, id) { // you pass an `id`, not `uid`?
var i;
for (i = 0; i < srcDS.length; ++i) {
if (srcDS[i].uid === uid) {
trgDS.push(srcDS[i]);
srcDS.splice(i, 1);
// optionally break here for just the first
i--; // remember; decrement `i` because we need to re-check the same
// index now that the length has changed
}
}
return this;
}

For Statement problem

I have a for statement like
function myspecialFunction(elem) {
var currentItem = elem;
var currentImg = elem.find('img');
jQuery(currentImg).append('some text');
...
The problem seems to be that when elem has >1 of the same image item - it overwrites the data of the previous ? That is, when the same currentImg is returned - the statement overwrites the data of the previous ?
How can I ensure that same currentImg values are preserved ? i.e. I was hoping to use like $(this) but it doesn't appear to work ? My html looks like
You could rewrite the above as
jQuery('.class').each(function() {
myspecialFunction(jQuery(this));
});
function myspecialFunction(elem) {
var currentItem = elem;
var currentImg = elem.find('img');
//....
}
this is more idiomatic and $(selector).each() introduces a closure to ensure that the correct value is captured in each loop iteration.
To do something similar with a plain for loop would be
var myClass = jQuery('.class');
for (var i = 0, len = myClass.length; i < len; i++) {
(function () {
myspecialFunction(jQuery(myClass[i]));
})();
}
function myspecialFunction(elem) {
var currentItem = elem;
var currentImg = elem.find('img');
// ...
}
Depending on your target browsers, being more specific than just a CSS class may help out too i.e. if all of the elements that you are selecting are all of the same tag name, then use the tag name in the selector.
Something like this using jQuery each loop
jQuery('.class').each(function(){
myspecialFunction($(this));
});
currentImg is local to the function it is in... not only will it be overwritten on each call, it won't even exist outside of that function.
Do you want to create a list of values that elem.find('img') returns? If so then I think you are going about it wrong. You need to scope a list variable outside of the function.

Categories