Callback function in for loop? - javascript

How do I get a function to run after all the fadeOuts in the for loop below?
What I need to do is fadeout anything that may be visible and then fadein a specific object. Due to styling requirements my topnav and dropdown nav are in to different uls which is why things are tricky.
I'm not very good at writing my own scripts yet so I'm (hopefully) missing something basic.
I've tried wrapping things in functions but that seems to mess with variable scope and screws things up that I don't understand...
Thanks for any help!
$('.ksddtop').on('mouseenter', function(){
var ddtop = $(this).text();
var dd = $('.' + ddtop);
$('.ksddwrap').fadeIn();
$(dd).fadeIn();
var ksdds = $('.ksdd');
for(var i = 0; i < ksdds.length; i++) {
var ksdd = ksdds[i];
if (! $(ksdd).hasClass(ddtop) ){
$(ksdd).fadeOut();
}
}
});

This should do it, if I understand the requirements:
$('.ksdd:not(' + ddtop + ')').fadeOut().promise().done(function(){
// all done fading!
});
Fade out all ksdd elements that don't have the ddtop class, then do something when they are all done animating.
More Information:
calling .promise on a jQuery collection gives you a promise object that will resolve when all animations on the collection of elements are complete. This includes queued animations.
If you instead passed a callback function directly to .fadeOut(), you would get a callback for each element rather than one after they were all done.

Instead of:
var ksdds = $('.ksdd');
for(var i = 0; i < ksdds.length; i++) {
var ksdd = ksdds[i];
if (! $(ksdd).hasClass(ddtop) ){
$(ksdd).fadeOut();
}
}
Try:
$('.ksdd').each(function(){
if (! $(this).hasClass(ddtop) ){
$(this).fadeOut();
}
});

Related

JavaScript function parameter error

I have this simple slideshow set up and it all works fine if I remove the parameters and add the class and speed manually.
If I use parameters to set the class and speed, it fails. With the first image getting the current class applied and then the dom seems to go crazy.
‘undefined’ pops up a lot although there are no errors in console.
Any help would be appreciated. Thanks
let pos = 0;
window.addEventListener("load", function(event) {
testIt('current', 5000);
});
function testIt(_class, _speed) {
const testPara = document.querySelectorAll('.bg_imgs');
let i;
for(i = 0; i < testPara.length; i++) {
testPara[i].classList.remove(_class);
}
pos++;
if(pos > testPara.length) {pos = 1;}
testPara[pos-1].classList.add(_class);
setTimeout(_class, _speed); }
It looks like you're sending the wrong parameters to setTimeout. It should take the form setTimeout(function, time), but instead, you're passing it what I assume is a string.
setTimeout(function() { testIt(_class, _speed); }, _speed);
should work
SetTimeout takes a function as its first param. If you are trying to delay the recursion try:
setTimeout(() => testIt(_class, _speed), _speed)
Thought window.requestAnimationFrame would be a better solution. More info on animation frame

Protractor nested for loops

