How do I clean up this jQuery function? - javascript

I have this function that I would like to condense into some iterator. How could this function be cleaned up?
All need to act sequentially, as in: When one function returns, the next one begins. All odd children should fade out after fading in, and all even children should fade in and not fade out after.
Please note that this code is in CoffeeScript, so no semicolons.
I'm also having a problem with after the 8th child (e.g. if I continue on to '.title-sword:nth-child(9), etc) the function stops working. My thinking is there is a limit for embedded functions in depth but I am not able to verify this.
$('.title-sword:nth-child(2)').css('visibility', 'visible').hide().fadeIn('fast').fadeOut('fast', ->
$('.title-sword:nth-child(3)').css('visibility', 'visible').hide().fadeIn('fast', ->
$('.title-sword:nth-child(4)').css('visibility', 'visible').hide().fadeIn('fast').fadeOut('fast', ->
$('.title-sword:nth-child(5)').css('visibility', 'visible').hide().fadeIn('fast', ->
$('.title-sword:nth-child(6)').css('visibility', 'visible').hide().fadeIn('fast').fadeOut('fast', ->
$('.title-sword:nth-child(7)').css('visibility', 'visible').hide().fadeIn('fast', ->
$('.title-sword:nth-child(8)').css('visibility', 'visible').hide().fadeIn('fast').fadeOut('fast')
)
)
)
)
)
)

