For Statement problem - javascript

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.

Related

I want to get an element tag printed in the console just by clicking on it

I want to get an element tag printed in the console just by clicking on it but it doesn't seem to work and I don't get why?
can anyone point the error in my logic?
let bodyChildren = document.body.children;
let bodyArr = Object.values(bodyChildren);
for (i = 0; i < bodyChildren.length; i++) {
bodyArr[i].onclick = function () {
console.log(bodyArr[i].tagName);
};
}
The problem is that when you define a function, everything in it is contained in a separate scope. Within the function bodyArr is not known. You can use this instead to refer to the clicked element, like below:
document.body.children will only refer to the direct children of the body element. If you want to refer to every element in the DOM, you can use document.getElementsByTagName("*") instead.
When the code is written globally, like in the snippet below, the variable bodyArr is actually available in the global scope, as is the variable i. But keep in mind that the code inside the function is only executed when an element is clicked. At that point in time the for loop has been fully executed leaving i with the value 3 (since in the snippet below the script tag also counts). bodyArr will always contain exactly 1 element less, no matter how many elements are in the DOM. In this case it has 3 elements with the last element being saved at position 2 (zero based) in the array, hence bodyArr[i] equals undefined.
let bodyChildren = document.body.children;
let bodyArr = Object.values(bodyChildren);
for (i = 0; i < bodyChildren.length; i++) {
bodyArr[i].onclick = function () {
console.log(this.tagName);
}
}
<span>child1</span>
<p>child2</p>
You need to get ALL elements from the document body and this is KEY: var all = document.getElementsByTagName("*");
var all = document.getElementsByTagName("*");
let bodyArr = Object.values(all);
for (i = 0; i < bodyArr.length; i++) {
bodyArr[i].onclick = function () {
console.log(this.tagName);
};
}
<span>Hello world</span>

Removing A Function from an Array

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)

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
}

Javascript function with dynamically generated arguments

Below code :
loop(n times)
create HTML Button Element
count++;
assign onclick event = function(){
openSomething("Value_"+count)
}
so if i create 3 input elements (n=3) and then go back click any of the three buttons then every time openSomething("Value_"+3) only gets called.
why openSomething("Value_"+1) and openSomething("Value_"+2) does not get called?
I am not sure what is going on may be it the scope issue but i dont know much about scope either, any help to push me in the right direction is much appreciated.
My original code
var count = 0;
for(var i =0;i<someValue;i++){
count++;
var button = document.createElement("img");
button.src = "/images/small_button.gif";
button.imageButton = true;
button.srcBase = "/images/small_button";
button.onclick = function () {
selectSomething("someIdText_"+count);};
cell.appendChild(button);
}
Because JavaScript doesn't have block-level scoping of variables, and as a result everything is scoped to the function. That means that when you have code that uses a variable (like your loop counter n or your count variable) at a later point (i.e. after the full execution of the function), it will have its value set to the last value for the loop. You need to create a closure (a new scope for the variable) inside of your loop. Something like this (since you didn't post your actual code):
for(var i = 0, l = list.length; i < l; i++) {
(function(count) {
something.onclick = function() {
openSomething("Value_" + count);
}
})(i);
}
For a more modern approtce use let,
works for firefox, chrome, and node
if you need to target all the browsers, use Anthony approach
for(var count = 0, l = list.length; count < l; count++) {
let count;
something.onclick = function() {
openSomething("Value_" + count);
}
}

JavaScript DOM references not holding up

For some reason ss.transition() does not affect the appropriate DOM elements after ss.goTo() is triggered by an onclick. The ss.transition() call under //Init does work as expected. I assume this is a scope problem. Little help?
var ss = {};
ss.goTo = function(i) {
ss.old = ss.current;
ss.current = ss.slides[i];
ss.transition();
}
ss.transition = function() {
ss.old.style.display = "none";
ss.current.style.display = "block";
}
// Hooks
ss.div = document.getElementById("slides");
ss.as = ss.div.getElementsByTagName("a");
// References
ss.slides = [];
for (i in ss.as) {
if (ss.as[i].rel == "slide") {
ss.slides.push(ss.as[i]);
}
}
ss.first = ss.slides[0];
ss.last = ss.slides[ss.slides.length-1];
// Init
ss.current = ss.first;
ss.old = ss.last;
ss.transition();
for (i in ss.as) {
You shouldn't use the for...in loop over an Array or, in this case, NodeList. You'll get member properties you don't want, like item and length. You also can't rely on the items being returned in any particular order; it is very likely that at least ss.last will not be what you expect. If it's a non-item property, ss.old.style.display will definitely fail with an exception, breaking the script.
The correct loop for a sequence is the old-school C construct:
for (var i= 0; i<ss.as.length; i++)
Also, where are you binding the calls to goTo? If you are doing it in a loop with a function inside you, you may well also have the classic loop closure problem. See eg. this question.
The reason for the failure is because you lose the reference to the currently hidden element before you make it show up again. You need to assign old to display:block, then do the switch of old = current, current = variable, then hide old.

Categories