I need some help with a nested for loop in protractor and converting/understanding promises correctly. In ‘test’ below the functionality works with all values, but as soon as I try to put in the nested for loops things go south. Any chance someone has a clean suggestion on this? I have tried the forEach which some indicate handle the promise issue inherently, but I seem to get the same results.
My Test data looks like:
objectPage.chartValues = {
[['chart','ChartID01'],['title','TitleText01'],['Name01','value01'],['Name02','Value02']],
[[‘chart','ChartID02'],['title','TitleText02'],['Name01','value01'],['Name02','Value02']],
[[‘chart','ChartID03'],['title','TitleText03'],['Name01','value01'], [‘Name02’,'Value02'],['Name03','Value03']]
}
it ('test', function (){
for (chartNumber = 0; chartNumber < objectPage.chartValues.length; chartNumber++) {
for (chartEntry = 1; chartEntry < ObjectPage.chartValues[chartNumber].length; chartEntry++) {
//for readability of next call pulled out here
chart = objectPage.chartValues[chartNumber][0][1];
name = objectPage.chartValues[chartNumber][chartEntry][0];
value = objectPage.chartValues[chartNumber][chartEntry][1];
pageObject.getbackgroundcolor(chart, name).then(function (color) {
expect(pageObject.getElementFromTable(chart, name, color).getText())
.toEqual(value);
});
}
}
});
//function calls in pageobject the call for get background is straight forward.
this.getbackgroundcolor = function (chartName, valueName) {
return element(by.id(chartName)).element(by.cssContainingText('.dxc-item', valueName)).element(by.tagName('rect')).getAttribute('fill');
//get element is very similar.
this.getElementFromTable = function(chartName, valueName, colorname) {
some searching stuff..
return element(by.css(‘tspan'));
My results seem to indicate the look executes, but not returning from the actual expect. Finally trying to find a value for an item with background color of null. I know this is not true as I have run all values individually and in sequence without issue. Hopefully I avoided cut and past/generalization errors.
Thank you.
Update:
it('Verify Charts on page ', function () {
myChartlength = objectPage.chartValues.length;
for (chartNumber = 0; chartNumber < myChartlength; chartNumber++) {
(function (chartNumber) {
myEntrylength = objectPage.chartValues[chartNumber].length;
chartValues = objectPage.chartValues[chartNumber];
for (chartEntry = 2; chartEntry < myEntrylength; chartEntry++) {
(function (chartEntry) {
//pulled these out for readablility of next call.
chart = chartValues[0][1];
name = chartValues[chartEntry][0];
value = chartValues[chartEntry][1];
console.log('chart: ' + chart + ', name: ' + name + ', value: ' + value);
page.getbackgroundcolor(chart, name).then(function (color) {
expect(objectPage.getElementFromTable(chart, name, color).getText()).toEqual(value);
});
})(chartEntry);
};
})(chartNumber);
};
});
Yeah, if I'm understanding your question correctly, your problem is async. It's firing through the loops before any promises are returned.
To loop tests, the best solution I've found is to use an IIFE (Instantly Invoked Function Expression). In which, you create your loop, create the iife, and pass in the index.
Here's a basic example:
describe('to loop tests', function() {
var data = ['1', '2', '3'];
for(var i = 0; i < data.length; i++) {
// create your iife
(function(i) {
it('pass in the index to an iife', function() {
console.log('i is: ' + i);
expect(data[i]).toBe(true);
});
})(i); // pass in index
}
});
This works great for data driving tests from say a data file, or whatever. And if you need multiple loops, like in your example code, you'll just make multiple iifes.
You shouldn't use for loops with protractor or you will have a bad time.
Due to the asynchronous nature of Protractor, if you need loops, I see async's map https://github.com/caolan/async as one good and clean solution.
Other option is to use ES5's map when you need loops in Protractor, such as:
[1,3,5,7].map(function(index,key){
expect(element.all(by.css('.pages-list')).get(index).isDisplayed()).toBeFalsy()
})
In your case, I see that you need a for loops to produce array, that you later can map over it.
You can have this array with function, that uses for loops inside and returns the needed array to a callback.
Simple example with one for loop
function returnIndexes(callback){
var exitArray = [];
for (var i = 0; i < someArray.length; i++) {
if(someArray[i].length > 12){
exitArray.push(someArray[i]);
}
if(i==someArray.length-1){
callback(exitArray);
}
}

Get iterator index into a onmousedown function

I am trying to do a seemingly trivial thing, but I cant figure this out. I am iterating over items found by the document.getElementByClassName method. I am doing so with indices so I can keep track of some stuff, and I need that index inside the onmousedown events for that specific element, however I can't figure out to do so.
var items = document.getElementsByClassName("someClass");
for (var i = items.length - 1; i >= 0; i--)
{
items[i].onmousedown=function(){
//This does not work:
var index = i; //I need the i variable from the loop above in here.
console.log(index);
this.innerHTML = doSomeWorkWith(index);
};
}
Anyone know how to do this? I have thought of adding it to the element itself so I can access a variable there, but I would prefere not to as it would clutter the html code.
You need to keep your indexes in closure, something as
for (var i = items.length - 1; i >= 0; i--){
(function(index){
...do anithing
})(i);
}
You'll need to create the handler functions on the fly, using another function. That can easily be done using immediately invoced function expressions (IIFEs). That way, you'll get the i to be evaluated when defining the handler, not when executing it.
var items = document.getElementsByClassName("someClass");
for (var i = items.length - 1; i >= 0; i--) {
items[i].onmousedown = (function (index) {
return function () {
console.log(index);
this.innerHTML = doSomeWorkWith(index);
}
})(i);
}
Basically, I'm not directly assigning a function to onmousedown, but creating one on the fly that has the value of i hardcoded.
To create that handler function, I'm using another function, that I immediately (in-place) invoke after defining it, without ever assigning a name. (Of course I just could create that function in global scope and us it here, but as I don't need it anywhere else, why should I?)
[Edit]: To use the event inside that function, use
var items = document.getElementsByClassName("someClass");
for (var i = items.length - 1; i >= 0; i--) {
items[i].onmousedown = (function (index) {
return function (event) {
this.innerHTML = doSomeWorkWith(index);
// do something with "event" here
}
})(i);
}
This is a classical problem, the anonymous function captures the variable and not its value, so when it is indeed called, the current value is not correct.
See this link for more information : Arguments to JavaScript Anonymous Function

Why doesn't this javascript bind the correct parameters with the event?

In this code I am supposed to bind a rollover effect to each <area> tag in a <map> element.
function initLinks(webrt) {
var areas = document.querySelectorAll("map#streetmap > area");
var links = new Array(areas.length);
for (var i=0; i<links.length; i++) {
links[i] = new Image(786,272);
links[i].src = webrt+"templates/default/sketches/links"+(i+1)+".png";
areas[i].onmouseover=function(){switchLinkImg(webrt+"templates/default/sketches/links"+(i+1)+".png");};
areas[i].onmouseout=function(){switchLinkImg(webrt+"templates/default/sketches/links.png");};
}
}
Strangely, each <area> onmouseover event tries to load the non-existing image: /templates/default/sketches/links6.png. Why does it keep this variable i which has incremented to 6 as a global variable rather than take the string I am passing to the function?
How do I fix this?
Note: No jQuery!
i often find it cleaner to use array methods when using the index because you don't need extra wrappers and everything reads a little cleaner (imho):
function initLinks(webrt) {
[].forEach.call(document.querySelectorAll("map#streetmap > area"),
function(elm, index){
var img = new Image(786,272);
img.src = webrt+"templates/default/sketches/links"+(index+1)+".png";
elm.onmouseover=function(){switchLinkImg(webrt+"templates/default/sketches/links"+(index+1)+".png");};
elm.onmouseout=function(){switchLinkImg(webrt+"templates/default/sketches/links.png");};
});
}
the variable count is way down, and we avoid extra ram-hogging closures by not creating a extra new function in each iteration of the "loop".
to be sure, both ways work, but the newer array methods can also allow the procedure to be recycled by ripping it out of the forEach() call, and giving it a name.
Try using the following code:
function initLinks(webrt) {
var areas = document.querySelectorAll("map#streetmap > area");
var links = new Array(areas.length);
for (var i=0; i<links.length; i++) {
(function(index) {
links[index] = new Image(786,272);
links[index].src = webrt+"templates/default/sketches/links"+(index+1)+".png";
areas[index].onmouseover=function(){switchLinkImg(webrt+"templates/default/sketches/links"+(index+1)+".png");};
areas[index].onmouseout=function(){switchLinkImg(webrt+"templates/default/sketches/links.png");};
})(i);
}
}
You should wrap the i variable into a closure. Otherwise it gets incremented.

JSHint error Don't make functions within a loop

I'm running some code through JSHint and I keep getting the following error:
Don't make functions within a loop.
I tried turning off the warning for 'About functions inside loops' off which does nothing to stop the error from being reported. I have decided to refactor the code, using JSHint's suggestions here, http://www.jshint.com/options/ but I'm still getting the error. I was hoping that somebody could help me to refactor this code slightly so it will pass. Here's a copy of the function:
function setSounds(parent) {
var i,
l;
parent.getElements('.sound').each(function (elem) {
var soundEvents = [];
if (elem.get('fk_click_sound')) {
soundEvents.push('click');
}
if (elem.get('fk_mouseover_sound')) {
soundEvents.push('mouseenter');
}
if (soundEvents.length !== 0) {
for (i = 0, l = soundEvents.length; i < l; i += 1) {
elem.addEvent(soundEvents[i], (function () {
return function (e) {
FKSoundAIR(FKSoundStd[this.get('fk_' + e.type + '_sound')]);
};
})(elem), false);
}
}
});
}
I'm using MooTools. The purpose of this function is to pass a parent element and then apply sound event to all of the children with the class 'sound.' I'm using custom HTML attributes, such as 'fk_click_sound' to feed additional information to the function. I picked up this method of assigning a function within a loop from http://blog.jbrantly.com/2010/04/creating-javascript-function-inside.html.
Any suggestions or resources that you can point me to would be great. Thanks!
You can try something like this:
function make_handler(div_id) {
return function () {
alert(div_id);
}
}
for (i ...) {
div_id = divs[i].id;
divs[i].onclick = make_handler(div_id);
}
You could create the function outside, assign it to a var and use it in your call to addEvent.
As it turns out JS Hint had a bug re: the warning for Looping inside of a function, which they fixed here. Now that this is fixed, this issue is resolved.

Categories

Resources