Try something like this (no CoffeeScript, since I'm just a regular JS guy):
(function() {
var i=2, elm,
step = function() {
elm = $('.title-sword:nth-child('+i+')');
if( !elm) return;
elm.css('visibility','visible').hide();
if( i%2 == 0) elm.fadeIn('fast').fadeOut('fast',step);
else elm.fadeIn('fast',step);
i++;
};
step();
})();
This code will run the desired function starting with the second child, and repeating until there are no more children.

try this:
$('.title-sword:nth-child(n+2):not(:nth-child(n+9))').css('visibility', 'visible').hide().fadeIn('fast').fadeOut('fast')
http://jsfiddle.net/RfHj2/1/

Assuming the elements are all siblings, this should work:
function doFades($el){
$el.css('visibility', 'visible').hide().fadeIn('fast').fadeOut('fast', function(){
var $next=$(this).next();
if($next.length){
doFades( $next);
}
});
}
doFades( $('.title-sword:nth-child(2)') );
I'm not familiar with coffeescript syntax but should be easy to modify

I prefer to run the selector operation just once and then iterate through the results of that. Here's a generic function that could be used for your purpose:
fadeInOutChildren(parentSelector, lowChild, highChild) {
var items = $(parentSelector).children();
var index = lowChild;
function next() {
if (index <= highChild) {
items.eq(index++).css('visibility', 'visible').hide().fadeIn('fast').fadeOut('fast', next);
}
}
next();
}
fadeInOutChildren(".title-sword", 1, 7);

Related

Cypress IO- Writing a For Loop

I have 15 buttons on a page. I need to test each button.
I tried a simple for loop, like
for (var i = 1; i < 15; i++) {
cy.get("[=buttonid=" + i + "]").click()
}
But Cypress didn't like this. How would I write for loops in Cypress?
To force an arbitrary loop, I create an array with the indices I want, and then call cy.wrap
var genArr = Array.from({length:15},(v,k)=>k+1)
cy.wrap(genArr).each((index) => {
cy.get("#button-" + index).click()
})
Lodash is bundled with Cypress and methods are used with Cypress._ prefix.
For this instance, you'll be using the _.times. So your code will look something like this:
Cypress._.times(15, (k) => {
cy.get("[=buttonid=" + k + "]").click()
})
You can achieve something similar to a "for loop" by using recursion.
I just posted a solution here: How to use a while loop in cypress? The control of is NOT entering the loop when running this spec file? The way I am polling the task is correct?
Add this to your custom commands:
Cypress.Commands.add('recursionLoop', {times: 'optional'}, function (fn, times) {
if (typeof times === 'undefined') {
times = 0;
}
cy.then(() => {
const result = fn(++times);
if (result !== false) {
cy.recursionLoop(fn, times);
}
});
});
Then you can use it by creating a function that returns false when you want to stop iterating.
cy.recursionLoop(times => {
cy.wait(1000);
console.log(`Iteration: ${times}`);
console.log('Here goes your code.');
return times < 5;
});
While cy.wrap().each() will work (one of the answers given for this question), I wanted to give an alternate way that worked for me. cy.wrap().each() will work, but regular while/for loops will not work with cypress because of the async nature of cypress. Cypress doesn't wait for everything to complete in the loop before starting the loop again. You can however do recursive functions instead and that waits for everything to complete before it hits the method/function again.
Here is a simple example to explain this. You could check to see if a button is visible, if it is visible you click it, then check again to see if it is still visible, and if it is visible you click it again, but if it isn't visible it won't click it. This will repeat, the button will continue to be clicked until the button is no longer visible. Basically the method/function is called over and over until the conditional is no longer met, which accomplishes the same thing as a for/while loop, but actually works with cypress.
clickVisibleButton = () => {
cy.get( 'body' ).then( $mainContainer => {
const isVisible = $mainContainer.find( '#idOfElement' ).is( ':visible' );
if ( isVisible ) {
cy.get( '#idOfElement' ).click();
this.clickVisibleButton();
}
} );
}
Then obviously call the this.clickVisibleButton() in your test. I'm using typescript and this method is setup in a class, but you could do this as a regular function as well.
// waits 2 seconds for each attempt
refreshQuote(attempts) {
let arry = []
for (let i = 0; i < attempts; i++) { arry.push(i) }
cy.wrap(arry).each(() => {
cy.get('.quote-wrapper').then(function($quoteBlock) {
if($quoteBlock.text().includes('Here is your quote')) {
}
else {
cy.get('#refreshQuoteButton').click()
cy.wait(2000)
}
})
})
}
Try template literals using backticks:
for(let i = 0; i < 3; i++){
cy.get(`ul li:nth-child(`${i}`)).click();
}

Some ideas to create a code to run functions one after another (DOM)

I have some functions to manipulate the DOM.
One create some css styles, other some html elements, then some css again an so on
(for example 7-8 steps)
Every function depends more or less of the previous. A callback approach is needed.
I know how to create that buy I'd like some function like that (avoiding to write the callback calls by hand )
My_dom_auto.add (function () {} ); // 1
My_dom_auto.add (function () {} ); // 2
My_dom_auto.add (function () {} ); // 3
My_dom_auto.run ();
My_dom_auto.add will encapsulate the function received into a new one, something like this:
My_dom_auto.add = fucntion (original_function_code) {
fake_function.push (
original_funcion_code_coied_line_by_line ;
My_dom.internal_function()
}
}
So I could call the fake_functions ...
The question is .... Could it be done ? How ? Using function Object ?
Other .... how to inject a new last line inside a function ?
What do you think? Any idea would be appreciated.
I think it will be useful to use Stack (abstract data type)
use an array in order to push functions
(cssStyle_1 -> HtmlElement_1 -> cssStyle_2 -> HtmlElement_2->... )
then pop function and execute it one after the other
a= function(){console.log("1") }
b= function(){console.log("2") }
c= function(){console.log("3") }
tab = []
tab.push(a)
tab.push(b)
tab.push(c)
tab.pop()
// 3
tab.pop()
// 2
tab.pop()
// 1

Callback function in for loop?

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();
}
});

Javascript Scope Issues for inner-function

Pretty sure this has been asked already, but I don't know what to search for. Anyway,
var livemarks = [];
var livemarkIds = PlacesUtils.annotations.getItemsWithAnnotation("livemark/feedURI", {});
for (var i = 0; i < livemarkIds.length; i++){
PlacesUtils.livemarks.getLivemark( {id : livemarkIds[i]}, function(result, livemark){
if (result == Components.results.NS_OK){
livemarks.push(livemark);
}
});
}
alert(livemarks.length);
I am trying to play a bit with a Firefox addon that's no longer maintained by its creator, just to learn a bit. I recently got an error saying getFeedURI is going to be deprecated and I want to change his old function.
EDIT:
From a function defined in a function (inner function), I am unable to access a var defined in the parent. Why?
E.g. I cannot access var livemarks from inside getLivemark(), or other similar internal functions.
I was checking (scroll down completely): this and his code works fine. So what's wrong with my code? I just wanted to avoid the recursion, if possible.
I suspect the PlacesUtils.livemarks.getLivemark function does its work asynchronously, so your callback is called after you alert the length. Put your alert inside the callback and you should see the correct length (eventually). Here's one way:
var expecting = livemarkIds.length;
for (var i = 0; i < livemarkIds.length; i++){
PlacesUtils.livemarks.getLivemark( {id : livemarkIds[i]}, function(result, livemark){
if (result == Components.results.NS_OK){
livemarks.push(livemark);
// ***New stuff***
if (livemarks.length === expecting) {
// Now you have them all, now you can do the next thing
doSomethingWithTheLiveMarks(livemarks);
}
}
});
}
Note that there I put livemarkIds.length into expecting, just in case you do other things with livemarkIds while the function is running. If you aren't, you can just use that directly.
Re your comment below:
However, the system works like this: I get the livemarks in an array. This code is in a class (and method) actually, so another class initializes this one and will call the function getFeeds(), which will return that array of livemarks.
If PlacesUtils.livemarks.getLivemark is asynchronous, it's impossible for getFeeds to return the array as a return value. E.g., it cannot be used like this:
a = b;
c = 42;
feeds = getFeeds(feedIds);
if (feeds.length === 0) {
// Do something
}
else {
// Do something else
}
The good news is it's really easy to fix: Have getFeeds accept a callback function that it calls when it has the feeds. The code above changes to look like this:
a = b;
c = 42;
feeds = getFeeds(feedIds, function(feeds) {
if (feeds.length === 0) {
// Do something
}
else {
// Do something else
}
});
As you can see, it's a pretty straightforward change. Assuming the loop above is all of getFeeds, then getFeeds ends up looking something like this:
function getFeeds(livemarkIds, callback) {
var livemarks = [];
for (var i = 0; i < livemarkIds.length; i++){
PlacesUtils.livemarks.getLivemark( {id : livemarkIds[i]}, function(result, livemark){
if (result == Components.results.NS_OK){
livemarks.push(livemark);
if (livemarks.length === livemarkIds.length) {
// Done, trigger the callback
callback(livemarks);
}
}
});
}
}
And the pattern continues: If the code calling getFeeds is being called by something else that's expecting a return value from the async stuff, instead of returning that value, you have that code accept a callback, and call the callback from the getFeeds callback. And so on.
Once you get used to it, it's very easy to do. Getting used to it can be tricky. :-)

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